aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.cli/dispatch_command.cc
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-29 00:42:31 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-29 00:42:31 +0100
commitd24315268c11d435bb9accbce87b7f46dda6ed3e (patch)
tree66589cb6a15fa74d4b09683105c583e4a5c222b4 /nihil.cli/dispatch_command.cc
parent7741a9698d29f79aca3e47495dcdf87c7a712f42 (diff)
downloadnihil-d24315268c11d435bb9accbce87b7f46dda6ed3e.tar.gz
nihil-d24315268c11d435bb9accbce87b7f46dda6ed3e.tar.bz2
cli: improve command dispatch a bit
Diffstat (limited to 'nihil.cli/dispatch_command.cc')
-rw-r--r--nihil.cli/dispatch_command.cc82
1 files changed, 82 insertions, 0 deletions
diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc
new file mode 100644
index 0000000..736e16e
--- /dev/null
+++ b/nihil.cli/dispatch_command.cc
@@ -0,0 +1,82 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cstdio>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <print>
+#include <ranges>
+#include <string>
+#include <utility>
+
+#include <unistd.h>
+
+module nihil.cli;
+
+import nihil.core;
+
+namespace nihil {
+
+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);
+
+ // 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.
+ */
+ auto *old_progname = ::getprogname();
+
+ {
+ auto cprogname = std::format("{} {}", ::getprogname(),
+ command->path());
+ ::setprogname(cprogname.c_str());
+ }
+
+ // Invoke the command see what it returns.
+ 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;
+ }
+
+ // We didn't recognise the error, so just print it and exit.
+ std::print(std::cerr, "{}\n", ret.error());
+ return 1;
+}
+
+} // namespace nihil