From 3192a5bc8313b1921b06873fc3ccfa48a9ac617b Mon Sep 17 00:00:00 2001 From: Tijmen van Nesselrooij Date: Fri, 29 Aug 2025 16:50:14 +0200 Subject: [PATCH] Update cxxopts --- include/cxxopts.hpp | 973 ++++++++++++++++++++++++++++---------------- 1 file changed, 626 insertions(+), 347 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 4855966..95f1786 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -1,6 +1,6 @@ /* -Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck +Copyright (c) 2014-2022 Jarryd Beck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,16 +22,19 @@ THE SOFTWARE. */ +// vim: ts=2:sw=2:expandtab + #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED #include -#include +#include +#include #include #include -#include +#include #include -#include +#include #include #include #include @@ -41,11 +44,22 @@ THE SOFTWARE. #include #include +#ifdef CXXOPTS_NO_EXCEPTIONS + #include +#endif + #if defined(__GNUC__) && !defined(__clang__) - #if(__GNUC__ * 10 + __GNUC_MINOR__) < 49 + #if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 #define CXXOPTS_NO_REGEX true #endif #endif +#if defined(_MSC_VER) && !defined(__clang__) + #define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern + #define CXXOPTS_LINKONCE __declspec(selectany) extern +#else + #define CXXOPTS_LINKONCE_CONST + #define CXXOPTS_LINKONCE +#endif #ifndef CXXOPTS_NO_REGEX #include @@ -59,6 +73,20 @@ THE SOFTWARE. #define CXXOPTS_HAS_OPTIONAL #endif #endif + #if __has_include() + #include + #ifdef __cpp_lib_filesystem + #define CXXOPTS_HAS_FILESYSTEM + #endif + #endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif #endif #if __cplusplus >= 201603L @@ -72,13 +100,31 @@ THE SOFTWARE. #endif #define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 0 -#define CXXOPTS__VERSION_PATCH 0 +#define CXXOPTS__VERSION_MINOR 3 +#define CXXOPTS__VERSION_PATCH 1 -#if(__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 #define CXXOPTS_NULL_DEREF_IGNORE #endif +#if defined(__GNUC__) + #define DO_PRAGMA(x) _Pragma(#x) + #define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) + #define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) + #define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else + // define other compilers here if needed + #define CXXOPTS_DIAGNOSTIC_PUSH + #define CXXOPTS_DIAGNOSTIC_POP + #define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI + #define CXXOPTS_RTTI_CAST static_cast +#else + #define CXXOPTS_RTTI_CAST dynamic_cast +#endif + namespace cxxopts { static constexpr struct @@ -98,20 +144,24 @@ namespace cxxopts namespace cxxopts { + using String = icu::UnicodeString; inline String toLocalString(std::string s) { return icu::UnicodeString::fromUTF8(std::move(s)); } - #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" - #pragma GCC diagnostic ignored "-Weffc++" + // 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 + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") // This will be ignored under other compilers like LLVM clang. - #endif - class UnicodeStringIterator : public std::iterator { + class UnicodeStringIterator { public: + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + UnicodeStringIterator(const icu::UnicodeString * string, int32_t pos) : s(string), i(pos) { } value_type operator*() const { return s->char32At(i); } @@ -132,15 +182,13 @@ namespace cxxopts const icu::UnicodeString * s; int32_t i; }; - #if defined(__GNUC__) - #pragma GCC diagnostic pop - #endif + CXXOPTS_DIAGNOSTIC_POP inline String & stringAppend(String & s, String a) { return s.append(std::move(a)); } - inline String & stringAppend(String & s, size_t n, UChar32 c) + inline String & stringAppend(String & s, std::size_t n, UChar32 c) { - for(size_t i = 0; i != n; ++i) + for(std::size_t i = 0; i != n; ++i) { s.append(c); } @@ -159,7 +207,7 @@ namespace cxxopts return s; } - inline size_t stringLength(const String & s) { return s.length(); } + inline size_t stringLength(const String & s) { return static_cast(s.length()); } inline std::string toUTF8String(const String & s) { @@ -170,10 +218,12 @@ namespace cxxopts } inline bool empty(const String & s) { return s.isEmpty(); } -} + +} // namespace cxxopts namespace std { + inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString & s) { return cxxopts::UnicodeStringIterator(&s, 0); @@ -183,22 +233,24 @@ namespace std { return cxxopts::UnicodeStringIterator(&s, s.length()); } -} + +} // namespace std //ifdef CXXOPTS_USE_UNICODE #else namespace cxxopts { + using String = std::string; template T toLocalString(T && t) { return std::forward(t); } - inline size_t stringLength(const String & s) { return s.length(); } + inline std::size_t stringLength(const String & s) { return s.length(); } inline String & stringAppend(String & s, const String & a) { return s.append(a); } - inline String & stringAppend(String & s, size_t n, char c) { return s.append(n, c); } + inline String & stringAppend(String & s, std::size_t n, char c) { return s.append(n, c); } template String & stringAppend(String & s, Iterator begin, Iterator end) { @@ -208,6 +260,7 @@ namespace cxxopts template std::string toUTF8String(T && t) { return std::forward(t); } inline bool empty(const std::string & s) { return s.empty(); } + } // namespace cxxopts //ifdef CXXOPTS_USE_UNICODE @@ -215,31 +268,30 @@ namespace cxxopts namespace cxxopts { + namespace { -#ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); -#else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); -#endif + CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); + CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); } // namespace -#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" - #pragma GCC diagnostic ignored "-Weffc++" -// This will be ignored under other compilers like LLVM clang. -#endif + // 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 This will be ignored under other compilers like LLVM clang. + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + + // some older versions of GCC warn under this warning + CXXOPTS_IGNORE_WARNING("-Weffc++") class Value : public std::enable_shared_from_this { public: virtual ~Value() = default; virtual std::shared_ptr clone() const = 0; + virtual void add(const std::string & text) const = 0; + virtual void parse(const std::string & text) const = 0; virtual void parse() const = 0; @@ -262,109 +314,107 @@ namespace cxxopts virtual bool is_boolean() const = 0; }; -#if defined(__GNUC__) - #pragma GCC diagnostic pop -#endif - class OptionException : public std::exception { - public: - explicit OptionException(std::string message) : m_message(std::move(message)) { } - CXXOPTS_NODISCARD - const char * what() const noexcept override { return m_message.c_str(); } + CXXOPTS_DIAGNOSTIC_POP - private: - std::string m_message; - }; + namespace exceptions + { - class OptionSpecException : public OptionException { - public: - explicit OptionSpecException(const std::string & message) : OptionException(message) { } - }; + class exception : public std::exception { + public: + explicit exception(std::string message) : m_message(std::move(message)) { } - class OptionParseException : public OptionException { - public: - explicit OptionParseException(const std::string & message) : OptionException(message) { } - }; + CXXOPTS_NODISCARD + const char * what() const noexcept override { return m_message.c_str(); } - class option_exists_error : public OptionSpecException { - public: - explicit option_exists_error(const std::string & option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { } - }; + private: + std::string m_message; + }; - class invalid_option_format_error : public OptionSpecException { - public: - explicit invalid_option_format_error(const std::string & format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) - { } - }; + class specification : public exception { + public: + explicit specification(const std::string & message) : exception(message) { } + }; - class option_syntax_exception : public OptionParseException { - public: - explicit option_syntax_exception(const std::string & text) - : OptionParseException("Argument " + LQUOTE + text + RQUOTE + " starts with a - but has incorrect syntax") - { } - }; + class parsing : public exception { + public: + explicit parsing(const std::string & message) : exception(message) { } + }; - class option_not_exists_exception : public OptionParseException { - public: - explicit option_not_exists_exception(const std::string & option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") - { } - }; + class option_already_exists : public specification { + public: + explicit option_already_exists(const std::string & option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") + { } + }; - class missing_argument_exception : public OptionParseException { - public: - explicit missing_argument_exception(const std::string & option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " is missing an argument") - { } - }; + class invalid_option_format : public specification { + public: + explicit invalid_option_format(const std::string & format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) + { } + }; - class option_requires_argument_exception : public OptionParseException { - public: - explicit option_requires_argument_exception(const std::string & option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " requires an argument") - { } - }; + class invalid_option_syntax : public parsing { + public: + explicit invalid_option_syntax(const std::string & text) + : parsing("Argument " + LQUOTE + text + RQUOTE + " starts with a - but has incorrect syntax") + { } + }; - class option_not_has_argument_exception : public OptionParseException { - public: - option_not_has_argument_exception(const std::string & option, const std::string & arg) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " does not take an argument, but argument " + LQUOTE + arg + RQUOTE - + " given") - { } - }; + class no_such_option : public parsing { + public: + explicit no_such_option(const std::string & option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { } + }; - class option_not_present_exception : public OptionParseException { - public: - explicit option_not_present_exception(const std::string & option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") - { } - }; + class missing_argument : public parsing { + public: + explicit missing_argument(const std::string & option) + : parsing("Option " + LQUOTE + option + RQUOTE + " is missing an argument") + { } + }; - class option_has_no_value_exception : public OptionException { - public: - explicit option_has_no_value_exception(const std::string & option) - : OptionException( - !option.empty() ? ("Option " + LQUOTE + option + RQUOTE + " has no value") : "Option has no value") - { } - }; + class option_requires_argument : public parsing { + public: + explicit option_requires_argument(const std::string & option) + : parsing("Option " + LQUOTE + option + RQUOTE + " requires an argument") + { } + }; - class argument_incorrect_type : public OptionParseException { - public: - explicit argument_incorrect_type(const std::string & arg) - : OptionParseException("Argument " + LQUOTE + arg + RQUOTE + " failed to parse") - { } - }; + class gratuitous_argument_for_option : public parsing { + public: + gratuitous_argument_for_option(const std::string & option, const std::string & arg) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " does not take an argument, but argument " + LQUOTE + arg + + RQUOTE + " given") + { } + }; - class option_required_exception : public OptionParseException { - public: - explicit option_required_exception(const std::string & option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " is required but not present") - { } - }; + class requested_option_not_present : public parsing { + public: + explicit requested_option_not_present(const std::string & option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { } + }; + + class option_has_no_value : public exception { + public: + explicit option_has_no_value(const std::string & option) + : exception( + !option.empty() ? ("Option " + LQUOTE + option + RQUOTE + " has no value") : "Option has no value") + { } + }; + + class incorrect_argument_type : public parsing { + public: + explicit incorrect_argument_type(const std::string & arg) + : parsing("Argument " + LQUOTE + arg + RQUOTE + " failed to parse") + { } + }; + + } // namespace exceptions template void throw_or_mimic(const std::string & text) { @@ -385,10 +435,14 @@ namespace cxxopts #endif } + using OptionNames = std::vector; + namespace values { + namespace parser_tool { + struct IntegerDesc { std::string negative = ""; @@ -402,12 +456,13 @@ namespace cxxopts bool set_value = false; std::string value = ""; }; + #ifdef CXXOPTS_NO_REGEX inline IntegerDesc SplitInteger(const std::string & text) { if(text.empty()) { - throw_or_mimic(text); + throw_or_mimic(text); } IntegerDesc desc; const char * pdata = text.c_str(); @@ -427,7 +482,7 @@ namespace cxxopts } else { - throw_or_mimic(text); + throw_or_mimic(text); } return desc; } @@ -468,37 +523,54 @@ namespace cxxopts return false; } - inline std::pair SplitSwitchDef(const std::string & text) + inline OptionNames split_option_names(const std::string & text) { - std::string short_sw, long_sw; - const char * pdata = text.c_str(); - if(isalnum(*pdata) && *(pdata + 1) == ',') + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if(length == 0) { - short_sw = std::string(1, *pdata); - pdata += 2; + throw_or_mimic(text); } - while(*pdata == ' ') + + while(token_start_pos < length) { - pdata += 1; + const auto & npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if(next_non_space_pos == npos) + { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if(next_delimiter_pos == token_start_pos) + { + throw_or_mimic(text); + } + if(next_delimiter_pos == npos) + { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char * option_name_valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if(!std::isalnum(text[token_start_pos], std::locale::classic()) + || text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) + { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; } - if(isalnum(*pdata)) - { - const char * store = pdata; - pdata += 1; - while(isalnum(*pdata) || *pdata == '-' || *pdata == '_') - { - pdata += 1; - } - if(*pdata == '\0') - { - long_sw = std::string(store, pdata - store); - } - else - { - throw_or_mimic(text); - } - } - return std::pair(short_sw, long_sw); + return split_names; } inline ArguDesc ParseArgument(const char * arg, bool & matched) @@ -509,11 +581,11 @@ namespace cxxopts if(strncmp(pdata, "--", 2) == 0) { pdata += 2; - if(isalnum(*pdata)) + if(isalnum(*pdata, std::locale::classic())) { argu_desc.arg_name.push_back(*pdata); pdata += 1; - while(isalnum(*pdata) || *pdata == '-' || *pdata == '_') + while(isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') { argu_desc.arg_name.push_back(*pdata); pdata += 1; @@ -541,7 +613,7 @@ namespace cxxopts { pdata += 1; argu_desc.grouping = true; - while(isalnum(*pdata)) + while(isalnum(*pdata, std::locale::classic())) { argu_desc.arg_name.push_back(*pdata); pdata += 1; @@ -555,24 +627,32 @@ namespace cxxopts namespace { - - std::basic_regex integer_pattern("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern("(f|F)(alse)?|0"); - - std::basic_regex option_matcher("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); - std::basic_regex option_specifier("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + CXXOPTS_LINKONCE + const char * const integer_pattern = "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; + CXXOPTS_LINKONCE + const char * const truthy_pattern = "(t|T)(rue)?|1"; + CXXOPTS_LINKONCE + const char * const falsy_pattern = "(f|F)(alse)?|0"; + CXXOPTS_LINKONCE + const char * const option_pattern = "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; + CXXOPTS_LINKONCE + const char * const option_specifier_pattern + = "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; + CXXOPTS_LINKONCE + const char * const option_specifier_separator_pattern = ", *"; } // namespace inline IntegerDesc SplitInteger(const std::string & text) { + static const std::basic_regex integer_matcher(integer_pattern); + std::smatch match; - std::regex_match(text, match, integer_pattern); + std::regex_match(text, match, integer_matcher); if(match.length() == 0) { - throw_or_mimic(text); + throw_or_mimic(text); } IntegerDesc desc; @@ -592,35 +672,48 @@ namespace cxxopts inline bool IsTrueText(const std::string & text) { + static const std::basic_regex truthy_matcher(truthy_pattern); std::smatch result; - std::regex_match(text, result, truthy_pattern); + std::regex_match(text, result, truthy_matcher); return !result.empty(); } inline bool IsFalseText(const std::string & text) { + static const std::basic_regex falsy_matcher(falsy_pattern); std::smatch result; - std::regex_match(text, result, falsy_pattern); + std::regex_match(text, result, falsy_matcher); return !result.empty(); } - inline std::pair SplitSwitchDef(const std::string & text) + // Gets the option names specified via a single, comma-separated string, + // and returns the separate, space-discarded, non-empty names + // (without considering which or how many are single-character) + inline OptionNames split_option_names(const std::string & text) { - std::match_results result; - std::regex_match(text.c_str(), result, option_specifier); - if(result.empty()) + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if(!std::regex_match(text.c_str(), option_specifier_matcher)) { - throw_or_mimic(text); + throw_or_mimic(text); } - const std::string & short_sw = result[2]; - const std::string & long_sw = result[3]; + OptionNames split_names; - return std::pair(short_sw, long_sw); + static const std::basic_regex option_specifier_separator_matcher( + option_specifier_separator_pattern); + constexpr int use_non_matches {-1}; + auto token_iterator = std::sregex_token_iterator( + text.begin(), + text.end(), + option_specifier_separator_matcher, + use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; } inline ArguDesc ParseArgument(const char * arg, bool & matched) { + static const std::basic_regex option_matcher(option_pattern); std::match_results result; std::regex_match(arg, result, option_matcher); matched = !result.empty(); @@ -643,10 +736,11 @@ namespace cxxopts #endif // CXXOPTS_NO_REGEX #undef CXXOPTS_NO_REGEX - } + } // namespace parser_tool namespace detail { + template struct SignedCheck; template struct SignedCheck @@ -657,14 +751,14 @@ namespace cxxopts { if(u > static_cast((std::numeric_limits::min)())) { - throw_or_mimic(text); + throw_or_mimic(text); } } else { if(u > static_cast((std::numeric_limits::max)())) { - throw_or_mimic(text); + throw_or_mimic(text); } } } @@ -679,6 +773,7 @@ namespace cxxopts { SignedCheck::is_signed>()(negative, value, text); } + } // namespace detail template void checked_negate(R & r, T && t, const std::string &, std::true_type) @@ -691,7 +786,7 @@ namespace cxxopts template void checked_negate(R &, T &&, const std::string & text, std::false_type) { - throw_or_mimic(text); + throw_or_mimic(text); } template void integer_parser(const std::string & text, T & value) @@ -725,16 +820,29 @@ namespace cxxopts } else { - throw_or_mimic(text); + throw_or_mimic(text); } - const US next = static_cast(result * base + digit); - if(result > next) + US limit = 0; + if(negative) { - throw_or_mimic(text); + limit = static_cast(std::abs(static_cast((std::numeric_limits::min)()))); + } + else + { + limit = (std::numeric_limits::max)(); } - result = next; + if(base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if(result * base > limit - digit) + { + throw_or_mimic(text); + } + + result = static_cast(result * base + digit); } detail::check_signed_range(negative, result, text); @@ -755,7 +863,7 @@ namespace cxxopts in >> value; if(!in) { - throw_or_mimic(text); + throw_or_mimic(text); } } @@ -779,7 +887,7 @@ namespace cxxopts return; } - throw_or_mimic(text); + throw_or_mimic(text); } inline void parse_value(const std::string & text, std::string & value) { value = text; } @@ -793,6 +901,29 @@ namespace cxxopts stringstream_parser(text, value); } +#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 + +#ifdef CXXOPTS_HAS_FILESYSTEM + inline void parse_value(const std::string & text, std::filesystem::path & value) { value.assign(text); } +#endif + + inline void parse_value(const std::string & text, char & c) + { + if(text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + template void parse_value(const std::string & text, std::vector & value) { if(text.empty()) @@ -812,23 +943,13 @@ namespace cxxopts } } -#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 + template void add_value(const std::string & text, T & value) { parse_value(text, value); } - inline void parse_value(const std::string & text, char & c) + template void add_value(const std::string & text, std::vector & value) { - if(text.length() != 1) - { - throw_or_mimic(text); - } - - c = text[0]; + T v; + add_value(text, v); + value.emplace_back(std::move(v)); } template struct type_is_container @@ -871,6 +992,8 @@ namespace cxxopts m_implicit_value = rhs.m_implicit_value; } + void add(const std::string & text) const override { add_value(text, *m_store); } + void parse(const std::string & text) const override { parse_value(text, *m_store); } bool is_container() const override { return type_is_container::value; } @@ -941,7 +1064,11 @@ namespace cxxopts standard_value() { set_default_and_implicit(); } - explicit standard_value(bool * b) : abstract_value(b) { set_default_and_implicit(); } + explicit standard_value(bool * b) : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } std::shared_ptr clone() const override { return std::make_shared>(*this); } @@ -954,6 +1081,7 @@ namespace cxxopts m_implicit_value = "true"; } }; + } // namespace values template std::shared_ptr value() { return std::make_shared>(); } @@ -962,13 +1090,20 @@ namespace cxxopts class OptionAdder; + CXXOPTS_NODISCARD + inline const std::string & first_or_empty(const OptionNames & long_names) + { + static const std::string empty {""}; + return long_names.empty() ? empty : long_names.front(); + } + class OptionDetails { public: - OptionDetails(std::string short_, std::string long_, String desc, std::shared_ptr val) + OptionDetails(std::string short_, OptionNames long_, String desc, std::shared_ptr val) : m_short(std::move(short_)), m_long(std::move(long_)), m_desc(std::move(desc)), m_value(std::move(val)), m_count(0) { - m_hash = std::hash {}(m_long + m_short); + m_hash = std::hash {}(first_long_name() + m_short); } OptionDetails(const OptionDetails & rhs) @@ -990,24 +1125,30 @@ namespace cxxopts const std::string & short_name() const { return m_short; } CXXOPTS_NODISCARD - const std::string & long_name() const { return m_long; } + const std::string & first_long_name() const { return first_or_empty(m_long); } - size_t hash() const { return m_hash; } + CXXOPTS_NODISCARD + const std::string & essential_name() const { return m_long.empty() ? m_short : m_long.front(); } + + CXXOPTS_NODISCARD + const OptionNames & long_names() const { return m_long; } + + std::size_t hash() const { return m_hash; } private: std::string m_short {}; - std::string m_long {}; + OptionNames m_long {}; String m_desc {}; std::shared_ptr m_value {}; int m_count; - size_t m_hash {}; + std::size_t m_hash {}; }; struct HelpOptionDetails { std::string s; - std::string l; + OptionNames l; String desc; bool has_default; std::string default_value; @@ -1027,37 +1168,45 @@ namespace cxxopts class OptionValue { public: + void add(const std::shared_ptr & details, const std::string & text) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + void parse(const std::shared_ptr & details, const std::string & text) { ensure_value(details); ++m_count; m_value->parse(text); - m_long_name = &details->long_name(); + m_long_names = &details->long_names(); } void parse_default(const std::shared_ptr & details) { ensure_value(details); m_default = true; - m_long_name = &details->long_name(); + m_long_names = &details->long_names(); m_value->parse(); } void parse_no_value(const std::shared_ptr & details) { - m_long_name = &details->long_name(); + m_long_names = &details->long_names(); } #if defined(CXXOPTS_NULL_DEREF_IGNORE) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wnull-dereference" + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Wnull-dereference") #endif CXXOPTS_NODISCARD - size_t count() const noexcept { return m_count; } + std::size_t count() const noexcept { return m_count; } #if defined(CXXOPTS_NULL_DEREF_IGNORE) - #pragma GCC diagnostic pop + CXXOPTS_DIAGNOSTIC_POP #endif // TODO: maybe default options should count towards the number of arguments @@ -1068,16 +1217,24 @@ namespace cxxopts { if(m_value == nullptr) { - throw_or_mimic(m_long_name == nullptr ? "" : *m_long_name); + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); } -#ifdef CXXOPTS_NO_RTTI - return static_cast &>(*m_value).get(); -#else - return dynamic_cast &>(*m_value).get(); -#endif + return CXXOPTS_RTTI_CAST &>(*m_value).get(); } +#ifdef CXXOPTS_HAS_OPTIONAL + template std::optional as_optional() const + { + if(m_value == nullptr) + { + return std::nullopt; + } + return as(); + } +#endif + private: void ensure_value(const std::shared_ptr & details) { @@ -1087,17 +1244,17 @@ namespace cxxopts } } - const std::string * m_long_name = nullptr; + const OptionNames * m_long_names = nullptr; // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, // where the key has the string we point to. std::shared_ptr m_value {}; - size_t m_count = 0; + std::size_t m_count = 0; bool m_default = false; }; class KeyValue { public: - KeyValue(std::string key_, std::string value_) : m_key(std::move(key_)), m_value(std::move(value_)) { } + KeyValue(std::string key_, std::string value_) noexcept : m_key(std::move(key_)), m_value(std::move(value_)) { } CXXOPTS_NODISCARD const std::string & key() const { return m_key; } @@ -1117,11 +1274,83 @@ namespace cxxopts std::string m_value; }; - using ParsedHashMap = std::unordered_map; - using NameHashMap = std::unordered_map; + using ParsedHashMap = std::unordered_map; + using NameHashMap = std::unordered_map; class ParseResult { public: + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue *; + using reference = const KeyValue &; + + Iterator() = default; + Iterator(const Iterator &) = default; + + // GCC complains about m_iter not being initialised in the member + // initializer list + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult * pr, bool end = false) : m_pr(pr) + { + if(end) + { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } + else + { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if(m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } + CXXOPTS_DIAGNOSTIC_POP + + Iterator & operator++() + { + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator & other) const + { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator & other) const { return !(*this == other); } + + const KeyValue & operator*() { return *m_iter; } + + const KeyValue * operator->() { return m_iter.operator->(); } + + private: + const ParseResult * m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + ParseResult() = default; ParseResult(const ParseResult &) = default; @@ -1129,15 +1358,20 @@ namespace cxxopts NameHashMap && keys, ParsedHashMap && values, std::vector sequential, + std::vector default_opts, std::vector && unmatched_args) : m_keys(std::move(keys)), m_values(std::move(values)), m_sequential(std::move(sequential)), - m_unmatched(std::move(unmatched_args)) + m_defaults(std::move(default_opts)), m_unmatched(std::move(unmatched_args)) { } ParseResult & operator=(ParseResult &&) = default; ParseResult & operator=(const ParseResult &) = default; - size_t count(const std::string & o) const + Iterator begin() const { return Iterator(this); } + + Iterator end() const { return Iterator(this, true); } + + std::size_t count(const std::string & o) const { auto iter = m_keys.find(o); if(iter == m_keys.end()) @@ -1155,33 +1389,68 @@ namespace cxxopts return viter->second.count(); } + bool contains(const std::string & o) const { return static_cast(count(o)); } + const OptionValue & operator[](const std::string & option) const { auto iter = m_keys.find(option); if(iter == m_keys.end()) { - throw_or_mimic(option); + throw_or_mimic(option); } auto viter = m_values.find(iter->second); if(viter == m_values.end()) { - throw_or_mimic(option); + throw_or_mimic(option); } return viter->second; } +#ifdef CXXOPTS_HAS_OPTIONAL + template std::optional as_optional(const std::string & option) const + { + auto iter = m_keys.find(option); + if(iter != m_keys.end()) + { + auto viter = m_values.find(iter->second); + if(viter != m_values.end()) + { + return viter->second.as_optional(); + } + } + return std::nullopt; + } +#endif + const std::vector & arguments() const { return m_sequential; } const std::vector & unmatched() const { return m_unmatched; } + const std::vector & defaults() const { return m_defaults; } + + const std::string arguments_string() const + { + std::string result; + for(const auto & kv: m_sequential) + { + result += kv.key() + " = " + kv.value() + "\n"; + } + for(const auto & kv: m_defaults) + { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + private: NameHashMap m_keys {}; ParsedHashMap m_values {}; std::vector m_sequential {}; + std::vector m_defaults {}; std::vector m_unmatched {}; }; @@ -1222,7 +1491,7 @@ namespace cxxopts const std::shared_ptr & value, const std::string & name); - void add_to_option(OptionMap::const_iterator iter, const std::string & option, const std::string & arg); + void add_to_option(const std::shared_ptr & value, const std::string & arg); void parse_option( const std::shared_ptr & value, @@ -1240,6 +1509,7 @@ namespace cxxopts const PositionalList & m_positional; std::vector m_sequential {}; + std::vector m_defaults {}; bool m_allow_unrecognised; ParsedHashMap m_parsed {}; @@ -1248,8 +1518,8 @@ namespace cxxopts class Options { public: - explicit Options(std::string program, std::string help_string = "") - : m_program(std::move(program)), m_help_string(toLocalString(std::move(help_string))), + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)), m_help_string(toLocalString(std::move(help_string))), m_custom_help("[OPTION...]"), m_positional_help("positional parameters"), m_show_positional(false), m_allow_unrecognised(false), m_width(76), m_tab_expansion(false), m_options(std::make_shared()) { } @@ -1278,7 +1548,7 @@ namespace cxxopts return *this; } - Options & set_width(size_t width) + Options & set_width(std::size_t width) { m_width = width; return *this; @@ -1301,11 +1571,24 @@ namespace cxxopts void add_option( const std::string & group, const std::string & s, - const std::string & l, + const OptionNames & l, std::string desc, const std::shared_ptr & value, std::string arg_help); + void add_option( + const std::string & group, + const std::string & short_name, + const std::string & single_long_name, + std::string desc, + const std::shared_ptr & value, + std::string arg_help) + { + OptionNames long_names; + long_names.emplace_back(single_long_name); + add_option(group, short_name, long_names, desc, value, arg_help); + } + //parse positional arguments into the given option void parse_positional(std::string option); @@ -1318,12 +1601,14 @@ namespace cxxopts parse_positional(std::vector {begin, end}); } - std::string help(const std::vector & groups = {}) const; + std::string help(const std::vector & groups = {}, bool print_usage = true) const; std::vector groups() const; const HelpGroupDetails & group_help(const std::string & group) const; + const std::string & program() const { return m_program; } + private: void add_one_option(const std::string & option, const std::shared_ptr & details); @@ -1339,7 +1624,7 @@ namespace cxxopts std::string m_positional_help {}; bool m_show_positional; bool m_allow_unrecognised; - size_t m_width; + std::size_t m_width; bool m_tab_expansion; std::shared_ptr m_options; @@ -1347,10 +1632,8 @@ namespace cxxopts std::unordered_set m_positional_set {}; //mapping from groups to help options + std::vector m_group {}; std::map m_help {}; - - std::list m_option_list {}; - std::unordered_map m_option_map {}; }; class OptionAdder { @@ -1370,13 +1653,13 @@ namespace cxxopts namespace { - constexpr size_t OPTION_LONGEST = 30; - constexpr size_t OPTION_DESC_GAP = 2; + constexpr std::size_t OPTION_LONGEST = 30; + constexpr std::size_t OPTION_DESC_GAP = 2; String format_option(const HelpOptionDetails & o) { const auto & s = o.s; - const auto & l = o.l; + const auto & l = first_or_empty(o.l); String result = " "; @@ -1415,7 +1698,8 @@ namespace cxxopts return result; } - String format_description(const HelpOptionDetails & o, size_t start, size_t allowed, bool tab_expansion) + String + format_description(const HelpOptionDetails & o, std::size_t start, std::size_t allowed, bool tab_expansion) { auto desc = o.desc; @@ -1436,7 +1720,7 @@ namespace cxxopts if(tab_expansion) { String desc2; - auto size = size_t {0}; + auto size = std::size_t {0}; for(auto c = std::begin(desc); c != std::end(desc); ++c) { if(*c == '\n') @@ -1466,7 +1750,7 @@ namespace cxxopts auto startLine = current; auto lastSpace = current; - auto size = size_t {}; + auto size = std::size_t {}; bool appendNewLine; bool onlyWhiteSpace = true; @@ -1474,13 +1758,11 @@ namespace cxxopts while(current != std::end(desc)) { appendNewLine = false; - - if(std::isblank(*previous)) + if(*previous == ' ' || *previous == '\t') { lastSpace = current; } - - if(!std::isblank(*current)) + if(*current != ' ' && *current != '\t') { onlyWhiteSpace = false; } @@ -1537,6 +1819,7 @@ namespace cxxopts return result; } + } // namespace inline void Options::add_options(const std::string & group, std::initializer_list