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 | |
| parent | e461c17c24115132601362a96b6a85c0dd12f471 (diff) | |
| download | nihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.gz nihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.bz2 | |
cli: clean up
| -rw-r--r-- | CMakeLists.txt | 12 | ||||
| -rw-r--r-- | nihil.cli/command_tree.ccm | 64 | ||||
| -rw-r--r-- | nihil.cli/dispatch_command.cc | 31 | ||||
| -rw-r--r-- | nihil.std/nihil.std.ccm | 12 |
4 files changed, 72 insertions, 47 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 32a8f81..c51c2aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,8 @@ option(NIHIL_CONFIG "Build the nihil.config library" ON) option(NIHIL_UCL "Build the nihil.ucl library" ON) option(NIHIL_TESTS "Build nihil's unit tests" ON) option(NIHIL_TIDY "Run clang-tidy during build" ON) +option(NIHIL_ASAN "Enable Address Sanitizer" OFF) +option(NIHIL_UBSAN "Enable Undefined Behaviour Sanitizer" OFF) set(CMAKE_CXX_STANDARD 26) @@ -41,6 +43,16 @@ add_compile_options(-Wextra) add_compile_options(-Werror) add_compile_options(-Wpedantic) +if(NIHIL_ASAN) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +if(NIHIL_UBSAN) + add_compile_options(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) +endif() + # Enable libc++ hardening add_compile_definitions(-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) 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); diff --git a/nihil.std/nihil.std.ccm b/nihil.std/nihil.std.ccm index e17d8e2..80ca3c9 100644 --- a/nihil.std/nihil.std.ccm +++ b/nihil.std/nihil.std.ccm @@ -90,12 +90,14 @@ using std::max; namespace ranges { using std::ranges::all_of; using std::ranges::copy; +using std::ranges::count_if; using std::ranges::equal; using std::ranges::fill; using std::ranges::fill_n; using std::ranges::find; using std::ranges::find_if; using std::ranges::find_if_not; +using std::ranges::fold_left; using std::ranges::generate; using std::ranges::generate_n; } // namespace ranges @@ -120,6 +122,7 @@ using std::cmp_less_equal; using std::cmp_not_equal; // <concepts> +using std::constructible_from; using std::convertible_to; using std::copy_constructible; using std::copy_constructible; @@ -304,6 +307,10 @@ using std::from_range; using std::from_range_t; namespace ranges { +using std::ranges::iterator_t; +//using std::ranges::const_iterator_t; // not in libc++ +using std::ranges::sentinel_t; +//using std::ranges::const_sentinel_t; // not in libc++ using std::ranges::range_value_t; using std::ranges::contiguous_range; @@ -312,6 +319,7 @@ using std::ranges::range; using std::ranges::sized_range; using std::ranges::begin; +using std::ranges::distance; using std::ranges::empty; using std::ranges::end; using std::ranges::data; @@ -320,14 +328,18 @@ using std::ranges::rbegin; using std::ranges::rend; using std::ranges::subrange; +using std::ranges::drop_view; using std::ranges::split_view; +using std::ranges::take_while_view; using std::ranges::transform_view; using std::ranges::values_view; using std::ranges::operator|; using std::ranges::to; namespace views { +using std::ranges::views::drop; using std::ranges::views::split; +using std::ranges::views::take_while; using std::ranges::views::transform; using std::ranges::views::values; } // namespace views |
