1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
// This source code is released into the public domain.
module;
// For EX_USAGE. While <sysexits.h> is deprecated, there's no other standard
// exit code for 'usage error'; some programs use 2 (common on Linux), but
// 2 is also used for many other exit codes.
#include <sysexits.h>
export module nihil.cli:command;
// command_node represents a possibly-invocable command.
import nihil.std;
import nihil.core;
import :usage_error;
namespace nihil {
export struct command;
// registry.ccm
auto register_command(command *cmd) noexcept -> void;
// A command. If constructed with a handler, this is a "real" command which can be invoked.
// Otherwise, it's a stub command that has children in the command tree.
//
// Because commands are intended to be global objects, they are not copyable or movable.
// Creating an object automatically registers it in the list of commands.
export struct command final
{
using command_handler_t = std::function<int(int, char **)>;
// Construct a new command with a handler and register it. Since this is usually invoked
// at global scope by static object construction, we handle exception internally.
command(std::string_view const path, std::string_view const usage, auto &&handler) noexcept
try : m_path(path)
, m_usage(usage)
, m_handler(std::forward<decltype(handler)>(handler))
{
register_command(this);
} catch (std::exception const &exc) {
std::println(std::cerr, "register_command(): {}", exc.what());
std::quick_exit(1);
} catch (...) {
std::println(std::cerr, "register_command(): unknown error");
std::quick_exit(1);
}
// Create a stub command which doesn't have a handler.
explicit command(std::string_view const path)
: m_path(path)
{
}
// Not copyable.
command(command const &) = delete;
auto operator=(command const &) -> command & = delete;
// Not movable.
command(command &&) = delete;
auto operator=(command &&) -> command & = delete;
~command() = default;
// Return the full path for this command.
[[nodiscard]] auto path(this command const &self) noexcept -> std::string_view
{
return self.m_path;
}
// Return the one-line usage summary for this command.
[[nodiscard]] auto usage(this command const &self) noexcept -> std::string_view
{
return self.m_usage;
}
// Test if this command can be invoked.
[[nodiscard]] auto invocable(this command const &self) noexcept -> bool
{
return static_cast<bool>(self.m_handler);
}
// Invoke this command and return its result.
[[nodiscard]] auto invoke(this command const &self, int argc, char **argv)
-> std::expected<int, error>
{
if (!self.m_handler)
return error(errc::incomplete_command);
try {
return std::invoke(self.m_handler, argc, argv);
} catch (usage_error const &err) {
std::println(std::cerr, "{}", err.what());
std::println(std::cerr, "usage: {} {}", self.path(), self.usage());
return EX_USAGE;
}
}
private:
std::string m_path;
std::string m_usage;
command_handler_t m_handler;
};
} // namespace nihil
|