/* * This source code is released into the public domain. */ #include #include #include "command_map.hh" namespace lfjail::command { namespace { // A node in the command hierarchy. struct node { // The command name of this node. std::string command; // Handler for this node. May be null, which means this node has // sub-commands but isn't a command itself. detail::command_base *handler = nullptr; node(std::string command); // Run the handler for this node. auto invoke(context const &ctx, int argc, char **argv) const -> int; // Create a new node under this one, or return it if it already exists. // If path is empty, return this node. auto create_node(std::string_view path) -> node&; auto print_usage(std::string prefix) const -> void; private: std::map commands; }; node::node(std::string command_) : command(std::move(command_)) {} auto node::invoke(context const &ctx, int argc, char **argv) const -> int { // Look for a subcommand with argv[0]. auto it = commands.find(argv[0]); if (it == commands.end()) { std::print(std::cerr, "{}: unknown command\n", ::getprogname()); throw usage_error(); } auto const &child = it->second; // If the child has a handler, invoke it. if (child.handler != nullptr) return child.handler->invoke(ctx, argc, argv); --argc; ++argv; if (argc == 0) { std::print(std::cerr, "{}: incomplete command\n", ::getprogname()); throw usage_error(); } // Otherwise, continue recursing. return child.invoke(ctx, argc, argv); } auto node::create_node(std::string_view path) -> node& { auto child = next_word(&path); if (child.empty()) return *this; // If the child node doesn't exist, insert an empty node. auto it = commands.find(child); if (it == commands.end()) { std::tie(it, std::ignore) = commands.insert(std::pair{child, node(std::string(child))}); } if (path.empty()) return it->second; return it->second.create_node(path); } void node::print_usage(std::string prefix) const { if (handler != nullptr) std::print("{}{}\n", prefix, command); for (auto const &it : commands) it.second.print_usage(prefix + command + " "); } // This may be called before main(), so catch any exceptions. auto get_root_node() noexcept -> node& try { static node root_node(""); return root_node; } catch (std::exception const &exc) { std::cerr << "lfjail: ERROR: get_root_node: " << exc.what() << "\n"; std::exit(1); } } // anonymous namespace auto dispatch(context const &ctx, int argc, char **argv) -> int { return get_root_node().invoke(ctx, argc, argv); } void print_usage(std::string_view prefix) { get_root_node().print_usage(std::string(prefix)); } namespace detail { /* * This is usually called at global scope before main() runs, so don't * propagate exceptions. */ command_base::command_base(std::string_view path) noexcept try { auto &node = get_root_node().create_node(path); node.handler = this; } catch (std::exception const &exc) { std::cerr << "lfjail: ERROR: failed to initialise command " << path << ": " << exc.what() << "\n"; std::abort(); } } } // namespace lfjail