From d24315268c11d435bb9accbce87b7f46dda6ed3e Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Sun, 29 Jun 2025 00:42:31 +0100 Subject: cli: improve command dispatch a bit --- nihil.cli/command_map.cc | 267 ----------------------------------------------- 1 file changed, 267 deletions(-) delete mode 100644 nihil.cli/command_map.cc (limited to 'nihil.cli/command_map.cc') diff --git a/nihil.cli/command_map.cc b/nihil.cli/command_map.cc deleted file mode 100644 index c656c62..0000000 --- a/nihil.cli/command_map.cc +++ /dev/null @@ -1,267 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include -#include - -#include - -module nihil.cli; - -/* - * command_map represents a hierarchical list of commands. At each level, - * a command is mapped to a handler, which can either be a function, in - * which case we execute the function, or another command_map, in which - * case we invoke the new map - */ - -namespace nihil { - -/* - * The string tree we store our commands in. This is sort of like a very - * basic hierarchical std::map. Keys are provided as a range of values, - * typically from argv. - */ - -struct command_tree_node final { - command_tree_node() - : _this_word("") - { - } - - command_tree_node(std::string_view this_word) - : _this_word(this_word) - { - } - - command_tree_node(std::string_view this_word, - command value) - : _this_word(this_word) - , _value(std::move(value)) - { - } - - /* - * Return a child node, or NULL if the child doesn't exist. - */ - auto get_child(this command_tree_node const &self, - std::string_view child) - -> command_tree_node const * - { - if (auto it = self.children.find(std::string(child)); - it != self.children.end()) - return &it->second; - - return nullptr; - } - - auto get_child(this command_tree_node &self, - std::string_view child) - -> command_tree_node * - { - if (auto it = self.children.find(std::string(child)); - it != self.children.end()) - return &it->second; - - return nullptr; - } - - /* - * Return a child node if it exists, or insert a new empty node. - */ - auto get_or_create_child(this command_tree_node &self, - std::string_view child) - -> command_tree_node * - { - if (auto ptr = self.get_child(child); ptr != nullptr) - return ptr; - - auto [it, ok] = self.children.emplace( - child, command_tree_node(child)); - return &it->second; - } - - /* - * Return this node's value. - */ - auto value(this command_tree_node const &self) - -> std::optional const & - { - return self._value; - } - - /* - * Set this node's value. - */ - auto value(this command_tree_node &self, command value) -> void - { - self._value = std::move(value); - } - - /* - * Print this node's children in a form useful to humans. - */ - auto print_commands(this command_tree_node const &self, - std::string_view prefix) -> void - { - for (auto &&[name, node] : self.children) { - std::print(" {} {}\n", prefix, name); - } - } - -private: - std::string _this_word; - std::optional _value; - std::unordered_map children; -}; - -struct command_tree { - /* - * Add a node to the tree. Returns false if the node already exists. - */ - auto insert(this command_tree &self, - std::ranges::range auto &&path, - command value) - -> bool - { - auto *this_node = &self._root_node; - - // Find the node for this key. - for (auto &&next : path) { - auto this_word = std::string_view(next); - this_node = this_node->get_or_create_child(this_word); - } - - if (this_node->value()) { - // The value already exists. - return false; - } - - // Set the new value. - this_node->value(std::move(value)); - return true; - } - - /* - * Find a node in the tree. Unlike insert(), iteration stops when - * we find any node with a value. - */ - auto find(this command_tree const &self, int &argc, char **&argv) - -> std::optional - { - auto *this_node = &self._root_node; - - // Assume the caller already stripped the program name from - // argv. This is usually the case since they would have - // called getopt(). - - // Store the command bits we got so far, so we can print them - // in the usage error if needed. - auto path = std::string(); - - while (argv[0] != nullptr) { - auto next = std::string_view(argv[0]); - - auto *next_node = this_node->get_child(next); - - if (next_node == nullptr) { - // The node doesn't exist, so this command is - // not valid. Print a list of valid commands. - std::print(std::cerr, - "{}: unknown command: {} {}\n", - ::getprogname(), path, next); - std::print(std::cerr, - "{}: expected one of:\n", - ::getprogname()); - - this_node->print_commands(path); - return {}; - } - - this_node = next_node; - - if (this_node->value()) - // This node has a value, so return it. - return {this_node->value()}; - - if (!path.empty()) - path += ' '; - path += next; - --argc; - ++argv; - } - - // We didn't find a value, so the key was incomplete. - std::print(std::cerr, "{}: {} command; expected:\n", - ::getprogname(), - path.empty() ? "missing" : "incomplete"); - this_node->print_commands(path); - - return {}; - } - -private: - command_tree_node _root_node; -}; - -/* - * The global command map. - */ -auto get_commands() -> command_tree & { - static auto commands = command_tree(); - return commands; -} - -auto register_command(std::string_view path, command *cmd) noexcept -> void -try { - auto &commands = get_commands(); - if (commands.insert(path | std::views::split(' '), *cmd) == false) { - std::printf("command registration failed\n"); - std::abort(); - } -} catch (...) { - std::printf("command registration failed\n"); - std::abort(); -} - -auto dispatch_command(int argc, char **argv) -> int -{ - auto &commands = get_commands(); - - // 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(). - - auto node = commands.find(argc, argv); - - if (!node) - // find() already printed the error message - return 1; - - // Reset getopt here for the command to use. - optreset = 1; - optind = 1; - - // Calling setprogname() makes error messages more relevant. - auto cprogname = std::format("{} {}", ::getprogname(), - node->path()); - ::setprogname(cprogname.c_str()); - - return node->invoke(argc, argv); -} - -void print_usage(std::string_view) -{ -// get_root_node().print_usage(std::string(prefix)); -} - -} // namespace nihil -- cgit v1.2.3