aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.cli
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-07-02 00:33:19 +0100
committerLexi Winter <lexi@le-fay.org>2025-07-02 00:33:19 +0100
commit8c9688fff4446a1b0f5fe9a9be0c50084726cc4d (patch)
treeca9a10be5795d976c0cbc73ad1111517bb4e22bf /nihil.cli
parent47999457e647352ae7e71d43c65e7b39ae5ca567 (diff)
downloadnihil-8c9688fff4446a1b0f5fe9a9be0c50084726cc4d.tar.gz
nihil-8c9688fff4446a1b0f5fe9a9be0c50084726cc4d.tar.bz2
CLI cleanups; fix the FreeBSD build
Diffstat (limited to 'nihil.cli')
-rw-r--r--nihil.cli/CMakeLists.txt2
-rw-r--r--nihil.cli/command.ccm17
-rw-r--r--nihil.cli/command_tree.ccm2
-rw-r--r--nihil.cli/dispatch_command.cc15
-rw-r--r--nihil.cli/registry.cc43
-rw-r--r--nihil.cli/registry.ccm27
6 files changed, 43 insertions, 63 deletions
diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt
index 091c34f..859fea5 100644
--- a/nihil.cli/CMakeLists.txt
+++ b/nihil.cli/CMakeLists.txt
@@ -4,6 +4,7 @@ add_library(nihil.cli STATIC)
target_link_libraries(nihil.cli PRIVATE
nihil.std
nihil.generator
+ nihil.posix;
nihil.util
)
target_sources(nihil.cli
@@ -18,7 +19,6 @@ target_sources(nihil.cli
PRIVATE
dispatch_command.cc
- registry.cc
)
if(NIHIL_TESTS)
diff --git a/nihil.cli/command.ccm b/nihil.cli/command.ccm
index dc3f29a..ea52bbf 100644
--- a/nihil.cli/command.ccm
+++ b/nihil.cli/command.ccm
@@ -31,15 +31,24 @@ export struct command final
{
using command_handler_t = std::function<int(int, char **)>;
- command(std::string_view const path, std::string_view const usage, command_handler_t handler) noexcept
- : m_path(path)
+ // 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::move(handler))
+ , 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);
}
- explicit command(std::string_view const path) noexcept
+ // Create a stub command which doesn't have a handler.
+ explicit command(std::string_view const path)
: m_path(path)
{
}
diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm
index 7399357..8ed16c7 100644
--- a/nihil.cli/command_tree.ccm
+++ b/nihil.cli/command_tree.ccm
@@ -166,7 +166,7 @@ private:
{
auto tree = command_tree();
- for (auto &&command : get_registered_commands()) {
+ for (auto &&command : get_registry()) {
// Throws std::logic_error on duplicates.
tree.insert(command);
}
diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc
index 6e3a757..e9bf779 100644
--- a/nihil.cli/dispatch_command.cc
+++ b/nihil.cli/dispatch_command.cc
@@ -9,6 +9,7 @@ module nihil.cli;
import nihil.std;
import nihil.core;
+import nihil.posix;
namespace nihil {
@@ -17,11 +18,11 @@ namespace {
auto print_commands(command_tree_node const &node) -> void
{
for (auto &&child : node.children())
- std::print(std::cerr, " {}\n", child.command().path());
+ std::println(std::cerr, " {}", child.command().path());
}
} // namespace
-auto dispatch_command(int argc, char **argv) -> int
+auto dispatch_command(int const argc, char **argv) -> int
{
// Reset getopt(3) for the command, in case main() used it already.
optreset = 1;
@@ -35,12 +36,8 @@ auto dispatch_command(int argc, char **argv) -> int
// Set the program name to the existing progname plus the full path to the command being
// invoked; this makes error messages nicer. Save the old progname so we can restore it
// after invoking the command.
- auto const *old_progname = ::getprogname();
-
- {
- auto cprogname = std::format("{} {}", ::getprogname(), command.path());
- ::setprogname(cprogname.c_str());
- }
+ auto progname_guard =
+ setprogname(std::format("{} {}", getprogname().value_or(""), command.path()));
// Invoke the command see what it returns. If it's an exit code, just return it.
// Otherwise, handle the error.
@@ -51,7 +48,7 @@ auto dispatch_command(int argc, char **argv) -> int
auto ret = command.invoke(nrest, argv + (argc - nrest));
// Restore the old progname.
- ::setprogname(old_progname);
+ progname_guard.release();
if (ret)
return *ret;
diff --git a/nihil.cli/registry.cc b/nihil.cli/registry.cc
deleted file mode 100644
index a972a65..0000000
--- a/nihil.cli/registry.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// This source code is released into the public domain.
-module nihil.cli;
-
-import nihil.std;
-
-namespace nihil {
-
-// Get the registry storage. Because this is called from global ctors,
-// it handles exceptions itself.
-auto get_registry() noexcept -> std::vector<std::shared_ptr<command>> &
-try {
- static auto commands = std::vector<std::shared_ptr<command>>();
- return commands;
-} catch (std::exception const &exc) {
- std::println(std::cerr, "{}", exc.what());
- std::exit(1); // NOLINT
-} catch (...) {
- std::println(std::cerr, "get_registered_commands(): unknown error\n");
- std::exit(1); // NOLINT
-}
-
-// Register a new command.
-auto register_command(command *cmd) noexcept -> void
-try {
- auto null_deleter = [] (command const *) -> void {};
-
- auto &commands = get_registry();
- commands.emplace_back(cmd, null_deleter);
-} catch (std::exception const &exc) {
- std::println(std::cerr, "{}", exc.what());
- std::exit(1); // NOLINT
-} catch (...) {
- std::println(std::cerr, "get_registered_commands(): unknown error\n");
- std::exit(1); // NOLINT
-}
-
-// Get the list of registered commands.
-auto get_registered_commands() -> std::span<std::shared_ptr<command>>
-{
- return {get_registry()};
-}
-
-} // namespace nihil
diff --git a/nihil.cli/registry.ccm b/nihil.cli/registry.ccm
index e5a7e29..5e31195 100644
--- a/nihil.cli/registry.ccm
+++ b/nihil.cli/registry.ccm
@@ -7,11 +7,28 @@ namespace nihil {
export struct command;
-// Register a command. This is guaranteed not to throw; errors will print
-// a diagnostic and exit.
-auto register_command(command *cmd) noexcept -> void;
+///////////////////////////////////////////////////////////////////////
+// Command registry storage. This is where command::command() registers
+// itself when the global command objects are constructed at startup.
+//
+// Because we sometimes create stub commands dynamically, the registry
+// storage is a list of shared_ptr<command>. The "real" commands (which
+// refer to global objects) will be created with a null deleter so they
+// are never deleted. This allows real and stub commands to mix in the
+// same command_tree.
-// Get previously registered commands.
-auto get_registered_commands() -> std::span<std::shared_ptr<command>>;
+// Get the current registry.
+auto get_registry() -> std::vector<std::shared_ptr<command>> &
+{
+ static auto commands = std::vector<std::shared_ptr<command>>();
+ return commands;
+}
+
+// Register a new command.
+auto register_command(command *cmd) noexcept -> void
+{
+ auto constexpr null_deleter = [] (command const *) -> void {};
+ get_registry().emplace_back(cmd, null_deleter);
+}
} // namespace nihil