//----------------------------------------------------------------------------- // ___ __ _ _ // / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ // / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / // / ___/ (_| | | \__ \ __/ /__| | | | | < // \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . // //----------------------------------------------------------------------------- // Author: Kurt Sassenrath // Module: Utility // // Command line argument parser. Pretty rough-n-ready, but gets the job done // for starting the server. // // Copyright (c) 2023 Kurt Sassenrath. // // License TBD. //----------------------------------------------------------------------------- #ifndef argparse_d2ddac0dab0d7b88 #define argparse_d2ddac0dab0d7b88 #include #include #include #include #include #include #include #include #include #include #include #include // Simple command line parser for testing executables. namespace argparse { namespace custom { template struct argument_parser {}; template concept has_parser = requires { { argument_parser>::parse(std::string_view{}) } -> std::same_as; }; } template struct argument_parser {}; template <> struct argument_parser { static bool* parse(std::string_view value) noexcept { if (value == "1" || value == "true") { return new bool{true}; } else if (value == "0" || value == "false") { return new bool{false}; } return nullptr; } }; inline constexpr std::initializer_list< std::tuple> unit_map = { {"ns", std::chrono::nanoseconds{1}}, {"us", std::chrono::microseconds{1}}, {"ms", std::chrono::milliseconds{1}}, {"s", std::chrono::seconds{1}}, {"m", std::chrono::minutes{1}}, {"h", std::chrono::hours{1}}, }; template struct argument_parser> { using duration = std::chrono::duration; static duration* parse(std::string_view value) noexcept { rep result; auto err = std::from_chars(value.begin(), value.end(), result); if (err.ec == std::errc{}) { auto this_unit = std::string_view{err.ptr, value.end()}; for (auto const& [unit, dura] : unit_map) { if (std::string_view{unit} == this_unit) { auto v = std::chrono::duration_cast(result * dura); return new duration{v}; } } } return nullptr; } }; template requires requires (T& t) { std::from_chars(nullptr, nullptr, t); } struct argument_parser { static T* parse(std::string_view value) noexcept { T result; auto err = std::from_chars(value.begin(), value.end(), result); return err.ec == std::errc{} ? new T{result} : nullptr; } }; template <> struct argument_parser { static std::string* parse(std::string_view value) noexcept { return new std::string{value}; } }; template concept has_parser = requires { { argument_parser>::parse(std::string_view{}) } -> std::same_as; }; static_assert(has_parser); namespace detail { // This wrapper acts similar to std::any, but also provides a method to // parse a string_view for the value as well. Parsers can be implemented // by creating an argparse::custom::argument_parser template specialization // for a given type. struct any_arg { constexpr any_arg() = default; template any_arg(T&& value) : iface(&dispatcher>::table) { dispatcher>::create(*this, std::forward(value)); } any_arg(any_arg const& other) : iface(other.iface) { if (other.iface) { data = other.iface->copy(other.data); } } any_arg(any_arg&& other) : data(std::move(other.data)), iface(other.iface) { other.iface = nullptr; } any_arg& operator=(any_arg const& other) { if (has_value()) { iface->destroy(data); } iface = other.iface; if (other.iface) { data = other.iface->copy(other.data); } return *this; } ~any_arg() { if (has_value()) { iface->destroy(data); } } bool parse(std::string_view sv) { return iface->parse(*this, sv); } bool has_value() const noexcept { return static_cast(iface); } template bool holds() const noexcept { return iface == &dispatcher>::table; } template friend T const* arg_cast(any_arg const*) noexcept; void* data = nullptr; struct interface { void (*destroy)(void*); void *(*copy)(void*); bool (*parse)(any_arg&, std::string_view); }; interface const* iface = nullptr; template struct dispatcher { template static void create(any_arg& self, Args&&... args) { self.data = new T{std::forward(args)...}; } static void* copy(void *ptr) { return new T{*static_cast(ptr)}; } static bool parse(any_arg& self, std::string_view sv) { if constexpr (custom::has_parser) { self.data = custom::argument_parser::parse(sv); return static_cast(self.data); } else if constexpr (has_parser) { self.data = argument_parser::parse(sv); return static_cast(self.data); } else { return false; } } static void destroy(void* ptr) { delete static_cast(ptr); } static T const* cast(void* ptr) { return static_cast(ptr); } static constexpr struct interface table { &dispatcher::destroy, &dispatcher::copy, &dispatcher::parse }; }; }; template T const* arg_cast(any_arg const* ar) noexcept { if (ar->holds()) { return any_arg::dispatcher>::cast(ar->data); } return nullptr; } } class command_line_parser { public: using argument = detail::any_arg; constexpr static char delimiter = '='; using opt_list = std::initializer_list< std::tuple>; struct result { enum class code { no_error, unknown_option, bad_option_value, }; code ec = code::no_error; std::string error_value; std::map> opts; std::vector arguments; explicit constexpr operator bool() const noexcept { return ec == code::no_error; } template T const* maybe_opt(std::string_view name) const noexcept { auto entry = opts.find(name); return entry != opts.end() ? detail::arg_cast(&entry->second) : nullptr; } template T const& opt(std::string_view opt_name) const { auto const* v = maybe_opt(opt_name); if (!v) { throw std::bad_cast{}; } return *v; } std::string_view operator[](std::size_t index) const { return arguments[index]; } }; command_line_parser() noexcept = default; explicit command_line_parser(opt_list opts) { for (auto const& [name, value_type] : opts) { add_option(name, value_type); } } bool add_option(std::string_view name, argument value) { options_[std::string{name}] = value; return true; } result parse(std::span arglist) { return parse_inner(arglist); } template T const* get_option(std::string_view name) { auto entry = options_.find(name); if (entry != options_.end()) { return detail::arg_cast(&entry->second); } return nullptr; } private: static auto split_option(std::string_view kv) { auto delim = kv.find(delimiter); if (delim != kv.npos) { return std::make_tuple(kv.substr(0, delim), kv.substr(delim + 1)); } else { return std::make_tuple(kv, std::string_view{}); } } result parse_inner(std::span arglist) { result res; for (auto const& [key, value] : options_) { res.opts[key] = value; } for (auto ar : arglist) { if (ar.empty()) continue; // ??? if (ar.substr(0, 2) == "--") { auto [key, value] = split_option(ar.substr(2)); auto opt_entry = res.opts.find(key); if (opt_entry == res.opts.end()) { res.ec = result::code::unknown_option; res.error_value = std::string{ar}; return res; } if (!opt_entry->second.parse(value)) { res.ec = result::code::bad_option_value; res.error_value = std::string{value}; return res; } } else { res.arguments.emplace_back(ar); } } return res; } std::map> options_; std::vector arguments_; }; } #endif // argparse_d2ddac0dab0d7b88