diff options
Diffstat (limited to 'third-party/cxxopts/cxxopts.hpp')
-rw-r--r-- | third-party/cxxopts/cxxopts.hpp | 3215 |
1 files changed, 1873 insertions, 1342 deletions
diff --git a/third-party/cxxopts/cxxopts.hpp b/third-party/cxxopts/cxxopts.hpp index 5cebc4bd17..8aa17bd63b 100644 --- a/third-party/cxxopts/cxxopts.hpp +++ b/third-party/cxxopts/cxxopts.hpp @@ -1,7 +1,6 @@ /* -Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck - +Copyright (c) 2014-2022 Jarryd Beck SPDX-License-Identifier: MIT Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,28 +23,55 @@ THE SOFTWARE. */ +// vim: ts=2:sw=2:expandtab + #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED -#include <cctype> #include <cstring> #include <exception> -#include <iostream> #include <limits> -#include <list> +#include <initializer_list> #include <map> #include <memory> -#include <regex> #include <sstream> #include <string> #include <unordered_map> #include <unordered_set> #include <utility> #include <vector> +#include <algorithm> +#include <locale> + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include <iostream> +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# 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 -#ifdef __cpp_lib_optional -#include <optional> -#define CXXOPTS_HAS_OPTIONAL +#ifndef CXXOPTS_NO_REGEX +# include <regex> +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for <optional> +#ifdef __has_include +# if __has_include(<optional>) +# include <optional> +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif #endif #if __cplusplus >= 201603L @@ -59,18 +85,39 @@ THE SOFTWARE. #endif #define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 0 -#define CXXOPTS__VERSION_PATCH 0 +#define CXXOPTS__VERSION_MINOR 1 +#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 - }; +#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 { + uint8_t major, minor, patch; +} version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH +}; } // namespace cxxopts //when we ask cxxopts to use Unicode, help strings are processed using ICU, @@ -82,1602 +129,2064 @@ namespace cxxopts #ifdef CXXOPTS_USE_UNICODE #include <unicode/unistr.h> -namespace cxxopts -{ - typedef icu::UnicodeString String; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } - - class UnicodeStringIterator : public - std::iterator<std::forward_iterator_tag, int32_t> - { - public: - - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) - { - } - - value_type - operator*() const - { - return s->char32At(i); - } - - bool - operator==(const UnicodeStringIterator& rhs) const - { - return s == rhs.s && i == rhs.i; - } +namespace cxxopts { - bool - operator!=(const UnicodeStringIterator& rhs) const - { - return !(*this == rhs); - } +using String = icu::UnicodeString; - UnicodeStringIterator& - operator++() - { - ++i; - return *this; - } +inline +String +toLocalString(std::string s) +{ + return icu::UnicodeString::fromUTF8(std::move(s)); +} - UnicodeStringIterator - operator+(int32_t v) - { - return UnicodeStringIterator(s, i + v); - } +// 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<cxxopts::Value>' has accessible non-virtual destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator +{ + public: - private: - const icu::UnicodeString* s; - int32_t i; - }; + 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&; - inline - String& - stringAppend(String&s, String a) + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) { - return s.append(std::move(a)); } - inline - String& - stringAppend(String& s, size_t n, UChar32 c) + value_type + operator*() const { - for (size_t i = 0; i != n; ++i) - { - s.append(c); - } - - return s; + return s->char32At(i); } - template <typename Iterator> - String& - stringAppend(String& s, Iterator begin, Iterator end) + bool + operator==(const UnicodeStringIterator& rhs) const { - while (begin != end) - { - s.append(*begin); - ++begin; - } - - return s; + return s == rhs.s && i == rhs.i; } - inline - size_t - stringLength(const String& s) + bool + operator!=(const UnicodeStringIterator& rhs) const { - return s.length(); + return !(*this == rhs); } - inline - std::string - toUTF8String(const String& s) + UnicodeStringIterator& + operator++() { - std::string result; - s.toUTF8String(result); - - return result; + ++i; + return *this; } - inline - bool - empty(const String& s) + UnicodeStringIterator + operator+(int32_t v) { - return s.isEmpty(); + return UnicodeStringIterator(s, i + v); } + + private: + const icu::UnicodeString* s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline +String& +stringAppend(String&s, String a) +{ + return s.append(std::move(a)); } -namespace std +inline +String& +stringAppend(String& s, std::size_t n, UChar32 c) { - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) + for (std::size_t i = 0; i != n; ++i) { - return cxxopts::UnicodeStringIterator(&s, 0); + s.append(c); } - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) + return s; +} + +template <typename Iterator> +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + while (begin != end) { - return cxxopts::UnicodeStringIterator(&s, s.length()); + s.append(*begin); + ++begin; } + + return s; +} + +inline +size_t +stringLength(const String& s) +{ + return static_cast<size_t>(s.length()); +} + +inline +std::string +toUTF8String(const String& s) +{ + std::string result; + s.toUTF8String(result); + + return result; +} + +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); +} + +inline +cxxopts::UnicodeStringIterator +end(const icu::UnicodeString& s) +{ + return cxxopts::UnicodeStringIterator(&s, s.length()); } +} // namespace std + //ifdef CXXOPTS_USE_UNICODE #else -namespace cxxopts +namespace cxxopts { + +using String = std::string; + +template <typename T> +T +toLocalString(T&& t) { - typedef std::string String; + return std::forward<T>(t); +} - template <typename T> - T - toLocalString(T&& t) +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, std::size_t n, char c) +{ + return s.append(n, c); +} + +template <typename Iterator> +String& +stringAppend(String& s, Iterator begin, Iterator end) +{ + return s.append(begin, end); +} + +template <typename T> +std::string +toUTF8String(T&& t) +{ + return std::forward<T>(t); +} + +inline +bool +empty(const std::string& s) +{ + return s.empty(); +} + +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace + +// 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<cxxopts::Value>' 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<Value> +{ + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr<Value> + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr<Value> + default_value(const std::string& value) = 0; + + virtual std::shared_ptr<Value> + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr<Value> + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception +{ + public: + explicit exception(std::string message) + : m_message(std::move(message)) { - return std::forward<T>(t); } - inline - size_t - stringLength(const String& s) + CXXOPTS_NODISCARD + const char* + what() const noexcept override { - return s.length(); + return m_message.c_str(); } - inline - String& - stringAppend(String&s, const String& a) + private: + std::string m_message; +}; + +class specification : public exception +{ + public: + + explicit specification(const std::string& message) + : exception(message) { - return s.append(a); } +}; - inline - String& - stringAppend(String& s, size_t n, char c) +class parsing : public exception +{ + public: + explicit parsing(const std::string& message) + : exception(message) { - return s.append(n, c); } +}; - template <typename Iterator> - String& - stringAppend(String& s, Iterator begin, Iterator end) +class option_already_exists : public specification +{ + public: + explicit option_already_exists(const std::string& option) + : specification("Option " + LQUOTE + option + RQUOTE + " already exists") { - return s.append(begin, end); } +}; - template <typename T> - std::string - toUTF8String(T&& t) +class invalid_option_format : public specification +{ + public: + explicit invalid_option_format(const std::string& format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) { - return std::forward<T>(t); } +}; - inline - bool - empty(const std::string& s) +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") { - return s.empty(); } -} // namespace cxxopts +}; -//ifdef CXXOPTS_USE_UNICODE -#endif +class no_such_option : public parsing +{ + public: + explicit no_such_option(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } +}; -namespace cxxopts +class missing_argument : public parsing { - namespace + public: + explicit missing_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) { -#ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); -#else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); -#endif - } // 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<cxxopts::Value>' has accessible non-virtual destructor -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic push -// This will be ignored under other compilers like LLVM clang. -#endif - class Value : public std::enable_shared_from_this<Value> +class option_requires_argument : public parsing +{ + public: + explicit option_requires_argument(const std::string& option) + : parsing( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) { - public: + } +}; - virtual ~Value() = default; +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" + ) + { + } +}; - virtual - std::shared_ptr<Value> - clone() const = 0; +class requested_option_not_present : public parsing +{ + public: + explicit requested_option_not_present(const std::string& option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") + { + } +}; - virtual void - parse(const std::string& text) const = 0; +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") + { + } +}; - virtual void - parse() const = 0; +class incorrect_argument_type : public parsing +{ + public: + explicit incorrect_argument_type + ( + const std::string& arg + ) + : parsing( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } +}; - virtual bool - has_default() const = 0; +} // namespace exceptions - virtual bool - is_container() const = 0; - virtual bool - has_implicit() const = 0; +template <typename T> +void throw_or_mimic(const std::string& text) +{ + static_assert(std::is_base_of<std::exception, T>::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); - virtual std::string - get_default_value() const = 0; +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} - virtual std::string - get_implicit_value() const = 0; +using OptionNames = std::vector<std::string>; - virtual std::shared_ptr<Value> - default_value(const std::string& value) = 0; +namespace values { - virtual std::shared_ptr<Value> - implicit_value(const std::string& value) = 0; +namespace parser_tool { - virtual std::shared_ptr<Value> - no_implicit_value() = 0; +struct IntegerDesc +{ + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) +{ + if (text.empty()) + { + throw_or_mimic<exceptions::incorrect_argument_type>(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic<exceptions::incorrect_argument_type>(text); + } + return desc; +} - virtual bool - is_boolean() const = 0; - }; -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - class OptionException : public std::exception +inline bool IsTrueText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') { - public: - explicit OptionException(std::string message) - : m_message(std::move(message)) + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) { + return true; } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; +} - CXXOPTS_NODISCARD - const char* - what() const noexcept override +inline bool IsFalseText(const std::string &text) +{ + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) { - return m_message.c_str(); + return true; } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; +} - private: - std::string m_message; - }; +inline OptionNames split_option_names(const std::string &text) +{ + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); - class OptionSpecException : public OptionException + if (length == 0) { - public: + throw_or_mimic<exceptions::invalid_option_format>(text); + } - explicit OptionSpecException(const std::string& message) - : OptionException(message) - { + while (token_start_pos < length) { + 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<exceptions::invalid_option_format>(text); } - }; - - class OptionParseException : public OptionException - { - public: - explicit OptionParseException(const std::string& message) - : OptionException(message) - { + 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<exceptions::invalid_option_format>(text); } - }; - - class option_exists_error : public OptionSpecException - { - public: - explicit option_exists_error(const std::string& option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; } - }; - - class invalid_option_format_error : public OptionSpecException - { - public: - explicit invalid_option_format_error(const std::string& format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + 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" + "_-.?"; - 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") - { + 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<exceptions::invalid_option_format>(text); + } } - }; + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} - class option_not_exists_exception : public OptionParseException +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) { - public: - explicit option_not_exists_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } } - }; - - class missing_argument_exception : public OptionParseException + } + else if (strncmp(pdata, "-", 1) == 0) { - public: - explicit missing_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is missing an argument" - ) + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; } - }; + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} - 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" - ) - { - } - }; +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +std::basic_regex<char> integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); +CXXOPTS_LINKONCE +std::basic_regex<char> truthy_pattern + ("(t|T)(rue)?|1"); +CXXOPTS_LINKONCE +std::basic_regex<char> falsy_pattern + ("(f|F)(alse)?|0"); +CXXOPTS_LINKONCE +std::basic_regex<char> option_matcher + ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"); +CXXOPTS_LINKONCE +std::basic_regex<char> option_specifier + ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"); +CXXOPTS_LINKONCE +std::basic_regex<char> option_specifier_separator(", *"); + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) +{ + std::smatch match; + std::regex_match(text, match, integer_pattern); - class option_not_has_argument_exception : public OptionParseException + if (match.length() == 0) { - 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" - ) - { - } - }; + throw_or_mimic<exceptions::incorrect_argument_type>(text); + } - class option_not_present_exception : public OptionParseException - { - public: - explicit option_not_present_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") - { - } - }; + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; - class option_has_no_value_exception : public OptionException + if (match.length(4) > 0) { - 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") - { - } - }; + desc.base = match[5]; + desc.value = "0"; + return desc; + } - class argument_incorrect_type : public OptionParseException - { - public: - explicit argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) - { - } - }; + return desc; +} - 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" - ) - { - } - }; +inline bool IsTrueText(const std::string &text) +{ + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); +} - template <typename T> - void throw_or_mimic(const std::string& text) - { - static_assert(std::is_base_of<std::exception, T>::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); +inline bool IsFalseText(const std::string &text) +{ + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); +} -#ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{text}; -#else - // Otherwise manually instantiate the exception, print what() to stderr, - // and exit - T exception{text}; - std::cerr << exception.what() << std::endl; - std::exit(EXIT_FAILURE); -#endif +// 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) +{ + if (!std::regex_match(text.c_str(), option_specifier)) + { + throw_or_mimic<exceptions::invalid_option_format>(text); } - namespace values - { - namespace - { - std::basic_regex<char> integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex<char> truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex<char> falsy_pattern - ("(f|F)(alse)?|0"); - } // namespace - - namespace detail - { - template <typename T, bool B> - struct SignedCheck; + OptionNames split_names; - template <typename T> - struct SignedCheck<T, true> - { - template <typename U> - void - operator()(bool negative, U u, const std::string& text) - { - if (negative) - { - if (u > static_cast<U>((std::numeric_limits<T>::min)())) - { - throw_or_mimic<argument_incorrect_type>(text); - } - } - else - { - if (u > static_cast<U>((std::numeric_limits<T>::max)())) - { - throw_or_mimic<argument_incorrect_type>(text); - } - } - } - }; + constexpr int use_non_matches { -1 }; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator, use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); + return split_names; +} - template <typename T> - struct SignedCheck<T, false> - { - template <typename U> - void - operator()(bool, U, const std::string&) {} - }; - - template <typename T, typename U> - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text); - } - } // namespace detail +inline ArguDesc ParseArgument(const char *arg, bool &matched) +{ + std::match_results<const char*> result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); - template <typename R, typename T> - void - checked_negate(R& r, T&& t, const std::string&, std::true_type) + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) { - // 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<R>(-static_cast<R>(t-1)-1); + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); } + } - template <typename R, typename T> - void - checked_negate(R&, T&&, const std::string& text, std::false_type) - { - throw_or_mimic<argument_incorrect_type>(text); - } + return argu_desc; +} - template <typename T> - void - integer_parser(const std::string& text, T& value) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template <typename T, bool B> +struct SignedCheck; - if (match.length() == 0) +template <typename T> +struct SignedCheck<T, true> +{ + template <typename U> + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast<U>((std::numeric_limits<T>::min)())) { - throw_or_mimic<argument_incorrect_type>(text); + throw_or_mimic<exceptions::incorrect_argument_type>(text); } - - if (match.length(4) > 0) + } + else + { + if (u > static_cast<U>((std::numeric_limits<T>::max)())) { - value = 0; - return; + throw_or_mimic<exceptions::incorrect_argument_type>(text); } + } + } +}; - using US = typename std::make_unsigned<T>::type; +template <typename T> +struct SignedCheck<T, false> +{ + template <typename U> + void + operator()(bool, U, const std::string&) const {} +}; - constexpr bool is_signed = std::numeric_limits<T>::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; +template <typename T, typename U> +void +check_signed_range(bool negative, U value, const std::string& text) +{ + SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text); +} - auto value_match = match[3]; +} // namespace detail - US result = 0; +template <typename R, typename T> +void +checked_negate(R& r, T&& t, const std::string&, std::true_type) +{ + // 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<R>(-static_cast<R>(t-1)-1); +} - for (auto iter = value_match.first; iter != value_match.second; ++iter) - { - US digit = 0; +template <typename R, typename T> +void +checked_negate(R&, T&&, const std::string& text, std::false_type) +{ + throw_or_mimic<exceptions::incorrect_argument_type>(text); +} - if (*iter >= '0' && *iter <= '9') - { - digit = static_cast<US>(*iter - '0'); - } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') - { - digit = static_cast<US>(*iter - 'a' + 10); - } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') - { - digit = static_cast<US>(*iter - 'A' + 10); - } - else - { - throw_or_mimic<argument_incorrect_type>(text); - } +template <typename T> +void +integer_parser(const std::string& text, T& value) +{ + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); - const US next = static_cast<US>(result * base + digit); - if (result > next) - { - throw_or_mimic<argument_incorrect_type>(text); - } + using US = typename std::make_unsigned<T>::type; + constexpr bool is_signed = std::numeric_limits<T>::is_signed; - result = next; - } + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; - detail::check_signed_range<T>(negative, result, text); + US result = 0; - if (negative) - { - checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>()); - } - else - { - value = static_cast<T>(result); - } - } + for (char ch : value_match) + { + US digit = 0; - template <typename T> - void stringstream_parser(const std::string& text, T& value) + if (ch >= '0' && ch <= '9') { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic<argument_incorrect_type>(text); - } + digit = static_cast<US>(ch - '0'); } - - inline - void - parse_value(const std::string& text, uint8_t& value) + else if (base == 16 && ch >= 'a' && ch <= 'f') { - integer_parser(text, value); + digit = static_cast<US>(ch - 'a' + 10); } - - inline - void - parse_value(const std::string& text, int8_t& value) + else if (base == 16 && ch >= 'A' && ch <= 'F') { - integer_parser(text, value); + digit = static_cast<US>(ch - 'A' + 10); } - - inline - void - parse_value(const std::string& text, uint16_t& value) + else { - integer_parser(text, value); + throw_or_mimic<exceptions::incorrect_argument_type>(text); } - inline - void - parse_value(const std::string& text, int16_t& value) + const US next = static_cast<US>(result * base + digit); + if (result > next) { - integer_parser(text, value); + throw_or_mimic<exceptions::incorrect_argument_type>(text); } - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } + result = next; + } - inline - void - parse_value(const std::string& text, int32_t& value) - { - integer_parser(text, value); - } + detail::check_signed_range<T>(negative, result, text); - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } + if (negative) + { + checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>()); + } + else + { + value = static_cast<T>(result); + } +} - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); - } +template <typename T> +void stringstream_parser(const std::string& text, T& value) +{ + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic<exceptions::incorrect_argument_type>(text); + } +} - inline - void - parse_value(const std::string& text, bool& value) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); +template <typename T, + typename std::enable_if<std::is_integral<T>::value>::type* = nullptr + > +void parse_value(const std::string& text, T& value) +{ + integer_parser(text, value); +} - if (!result.empty()) - { - value = true; - return; - } +inline +void +parse_value(const std::string& text, bool& value) +{ + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) - { - value = false; - return; - } + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } - throw_or_mimic<argument_incorrect_type>(text); - } + throw_or_mimic<exceptions::incorrect_argument_type>(text); +} - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } +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 <typename T> - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); - } +// 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 <typename T, + typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr + > +void +parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); +} - template <typename T> - void - parse_value(const std::string& text, std::vector<T>& value) - { - std::stringstream in(text); - std::string token; - while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } - } +template <typename T> +void +parse_value(const std::string& text, std::vector<T>& value) +{ + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} #ifdef CXXOPTS_HAS_OPTIONAL - template <typename T> - void - parse_value(const std::string& text, std::optional<T>& value) - { - T result; - parse_value(text, result); - value = std::move(result); - } +template <typename T> +void +parse_value(const std::string& text, std::optional<T>& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} #endif - inline - void parse_value(const std::string& text, char& c) - { - if (text.length() != 1) - { - throw_or_mimic<argument_incorrect_type>(text); - } +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic<exceptions::incorrect_argument_type>(text); + } - c = text[0]; - } + c = text[0]; +} - template <typename T> - struct type_is_container - { - static constexpr bool value = false; - }; +template <typename T> +struct type_is_container +{ + static constexpr bool value = false; +}; - template <typename T> - struct type_is_container<std::vector<T>> - { - static constexpr bool value = true; - }; +template <typename T> +struct type_is_container<std::vector<T>> +{ + static constexpr bool value = true; +}; - template <typename T> - class abstract_value : public Value +template <typename T> +class abstract_value : public Value +{ + using Self = abstract_value<T>; + + public: + abstract_value() + : m_result(std::make_shared<T>()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) { - using Self = abstract_value<T>; + m_result = std::make_shared<T>(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } - public: - abstract_value() - : m_result(std::make_shared<T>()) - , m_store(m_result.get()) - { - } + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } - explicit abstract_value(T* t) - : m_store(t) - { - } + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } - ~abstract_value() override = default; + bool + is_container() const override + { + return type_is_container<T>::value; + } - abstract_value& operator=(const abstract_value&) = default; + void + parse() const override + { + parse_value(m_default_value, *m_store); + } - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) - { - m_result = std::make_shared<T>(); - m_store = m_result.get(); - } - else - { - m_store = rhs.m_store; - } + bool + has_default() const override + { + return m_default; + } - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } + bool + has_implicit() const override + { + return m_implicit; + } - void - parse(const std::string& text) const override - { - parse_value(text, *m_store); - } + std::shared_ptr<Value> + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } - bool - is_container() const override - { - return type_is_container<T>::value; - } + std::shared_ptr<Value> + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } - void - parse() const override - { - parse_value(m_default_value, *m_store); - } + std::shared_ptr<Value> + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } - bool - has_default() const override - { - return m_default; - } + std::string + get_default_value() const override + { + return m_default_value; + } - bool - has_implicit() const override - { - return m_implicit; - } + std::string + get_implicit_value() const override + { + return m_implicit_value; + } - std::shared_ptr<Value> - default_value(const std::string& value) override - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } + bool + is_boolean() const override + { + return std::is_same<T, bool>::value; + } - std::shared_ptr<Value> - implicit_value(const std::string& value) override - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } - std::shared_ptr<Value> - no_implicit_value() override - { - m_implicit = false; - return shared_from_this(); - } + protected: + std::shared_ptr<T> m_result{}; + T* m_store{}; - std::string - get_default_value() const override - { - return m_default_value; - } + bool m_default = false; + bool m_implicit = false; - std::string - get_implicit_value() const override - { - return m_implicit_value; - } + std::string m_default_value{}; + std::string m_implicit_value{}; +}; - bool - is_boolean() const override - { - return std::is_same<T, bool>::value; - } +template <typename T> +class standard_value : public abstract_value<T> +{ + public: + using abstract_value<T>::abstract_value; - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - return *m_store; - } + CXXOPTS_NODISCARD + std::shared_ptr<Value> + clone() const override + { + return std::make_shared<standard_value<T>>(*this); + } +}; - protected: - std::shared_ptr<T> m_result{}; - T* m_store{}; +template <> +class standard_value<bool> : public abstract_value<bool> +{ + public: + ~standard_value() override = default; - bool m_default = false; - bool m_implicit = false; + standard_value() + { + set_default_and_implicit(); + } - std::string m_default_value{}; - std::string m_implicit_value{}; - }; + explicit standard_value(bool* b) + : abstract_value(b) + { + m_implicit = true; + m_implicit_value = "true"; + } - template <typename T> - class standard_value : public abstract_value<T> - { - public: - using abstract_value<T>::abstract_value; + std::shared_ptr<Value> + clone() const override + { + return std::make_shared<standard_value<bool>>(*this); + } - CXXOPTS_NODISCARD - std::shared_ptr<Value> - clone() const - { - return std::make_shared<standard_value<T>>(*this); - } - }; + private: - template <> - class standard_value<bool> : public abstract_value<bool> - { - public: - ~standard_value() override = default; + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; - standard_value() - { - set_default_and_implicit(); - } +} // namespace values - explicit standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } +template <typename T> +std::shared_ptr<Value> +value() +{ + return std::make_shared<values::standard_value<T>>(); +} - std::shared_ptr<Value> - clone() const override - { - return std::make_shared<standard_value<bool>>(*this); - } +template <typename T> +std::shared_ptr<Value> +value(T& t) +{ + return std::make_shared<values::standard_value<T>>(&t); +} - private: +class OptionAdder; - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; - } // namespace values +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(); +} - template <typename T> - std::shared_ptr<Value> - value() +class OptionDetails +{ + public: + OptionDetails + ( + std::string short_, + OptionNames long_, + String desc, + std::shared_ptr<const Value> val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) { - return std::make_shared<values::standard_value<T>>(); + m_hash = std::hash<std::string>{}(first_long_name() + m_short); } - template <typename T> + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD std::shared_ptr<Value> - value(T& t) + make_storage() const { - return std::make_shared<values::standard_value<T>>(&t); + return m_value->clone(); } - class OptionAdder; + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } - class OptionDetails + CXXOPTS_NODISCARD + const std::string& + first_long_name() const { - public: - OptionDetails - ( - std::string short_, - std::string long_, - String desc, - std::shared_ptr<const Value> 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<std::string>{}(m_long + m_short); - } + return first_or_empty(m_long); + } - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_count(rhs.m_count) - { - m_value = rhs.m_value->clone(); - } + CXXOPTS_NODISCARD + const std::string& + essential_name() const + { + return m_long.empty() ? m_short : m_long.front(); + } - OptionDetails(OptionDetails&& rhs) = default; + CXXOPTS_NODISCARD + const OptionNames & + long_names() const + { + return m_long; + } - CXXOPTS_NODISCARD - const String& - description() const - { - return m_desc; - } + std::size_t + hash() const + { + return m_hash; + } - CXXOPTS_NODISCARD - const Value& - value() const { - return *m_value; - } + private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr<const Value> m_value{}; + int m_count; - CXXOPTS_NODISCARD - std::shared_ptr<Value> - make_storage() const - { - return m_value->clone(); - } + std::size_t m_hash{}; +}; - CXXOPTS_NODISCARD - const std::string& - short_name() const - { - return m_short; - } +struct HelpOptionDetails +{ + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails +{ + std::string name{}; + std::string description{}; + std::vector<HelpOptionDetails> options{}; +}; - CXXOPTS_NODISCARD - const std::string& - long_name() const - { - return m_long; - } +class OptionValue +{ + public: + void + parse + ( + const std::shared_ptr<const OptionDetails>& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } - size_t - hash() const - { - return m_hash; - } + void + parse_default(const std::shared_ptr<const OptionDetails>& details) + { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } - private: - std::string m_short{}; - std::string m_long{}; - String m_desc{}; - std::shared_ptr<const Value> m_value{}; - int m_count; + void + parse_no_value(const std::shared_ptr<const OptionDetails>& details) + { + m_long_names = &details->long_names(); + } - size_t m_hash{}; - }; +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; + CXXOPTS_NODISCARD + std::size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +CXXOPTS_DIAGNOSTIC_POP +#endif - struct HelpGroupDetails + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept { - std::string name{}; - std::string description{}; - std::vector<HelpOptionDetails> options{}; - }; + return m_default; + } - class OptionValue + template <typename T> + const T& + as() const { - public: - void - parse - ( - const std::shared_ptr<const OptionDetails>& details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - m_long_name = &details->long_name(); + if (m_value == nullptr) { + throw_or_mimic<exceptions::option_has_no_value>( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); } - void - parse_default(const std::shared_ptr<const OptionDetails>& details) - { - ensure_value(details); - m_default = true; - m_long_name = &details->long_name(); - m_value->parse(); - } + return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get(); + } - CXXOPTS_NODISCARD - size_t - count() const noexcept + private: + void + ensure_value(const std::shared_ptr<const OptionDetails>& details) + { + if (m_value == nullptr) { - return m_count; + m_value = details->make_storage(); } + } - // TODO: maybe default options should count towards the number of arguments - CXXOPTS_NODISCARD - bool - has_default() const noexcept - { - return m_default; - } - template <typename T> - const T& - as() const - { - if (m_value == nullptr) { - throw_or_mimic<option_has_no_value_exception>( - m_long_name == nullptr ? "" : *m_long_name); + 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<Value> m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue +{ + public: + 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; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template <typename T> + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map<std::size_t, OptionValue>; +using NameHashMap = std::unordered_map<std::string, std::size_t>; + +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(); -#ifdef CXXOPTS_NO_RTTI - return static_cast<const values::standard_value<T>&>(*m_value).get(); -#else - return dynamic_cast<const values::standard_value<T>&>(*m_value).get(); -#endif + if (m_iter == m_pr->m_sequential.end()) + { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } } +CXXOPTS_DIAGNOSTIC_POP - private: - void - ensure_value(const std::shared_ptr<const OptionDetails>& details) + Iterator& operator++() { - if (m_value == nullptr) + ++m_iter; + if(m_sequential && m_iter == m_pr->m_sequential.end()) { - m_value = details->make_storage(); + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; } + return *this; } + Iterator operator++(int) + { + Iterator retval = *this; + ++(*this); + return retval; + } - const std::string* m_long_name = 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<Value> m_value{}; - 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_)) + bool operator==(const Iterator& other) const { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); } - CXXOPTS_NODISCARD - const std::string& - key() const + bool operator!=(const Iterator& other) const { - return m_key; + return !(*this == other); } - CXXOPTS_NODISCARD - const std::string& - value() const + const KeyValue& operator*() { - return m_value; + return *m_iter; } - template <typename T> - T - as() const + const KeyValue* operator->() { - T result; - values::parse_value(m_value, result); - return result; + return m_iter.operator->(); } private: - std::string m_key; - std::string m_value; + const ParseResult* m_pr; + std::vector<KeyValue>::const_iterator m_iter; + bool m_sequential = true; }; - using ParsedHashMap = std::unordered_map<size_t, OptionValue>; - using NameHashMap = std::unordered_map<std::string, size_t>; + ParseResult() = default; + ParseResult(const ParseResult&) = default; - class ParseResult + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, + std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_defaults(std::move(default_opts)) + , m_unmatched(std::move(unmatched_args)) { - public: + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; - ParseResult() {} + Iterator + begin() const + { + return Iterator(this); + } - ParseResult(const ParseResult&) = default; + Iterator + end() const + { + return Iterator(this, true); + } - ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args) - : m_keys(std::move(keys)) - , m_values(std::move(values)) - , m_sequential(std::move(sequential)) - , m_unmatched(std::move(unmatched_args)) + std::size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) { + return 0; } - ParseResult& operator=(ParseResult&&) = default; - ParseResult& operator=(const ParseResult&) = default; + auto viter = m_values.find(iter->second); - size_t - count(const std::string& o) const + if (viter == m_values.end()) { - auto iter = m_keys.find(o); - if (iter == m_keys.end()) - { - return 0; - } + return 0; + } - auto viter = m_values.find(iter->second); + return viter->second.count(); + } - if (viter == m_values.end()) - { - return 0; - } + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); - return viter->second.count(); + if (iter == m_keys.end()) + { + throw_or_mimic<exceptions::requested_option_not_present>(option); } - const OptionValue& - operator[](const std::string& option) const + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) { - auto iter = m_keys.find(option); + throw_or_mimic<exceptions::requested_option_not_present>(option); + } - if (iter == m_keys.end()) - { - throw_or_mimic<option_not_present_exception>(option); - } + return viter->second; + } - auto viter = m_values.find(iter->second); + const std::vector<KeyValue>& + arguments() const + { + return m_sequential; + } - if (viter == m_values.end()) - { - throw_or_mimic<option_not_present_exception>(option); - } + const std::vector<std::string>& + unmatched() const + { + return m_unmatched; + } - return viter->second; - } + const std::vector<KeyValue>& + defaults() const + { + return m_defaults; + } - const std::vector<KeyValue>& - arguments() const + const std::string + arguments_string() const + { + std::string result; + for(const auto& kv: m_sequential) { - return m_sequential; + result += kv.key() + " = " + kv.value() + "\n"; } - - const std::vector<std::string>& - unmatched() const + for(const auto& kv: m_defaults) { - return m_unmatched; + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; } + return result; + } - private: - NameHashMap m_keys{}; - ParsedHashMap m_values{}; - std::vector<KeyValue> m_sequential{}; - std::vector<std::string> m_unmatched{}; - }; + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector<KeyValue> m_sequential{}; + std::vector<KeyValue> m_defaults{}; + std::vector<std::string> m_unmatched{}; +}; - struct Option +struct Option +{ + Option + ( + std::string opts, + std::string desc, + std::shared_ptr<const Value> value = ::cxxopts::value<bool>(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) { - Option - ( - std::string opts, - std::string desc, - std::shared_ptr<const Value> value = ::cxxopts::value<bool>(), - std::string arg_help = "" - ) - : opts_(std::move(opts)) - , desc_(std::move(desc)) - , value_(std::move(value)) - , arg_help_(std::move(arg_help)) - { - } + } - std::string opts_; - std::string desc_; - std::shared_ptr<const Value> value_; - std::string arg_help_; - }; + std::string opts_; + std::string desc_; + std::shared_ptr<const Value> value_; + std::string arg_help_; +}; - using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>; - using PositionalList = std::vector<std::string>; - using PositionalListIterator = PositionalList::const_iterator; +using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>; +using PositionalList = std::vector<std::string>; +using PositionalListIterator = PositionalList::const_iterator; - class OptionParser +class OptionParser +{ + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) { - public: - OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) - : m_options(options) - , m_positional(positional) - , m_allow_unrecognised(allow_unrecognised) - { - } + } - ParseResult - parse(int argc, const char* const* argv); + ParseResult + parse(int argc, const char* const* argv); - bool - consume_positional(const std::string& a, PositionalListIterator& next); + bool + consume_positional(const std::string& a, PositionalListIterator& next); - void - checked_parse_arg - ( - int argc, - const char* const* argv, - int& current, - const std::shared_ptr<OptionDetails>& value, - const std::string& name - ); + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr<OptionDetails>& 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(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); - void - parse_option - ( - const std::shared_ptr<OptionDetails>& value, - const std::string& name, - const std::string& arg = "" - ); + void + parse_option + ( + const std::shared_ptr<OptionDetails>& value, + const std::string& name, + const std::string& arg = "" + ); - void - parse_default(const std::shared_ptr<OptionDetails>& details); + void + parse_default(const std::shared_ptr<OptionDetails>& details); - private: + void + parse_no_value(const std::shared_ptr<OptionDetails>& details); - void finalise_aliases(); + private: - const OptionMap& m_options; - const PositionalList& m_positional; + void finalise_aliases(); - std::vector<KeyValue> m_sequential{}; - bool m_allow_unrecognised; + const OptionMap& m_options; + const PositionalList& m_positional; - ParsedHashMap m_parsed{}; - NameHashMap m_keys{}; - }; + std::vector<KeyValue> m_sequential{}; + std::vector<KeyValue> m_defaults{}; + bool m_allow_unrecognised; - class Options + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options +{ + public: + + 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<OptionMap>()) { - public: + } - explicit Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , 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_options(std::make_shared<OptionMap>()) - { - } + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } - Options& - positional_help(std::string help_text) - { - m_positional_help = std::move(help_text); - return *this; - } + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } - Options& - custom_help(std::string help_text) - { - m_custom_help = std::move(help_text); - return *this; - } + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } - Options& - show_positional_help() - { - m_show_positional = true; - return *this; - } + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } - Options& - allow_unrecognised_options() - { - m_allow_unrecognised = true; - return *this; - } + Options& + set_width(std::size_t width) + { + m_width = width; + return *this; + } - ParseResult - parse(int argc, const char* const* argv); + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } - OptionAdder - add_options(std::string group = ""); + ParseResult + parse(int argc, const char* const* argv); - void - add_options - ( - const std::string& group, - std::initializer_list<Option> options - ); + OptionAdder + add_options(std::string group = ""); - void - add_option - ( - const std::string& group, - const Option& option - ); + void + add_options + ( + const std::string& group, + std::initializer_list<Option> options + ); - void - add_option - ( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - const std::shared_ptr<const Value>& value, - std::string arg_help - ); + void + add_option + ( + const std::string& group, + const Option& option + ); - //parse positional arguments into the given option - void - parse_positional(std::string option); + void + add_option + ( + const std::string& group, + const std::string& s, + const OptionNames& l, + std::string desc, + const std::shared_ptr<const Value>& value, + std::string arg_help + ); - void - parse_positional(std::vector<std::string> options); + 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<const Value>& 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); + } - void - parse_positional(std::initializer_list<std::string> options); + //parse positional arguments into the given option + void + parse_positional(std::string option); - template <typename Iterator> - void - parse_positional(Iterator begin, Iterator end) { - parse_positional(std::vector<std::string>{begin, end}); - } + void + parse_positional(std::vector<std::string> options); + + void + parse_positional(std::initializer_list<std::string> options); - std::string - help(const std::vector<std::string>& groups = {}) const; + template <typename Iterator> + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector<std::string>{begin, end}); + } - std::vector<std::string> - groups() const; + std::string + help(const std::vector<std::string>& groups = {}, bool print_usage=true) const; - const HelpGroupDetails& - group_help(const std::string& group) const; + std::vector<std::string> + groups() const; - private: + const HelpGroupDetails& + group_help(const std::string& group) const; - void - add_one_option - ( - const std::string& option, - const std::shared_ptr<OptionDetails>& details - ); + const std::string& program() const + { + return m_program; + } + + private: + + void + add_one_option + ( + const std::string& option, + const std::shared_ptr<OptionDetails>& details + ); + + String + help_one_group(const std::string& group) const; - String - help_one_group(const std::string& group) const; + void + generate_group_help + ( + String& result, + const std::vector<std::string>& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program{}; + String m_help_string{}; + std::string m_custom_help{}; + std::string m_positional_help{}; + bool m_show_positional; + bool m_allow_unrecognised; + std::size_t m_width; + bool m_tab_expansion; + + std::shared_ptr<OptionMap> m_options; + std::vector<std::string> m_positional{}; + std::unordered_set<std::string> m_positional_set{}; + + //mapping from groups to help options + std::map<std::string, HelpGroupDetails> m_help{}; +}; + +class OptionAdder +{ + public: - void - generate_group_help - ( - String& result, - const std::vector<std::string>& groups - ) const; + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } - void - generate_all_groups_help(String& result) const; + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + const std::shared_ptr<const Value>& value + = ::cxxopts::value<bool>(), + std::string arg_help = "" + ); - std::string m_program{}; - String m_help_string{}; - std::string m_custom_help{}; - std::string m_positional_help{}; - bool m_show_positional; - bool m_allow_unrecognised; + private: + Options& m_options; + std::string m_group; +}; - std::shared_ptr<OptionMap> m_options; - std::vector<std::string> m_positional{}; - std::unordered_set<std::string> m_positional_set{}; +namespace { +constexpr std::size_t OPTION_LONGEST = 30; +constexpr std::size_t OPTION_DESC_GAP = 2; - //mapping from groups to help options - std::map<std::string, HelpGroupDetails> m_help{}; +String +format_option +( + const HelpOptionDetails& o +) +{ + const auto& s = o.s; + const auto& l = first_or_empty(o.l); - std::list<OptionDetails> m_option_list{}; - std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{}; - }; + String result = " "; - class OptionAdder + if (!s.empty()) { - public: - - OptionAdder(Options& options, std::string group) - : m_options(options), m_group(std::move(group)) + result += "-" + toLocalString(s); + if (!l.empty()) { + result += ","; } + } + else + { + result += " "; + } - OptionAdder& - operator() - ( - const std::string& opts, - const std::string& desc, - const std::shared_ptr<const Value>& value - = ::cxxopts::value<bool>(), - std::string arg_help = "" - ); + if (!l.empty()) + { + result += " --" + toLocalString(l); + } - private: - Options& m_options; - std::string m_group; - }; + auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg"; - namespace + if (!o.is_boolean) { - constexpr int OPTION_LONGEST = 30; - constexpr int OPTION_DESC_GAP = 2; + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } - std::basic_regex<char> option_matcher - ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + return result; +} - std::basic_regex<char> option_specifier - ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); +String +format_description +( + const HelpOptionDetails& o, + std::size_t start, + std::size_t allowed, + bool tab_expansion +) +{ + auto desc = o.desc; - String - format_option - ( - const HelpOptionDetails& o - ) + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + if(!o.default_value.empty()) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + else { - const auto& s = o.s; - const auto& l = o.l; + desc += toLocalString(" (default: \"\")"); + } + } - String result = " "; + String result; - if (!s.empty()) + if (tab_expansion) + { + String desc2; + auto size = std::size_t{ 0 }; + for (auto c = std::begin(desc); c != std::end(desc); ++c) + { + if (*c == '\n') { - result += "-" + toLocalString(s); - if (!l.empty()) - { - result += ","; - } + desc2 += *c; + size = 0; } - else + else if (*c == '\t') { - result += " "; + auto skip = 8 - size % 8; + stringAppend(desc2, skip, ' '); + size += skip; } - - if (!l.empty()) + else { - result += " --" + toLocalString(l); + desc2 += *c; + ++size; } + } + desc = desc2; + } - auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg"; + desc += " "; - if (!o.is_boolean) - { - if (o.has_implicit) - { - result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; - } - else - { - result += " " + arg; - } - } + auto current = std::begin(desc); + auto previous = current; + auto startLine = current; + auto lastSpace = current; + + auto size = std::size_t{}; + + bool appendNewLine; + bool onlyWhiteSpace = true; - return result; + while (current != std::end(desc)) + { + appendNewLine = false; + if (*previous == ' ' || *previous == '\t') + { + lastSpace = current; + } + if (*current != ' ' && *current != '\t') + { + onlyWhiteSpace = false; } - String - format_description - ( - const HelpOptionDetails& o, - size_t start, - size_t width - ) + while (*current == '\n') { - auto desc = o.desc; + previous = current; + ++current; + appendNewLine = true; + } - if (o.has_default && (!o.is_boolean || o.default_value != "false")) + if (!appendNewLine && size >= allowed) + { + if (lastSpace != startLine) { - if(!o.default_value.empty()) - { - desc += toLocalString(" (default: " + o.default_value + ")"); - } - else - { - desc += toLocalString(" (default: \"\")"); - } + current = lastSpace; + previous = current; } + appendNewLine = true; + } - String result; + if (appendNewLine) + { + stringAppend(result, startLine, current); + startLine = current; + lastSpace = current; - auto current = std::begin(desc); - auto startLine = current; - auto lastSpace = current; + if (*previous != '\n') + { + stringAppend(result, "\n"); + } - auto size = size_t{}; + stringAppend(result, start, ' '); - while (current != std::end(desc)) + if (*previous != '\n') { - if (*current == ' ') - { - lastSpace = current; - } + stringAppend(result, lastSpace, current); + } - if (*current == '\n') - { - startLine = current + 1; - lastSpace = startLine; - } - else if (size > width) - { - if (lastSpace == startLine) - { - stringAppend(result, startLine, current + 1); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = current + 1; - lastSpace = startLine; - } - else - { - stringAppend(result, startLine, lastSpace); - stringAppend(result, "\n"); - stringAppend(result, start, ' '); - startLine = lastSpace + 1; - lastSpace = startLine; - } - size = 0; - } - else - { - ++size; - } + onlyWhiteSpace = true; + size = 0; + } - ++current; - } + previous = current; + ++current; + ++size; + } - //append whatever is left - stringAppend(result, startLine, current); + //append whatever is left but ignore whitespace + if (!onlyWhiteSpace) + { + stringAppend(result, startLine, previous); + } - return result; - } - } // namespace + return result; +} + +} // namespace inline void @@ -1711,43 +2220,31 @@ OptionAdder::operator() std::string arg_help ) { - std::match_results<const char*> result; - std::regex_match(opts.c_str(), result, option_specifier); - - if (result.empty()) - { - throw_or_mimic<invalid_option_format_error>(opts); - } - - const auto& short_match = result[2]; - const auto& long_match = result[3]; - - if (!short_match.length() && !long_match.length()) - { - throw_or_mimic<invalid_option_format_error>(opts); - } else if (long_match.length() == 1 && short_match.length()) - { - throw_or_mimic<invalid_option_format_error>(opts); - } - - auto option_names = [] - ( - const std::sub_match<const char*>& short_, - const std::sub_match<const char*>& long_ - ) - { - if (long_.length() == 1) - { - return std::make_tuple(long_.str(), short_.str()); - } - return std::make_tuple(short_.str(), long_.str()); - }(short_match, long_match); + OptionNames option_names = values::parser_tool::split_option_names(opts); + // Note: All names will be non-empty; but we must separate the short + // (length-1) and longer names + std::string short_name {""}; + auto first_short_name_iter = + std::partition(option_names.begin(), option_names.end(), + [&](const std::string& name) { return name.length() > 1; } + ); + auto num_length_1_names = (option_names.end() - first_short_name_iter); + switch(num_length_1_names) { + case 1: + short_name = *first_short_name_iter; + option_names.erase(first_short_name_iter); + break; + case 0: + break; + default: + throw_or_mimic<exceptions::invalid_option_format>(opts); + }; m_options.add_option ( m_group, - std::get<0>(option_names), - std::get<1>(option_names), + short_name, + option_names, desc, value, std::move(arg_help) @@ -1763,6 +2260,15 @@ OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details) // TODO: remove the duplicate code here auto& store = m_parsed[details->hash()]; store.parse_default(details); + m_defaults.emplace_back(details->essential_name(), details->value().get_default_value()); +} + +inline +void +OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details) +{ + auto& store = m_parsed[details->hash()]; + store.parse_no_value(details); } inline @@ -1778,7 +2284,7 @@ OptionParser::parse_option auto& result = m_parsed[hash]; result.parse(value, arg); - m_sequential.emplace_back(value->long_name(), arg); + m_sequential.emplace_back(value->essential_name(), arg); } inline @@ -1800,7 +2306,7 @@ OptionParser::checked_parse_arg } else { - throw_or_mimic<missing_argument_exception>(name); + throw_or_mimic<exceptions::missing_argument>(name); } } else @@ -1833,9 +2339,9 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n auto iter = m_options.find(*next); if (iter != m_options.end()) { - auto& result = m_parsed[iter->second->hash()]; if (!iter->second->value().is_container()) { + auto& result = m_parsed[iter->second->hash()]; if (result.count() == 0) { add_to_option(iter, *next, a); @@ -1848,7 +2354,7 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n add_to_option(iter, *next, a); return true; } - throw_or_mimic<option_not_exists_exception>(*next); + throw_or_mimic<exceptions::no_such_option>(*next); } return false; @@ -1891,7 +2397,7 @@ OptionParser::parse(int argc, const char* const* argv) { int current = 1; bool consume_remaining = false; - PositionalListIterator next_positional = m_positional.begin(); + auto next_positional = m_positional.begin(); std::vector<std::string> unmatched; @@ -1903,18 +2409,18 @@ OptionParser::parse(int argc, const char* const* argv) ++current; break; } + bool matched = false; + values::parser_tool::ArguDesc argu_desc = + values::parser_tool::ParseArgument(argv[current], matched); - std::match_results<const char*> result; - std::regex_match(argv[current], result, option_matcher); - - if (result.empty()) + if (!matched) { //not a flag // but if it starts with a `-`, then it's an error if (argv[current][0] == '-' && argv[current][1] != '\0') { if (!m_allow_unrecognised) { - throw_or_mimic<option_syntax_exception>(argv[current]); + throw_or_mimic<exceptions::invalid_option_syntax>(argv[current]); } } @@ -1925,16 +2431,16 @@ OptionParser::parse(int argc, const char* const* argv) } else { - unmatched.push_back(argv[current]); + unmatched.emplace_back(argv[current]); } //if we return from here then it was parsed successfully, so continue } else { //short or long option? - if (result[4].length() != 0) + if (argu_desc.grouping) { - const std::string& s = result[4]; + const std::string& s = argu_desc.arg_name; for (std::size_t i = 0; i != s.size(); ++i) { @@ -1945,10 +2451,11 @@ OptionParser::parse(int argc, const char* const* argv) { if (m_allow_unrecognised) { + unmatched.push_back(std::string("-") + s[i]); continue; } //error - throw_or_mimic<option_not_exists_exception>(name); + throw_or_mimic<exceptions::no_such_option>(name); } auto value = iter->second; @@ -1962,16 +2469,22 @@ OptionParser::parse(int argc, const char* const* argv) { parse_option(value, name, value->value().get_implicit_value()); } + else if (i + 1 < s.size()) + { + std::string arg_value = s.substr(i + 1); + parse_option(value, name, arg_value); + break; + } else { //error - throw_or_mimic<option_requires_argument_exception>(name); + throw_or_mimic<exceptions::option_requires_argument>(name); } } } - else if (result[1].length() != 0) + else if (argu_desc.arg_name.length() != 0) { - const std::string& name = result[1]; + const std::string& name = argu_desc.arg_name; auto iter = m_options.find(name); @@ -1980,22 +2493,22 @@ OptionParser::parse(int argc, const char* const* argv) if (m_allow_unrecognised) { // keep unrecognised options in argument list, skip to next argument - unmatched.push_back(argv[current]); + unmatched.emplace_back(argv[current]); ++current; continue; } //error - throw_or_mimic<option_not_exists_exception>(name); + throw_or_mimic<exceptions::no_such_option>(name); } auto opt = iter->second; //equals provided for long option? - if (result[2].length() != 0) + if (argu_desc.set_value) { //parse the option given - parse_option(opt, name, result[3]); + parse_option(opt, name, argu_desc.value); } else { @@ -2016,8 +2529,13 @@ OptionParser::parse(int argc, const char* const* argv) auto& store = m_parsed[detail->hash()]; - if(value.has_default() && !store.count() && !store.has_default()){ - parse_default(detail); + if (value.has_default()) { + if (!store.count() && !store.has_default()) { + parse_default(detail); + } + } + else { + parse_no_value(detail); } } @@ -2033,14 +2551,14 @@ OptionParser::parse(int argc, const char* const* argv) //adjust argv for any that couldn't be swallowed while (current != argc) { - unmatched.push_back(argv[current]); + unmatched.emplace_back(argv[current]); ++current; } } finalise_aliases(); - ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched)); + ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched)); return parsed; } @@ -2053,7 +2571,9 @@ OptionParser::finalise_aliases() auto& detail = *option.second; auto hash = detail.hash(); m_keys[detail.short_name()] = hash; - m_keys[detail.long_name()] = hash; + for(const auto& long_name : detail.long_names()) { + m_keys[long_name] = hash; + } m_parsed.emplace(hash, OptionValue()); } @@ -2076,7 +2596,7 @@ Options::add_option ( const std::string& group, const std::string& s, - const std::string& l, + const OptionNames& l, std::string desc, const std::shared_ptr<const Value>& value, std::string arg_help @@ -2090,16 +2610,10 @@ Options::add_option add_one_option(s, option); } - if (!l.empty()) - { - add_one_option(l, option); + for(const auto& long_name : l) { + add_one_option(long_name, option); } - m_option_list.push_front(*option.get()); - auto iter = m_option_list.begin(); - m_option_map[s] = iter; - m_option_map[l] = iter; - //add the help details auto& options = m_help[group]; @@ -2123,7 +2637,7 @@ Options::add_one_option if (!in.second) { - throw_or_mimic<option_exists_error>(option); + throw_or_mimic<exceptions::option_already_exists>(option); } } @@ -2141,7 +2655,7 @@ Options::help_one_group(const std::string& g) const OptionHelp format; - size_t longest = 0; + std::size_t longest = 0; String result; @@ -2152,7 +2666,8 @@ Options::help_one_group(const std::string& g) const for (const auto& o : group->second.options) { - if (m_positional_set.find(o.l) != m_positional_set.end() && + if (o.l.size() && + m_positional_set.find(o.l.front()) != m_positional_set.end() && !m_show_positional) { continue; @@ -2162,22 +2677,26 @@ Options::help_one_group(const std::string& g) const longest = (std::max)(longest, stringLength(s)); format.push_back(std::make_pair(s, String())); } + longest = (std::min)(longest, OPTION_LONGEST); - longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST)); - - //widest allowed description - auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + //widest allowed description -- min 10 chars for helptext/line + std::size_t allowed = 10; + if (m_width > allowed + longest + OPTION_DESC_GAP) + { + allowed = m_width - longest - OPTION_DESC_GAP; + } auto fiter = format.begin(); for (const auto& o : group->second.options) { - if (m_positional_set.find(o.l) != m_positional_set.end() && + if (o.l.size() && + m_positional_set.find(o.l.front()) != m_positional_set.end() && !m_show_positional) { continue; } - auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion); result += fiter->first; if (stringLength(fiter->first) > longest) @@ -2208,7 +2727,7 @@ Options::generate_group_help const std::vector<std::string>& print_groups ) const { - for (size_t i = 0; i != print_groups.size(); ++i) + for (std::size_t i = 0; i != print_groups.size(); ++i) { const String& group_help_text = help_one_group(print_groups[i]); if (empty(group_help_text)) @@ -2228,22 +2747,34 @@ void Options::generate_all_groups_help(String& result) const { std::vector<std::string> all_groups; - all_groups.reserve(m_help.size()); - for (const auto& group : m_help) - { - all_groups.push_back(group.first); - } + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(all_groups), + [] (const std::map<std::string, HelpGroupDetails>::value_type& group) + { + return group.first; + } + ); generate_group_help(result, all_groups); } inline std::string -Options::help(const std::vector<std::string>& help_groups) const +Options::help(const std::vector<std::string>& help_groups, bool print_usage) const { - String result = m_help_string + "\nUsage:\n " + - toLocalString(m_program) + " " + toLocalString(m_custom_help); + String result = m_help_string; + if(print_usage) + { + result+= "\nUsage:\n " + toLocalString(m_program); + } + + if (!m_custom_help.empty()) + { + result += " " + toLocalString(m_custom_help); + } if (!m_positional.empty() && !m_positional_help.empty()) { result += " " + toLocalString(m_positional_help); |