// This source code is released into the public domain. module; // For EX_USAGE. While 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 export module nihil.cli:command; // command_node represents a possibly-invocable command. import nihil.std; import nihil.core; import nihil.error; import :usage_error; namespace nihil { export struct command; // 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; // Construct a new command with a handler and register it. Since this is usually invoked // at global scope by static object construction, we handle exception internally. command(std::string_view const path, std::string_view const usage, auto &&handler) noexcept try : m_path(path) , m_usage(usage) , m_handler(std::forward(handler)) { register_command(this); } catch (std::exception const &exc) { std::println(std::cerr, "register_command(): {}", exc.what()); std::quick_exit(1); } catch (...) { std::println(std::cerr, "register_command(): unknown error"); std::quick_exit(1); } // Create a stub command which doesn't have a handler. explicit command(std::string_view const path) : 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; // Return the full path for this command. [[nodiscard]] auto path(this command const &self) noexcept -> std::string_view { return self.m_path; } // Return the one-line usage summary for this command. [[nodiscard]] auto usage(this command const &self) noexcept -> std::string_view { return self.m_usage; } // Test if this command can be invoked. [[nodiscard]] auto invocable(this command const &self) noexcept -> bool { return static_cast(self.m_handler); } // Invoke this command and return its result. [[nodiscard]] auto invoke(this command const &self, int argc, char **argv) -> std::expected { if (!self.m_handler) return error(errc::incomplete_command); 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 m_path; std::string m_usage; command_handler_t m_handler; }; } // namespace nihil