aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-07-01 21:12:11 +0100
committerLexi Winter <lexi@le-fay.org>2025-07-01 21:12:11 +0100
commit85baf16dd366fb501dc522a0957ec680dc9478f0 (patch)
tree64b9a9463c6b886e9784b1fce9ff5c80dc7e0a23
parente461c17c24115132601362a96b6a85c0dd12f471 (diff)
downloadnihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.gz
nihil-85baf16dd366fb501dc522a0957ec680dc9478f0.tar.bz2
cli: clean up
-rw-r--r--CMakeLists.txt12
-rw-r--r--nihil.cli/command_tree.ccm64
-rw-r--r--nihil.cli/dispatch_command.cc31
-rw-r--r--nihil.std/nihil.std.ccm12
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