From 8c9688fff4446a1b0f5fe9a9be0c50084726cc4d Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Wed, 2 Jul 2025 00:33:19 +0100 Subject: CLI cleanups; fix the FreeBSD build --- nihil.cli/CMakeLists.txt | 2 +- nihil.cli/command.ccm | 17 +++++++++++++---- nihil.cli/command_tree.ccm | 2 +- nihil.cli/dispatch_command.cc | 15 ++++++--------- nihil.cli/registry.cc | 43 ------------------------------------------- nihil.cli/registry.ccm | 27 ++++++++++++++++++++++----- 6 files changed, 43 insertions(+), 63 deletions(-) delete mode 100644 nihil.cli/registry.cc (limited to 'nihil.cli') 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; - 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(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> & -try { - static auto commands = std::vector>(); - 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> -{ - 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. 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>; +// Get the current registry. +auto get_registry() -> std::vector> & +{ + static auto commands = std::vector>(); + 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 -- cgit v1.2.3