diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-07-01 21:12:11 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-07-01 21:12:11 +0100 |
| commit | 85baf16dd366fb501dc522a0957ec680dc9478f0 (patch) | |
| tree | 64b9a9463c6b886e9784b1fce9ff5c80dc7e0a23 /nihil.cli | |
| parent | e461c17c24115132601362a96b6a85c0dd12f471 (diff) | |
| download | nihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.gz nihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.bz2 | |
cli: clean up
Diffstat (limited to 'nihil.cli')
| -rw-r--r-- | nihil.cli/command_tree.ccm | 64 | ||||
| -rw-r--r-- | nihil.cli/dispatch_command.cc | 31 |
2 files changed, 48 insertions, 47 deletions
diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm index 6eae9e6..6cfabe9 100644 --- a/nihil.cli/command_tree.ccm +++ b/nihil.cli/command_tree.ccm @@ -30,6 +30,16 @@ struct command_tree_node final { } + // Not copyable. + command_tree_node(command_tree_node const &) = delete; + auto operator=(command_tree_node const &) -> command_tree_node & = delete; + + // Movable. + command_tree_node(command_tree_node &&) = default; + auto operator=(command_tree_node &&) -> command_tree_node & = default; + + ~command_tree_node() = default; + // Return a child node, or NULL if the child doesn't exist. [[nodiscard]] auto get_child(this command_tree_node const &self, std::string_view const child) -> command_tree_node const * @@ -63,7 +73,7 @@ struct command_tree_node final : std::string(child); auto node = command_tree_node(&self, child, std::make_shared<nihil::command>(path)); - auto [it, ok] = self.m_children.emplace(child, node); + auto [it, ok] = self.m_children.emplace(child, std::move(node)); if (!ok) throw std::logic_error("failed to insert command tree node"); return &it->second; @@ -113,41 +123,41 @@ private: struct command_tree { // Add a node to the tree. Returns false if the node already exists. - auto insert(this command_tree &self, std::vector<std::string_view> const &path, + auto insert(this command_tree &self, std::ranges::range auto &&path, std::shared_ptr<command> command) -> void + requires(std::constructible_from<std::string_view, + std::ranges::range_value_t<decltype(path)>>) { auto *this_node = &self.m_root_node; // Find the node for this key. for (auto &&this_word : path) - this_node = this_node->get_or_create_child(this_word); + this_node = this_node->get_or_create_child(std::string_view(this_word)); // Set the new value. this_node->command(std::move(command)); } - // Find a node in the tree. - [[nodiscard]] auto - find(this command_tree const &self, int &argc, char **&argv) -> command_tree_node const * + // Find a node in the tree. Returns the node and the remaining unprocessed arguments. + [[nodiscard]] auto find(this command_tree const &self, std::ranges::range auto &&args) + requires(std::constructible_from<std::string_view, + std::ranges::range_value_t<decltype(args)>>) { auto const *this_node = &self.m_root_node; - // Iterate until we don't find a child command, then return that node. - while (argv[0] != nullptr) { - auto const *next_node = this_node->get_child(argv[0]); - - if (next_node == nullptr) - return this_node; - - this_node = next_node; - - --argc; - ++argv; - } - - // We ran out of path without finding a valid command. Return this - // node; the caller will notice the missing command. - return this_node; + auto rest = + args | std::views::take_while([&](auto &&str) { + auto const *child = this_node->get_child(std::string_view(str)); + if (child == nullptr) + return false; + this_node = child; + return true; + }); + + // Force evaluation of the view, otherwise the reference to this_node + // would be dangling. + auto taken = std::ranges::distance(args) - std::ranges::distance(rest); + return std::make_pair(this_node, args | std::views::drop(taken)); } private: @@ -159,15 +169,9 @@ private: { auto tree = command_tree(); - for (auto &&command : get_registered_commands()) { - auto split_path = command->path() // - | std::views::split(' ') // - | std::views::transform(construct<std::string_view>) // - | std::ranges::to<std::vector>(); - + for (auto &&command : get_registered_commands()) // Throws std::logic_error on duplicates. - tree.insert(split_path, command); - } + tree.insert(command->path() | std::views::split(' '), command); return tree; } diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc index 6cf7f05..6e3a757 100644 --- a/nihil.cli/dispatch_command.cc +++ b/nihil.cli/dispatch_command.cc @@ -1,9 +1,9 @@ // This source code is released into the public domain. module; -#include <stdlib.h> // getprogname, NOLINT +#include <stdlib.h> // getprogname, NOLINT #include <sysexits.h> // EX_USAGE -#include <unistd.h> // getopt +#include <unistd.h> // getopt module nihil.cli; @@ -19,39 +19,36 @@ auto print_commands(command_tree_node const &node) -> void for (auto &&child : node.children()) std::print(std::cerr, " {}\n", child.command().path()); } -} +} // namespace auto dispatch_command(int argc, char **argv) -> int { - auto tree = build_command_tree(); - - // The caller should have stripped argv[0] already. find() will - // strip all the remaining elements except the last, which means - // argv[0] will be set to something reasonable for the next call - // to getopt(). - - // find() never returns nullptr; at worst it will return the root node. - auto const *node = tree.find(argc, argv); - auto const &command = node->command(); - // Reset getopt(3) for the command, in case main() used it already. optreset = 1; optind = 1; + // Node that tree.find() never fails, at worst it will return the root node. + auto tree = build_command_tree(); + auto [node, rest] = tree.find(std::span(argv, argc)); + auto const &command = node->command(); + // 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()); + auto cprogname = std::format("{} {}", ::getprogname(), command.path()); ::setprogname(cprogname.c_str()); } // Invoke the command see what it returns. If it's an exit code, just return it. // Otherwise, handle the error. - auto ret = command.invoke(argc, argv); + auto nrest = static_cast<int>(std::ranges::distance(rest)); + // Keep the first argument, because getopt() wants it + if (nrest < argc) + ++nrest; + auto ret = command.invoke(nrest, argv + (argc - nrest)); // Restore the old progname. ::setprogname(old_progname); |
