aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nihil.cli/CMakeLists.txt11
-rw-r--r--nihil.cli/command.cc44
-rw-r--r--nihil.cli/command.ccm101
-rw-r--r--nihil.cli/command.test.cc57
-rw-r--r--nihil.cli/command_node.cc40
-rw-r--r--nihil.cli/command_node.ccm26
-rw-r--r--nihil.cli/command_tree.cc169
-rw-r--r--nihil.cli/command_tree.ccm232
-rw-r--r--nihil.cli/dispatch_command.cc42
-rw-r--r--nihil.cli/nihil.cli.ccm8
-rw-r--r--nihil.cli/registry.cc22
-rw-r--r--nihil.cli/registry.ccm3
-rw-r--r--nihil.cli/test.cc6
-rw-r--r--nihil.config/option.cc1
-rw-r--r--nihil.core/CMakeLists.txt3
-rw-r--r--nihil.core/errc.cc49
-rw-r--r--nihil.core/errc.ccm55
-rw-r--r--nihil.generator/forward.ccm2
-rw-r--r--nihil.generator/generator.ccm2
-rw-r--r--nihil.generator/generator_promise_base.ccm2
-rw-r--r--nihil.generator/manual_lifetime.ccm6
-rw-r--r--nihil.std/nihil.std.ccm11
-rw-r--r--nihil.util/CMakeLists.txt1
-rw-r--r--nihil.util/construct.ccm21
-rw-r--r--nihil.util/nihil.util.ccm1
25 files changed, 417 insertions, 498 deletions
diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt
index 9f20cf7..41fae52 100644
--- a/nihil.cli/CMakeLists.txt
+++ b/nihil.cli/CMakeLists.txt
@@ -1,22 +1,22 @@
# This source code is released into the public domain.
add_library(nihil.cli STATIC)
-target_link_libraries(nihil.cli PRIVATE nihil.std nihil.util)
+target_link_libraries(nihil.cli PRIVATE
+ nihil.std
+ nihil.generator
+ nihil.util
+)
target_sources(nihil.cli
PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
nihil.cli.ccm
command.ccm
command_tree.ccm
- command_node.ccm
dispatch_command.ccm
registry.ccm
usage_error.ccm
PRIVATE
- command.cc
- command_tree.cc
- command_node.cc
dispatch_command.cc
registry.cc
)
@@ -26,6 +26,7 @@ if(NIHIL_TESTS)
add_executable(nihil.cli.test
test.cc
+ command.test.cc
)
target_link_libraries(nihil.cli.test PRIVATE
nihil.cli
diff --git a/nihil.cli/command.cc b/nihil.cli/command.cc
deleted file mode 100644
index 6271cc6..0000000
--- a/nihil.cli/command.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// This source code is released into the public domain.
-module;
-
-// For EX_USAGE. While <sysexits.h> is deprecated, there's no other standard
-// exit code for 'usage error'; some programs use 2 (common on Linux), but
-// 2 is also used for many other exit codes.
-#include <sysexits.h>
-
-module nihil.cli;
-
-import nihil.std;
-import nihil.error;
-import :registry;
-
-namespace nihil {
-
-//NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
-command::command(std::string_view path, std::string_view usage,
- command_function_t handler)
- : command_node(path)
- , m_usage(usage)
- , m_handler(std::move(handler))
-{
- register_command(this);
-}
-
-auto command::usage(this command const &self) noexcept -> std::string_view
-{
- return self.m_usage;
-}
-
-auto command::invoke(int argc, char **argv) const
- -> std::expected<int, error>
-{
- try {
- return std::invoke(m_handler, argc, argv);
- } catch (usage_error const &err) {
- std::print(std::cerr, "{}\n", err.what());
- std::print(std::cerr, "usage: {} {}", path(), usage());
- return EX_USAGE;
- }
-}
-
-} // namespace nihil
diff --git a/nihil.cli/command.ccm b/nihil.cli/command.ccm
index da3444a..dc3f29a 100644
--- a/nihil.cli/command.ccm
+++ b/nihil.cli/command.ccm
@@ -1,44 +1,97 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
+// For EX_USAGE. While <sysexits.h> is deprecated, there's no other standard
+// exit code for 'usage error'; some programs use 2 (common on Linux), but
+// 2 is also used for many other exit codes.
+#include <sysexits.h>
+
export module nihil.cli:command;
-import nihil.error;
+// command_node represents a possibly-invocable command.
+
import nihil.std;
-import :command_node;
+import nihil.core;
+import nihil.error;
+import :usage_error;
namespace nihil {
export struct command;
-/*
- * A command that can be invoked. Instantiating a command adds this command
- * to the global command table. If an error occurs, the program will abort.
- */
+// registry.ccm
+auto register_command(command *cmd) noexcept -> void;
+
+// A command. If constructed with a handler, this is a "real" command which can be invoked.
+// Otherwise, it's a stub command that has children in the command tree.
+//
+// Because commands are intended to be global objects, they are not copyable or movable.
+// Creating an object automatically registers it in the list of commands.
+export struct command final
+{
+ using command_handler_t = std::function<int(int, char **)>;
+
+ command(std::string_view const path, std::string_view const usage, command_handler_t handler) noexcept
+ : m_path(path)
+ , m_usage(usage)
+ , m_handler(std::move(handler))
+ {
+ register_command(this);
+ }
+
+ explicit command(std::string_view const path) noexcept
+ : m_path(path)
+ {
+ }
+
+ // Not copyable.
+ command(command const &) = delete;
+ auto operator=(command const &) -> command & = delete;
+
+ // Not movable.
+ command(command &&) = delete;
+ auto operator=(command &&) -> command & = delete;
+
+ ~command() = default;
-using command_handler_t = int (int, char **);
-using command_function_t = std::function<command_handler_t>;
+ // Return the full path for this command.
+ [[nodiscard]] auto path(this command const &self) noexcept -> std::string_view
+ {
+ return self.m_path;
+ }
-export struct command final : command_node {
- command(std::string_view path, std::string_view usage,
- command_function_t);
+ // Return the one-line usage summary for this command.
+ [[nodiscard]] auto usage(this command const &self) noexcept -> std::string_view
+ {
+ return self.m_usage;
+ }
- command(std::string_view path, std::string_view usage, auto &&fn)
- : command(path, usage, command_function_t(fn))
- {}
+ // Test if this command can be invoked.
+ [[nodiscard]] auto invocable(this command const &self) noexcept -> bool
+ {
+ return static_cast<bool>(self.m_handler);
+ }
- [[nodiscard]] auto usage(this command const &) noexcept
- -> std::string_view;
+ // Invoke this command and return its result.
+ [[nodiscard]] auto invoke(this command const &self, int argc, char **argv)
+ -> std::expected<int, error>
+ {
+ if (!self.m_handler)
+ return error(errc::incomplete_command);
- [[nodiscard]] auto invoke(int argc, char **argv) const
- -> std::expected<int, error> override;
+ try {
+ return std::invoke(self.m_handler, argc, argv);
+ } catch (usage_error const &err) {
+ std::println(std::cerr, "{}", err.what());
+ std::println(std::cerr, "usage: {} {}", self.path(), self.usage());
+ return EX_USAGE;
+ }
+ }
private:
- std::string_view m_usage;
- command_function_t m_handler;
+ std::string m_path;
+ std::string m_usage;
+ command_handler_t m_handler;
};
} // namespace nihil
diff --git a/nihil.cli/command.test.cc b/nihil.cli/command.test.cc
new file mode 100644
index 0000000..5ac52ed
--- /dev/null
+++ b/nihil.cli/command.test.cc
@@ -0,0 +1,57 @@
+// This source code is released into the public domain.
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.std;
+import nihil.cli;
+
+namespace {
+inline constexpr auto *test_tags = "[nihil][nihil.cli][nihil.cli.command]";
+
+TEST_CASE("nihil::command invariants", test_tags)
+{
+ static_assert(!std::move_constructible<nihil::command>);
+ static_assert(!std::copy_constructible<nihil::command>);
+ static_assert(!std::is_copy_assignable_v<nihil::command>);
+ static_assert(!std::is_move_assignable_v<nihil::command>);
+
+ static_assert(std::destructible<nihil::command>);
+}
+
+SCENARIO("A command has a path", test_tags)
+{
+ GIVEN ("A command object with a path") {
+ auto cmd = nihil::command("foo bar baz");
+
+ THEN ("The path is correct") {
+ REQUIRE(cmd.path() == "foo bar baz");
+ }
+ }
+}
+
+SCENARIO("A command has a handler", test_tags)
+{
+ GIVEN ("A command object with a handler") {
+ auto handler_called = false;
+ auto cmd = nihil::command("foo bar baz", "foo bar baz -x", [&](int, char **) {
+ handler_called = true;
+ return 0;
+ });
+
+ THEN ("The usage is correct") {
+ REQUIRE(cmd.usage() == "foo bar baz -x");
+ }
+
+ AND_WHEN ("The command is invoked") {
+ auto ret = cmd.invoke(0, nullptr);
+
+ THEN ("The return value is 0") {
+ REQUIRE(ret.value() == 0);
+ }
+ AND_THEN ("The handler was called") {
+ REQUIRE(handler_called == true);
+ }
+ }
+ }
+}
+} // namespace
diff --git a/nihil.cli/command_node.cc b/nihil.cli/command_node.cc
deleted file mode 100644
index 5936b8c..0000000
--- a/nihil.cli/command_node.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// This source code is released into the public domain.
-module;
-
-#include <unistd.h> // _exit
-
-module nihil.cli;
-
-import nihil.std;
-import nihil.core;
-import nihil.error;
-
-namespace nihil {
-
-//NOLINTNEXTLINE(bugprone-exception-escape)
-command_node::command_node(std::string_view path) noexcept
-try : m_path(path)
-{
-} catch (std::exception const &exc) {
- std::print(std::cerr, "%s\n", exc.what());
- _exit(1);
- /*NOTREACHED*/
-}
-
-command_node::~command_node() = default;
-
-auto command_node::path(this command_node const &self) noexcept
- -> std::string_view
-{
- return self.m_path;
-}
-
-auto command_node::invoke(int, char **) const
- -> std::expected<int, error>
-{
- // If invoke() wasn't overridden, then this is an empty node,
- // so the command was incomplete.
- return std::unexpected(error(errc::incomplete_command));
-}
-
-} // namespace nihil
diff --git a/nihil.cli/command_node.ccm b/nihil.cli/command_node.ccm
deleted file mode 100644
index 25b5006..0000000
--- a/nihil.cli/command_node.ccm
+++ /dev/null
@@ -1,26 +0,0 @@
-// This source code is released into the public domain.
-export module nihil.cli:command_node;
-
-// command_node represents a possibly-invocable command.
-
-import nihil.std;
-import nihil.error;
-
-namespace nihil {
-
-export struct command_node {
- command_node(std::string_view path) noexcept;
-
- virtual ~command_node();
-
- [[nodiscard]] auto path(this command_node const &) noexcept
- -> std::string_view;
-
- [[nodiscard]] virtual auto invoke(int argc, char **argv) const
- -> std::expected<int, error>;
-
-private:
- std::string m_path;
-};
-
-} // namespace nihil
diff --git a/nihil.cli/command_tree.cc b/nihil.cli/command_tree.cc
deleted file mode 100644
index a77131a..0000000
--- a/nihil.cli/command_tree.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// This source code is released into the public domain.
-module nihil.cli;
-
-import nihil.std;
-
-namespace nihil {
-
-command_tree_node::command_tree_node()
- : m_this_word("")
- , m_command(std::make_shared<command_node>(command_node("")))
-{
-}
-
-command_tree_node::command_tree_node(command_tree_node *parent,
- std::string_view this_word)
- : m_parent(parent)
- , m_this_word(this_word)
-{
-}
-
-command_tree_node::command_tree_node(command_tree_node *parent,
- std::string_view this_word,
- std::shared_ptr<command_node> command)
- : m_parent(parent)
- , m_this_word(this_word)
- , m_command(std::move(command))
-{
-}
-
-auto command_tree_node::get_child(this command_tree_node const &self,
- std::string_view child)
- -> command_tree_node const *
-{
- if (auto it = self.m_children.find(std::string(child));
- it != self.m_children.end())
- return &it->second;
-
- return nullptr;
-}
-
-auto command_tree_node::get_child(this command_tree_node &self,
- std::string_view child)
- -> command_tree_node *
-{
- if (auto it = self.m_children.find(std::string(child));
- it != self.m_children.end())
- return &it->second;
-
- return nullptr;
-}
-
-auto command_tree_node::get_or_create_child(this command_tree_node &self,
- std::string_view child)
- -> command_tree_node *
-{
- // Return the existing child, if there is one.
- if (auto *ptr = self.get_child(child); ptr != nullptr)
- return ptr;
-
- // Insert a new child.
- auto [it, ok] = self.m_children.emplace(
- child,
- command_tree_node(&self, child));
-
- // Give the child a dummy command.
- auto path = self.m_parent != nullptr
- ? std::format("{} {}", self.m_parent->path(), child)
- : std::string(child);
-
- it->second.m_command = std::make_shared<command_node>(path);
-
- return &it->second;
-}
-
-auto command_tree_node::command(this command_tree_node const &self)
- -> std::shared_ptr<command_node> const &
-{
- return self.m_command;
-}
-
-auto command_tree_node::command(this command_tree_node &self,
- std::shared_ptr<command_node> command)
- -> void
-{
- // TODO: Put this check back without tripping from the dummy command.
- //if (self.m_command != nullptr)
- // throw std::logic_error("duplicate command");
- self.m_command = std::move(command);
-}
-
-auto command_tree_node::print_commands(this command_tree_node const &self)
- -> void
-{
- auto prefix = std::string(self.path());
-
- for (auto &&[name, node] : self.m_children) {
- auto command = prefix.empty()
- ? name
- : (prefix + ' ' + name);
- std::print(std::cerr, " {}\n", command);
- }
-}
-
-auto command_tree_node::path(this command_tree_node const &self)
- -> std::string_view
-{
- return self.m_command->path();
-}
-
-auto command_tree::insert(this command_tree &self,
- std::vector<std::string_view> const &path,
- std::shared_ptr<command_node> command)
- -> void
-{
- 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);
-
- // Set the new value.
- this_node->command(std::move(command));
-}
-
-auto command_tree::find(this command_tree const &self, int &argc, char **&argv)
- -> command_tree_node const *
-{
- 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 build_command_tree() -> command_tree
-{
- auto const &commands = get_registered_commands();
- auto tree = command_tree();
-
- for (auto &&command : commands) {
- auto split_path = std::vector<std::string_view>(
- std::from_range,
- command->path()
- | std::views::split(' ')
- | std::views::transform([] (auto &&r) {
- return std::string_view(r);
- }));
-
- // Throws std::logic_error on duplicates.
- tree.insert(split_path, command);
- }
-
- return tree;
-}
-
-} // namespace nihil
diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm
index 84e4a0d..6eae9e6 100644
--- a/nihil.cli/command_tree.ccm
+++ b/nihil.cli/command_tree.ccm
@@ -2,96 +2,174 @@
export module nihil.cli:command_tree;
import nihil.std;
+import nihil.generator;
+import nihil.util;
import :command;
+import :registry;
namespace nihil {
-/*
- * command_tree_node represents a possibly-empty node in the command tree.
- * For example, if two commands "add foo" and "add bar" are defined,
- * then "add" will be implicitly created as an empty node.
- */
-struct command_tree_node final {
- command_tree_node();
-
- command_tree_node(command_tree_node *parent,
- std::string_view this_word);
-
- command_tree_node(command_tree_node *parent,
- std::string_view this_word,
- std::shared_ptr<command_node> command);
-
- /*
- * Return a child node, or NULL if the child doesn't exist.
- */
+// command_tree_node represents a node in the command tree. Each node contains a command, which is
+// either a real command which can be invoked, or a stub command which acts as a placeholder in the
+// tree. For example, if two commands "add foo" and "add bar" are defined, then "add" will be
+// implicitly created as a stub command.
+struct command_tree_node final
+{
+ // Create an empty node with no parent. This is used as the root of the tree.
+ command_tree_node()
+ : m_command(std::make_shared<nihil::command>(""))
+ {
+ }
+
+ // Create a node that contains a command and has a parent.
+ command_tree_node(command_tree_node *parent, std::string_view const this_word,
+ std::shared_ptr<nihil::command> command)
+ : m_parent(parent)
+ , m_this_word(this_word)
+ , m_command(std::move(command))
+ {
+ }
+
+ // 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 child)
- -> command_tree_node const *;
-
- [[nodiscard]] auto get_child(this command_tree_node &self,
- std::string_view child)
- -> command_tree_node *;
-
- /*
- * Return a child node if it exists, or insert a new empty node.
- */
- [[nodiscard]] auto get_or_create_child(this command_tree_node &self,
- std::string_view child)
- -> command_tree_node *;
-
- /*
- * Set or get this node's command.
- */
- [[nodiscard]] auto command(this command_tree_node const &self)
- -> std::shared_ptr<command_node> const &;
- auto command(this command_tree_node &self,
- std::shared_ptr<command_node>)
- -> void;
-
- /*
- * Get the path of this command_node.
- */
- [[nodiscard]] auto path(this command_tree_node const &self)
- -> std::string_view;
-
- /*
- * Print this node's children in a form useful to humans.
- */
- auto print_commands(this command_tree_node const &self) -> void;
+ std::string_view const child) -> command_tree_node const *
+ {
+ if (auto it = self.m_children.find(std::string(child)); it != self.m_children.end())
+ return &it->second;
+
+ return nullptr;
+ }
+
+ [[nodiscard]] auto
+ get_child(this command_tree_node &self, std::string_view const child) -> command_tree_node *
+ {
+ if (auto it = self.m_children.find(std::string(child)); it != self.m_children.end())
+ return &it->second;
+
+ return nullptr;
+ }
+
+ // Return a child node if it exists, or insert a new empty node.
+ [[nodiscard]] auto get_or_create_child(this command_tree_node &self, std::string_view child)
+ -> command_tree_node *
+ {
+ // Return the existing child, if there is one.
+ if (auto *ptr = self.get_child(child); ptr != nullptr)
+ return ptr;
+
+ // Create a new node with a dummy command.
+ auto path = self.m_parent != nullptr
+ ? std::format("{} {}", self.m_parent->command().path(), child)
+ : 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);
+ if (!ok)
+ throw std::logic_error("failed to insert command tree node");
+ return &it->second;
+ }
+
+ // Get node's command.
+ [[nodiscard]] auto command(this command_tree_node const &self) -> command &
+ {
+ return *self.m_command;
+ }
+
+ auto command(this command_tree_node &self, std::shared_ptr<nihil::command> command) -> void
+ {
+ // If we have a stub command, allow it to be replaced. This happens when
+ // a stub node is replaced with a real node during tree building. However,
+ // if we already have an invocable command, this is an error.
+ if (self.m_command->invocable())
+ throw std::logic_error("duplicate command");
+
+ self.m_command = std::move(command);
+ }
+
+ // Yield all children of this node, depth-first.
+ auto
+ all_children(this command_tree_node const &self) -> generator<command_tree_node const &>
+ {
+ for (auto &&node : self.m_children | std::views::values) {
+ co_yield node;
+ co_yield elements_of(node.all_children());
+ }
+ }
+
+ // Yield direct children in this node.
+ auto children(this command_tree_node const &self)
+ {
+ return self.m_children | std::views::values;
+ }
private:
- command_tree_node *m_parent = nullptr;
- std::string m_this_word;
- std::shared_ptr<command_node> m_command;
- std::map<std::string, command_tree_node>
- m_children;
+ command_tree_node *m_parent = nullptr;
+ std::string m_this_word;
+ std::shared_ptr<nihil::command> m_command;
+ std::map<std::string, command_tree_node> m_children;
};
-/*
- * The command tree stores commands in a tree structure suitable for searching.
- */
-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,
- std::shared_ptr<command_node> command)
- -> void;
-
- /*
- * Find a node in the tree.
- */
- auto find(this command_tree const &self, int &argc, char **&argv)
- -> command_tree_node const *;
+// The command tree stores commands in a tree structure suitable for searching.
+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,
+ std::shared_ptr<command> command) -> void
+ {
+ 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);
+
+ // 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 *
+ {
+ 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;
+ }
private:
command_tree_node m_root_node;
};
-/*
- * Build a command tree from the registry.
- */
-[[nodiscard]] auto build_command_tree() -> command_tree;
+// Build a command tree from the registry.
+[[nodiscard]] auto build_command_tree() -> command_tree
+{
+ 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>();
+
+ // Throws std::logic_error on duplicates.
+ tree.insert(split_path, command);
+ }
+
+ return tree;
+}
} // namespace nihil
diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc
index 7b4cf39..6cf7f05 100644
--- a/nihil.cli/dispatch_command.cc
+++ b/nihil.cli/dispatch_command.cc
@@ -2,6 +2,7 @@
module;
#include <stdlib.h> // getprogname, NOLINT
+#include <sysexits.h> // EX_USAGE
#include <unistd.h> // getopt
module nihil.cli;
@@ -11,6 +12,15 @@ import nihil.core;
namespace nihil {
+namespace {
+// Print this node's children in a form useful to humans.
+auto print_commands(command_tree_node const &node) -> void
+{
+ for (auto &&child : node.children())
+ std::print(std::cerr, " {}\n", child.command().path());
+}
+}
+
auto dispatch_command(int argc, char **argv) -> int
{
auto tree = build_command_tree();
@@ -20,52 +30,44 @@ auto dispatch_command(int argc, char **argv) -> int
// 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.
+ // find() never returns nullptr; at worst it will return the root node.
auto const *node = tree.find(argc, argv);
-
- // Get the command_node.
auto const &command = node->command();
// Reset getopt(3) for the command, in case main() used it already.
optreset = 1;
optind = 1;
- /*
- * Set the program name to the existing progname plus the full path
- * to the command being invoked; this makes error messages nicer.
- */
+ // 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());
+ command.path());
::setprogname(cprogname.c_str());
}
- // Invoke the command see what it returns.
- auto ret = command->invoke(argc, argv);
+ // 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);
// Restore the old progname.
::setprogname(old_progname);
- // If the command produced an exit code, return it.
if (ret)
return *ret;
- /*
- * We have special handling for some errors.
- */
-
// Incomplete command: print the list of valid commands at this node.
if (ret.error() == errc::incomplete_command) {
- std::print(std::cerr, "{}: usage:\n", ::getprogname());
- node->print_commands();
- return 1;
+ std::println(std::cerr, "{}: usage:", ::getprogname());
+ print_commands(*node);
+ return EX_USAGE;
}
// We didn't recognise the error, so just print it and exit.
- std::print(std::cerr, "{}\n", ret.error());
+ std::println(std::cerr, "{}", ret.error());
return 1;
}
diff --git a/nihil.cli/nihil.cli.ccm b/nihil.cli/nihil.cli.ccm
index 6d98c05..5463fd9 100644
--- a/nihil.cli/nihil.cli.ccm
+++ b/nihil.cli/nihil.cli.ccm
@@ -1,14 +1,8 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
+// This source code is released into the public domain.
export module nihil.cli;
export import :command;
export import :command_tree;
-export import :command_node;
export import :dispatch_command;
export import :registry;
export import :usage_error;
diff --git a/nihil.cli/registry.cc b/nihil.cli/registry.cc
index 0f0041b..a972a65 100644
--- a/nihil.cli/registry.cc
+++ b/nihil.cli/registry.cc
@@ -5,13 +5,11 @@ 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<std::shared_ptr<command_node>> &
+// Get the registry storage. Because this is called from global ctors,
+// it handles exceptions itself.
+auto get_registry() noexcept -> std::vector<std::shared_ptr<command>> &
try {
- static auto commands = std::vector<std::shared_ptr<command_node>>();
+ static auto commands = std::vector<std::shared_ptr<command>>();
return commands;
} catch (std::exception const &exc) {
std::println(std::cerr, "{}", exc.what());
@@ -21,12 +19,10 @@ try {
std::exit(1); // NOLINT
}
-/*
- * Register a new command.
- */
+// Register a new command.
auto register_command(command *cmd) noexcept -> void
try {
- auto null_deleter = [] (command_node const *) -> void {};
+ auto null_deleter = [] (command const *) -> void {};
auto &commands = get_registry();
commands.emplace_back(cmd, null_deleter);
@@ -38,10 +34,8 @@ try {
std::exit(1); // NOLINT
}
-/*
- * Get the list of registered commands.
- */
-auto get_registered_commands() -> std::span<std::shared_ptr<command_node>>
+// Get the list of registered commands.
+auto get_registered_commands() -> std::span<std::shared_ptr<command>>
{
return {get_registry()};
}
diff --git a/nihil.cli/registry.ccm b/nihil.cli/registry.ccm
index 673f516..e5a7e29 100644
--- a/nihil.cli/registry.ccm
+++ b/nihil.cli/registry.ccm
@@ -6,13 +6,12 @@ import nihil.std;
namespace nihil {
export struct command;
-export struct command_node;
// Register a command. This is guaranteed not to throw; errors will print
// a diagnostic and exit.
auto register_command(command *cmd) noexcept -> void;
// Get previously registered commands.
-auto get_registered_commands() -> std::span<std::shared_ptr<command_node>>;
+auto get_registered_commands() -> std::span<std::shared_ptr<command>>;
} // namespace nihil
diff --git a/nihil.cli/test.cc b/nihil.cli/test.cc
index 61d164d..8253bdc 100644
--- a/nihil.cli/test.cc
+++ b/nihil.cli/test.cc
@@ -1,5 +1,7 @@
// This source code is released into the public domain.
+#include <sysexits.h>
+
#include <catch2/catch_test_macros.hpp>
import nihil.std;
@@ -69,7 +71,7 @@ TEST_CASE("nihil.cli: dispatch_command: unknown command", "[nihil.cli]")
output = capture.str();
}
- REQUIRE(ret == 1);
+ REQUIRE(ret == EX_USAGE);
auto const *progname = ::getprogname();
REQUIRE(output == std::format("{}: usage:\n cmd\n", progname));
@@ -92,7 +94,7 @@ TEST_CASE("nihil.cli: dispatch_command: incomplete command", "[nihil.cli]")
output = capture.str();
}
- REQUIRE(ret == 1);
+ REQUIRE(ret == EX_USAGE);
auto const *progname = ::getprogname();
REQUIRE(output == std::format("{}: usage:\n cmd sub1\n cmd sub2\n",
diff --git a/nihil.config/option.cc b/nihil.config/option.cc
index e09842e..50c7a1f 100644
--- a/nihil.config/option.cc
+++ b/nihil.config/option.cc
@@ -26,6 +26,7 @@ option::option(std::string_view const name, std::string_view const description)
option::~option()
{
+ std::ignore = store::get().unregister_option(this);
}
auto option::name(this option const &self) noexcept
diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt
index cbb1b6b..637b825 100644
--- a/nihil.core/CMakeLists.txt
+++ b/nihil.core/CMakeLists.txt
@@ -8,7 +8,4 @@ target_sources(nihil.core
nihil.core.ccm
errc.ccm
features.ccm
-
- PRIVATE
- errc.cc
)
diff --git a/nihil.core/errc.cc b/nihil.core/errc.cc
deleted file mode 100644
index 411ad66..0000000
--- a/nihil.core/errc.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// This source code is released into the public domain.
-module nihil.core;
-
-import nihil.std;
-
-namespace nihil {
-
-struct nihil_error_category final : std::error_category {
- [[nodiscard]] auto name() const noexcept -> char const * override;
- [[nodiscard]] auto message(int err) const -> std::string override;
-};
-
-[[nodiscard]] auto nihil_category() noexcept -> std::error_category &
-{
- static auto category = nihil_error_category();
- return category;
-}
-
-auto make_error_condition(errc ec) -> std::error_condition
-{
- return {static_cast<int>(ec), nihil_category()};
-}
-
-auto nihil_error_category::name() const noexcept -> char const *
-{
- return "nihil";
-}
-
-auto nihil_error_category::message(int err) const -> std::string
-{
- switch (static_cast<errc>(err)) {
- case errc::no_error:
- return "No error";
- case errc::incomplete_command:
- return "Incomplete command";
- case errc::empty_string:
- return "Empty string is not permitted";
- case errc::invalid_unit:
- return "Invalid unit specifier";
- case errc::failed_to_create_object:
- return "Failed to create UCL object";
- case errc::type_mismatch:
- return "UCL type does not match expected type";
- default:
- return "Undefined error";
- }
-}
-
-} // namespace nihil
diff --git a/nihil.core/errc.ccm b/nihil.core/errc.ccm
index f5aac0b..fe36274 100644
--- a/nihil.core/errc.ccm
+++ b/nihil.core/errc.ccm
@@ -8,22 +8,19 @@ namespace nihil {
export enum struct errc : std::uint8_t {
no_error = 0,
- //
+ ///////////////////////////////////////////////////////////////
// nihil.command
- //
incomplete_command,
- //
+ ///////////////////////////////////////////////////////////////
// nihil.ucl
- //
failed_to_create_object,
type_mismatch,
- //
+ ///////////////////////////////////////////////////////////////
// nihil.util
- //
// Empty string is not allowed.
empty_string,
@@ -32,10 +29,48 @@ export enum struct errc : std::uint8_t {
invalid_unit,
};
-export [[nodiscard]] auto nihil_category() noexcept -> std::error_category &;
-export [[nodiscard]] auto make_error_condition(errc ec) -> std::error_condition;
+struct nihil_error_category final : std::error_category
+{
+ [[nodiscard]] auto name() const noexcept -> char const * override
+ {
+ return "nihil";
+ }
+
+ [[nodiscard]] auto message(int err) const -> std::string override
+ {
+ switch (static_cast<errc>(err)) {
+ case errc::no_error:
+ return "No error";
+ case errc::incomplete_command:
+ return "Incomplete command";
+ case errc::empty_string:
+ return "Empty string is not permitted";
+ case errc::invalid_unit:
+ return "Invalid unit specifier";
+ case errc::failed_to_create_object:
+ return "Failed to create UCL object";
+ case errc::type_mismatch:
+ return "UCL type does not match expected type";
+ default:
+ return "Undefined error";
+ }
+ }
+};
+
+export [[nodiscard]] auto nihil_category() noexcept -> std::error_category &
+{
+ static auto category = nihil_error_category();
+ return category;
+}
+
+export [[nodiscard]] auto make_error_condition(errc ec) -> std::error_condition
+{
+ return {static_cast<int>(ec), nihil_category()};
+}
} // namespace nihil
-template<>
-struct std::is_error_condition_enum<nihil::errc> : std::true_type {};
+export template <>
+struct std::is_error_condition_enum<nihil::errc> : std::true_type
+{
+};
diff --git a/nihil.generator/forward.ccm b/nihil.generator/forward.ccm
index 71e0ddd..2aa2448 100644
--- a/nihil.generator/forward.ccm
+++ b/nihil.generator/forward.ccm
@@ -21,6 +21,6 @@ namespace nihil {
export template <typename Ref,
typename Value = std::remove_cvref_t<Ref>,
typename Allocator = use_allocator_arg>
-class generator;
+struct generator;
} // namespace nihil
diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm
index 7627052..a2fcafb 100644
--- a/nihil.generator/generator.ccm
+++ b/nihil.generator/generator.ccm
@@ -32,7 +32,7 @@ namespace nihil {
// TODO : make layout compatible promise casts possible
export template <typename Ref, typename Value, typename Alloc>
-class generator
+struct generator
{
using byte_allocator = byte_allocator_t<Alloc>;
diff --git a/nihil.generator/generator_promise_base.ccm b/nihil.generator/generator_promise_base.ccm
index 30c3011..6b11b40 100644
--- a/nihil.generator/generator_promise_base.ccm
+++ b/nihil.generator/generator_promise_base.ccm
@@ -24,7 +24,7 @@ template <typename Ref>
struct generator_promise_base
{
template <typename Ref2, typename Value, typename Alloc>
- friend class generator;
+ friend struct generator;
generator_promise_base *m_root;
std::coroutine_handle<> m_parent_or_leaf;
diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm
index 4a383ad..44bc0a8 100644
--- a/nihil.generator/manual_lifetime.ccm
+++ b/nihil.generator/manual_lifetime.ccm
@@ -64,14 +64,14 @@ private:
};
template <typename T>
-class manual_lifetime<T &> {
+struct manual_lifetime<T &> {
manual_lifetime() noexcept = default;
~manual_lifetime() = default;
auto construct(this manual_lifetime &self, T &value) noexcept -> T &
{
self.m_value = std::addressof(value);
- return self.m_value;
+ return value;
}
auto destruct(this manual_lifetime &) noexcept -> void
@@ -88,7 +88,7 @@ private:
};
template <typename T>
-class manual_lifetime<T &&> {
+struct manual_lifetime<T &&> {
manual_lifetime() noexcept = default;
~manual_lifetime() = default;
diff --git a/nihil.std/nihil.std.ccm b/nihil.std/nihil.std.ccm
index 2f01c91..e17d8e2 100644
--- a/nihil.std/nihil.std.ccm
+++ b/nihil.std/nihil.std.ccm
@@ -322,11 +322,14 @@ using std::ranges::subrange;
using std::ranges::split_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::split;
using std::ranges::views::transform;
+using std::ranges::views::values;
} // namespace views
} // namespace ranges
@@ -386,8 +389,16 @@ using std::false_type;
using std::invoke_result;
using std::is_convertible;
using std::is_convertible_v;
+using std::is_copy_constructible;
+using std::is_copy_constructible_v;
+using std::is_copy_assignable;
+using std::is_copy_assignable_v;
using std::is_default_constructible;
using std::is_default_constructible_v;
+using std::is_move_constructible;
+using std::is_move_constructible_v;
+using std::is_move_assignable;
+using std::is_move_assignable_v;
using std::is_nothrow_constructible;
using std::is_nothrow_constructible_v;
using std::is_nothrow_destructible;
diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt
index 109e4d4..2fb5111 100644
--- a/nihil.util/CMakeLists.txt
+++ b/nihil.util/CMakeLists.txt
@@ -12,6 +12,7 @@ target_sources(nihil.util
nihil.util.ccm
capture_stream.ccm
+ construct.ccm
ctype.ccm
parse_size.ccm
next_word.ccm
diff --git a/nihil.util/construct.ccm b/nihil.util/construct.ccm
new file mode 100644
index 0000000..0a0d85d
--- /dev/null
+++ b/nihil.util/construct.ccm
@@ -0,0 +1,21 @@
+// This source code is released into the public domain.
+export module nihil.util:construct;
+
+import nihil.std;
+
+namespace nihil {
+
+// A functor that constructs objects of an arbitrary type.
+// Useful for std::views::transform.
+template <typename T>
+struct construct_fn {
+ auto operator()(this construct_fn const &, auto &&...args) -> T
+ {
+ return T(std::forward<decltype(args)>(args)...);
+ }
+};
+
+export template<typename T>
+inline constexpr auto construct = construct_fn<T>{};
+
+}
diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm
index d8628a4..132978e 100644
--- a/nihil.util/nihil.util.ccm
+++ b/nihil.util/nihil.util.ccm
@@ -2,6 +2,7 @@
export module nihil.util;
export import :capture_stream;
+export import :construct;
export import :ctype;
export import :parse_size;
export import :next_word;