From 3d5ee02b17481270b79fcd20182aa913045e25d3 Mon Sep 17 00:00:00 2001 From: tqcq <99722391+tqcq@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:42:43 +0800 Subject: [PATCH] feat add cxxopts --- src/sled/nonstd/cxxopts.h | 2007 +++++++++++++++++++++++++++++++++++++ src/sled/sled.h | 1 + 2 files changed, 2008 insertions(+) create mode 100644 src/sled/nonstd/cxxopts.h diff --git a/src/sled/nonstd/cxxopts.h b/src/sled/nonstd/cxxopts.h new file mode 100644 index 0000000..8c141c2 --- /dev/null +++ b/src/sled/nonstd/cxxopts.h @@ -0,0 +1,2007 @@ +/* + +Copyright (c) 2014 - 2021 Jarryd Beck +Copyright (c) 2021 - 2023 Pavel Artemkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __has_include +#if __has_include() +#include +#ifdef __cpp_lib_optional +#define CXXOPTS_HAS_OPTIONAL +#endif +#endif +#endif + +#ifdef CXXOPTS_USE_UNICODE +#include +#endif + +#if __cplusplus >= 200809L +#define CXXOPTS_NORETURN [[noreturn]] +#else +#define CXXOPTS_NORETURN +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#if __cplusplus >= 202002L +#define CXXOPTS_CONSTEXPR constexpr +#else +#define CXXOPTS_CONSTEXPR +#endif + +// Disable exceptions if the specific compiler flags are set. +#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#define CXXOPTS_NO_EXCEPTIONS +#endif + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 5 +#define CXXOPTS__VERSION_MINOR 3 +#define CXXOPTS__VERSION_PATCH 1 + +namespace cxxopts { + +static constexpr struct { + uint8_t major, minor, patch; +} version = {CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, CXXOPTS__VERSION_PATCH}; + +#ifdef _WIN32 +static const std::string LQUOTE("\'"); +static const std::string RQUOTE("\'"); +#else +static const std::string LQUOTE("‘"); +static const std::string RQUOTE("’"); +#endif + +static constexpr std::size_t OPTION_LONGEST = 30; +static constexpr std::size_t OPTION_DESC_GAP = 2; +static constexpr std::size_t OPTION_TAB_SIZE = 8; + +#ifdef CXXOPTS_USE_UNICODE +using cxx_string = icu::UnicodeString; +#else +using cxx_string = std::string; +#endif + +}// namespace cxxopts + +// when we ask cxxopts to use Unicode, help strings are processed using ICU, +// which results in the correct lengths being computed for strings when they +// are formatted for the help output +// it is necessary to make sure that can be found by the +// compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE + +namespace cxxopts { + +static inline cxx_string +to_local_string(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +class unicode_string_iterator { +public: + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + using pointer = value_type *; + using reference = value_type &; + + unicode_string_iterator(const icu::UnicodeString *string, int32_t pos) : s(string), i(pos) {} + + value_type operator*() const { return s->char32At(i); } + + bool operator==(const unicode_string_iterator &rhs) const { return s == rhs.s && i == rhs.i; } + + bool operator!=(const unicode_string_iterator &rhs) const { return !(*this == rhs); } + + unicode_string_iterator &operator++() + { + ++i; + return *this; + } + + unicode_string_iterator operator+(int32_t v) { return unicode_string_iterator(s, i + v); } + +private: + const icu::UnicodeString *s; + int32_t i; +}; + +static inline cxx_string & +string_append(cxx_string &s, cxx_string a) +{ + return s.append(std::move(a)); +} + +static inline cxx_string & +string_append(cxx_string &s, std::size_t n, UChar32 c) +{ + for (std::size_t i = 0; i != n; ++i) { s.append(c); } + + return s; +} + +template +static inline cxx_string & +string_append(cxx_string &s, Iterator begin, Iterator end) +{ + while (begin != end) { + s.append(*begin); + ++begin; + } + + return s; +} + +static inline std::size_t +string_length(const cxx_string &s) +{ + return s.length(); +} + +static inline std::string +to_utf8_string(const cxx_string &s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +static inline bool +empty(const cxx_string &s) +{ + return s.isEmpty(); +} + +}// namespace cxxopts + +namespace std { + +cxxopts::unicode_string_iterator +begin(const icu::UnicodeString &s) +{ + return cxxopts::unicode_string_iterator(&s, 0); +} + +cxxopts::unicode_string_iterator +end(const icu::UnicodeString &s) +{ + return cxxopts::unicode_string_iterator(&s, s.length()); +} + +}// namespace std + +#else// ifdef CXXOPTS_USE_UNICODE + +namespace cxxopts { + +template +static inline T +to_local_string(T &&t) +{ + return std::forward(t); +} + +CXXOPTS_CONSTEXPR +static inline size_t +string_length(const cxx_string &s) noexcept +{ + return s.length(); +} + +CXXOPTS_CONSTEXPR +static inline cxx_string & +string_append(cxx_string &s, const cxx_string &a) +{ + return s.append(a); +} + +CXXOPTS_CONSTEXPR +static inline cxx_string & +string_append(cxx_string &s, std::size_t n, char c) +{ + return s.append(n, c); +} + +template +CXXOPTS_CONSTEXPR static inline cxx_string & +string_append(cxx_string &s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template +static inline std::string +to_utf8_string(T &&t) +{ + return std::forward(t); +} + +CXXOPTS_CONSTEXPR +static inline bool +empty(const std::string &s) noexcept +{ + return s.empty(); +} + +}// namespace cxxopts + +#endif// ifdef CXXOPTS_USE_UNICODE + +/** + * \defgroup Exceptions + * @{ + */ + +namespace cxxopts { + +class option_error : public std::runtime_error { +public: + explicit option_error(const std::string &what_arg) : std::runtime_error(what_arg) {} +}; + +class parse_error : public option_error { +public: + explicit parse_error(const std::string &what_arg) : option_error(what_arg) {} +}; + +class spec_error : public option_error { +public: + explicit spec_error(const std::string &what_arg) : option_error(what_arg) {} +}; + +class option_exists_error : public spec_error { +public: + explicit option_exists_error(const std::string &option) + : spec_error("Option " + LQUOTE + option + RQUOTE + " already exists") + {} +}; + +class invalid_option_format_error : public spec_error { +public: + explicit invalid_option_format_error(const std::string &format) + : spec_error("Invalid option format " + LQUOTE + format + RQUOTE) + {} +}; + +class option_syntax_error : public parse_error { +public: + explicit option_syntax_error(const std::string &text) + : parse_error("Argument " + LQUOTE + text + RQUOTE + " starts with '-' but has incorrect syntax") + {} +}; + +class option_not_exists_error : public parse_error { +public: + explicit option_not_exists_error(const std::string &option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " does not exist") + {} +}; + +class missing_argument_error : public parse_error { +public: + explicit missing_argument_error(const std::string &option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " is missing an argument") + {} +}; + +class option_requires_argument_error : public parse_error { +public: + explicit option_requires_argument_error(const std::string &option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " requires an argument") + {} +}; + +class option_not_present_error : public parse_error { +public: + explicit option_not_present_error(const std::string &option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " not present") + {} +}; + +class argument_incorrect_type : public parse_error { +public: + explicit argument_incorrect_type(const std::string &arg, const std::string &type = {}) + : parse_error("Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + + (type.empty() ? std::string() : (": " + type + " expected"))) + {} +}; + +class option_has_no_value_error : public option_error { +public: + explicit option_has_no_value_error(const std::string &name) + : option_error(name.empty() ? "Option has no value" : "Option " + LQUOTE + name + RQUOTE + " has no value") + {} +}; + +namespace detail { + +template +CXXOPTS_NORETURN void +throw_or_mimic(Args &&...args) +{ + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{std::forward(args)...}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and terminate. + T exception{std::forward(args)...}; + std::cerr << exception.what() << std::endl; + std::terminate(); +#endif +} + +}// namespace detail +}// namespace cxxopts + +/**@}*/ + +/** + * \defgroup Value parsing + * @{ + */ + +namespace cxxopts { +namespace detail { + +template +struct signed_check; + +template +struct signed_check { + template + CXXOPTS_CONSTEXPR bool operator()(const U u, const bool negative) const noexcept + { + return ((static_cast(std::numeric_limits::min()) >= u) && negative) + || (static_cast(std::numeric_limits::max()) >= u); + } +}; + +template +struct signed_check { + template + CXXOPTS_CONSTEXPR bool operator()(const U, const bool) const noexcept + { + return true; + } +}; + +template +CXXOPTS_CONSTEXPR inline bool +check_signed_range(const U value, const bool negative) noexcept +{ + return signed_check::is_signed>()(value, negative); +} + +template +CXXOPTS_CONSTEXPR inline bool +checked_negate(R &r, T &&t, std::true_type) noexcept +{ + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t - 1) - 1); + return true; +} + +template +CXXOPTS_CONSTEXPR inline bool +checked_negate(R &, T &&, std::false_type) noexcept +{ + return false; +} + +inline bool +parse_uint64(const std::string &text, uint64_t &value, bool &negative) noexcept +{ + const char *p = text.c_str(); + // String should not be empty. + if (*p == 0) { return false; } + // Parse sign. + if (*p == '+') { + ++p; + } else if (*p == '-') { + negative = true; + ++p; + } + // Not an integer value. + if (*p == 0) { + return false; + } else { + value = 0; + } + // Hex number. + if (*p == '0' && *(p + 1) == 'x') { + p += 2; + if (*p == 0) { return false; } + for (; *p; ++p) { + uint64_t digit = 0; + + if (*p >= '0' && *p <= '9') { + digit = static_cast(*p - '0'); + } else if (*p >= 'a' && *p <= 'f') { + digit = static_cast(*p - 'a' + 10); + } else if (*p >= 'A' && *p <= 'F') { + digit = static_cast(*p - 'A' + 10); + } else { + return false; + } + + const uint64_t next = value * 16u + digit; + if (value > next) { + return false; + } else { + value = next; + } + } + // Decimal number. + } else { + for (; *p; ++p) { + uint64_t digit = 0; + + if (*p >= '0' && *p <= '9') { + digit = static_cast(*p - '0'); + } else { + return false; + } + if ((value > std::numeric_limits::max() / 10u) + || (value == std::numeric_limits::max() / 10u && digit > 5)) { + return false; + } else { + value = value * 10u + digit; + } + } + } + return true; +} + +template::value>::type * = nullptr> +inline void +parse_value(const std::string &text, T &value) +{ + using US = typename std::make_unsigned::type; + + uint64_t u64_result{0}; + US result{0}; + bool negative{false}; + + // Parse text to the uint64_t value. + if (!parse_uint64(text, u64_result, negative)) { throw_or_mimic(text, "integer"); } + // Check unsigned overflow. + if (u64_result > std::numeric_limits::max()) { + throw_or_mimic(text, "integer"); + } else { + result = static_cast(u64_result); + } + // Check signed overflow. + if (!check_signed_range(result, negative)) { throw_or_mimic(text, "integer"); } + // Negate value. + if (negative) { + if (!checked_negate(value, result, std::integral_constant::is_signed>())) { + throw_or_mimic(text, "integer"); + } + } else { + value = static_cast(result); + } +} + +inline void +parse_value(const std::string &text, float &value) +{ + value = std::stof(text); +} + +inline void +parse_value(const std::string &text, double &value) +{ + value = std::stod(text); +} + +inline void +parse_value(const std::string &text, long double &value) +{ + value = std::stold(text); +} + +inline void +parse_value(const std::string &text, bool &value) +{ + switch (text.size()) { + case 1: { + const char ch = text[0]; + if (ch == '1' || ch == 't' || ch == 'T') { + value = true; + return; + } + if (ch == '0' || ch == 'f' || ch == 'F') { + value = false; + return; + } + break; + } + case 4: + if ((text[0] == 't' || text[0] == 'T') && (text[1] == 'r' && text[2] == 'u' && text[3] == 'e')) { + value = true; + return; + } + break; + case 5: + if ((text[0] == 'f' || text[0] == 'F') + && (text[1] == 'a' && text[2] == 'l' && text[3] == 's' && text[4] == 'e')) { + value = false; + return; + } + break; + } + throw_or_mimic(text, "bool"); +} + +inline void +parse_value(const std::string &text, char &c) +{ + if (text.length() != 1) { throw_or_mimic(text, "char"); } + + c = text[0]; +} + +inline void +parse_value(const std::string &text, std::string &value) +{ + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template::value>::type * = nullptr> +void +parse_value(const std::string &text, T &value) +{ + std::istringstream in{text}; + in >> value; + if (!in) { throw_or_mimic(text); } +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string &text, std::optional &value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +}// namespace detail + +/** + * Settings for customizing parser behaviour. + */ +struct parse_context { + char delimiter{CXXOPTS_VECTOR_DELIMITER}; +}; + +/** + * A parser for values of type T. + */ +template +struct value_parser { + using value_type = T; + /// By default, value cannot act as a container. + static constexpr bool is_container = false; + + void parse(const parse_context &, const std::string &text, T &value) { detail::parse_value(text, value); } +}; + +template +struct value_parser> { + using value_type = T; + /// Value of type std::vector can act as container. + static constexpr bool is_container = true; + + void parse(const parse_context &ctx, const std::string &text, std::vector &value) + { + using parser_type = value_parser; + + static_assert(!parser_type::is_container || !value_parser::is_container, + "dimensions of a container type should not exceed 2"); + + auto parse_item = [&ctx](const std::string &txt) { + T v; + parser_type().parse(ctx, txt, v); + return v; + }; + + if (text.empty() || parser_type::is_container) { + value.push_back(parse_item(text)); + } else { + std::istringstream in{text}; + std::string token; + while (!in.eof() && std::getline(in, token, ctx.delimiter)) { value.push_back(parse_item(token)); } + } + } +}; + +}// namespace cxxopts + +/**@}*/ + +/** + * \defgroup Value setup + * @{ + */ + +namespace cxxopts { +namespace detail { + +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +// This will be ignored under other compilers like LLVM clang. +#endif +class value_base : public std::enable_shared_from_this { +public: + value_base() = default; + + virtual ~value_base() = default; + + /** Returns whether the default value was set. */ + CXXOPTS_NODISCARD + bool has_default() const noexcept { return default_; } + + /** Returns whether the env variable was set. */ + CXXOPTS_NODISCARD + bool has_env() const noexcept { return env_; } + + /** Returns whether the implicit value was set. */ + CXXOPTS_NODISCARD + bool has_implicit() const noexcept { return implicit_; } + + /** Returns default value. */ + CXXOPTS_NODISCARD + const std::string &get_default_value() const noexcept { return default_value_; } + + /** Returns env variable. */ + CXXOPTS_NODISCARD + const std::string &get_env_var() const noexcept { return env_var_; } + + /** Returns implicit value. */ + CXXOPTS_NODISCARD + const std::string &get_implicit_value() const noexcept { return implicit_value_; } + + CXXOPTS_NODISCARD + bool get_no_value() const noexcept { return no_value_; } + + /** + * Sets default value. + * + * Templated version is used to avoid implicit conversion 0u or nullptr + * to a string, that leads to runtime error. + */ + template + typename std::enable_if::type>::value, + std::shared_ptr>::type + default_value(T &&value) + { + default_ = true; + default_value_.assign(std::forward(value)); + return shared_from_this(); + } + + /** Sets delimiter for list values. */ + std::shared_ptr delimiter(const char del) + { + parse_ctx_.delimiter = del; + return shared_from_this(); + } + + /** Sets env variable. */ + template + typename std::enable_if::type>::value, + std::shared_ptr>::type + env(T &&var) + { + env_ = true; + env_var_.assign(std::forward(var)); + return shared_from_this(); + } + + /** + * Sets implicit value. + * + * Templated version is used to avoid implicit conversion 0u or nullptr + * to a string, that leads to runtime error. + */ + template + typename std::enable_if::type>::value, + std::shared_ptr>::type + implicit_value(T &&value) + { + implicit_ = true; + implicit_value_.assign(std::forward(value)); + return shared_from_this(); + } + + /** Clears implicit value. */ + std::shared_ptr no_implicit_value() + { + no_value_ = false; + implicit_ = false; + implicit_value_.clear(); + return shared_from_this(); + } + + /** Sets no-value field. */ + std::shared_ptr no_value(const bool on = true) + { + no_value_ = on; + return shared_from_this(); + } + + /** Returns whether the type of the value is boolean. */ + bool is_boolean() const noexcept { return do_is_boolean(); } + + /** Returns whether the type of the value is container. */ + bool is_container() const noexcept { return do_is_container(); } + + /** Parses the given text into the value. */ + void parse(const std::string &text) { return do_parse(parse_ctx_, text); } + + /** Parses the default value. */ + void parse() { return do_parse(parse_ctx_, default_value_); } + +protected: + virtual bool do_is_boolean() const noexcept = 0; + + virtual bool do_is_container() const noexcept = 0; + + virtual void do_parse(const parse_context &ctx, const std::string &text) = 0; + + void set_default_and_implicit(const bool set_default) + { + if (is_boolean()) { + if (set_default) { + default_ = true; + default_value_ = "false"; + } + implicit_ = true; + implicit_value_ = "true"; + no_value_ = true; + } + } + +private: + /// A default value for the option. + std::string default_value_{}; + /// A name of an environment variable from which a default + /// value will be read. + std::string env_var_{}; + /// An implicit value for the option. + std::string implicit_value_{}; + /// Configuration of the value parser. + parse_context parse_ctx_{}; + + /// The default value has been set. + bool default_{false}; + /// The environment variable has been set. + bool env_{false}; + /// The implicit value has been set. + bool implicit_{false}; + /// There should be no value for the option. + bool no_value_{false}; +}; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +template +class basic_value : public value_base { + using parser_type = value_parser; + +public: + basic_value() : result_(new T{}), store_(result_.get()) { set_default_and_implicit(true); } + + explicit basic_value(T *const t) : store_(t) { set_default_and_implicit(false); } + + const T &get() const noexcept { return *store_; } + +protected: + bool do_is_boolean() const noexcept final override { return std::is_same::value; } + + bool do_is_container() const noexcept final override { return parser_type::is_container; } + + void do_parse(const parse_context &ctx, const std::string &text) override + { + parser_type().parse(ctx, text, *store_); + } + +private: + basic_value(const basic_value &rhs) = delete; + basic_value &operator=(const basic_value &rhs) = delete; + +private: + std::unique_ptr result_{}; + T *store_{}; +}; + +}// namespace detail + +/** + * Creates value holder for the specific type. + */ +template +std::shared_ptr> inline value() +{ + return std::make_shared>(); +} + +/** + * Creates value holder for the specific type. + */ +template +std::shared_ptr> inline value(T &t) +{ + return std::make_shared>(&t); +} + +}// namespace cxxopts + +/**@}*/ + +namespace cxxopts { + +class option_details { +public: + option_details(std::string short_name, + std::string long_name, + std::string arg_help, + cxx_string desc, + std::shared_ptr val) + : short_(std::move(short_name)), + long_(std::move(long_name)), + arg_help_(std::move(arg_help)), + desc_(std::move(desc)), + hash_(std::hash{}(long_ + short_)), + value_(std::move(val)) + {} + + CXXOPTS_NODISCARD + const std::string &arg_help() const noexcept { return arg_help_; } + + CXXOPTS_NODISCARD + const cxx_string &description() const noexcept { return desc_; } + + CXXOPTS_NODISCARD + std::shared_ptr value() const { return value_; } + + CXXOPTS_NODISCARD + const std::string &canonical_name() const noexcept { return long_.empty() ? short_ : long_; } + + CXXOPTS_NODISCARD + const std::string &short_name() const noexcept { return short_; } + + CXXOPTS_NODISCARD + const std::string &long_name() const noexcept { return long_; } + + CXXOPTS_NODISCARD + std::size_t hash() const noexcept { return hash_; } + + CXXOPTS_NODISCARD + const std::string &default_value() const noexcept { return value_->get_default_value(); } + + CXXOPTS_NODISCARD + const std::string &implicit_value() const noexcept { return value_->get_implicit_value(); } + + CXXOPTS_NODISCARD + bool has_default() const noexcept { return value_->has_default(); } + + CXXOPTS_NODISCARD + bool has_implicit() const noexcept { return value_->has_implicit(); } + + CXXOPTS_NODISCARD + bool is_container() const noexcept { return value_->is_container(); } + + CXXOPTS_NODISCARD + bool is_boolean() const noexcept { return value_->is_boolean(); } + +private: + /// Short name of the option. + std::string short_; + /// Long name of the option. + std::string long_; + std::string arg_help_; + /// Description of the option. + cxx_string desc_; + std::size_t hash_; + std::shared_ptr value_; +}; + +/** + * Parsed value of an option. + */ +class option_value { +public: + /** + * A number of occurrences of the option value in + * the command line arguments. + */ + CXXOPTS_NODISCARD + std::size_t count() const noexcept { return count_; } + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool has_default() const noexcept { return default_; } + + /** + * Returns true if there was a value for the option in the + * command line arguments. + */ + CXXOPTS_NODISCARD + bool has_value() const noexcept { return value_ != nullptr; } + + /** + * Casts option value to the specific type. + */ + template + const T &as() const + { + if (!has_value()) { detail::throw_or_mimic(long_name_); } +#ifdef CXXOPTS_NO_RTTI + return static_cast &>(*value_).get(); +#else + return dynamic_cast &>(*value_).get(); +#endif + } + +public: + /** + * Parses option value from the given text. + */ + void parse(const option_details &details, const std::string &text) + { + ensure_value(details); + ++count_; + value_->parse(text); + long_name_ = details.long_name(); + } + + /** + * Parses option value from the default value. + */ + void parse_default(const option_details &details) + { + ensure_value(details); + default_ = true; + long_name_ = details.long_name(); + value_->parse(); + } + + void parse_no_value(const option_details &details) { long_name_ = details.long_name(); } + +private: + void ensure_value(const option_details &details) + { + if (value_ == nullptr) { value_ = details.value(); } + } + + std::string long_name_{}; + // Holding this pointer is safe, since option_value's only exist + // in key-value pairs, where the key has the string we point to. + std::shared_ptr value_{}; + std::size_t count_{0}; + bool default_{false}; +}; + +/** + * Provides the result of parsing of the command line arguments. + */ +class parse_result { +public: + /// Maps option name to hash of the name. + using name_hash_map = std::unordered_map; + /// Maps hash of an option name to the option value. + using parsed_hash_map = std::unordered_map; + + class key_value { + public: + key_value(std::string key, std::string value) noexcept : key_(std::move(key)), value_(std::move(value)) {} + + CXXOPTS_NODISCARD + const std::string &key() const noexcept { return key_; } + + CXXOPTS_NODISCARD + const std::string &value() const noexcept { return value_; } + + /** + * Parses the value to a variable of the specific type. + */ + template + T as() const + { + T result; + value_parser().parse(parse_context(), value_, result); + return result; + } + + private: + const std::string key_; + const std::string value_; + }; + +public: + parse_result() = default; + parse_result(const parse_result &) = default; + parse_result(parse_result &&) = default; + + parse_result(name_hash_map &&keys, + parsed_hash_map &&values, + std::vector &&sequential, + std::vector &&unmatched_args, + std::size_t consumed) + : keys_(std::move(keys)), + values_(std::move(values)), + sequential_(std::move(sequential)), + unmatched_(std::move(unmatched_args)), + consumed_arguments_(consumed) + {} + + parse_result &operator=(const parse_result &) = default; + parse_result &operator=(parse_result &&) = default; + + /** + * Returns a number of occurrences of the option in + * the command line arguments. + */ + CXXOPTS_NODISCARD + std::size_t count(const std::string &name) const + { + const auto ki = keys_.find(name); + if (ki == keys_.end()) { return 0; } + + const auto vi = values_.find(ki->second); + if (vi == values_.end()) { return 0; } + + return vi->second.count(); + } + + CXXOPTS_NODISCARD + bool has(const std::string &name) const { return count(name) != 0; } + + const option_value &operator[](const std::string &name) const + { + const auto ki = keys_.find(name); + if (ki == keys_.end()) { detail::throw_or_mimic(name); } + + const auto vi = values_.find(ki->second); + if (vi == values_.end()) { detail::throw_or_mimic(name); } + + return vi->second; + } + + /** + * Returns list of recognized options with non empty value. + */ + CXXOPTS_NODISCARD + const std::vector &arguments() const noexcept { return sequential_; } + + /** + * Returns number of consumed command line arguments. + */ + CXXOPTS_NODISCARD + std::size_t consumed() const noexcept { return consumed_arguments_; } + + /** + * Returns list of unmatched arguments. + */ + CXXOPTS_NODISCARD + const std::vector &unmatched() const noexcept { return unmatched_; } + +private: + name_hash_map keys_{}; + parsed_hash_map values_{}; + std::vector sequential_{}; + /// List of arguments that did not match to any defined option. + std::vector unmatched_{}; + /// Number of consument command line arguments. + std::size_t consumed_arguments_{0}; +}; + +namespace detail { + +class option_parser { + using option_map = std::unordered_map>; + using positional_list = std::vector; + using positional_list_iterator = positional_list::const_iterator; + + struct option_data { + std::string name{}; + std::string value{}; + bool is_long{false}; + bool has_value{false}; + }; + +public: + option_parser(const option_map &options, + const positional_list &positional, + bool allow_unrecognised, + bool stop_on_positional) + : options_(options), + positional_(positional), + allow_unrecognised_(allow_unrecognised), + stop_on_positional_(stop_on_positional) + {} + + parse_result parse(const int argc, const char *const *argv) + { + int current = 1; + auto next_positional = positional_.begin(); + std::vector unmatched; + + while (current < argc) { + if (is_dash_dash(argv[current])) { + // Skip dash-dash argument. + ++current; + if (stop_on_positional_) { break; } + // Try to consume all remaining arguments as positional. + for (; current < argc; ++current) { + if (!consume_positional(argv[current], next_positional)) { break; } + } + // Adjust argv for any that couldn't be swallowed. + for (; current != argc; ++current) { unmatched.emplace_back(argv[current]); } + break; + } + + option_data result; + + if (!parse_argument(argv[current], result)) { + // Not a flag. + // But if it starts with a `-`, then it's an error. + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!allow_unrecognised_) { detail::throw_or_mimic(argv[current]); } + } + if (stop_on_positional_) { break; } + // If true is returned here then it was consumed, otherwise it + // is ignored. + if (!consume_positional(argv[current], next_positional)) { unmatched.emplace_back(argv[current]); } + // If we return from here then it was parsed successfully, so + // continue. + } else if (result.is_long) { + // Long option. + const std::string &name = result.name; + const auto oi = options_.find(name); + + if (oi == options_.end()) { + if (allow_unrecognised_) { + // Keep unrecognised options in argument list, + // skip to next argument. + unmatched.emplace_back(argv[current]); + ++current; + continue; + } + // Error. + detail::throw_or_mimic(name); + } + + const auto &opt = oi->second; + // Equal sign provided for the long option? + if (result.has_value) { + // Parse the option given. + parse_option(opt, result.value); + } else { + // Parse the next argument. + checked_parse_arg(argc, argv, current, opt, name); + } + } else { + // Single short option or a group of short options. + const std::string &seq = result.name; + // Iterate over the sequence of short options. + for (std::size_t i = 0; i != seq.size(); ++i) { + const std::string name(1, seq[i]); + const auto oi = options_.find(name); + + if (oi == options_.end()) { + if (allow_unrecognised_) { + unmatched.push_back(std::string("-") + seq[i]); + continue; + } + // Error. + detail::throw_or_mimic(name); + } + + const auto &opt = oi->second; + if (i + 1 == seq.size()) { + // It must be the last argument. + checked_parse_arg(argc, argv, current, opt, name); + } else if (opt->has_implicit()) { + parse_option(opt, opt->implicit_value()); + } else { + parse_option(opt, seq.substr(i + 1)); + break; + } + } + } + + ++current; + } + + // Setup default or env values. + for (auto &opt : options_) { + auto &detail = opt.second; + auto &store = parsed_[detail->hash()]; + const auto &value = detail->value(); + + // Skip options with parsed values. + if (store.count() || store.has_default()) { continue; } + // Try to setup env value. + if (value->has_env()) { + if (const char *env = std::getenv(value->get_env_var().c_str())) { + store.parse(*detail, std::string(env)); + continue; + } + } + // Try to setup default value. + if (value->has_default()) { + store.parse_default(*detail); + } else { + store.parse_no_value(*detail); + } + } + + parse_result::name_hash_map keys; + // Finalize aliases. + for (const auto &option : options_) { + const auto &detail = option.second; + const auto hash = detail->hash(); + + if (detail->short_name().size()) { keys[detail->short_name()] = hash; } + if (detail->long_name().size()) { keys[detail->long_name()] = hash; } + } + + assert(stop_on_positional_ || argc == current || argc == 0); + + return parse_result(std::move(keys), std::move(parsed_), std::move(sequential_), std::move(unmatched), current); + } + +private: + bool consume_positional(const std::string &arg, positional_list_iterator &next) + { + for (; next != positional_.end(); ++next) { + const auto oi = options_.find(*next); + if (oi == options_.end()) { detail::throw_or_mimic(*next); } + if (oi->second->value()->is_container()) { + parse_option(oi->second, arg); + return true; + } + if (parsed_[oi->second->hash()].count() == 0) { + parse_option(oi->second, arg); + ++next; + return true; + } + } + return false; + } + + bool is_dash_dash(const char *str) const noexcept + { + return (str[0] != 0 && str[0] == '-') && (str[1] != 0 && str[1] == '-') && (str[2] == 0); + } + + bool is_dash_dash_or_option_name(const char *const arg) const + { + // The dash-dash symbol has a special meaning and cannot + // be interpreted as an option value. + if (is_dash_dash(arg)) { return true; } + + option_data result; + // The argument does not match an option format + // so that it can be safely consumed as a value. + if (!parse_argument(arg, result)) { return false; } + + auto check_name = [this](const std::string &opt) { return options_.find(opt) != options_.end(); }; + // Check that the argument does not match any + // existing option. + if (result.is_long) { + return check_name(result.name); + } else { + return check_name(result.name.substr(0, 1)); + } + + return false; + } + + void checked_parse_arg(const int argc, + const char *const *argv, + int ¤t, + const std::shared_ptr &value, + const std::string &name) + { + auto parse_implicit = [&]() { + if (value->has_implicit()) { + parse_option(value, value->implicit_value()); + } else { + detail::throw_or_mimic(name); + } + }; + + if (current + 1 == argc || value->value()->get_no_value()) { + // Last argument or the option without value. + parse_implicit(); + } else { + const char *const arg = argv[current + 1]; + // Check that we do not silently consume any option as a value + // of another option. + if (arg[0] == '-' && is_dash_dash_or_option_name(arg)) { + parse_implicit(); + } else { + // Parse argument as a value for the option. + parse_option(value, arg); + ++current; + } + } + } + + bool parse_argument(const std::string &text, option_data &data) const + { + const char *p = text.c_str(); + const char *end = text.c_str() + text.size(); + // The string should be at least two character long and starts with '-'. + if (*p == 0 || *(p + 1) == 0 || *p != '-') { return false; } + // Skip the '-'. + ++p; + // Long option starts with '--'. + if (*p == '-') { + ++p; + if (std::isalnum(*p) && *(p + 1) != 0) { + data.is_long = true; + data.name += *p; + ++p; + } else { + return false; + } + for (; *p; ++p) { + if (*p == '=') { + ++p; + data.has_value = true; + data.value.assign(p, end); + break; + } + if (*p == '-' || *p == '_' || *p == '.' || std::isalnum(*p)) { + data.name += *p; + } else { + return false; + } + } + return data.name.size() > 1; + } else { + // Single char short option should start with an alnum or + // be a question mark. + if (!(std::isalnum(*p) || (*p == '?' && *(p + 1) == 0))) { return false; } + // Copy the whole string and interpret it later as + // a group of short options. + data.name.assign(p, end); + return true; + } + return false; + } + + void parse_option(const std::shared_ptr &details, const std::string &arg) + { + parsed_[details->hash()].parse(*details, arg); + sequential_.emplace_back(details->canonical_name(), arg); + } + +private: + const option_map &options_; + const positional_list &positional_; + const bool allow_unrecognised_; + const bool stop_on_positional_; + + std::vector sequential_{}; + parse_result::parsed_hash_map parsed_{}; +}; + +}// namespace detail + +class options; + +class option { +public: + option(std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = {}) noexcept + : opts_(std::move(opts)), + desc_(std::move(desc)), + value_(std::move(value)), + arg_help_(std::move(arg_help)) + {} + +private: + friend class ::cxxopts::options; + + /// Short and long names of the option. + const std::string opts_; + /// Description of the option. + const std::string desc_; + const std::shared_ptr value_; + const std::string arg_help_; +}; + +/** + * Specification of command line options. + */ +class options { +public: + struct help_group_details { + std::string name{}; + std::vector> options{}; + }; + + class option_adder { + public: + option_adder(std::string group, options &options) noexcept : group_(std::move(group)), options_(options) {} + + option_adder &operator()(const std::string &opts, + const std::string &desc, + const std::shared_ptr &value = ::cxxopts::value(), + const std::string arg_help = {}) + { + std::string s; + std::string l; + if (parse_option_specifier(opts, s, l)) { + assert(s.empty() || s.size() == 1); + assert(l.empty() || l.size() > 1); + + options_.add_option(group_, std::move(s), std::move(l), desc, value, std::move(arg_help)); + } else { + detail::throw_or_mimic(opts); + } + return *this; + } + + private: + bool parse_option_specifier(const std::string &text, std::string &s, std::string &l) const + { + const char *p = text.c_str(); + if (*p == 0) { + return false; + } else { + s.clear(); + l.clear(); + } + // Short option. + if (*(p + 1) == 0 || *(p + 1) == ',') { + if (*p == '?' || std::isalnum(*p)) { + s = *p; + ++p; + } else { + return false; + } + } + // Skip comma. + if (*p == ',') { + if (s.empty()) { return false; } + ++p; + } + // Skip spaces. + while (*p && *p == ' ') { ++p; } + // Valid specifier without long option. + if (*p == 0) { + return true; + } else { + l.reserve((text.c_str() + text.size()) - p); + } + // First char of an option name should be alnum. + if (std::isalnum(*p)) { + l += *p; + ++p; + } + for (; *p; ++p) { + if (*p == '-' || *p == '_' || *p == '.' || std::isalnum(*p)) { + l += *p; + } else { + return false; + } + } + return l.size() > 1; + } + + private: + const std::string group_; + options &options_; + }; + +public: + explicit options(std::string program, std::string help_string = {}) + : program_(std::move(program)), + help_string_(to_local_string(std::move(help_string))), + custom_help_("[OPTION...]"), + positional_help_("positional parameters") + {} + + /** + * Adds list of options to the specific group. + */ + void add_options(const std::string &group, std::initializer_list