/* __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse \__,_|_| \__, | .__/ \__,_|_| |___/\___| |___/|_| Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2019 Pranav Srinivas Kumar . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace argparse { namespace details { // namespace for helper methods template struct is_container : std::false_type {}; template <> struct is_container : std::false_type {}; template struct is_container().begin()), decltype(std::declval().end()), decltype(std::declval().size())>> : std::true_type {}; template static constexpr bool is_container_v = is_container::value; template struct is_streamable : std::false_type {}; template struct is_streamable< T, std::void_t() << std::declval())>> : std::true_type {}; template static constexpr bool is_streamable_v = is_streamable::value; template static constexpr bool is_representable_v = is_streamable_v || is_container_v; constexpr size_t repr_max_container_size = 5; template std::string repr(T const &val) { if constexpr (std::is_same_v) { return val ? "true" : "false"; } else if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; } else if constexpr (is_container_v) { std::stringstream out; out << "{"; const auto size = val.size(); if (size > 1) { out << repr(*val.begin()); std::for_each( std::next(val.begin()), std::next(val.begin(), std::min(size, repr_max_container_size) - 1), [&out](const auto &v) { out << " " << repr(v); }); if (size <= repr_max_container_size) out << " "; else out << "..."; } if (size > 0) out << repr(*std::prev(val.end())); out << "}"; return out.str(); } else if constexpr (is_streamable_v) { std::stringstream out; out << val; return out.str(); } else { return ""; } } namespace { template constexpr bool standard_signed_integer = false; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template <> constexpr bool standard_signed_integer = true; template constexpr bool standard_unsigned_integer = false; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; } // namespace template constexpr bool standard_integer = standard_signed_integer || standard_unsigned_integer; template constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, std::index_sequence) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } template constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { return details::apply_plus_one_impl( std::forward(f), std::forward(t), std::forward(x), std::make_index_sequence< std::tuple_size_v>>{}); } constexpr auto pointer_range(std::string_view s) noexcept { return std::tuple(s.data(), s.data() + s.size()); } template constexpr bool starts_with(std::basic_string_view prefix, std::basic_string_view s) noexcept { return s.substr(0, prefix.size()) == prefix; } enum class chars_format { scientific = 0x1, fixed = 0x2, hex = 0x4, general = fixed | scientific }; struct consume_hex_prefix_result { bool is_hexadecimal; std::string_view rest; }; using namespace std::literals; constexpr auto consume_hex_prefix(std::string_view s) -> consume_hex_prefix_result { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); return {true, s}; } else { return {false, s}; } } template inline auto do_from_chars(std::string_view s) -> T { T x; auto [first, last] = pointer_range(s); auto [ptr, ec] = std::from_chars(first, last, x, Param); if (ec == std::errc()) { if (ptr == last) return x; else throw std::invalid_argument{"pattern does not match to the end"}; } else if (ec == std::errc::invalid_argument) { throw std::invalid_argument{"pattern not found"}; } else if (ec == std::errc::result_out_of_range) { throw std::range_error{"not representable"}; } else { return x; // unreachable } } template struct parse_number { auto operator()(std::string_view s) -> T { return do_from_chars(s); } }; template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_hex_prefix(s); ok) return do_from_chars(rest); else throw std::invalid_argument{"pattern not found"}; } }; template struct parse_number { auto operator()(std::string_view s) -> T { if (auto [ok, rest] = consume_hex_prefix(s); ok) return do_from_chars(rest); else if (starts_with("0"sv, s)) return do_from_chars(rest); else return do_from_chars(rest); } }; namespace { template constexpr auto generic_strtod = nullptr; template <> constexpr auto generic_strtod = strtof; template <> constexpr auto generic_strtod = strtod; template <> constexpr auto generic_strtod = strtold; } // namespace template inline auto do_strtod(std::string const &s) -> T { if (isspace(static_cast(s[0])) || s[0] == '+') throw std::invalid_argument{"pattern not found"}; auto [first, last] = pointer_range(s); char *ptr; errno = 0; if (auto x = generic_strtod(first, &ptr); errno == 0) { if (ptr == last) return x; else throw std::invalid_argument{"pattern does not match to the end"}; } else if (errno == ERANGE) { throw std::range_error{"not representable"}; } else { return x; // unreachable } } template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) throw std::invalid_argument{ "chars_format::general does not parse hexfloat"}; return do_strtod(s); } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) throw std::invalid_argument{"chars_format::hex parses hexfloat"}; return do_strtod(s); } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) throw std::invalid_argument{ "chars_format::scientific does not parse hexfloat"}; if (s.find_first_of("eE") == s.npos) throw std::invalid_argument{ "chars_format::scientific requires exponent part"}; return do_strtod(s); } }; template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) throw std::invalid_argument{ "chars_format::fixed does not parse hexfloat"}; if (s.find_first_of("eE") != s.npos) throw std::invalid_argument{ "chars_format::fixed does not parse exponent part"}; return do_strtod(s); } }; } // namespace details class ArgumentParser; class Argument { friend class ArgumentParser; friend auto operator<<(std::ostream &, ArgumentParser const &) -> std::ostream &; template explicit Argument(std::string_view(&&a)[N], std::index_sequence) : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), mIsUsed(false) { ((void)mNames.emplace_back(a[I]), ...); std::sort( mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) { return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); }); } public: template explicit Argument(std::string_view(&&a)[N]) : Argument(std::move(a), std::make_index_sequence{}) {} Argument &help(std::string aHelp) { mHelp = std::move(aHelp); return *this; } template Argument &default_value(T &&aDefaultValue) { mDefaultValueRepr = details::repr(aDefaultValue); mDefaultValue = std::forward(aDefaultValue); return *this; } Argument &required() { mIsRequired = true; return *this; } Argument &implicit_value(std::any aImplicitValue) { mImplicitValue = std::move(aImplicitValue); mNumArgs = 0; return *this; } template auto action(F &&aAction, Args &&... aBound) -> std::enable_if_t, Argument &> { using action_type = std::conditional_t< std::is_void_v>, void_action, valued_action>; if constexpr (sizeof...(Args) == 0) mAction.emplace(std::forward(aAction)); else mAction.emplace( [f = std::forward(aAction), tup = std::make_tuple(std::forward(aBound)...)]( std::string const &opt) mutable { return details::apply_plus_one(f, tup, opt); }); return *this; } template auto scan() -> std::enable_if_t, Argument &> { static_assert(!(std::is_const_v || std::is_volatile_v), "T should not be cv-qualified"); auto is_one_of = [](char c, auto... x) constexpr { return ((c == x) || ...); }; if constexpr (is_one_of(Shape, 'd') && details::standard_integer) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'u') && details::standard_unsigned_integer) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'o') && details::standard_unsigned_integer) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'x', 'X') && details::standard_unsigned_integer) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'a', 'A') && std::is_floating_point_v) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'e', 'E') && std::is_floating_point_v) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'f', 'F') && std::is_floating_point_v) action(details::parse_number()); else if constexpr (is_one_of(Shape, 'g', 'G') && std::is_floating_point_v) action(details::parse_number()); else static_assert(alignof(T) == 0, "No scan specification for T"); return *this; } Argument &nargs(int aNumArgs) { if (aNumArgs < 0) throw std::logic_error("Number of arguments must be non-negative"); mNumArgs = aNumArgs; return *this; } Argument &remaining() { mNumArgs = -1; return *this; } template Iterator consume(Iterator start, Iterator end, std::string_view usedName = {}) { if (mIsUsed) { throw std::runtime_error("Duplicate argument"); } mIsUsed = true; mUsedName = usedName; if (mNumArgs == 0) { mValues.emplace_back(mImplicitValue); return start; } else if (mNumArgs <= std::distance(start, end)) { if (auto expected = maybe_nargs()) { end = std::next(start, *expected); if (std::any_of(start, end, Argument::is_optional)) { throw std::runtime_error("optional argument in parameter sequence"); } } struct action_apply { void operator()(valued_action &f) { std::transform(start, end, std::back_inserter(self.mValues), f); } void operator()(void_action &f) { std::for_each(start, end, f); if (!self.mDefaultValue.has_value()) { if (auto expected = self.maybe_nargs()) self.mValues.resize(*expected); } } Iterator start, end; Argument &self; }; std::visit(action_apply{start, end, *this}, mAction); return end; } else if (mDefaultValue.has_value()) { return start; } else { throw std::runtime_error("Too few arguments"); } } /* * @throws std::runtime_error if argument values are not valid */ void validate() const { if (auto expected = maybe_nargs()) { if (mIsOptional) { if (mIsUsed && mValues.size() != *expected && !mDefaultValue.has_value()) { std::stringstream stream; stream << mUsedName << ": expected " << *expected << " argument(s). " << mValues.size() << " provided."; throw std::runtime_error(stream.str()); } else { // TODO: check if an implicit value was programmed for this argument if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { std::stringstream stream; stream << mNames[0] << ": required."; throw std::runtime_error(stream.str()); } if (mIsUsed && mIsRequired && mValues.size() == 0) { std::stringstream stream; stream << mUsedName << ": no value provided."; throw std::runtime_error(stream.str()); } } } else { if (mValues.size() != expected && !mDefaultValue.has_value()) { std::stringstream stream; if (!mUsedName.empty()) stream << mUsedName << ": "; stream << *expected << " argument(s) expected. " << mValues.size() << " provided."; throw std::runtime_error(stream.str()); } } } } auto maybe_nargs() const -> std::optional { if (mNumArgs < 0) return std::nullopt; else return static_cast(mNumArgs); } size_t get_arguments_length() const { return std::accumulate(std::begin(mNames), std::end(mNames), size_t(0), [](const auto &sum, const auto &s) { return sum + s.size() + 1; // +1 for space between names }); } friend std::ostream &operator<<(std::ostream &stream, const Argument &argument) { std::stringstream nameStream; std::copy(std::begin(argument.mNames), std::end(argument.mNames), std::ostream_iterator(nameStream, " ")); stream << nameStream.str() << "\t" << argument.mHelp; if (argument.mDefaultValue.has_value()) { if (!argument.mHelp.empty()) stream << " "; stream << "[default: " << argument.mDefaultValueRepr << "]"; } else if (argument.mIsRequired) { if (!argument.mHelp.empty()) stream << " "; stream << "[required]"; } stream << "\n"; return stream; } template bool operator!=(const T &aRhs) const { return !(*this == aRhs); } /* * Compare to an argument value of known type * @throws std::logic_error in case of incompatible types */ template bool operator==(const T &aRhs) const { if constexpr (!details::is_container_v) { return get() == aRhs; } else { using ValueType = typename T::value_type; auto tLhs = get(); return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), std::end(aRhs), [](const auto &lhs, const auto &rhs) { return std::any_cast(lhs) == rhs; }); } } private: static constexpr int eof = std::char_traits::eof(); static auto lookahead(std::string_view s) -> int { if (s.empty()) return eof; else return static_cast(static_cast(s[0])); } /* * decimal-literal: * '0' * nonzero-digit digit-sequence_opt * integer-part fractional-part * fractional-part * integer-part '.' exponent-part_opt * integer-part exponent-part * * integer-part: * digit-sequence * * fractional-part: * '.' post-decimal-point * * post-decimal-point: * digit-sequence exponent-part_opt * * exponent-part: * 'e' post-e * 'E' post-e * * post-e: * sign_opt digit-sequence * * sign: one of * '+' '-' */ static bool is_decimal_literal(std::string_view s) { auto is_digit = [](auto c) constexpr { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return true; default: return false; } }; // precondition: we have consumed or will consume at least one digit auto consume_digits = [=](std::string_view s) { auto it = std::find_if_not(std::begin(s), std::end(s), is_digit); return s.substr(it - std::begin(s)); }; switch (lookahead(s)) { case '0': { s.remove_prefix(1); if (s.empty()) return true; else goto integer_part; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { s = consume_digits(s); if (s.empty()) return true; else goto integer_part_consumed; } case '.': { s.remove_prefix(1); goto post_decimal_point; } default: return false; } integer_part: s = consume_digits(s); integer_part_consumed: switch (lookahead(s)) { case '.': { s.remove_prefix(1); if (is_digit(lookahead(s))) goto post_decimal_point; else goto exponent_part_opt; } case 'e': case 'E': { s.remove_prefix(1); goto post_e; } default: return false; } post_decimal_point: if (is_digit(lookahead(s))) { s = consume_digits(s); goto exponent_part_opt; } else { return false; } exponent_part_opt: switch (lookahead(s)) { case eof: return true; case 'e': case 'E': { s.remove_prefix(1); goto post_e; } default: return false; } post_e: switch (lookahead(s)) { case '-': case '+': s.remove_prefix(1); } if (is_digit(lookahead(s))) { s = consume_digits(s); return s.empty(); } else { return false; } } static bool is_optional(std::string_view aName) { return !is_positional(aName); } /* * positional: * _empty_ * '-' * '-' decimal-literal * !'-' anything */ static bool is_positional(std::string_view aName) { switch (lookahead(aName)) { case eof: return true; case '-': { aName.remove_prefix(1); if (aName.empty()) return true; else return is_decimal_literal(aName); } default: return true; } } /* * Get argument value given a type * @throws std::logic_error in case of incompatible types */ template T get() const { if (!mValues.empty()) { if constexpr (details::is_container_v) return any_cast_container(mValues); else return std::any_cast(mValues.front()); } if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); } throw std::logic_error("No value provided"); } /* * Get argument value given a type. * @pre The object has no default value. * @returns The stored value if any, std::nullopt otherwise. */ template auto present() const -> std::optional { if (mDefaultValue.has_value()) throw std::logic_error("Argument with default value always presents"); if (mValues.empty()) return std::nullopt; else if constexpr (details::is_container_v) return any_cast_container(mValues); else return std::any_cast(mValues.front()); } template static auto any_cast_container(const std::vector &aOperand) -> T { using ValueType = typename T::value_type; T tResult; std::transform( std::begin(aOperand), std::end(aOperand), std::back_inserter(tResult), [](const auto &value) { return std::any_cast(value); }); return tResult; } std::vector mNames; std::string_view mUsedName; std::string mHelp; std::any mDefaultValue; std::string mDefaultValueRepr; std::any mImplicitValue; using valued_action = std::function; using void_action = std::function; std::variant mAction{ std::in_place_type, [](const std::string &aValue) { return aValue; }}; std::vector mValues; int mNumArgs = 1; bool mIsOptional : 1; bool mIsRequired : 1; bool mIsUsed : 1; // True if the optional argument is used by user }; class ArgumentParser { public: explicit ArgumentParser(std::string aProgramName = {}, std::string aVersion = "1.0") : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { add_argument("-h", "--help").help("shows help message and exits").nargs(0); add_argument("-v", "--version") .help("prints version information and exits") .nargs(0); } ArgumentParser(ArgumentParser &&) noexcept = default; ArgumentParser &operator=(ArgumentParser &&) = default; ArgumentParser(const ArgumentParser &other) : mProgramName(other.mProgramName), mPositionalArguments(other.mPositionalArguments), mOptionalArguments(other.mOptionalArguments) { for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments); ++it) index_argument(it); for (auto it = std::begin(mOptionalArguments); it != std::end(mOptionalArguments); ++it) index_argument(it); } ArgumentParser &operator=(const ArgumentParser &other) { auto tmp = other; std::swap(*this, tmp); return *this; } // Parameter packing // Call add_argument with variadic number of string arguments template Argument &add_argument(Targs... Fargs) { using array_of_sv = std::string_view[sizeof...(Targs)]; auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments), array_of_sv{Fargs...}); if (!tArgument->mIsOptional) mPositionalArguments.splice(cend(mPositionalArguments), mOptionalArguments, tArgument); index_argument(tArgument); return *tArgument; } // Parameter packed add_parents method // Accepts a variadic number of ArgumentParser objects template ArgumentParser &add_parents(const Targs &... Fargs) { for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) { for (auto &tArgument : tParentParser.mPositionalArguments) { auto it = mPositionalArguments.insert(cend(mPositionalArguments), tArgument); index_argument(it); } for (auto &tArgument : tParentParser.mOptionalArguments) { auto it = mOptionalArguments.insert(cend(mOptionalArguments), tArgument); index_argument(it); } } return *this; } ArgumentParser &add_description(std::string aDescription) { mDescription = std::move(aDescription); return *this; } ArgumentParser &add_epilog(std::string aEpilog) { mEpilog = std::move(aEpilog); return *this; } /* Call parse_args_internal - which does all the work * Then, validate the parsed arguments * This variant is used mainly for testing * @throws std::runtime_error in case of any invalid argument */ void parse_args(const std::vector &aArguments) { parse_args_internal(aArguments); parse_args_validate(); } /* Main entry point for parsing command-line arguments using this * ArgumentParser * @throws std::runtime_error in case of any invalid argument */ void parse_args(int argc, const char *const argv[]) { std::vector arguments; std::copy(argv, argv + argc, std::back_inserter(arguments)); parse_args(arguments); } /* Getter for options with default values. * @throws std::logic_error if there is no such option * @throws std::logic_error if the option has no value * @throws std::bad_any_cast if the option is not of type T */ template T get(std::string_view aArgumentName) const { return (*this)[aArgumentName].get(); } /* Getter for options without default values. * @pre The option has no default value. * @throws std::logic_error if there is no such option * @throws std::bad_any_cast if the option is not of type T */ template auto present(std::string_view aArgumentName) -> std::optional { return (*this)[aArgumentName].present(); } /* Indexing operator. Return a reference to an Argument object * Used in conjuction with Argument.operator== e.g., parser["foo"] == true * @throws std::logic_error in case of an invalid argument name */ Argument &operator[](std::string_view aArgumentName) const { auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return *(tIterator->second); } throw std::logic_error("No such argument"); } // Print help message friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) -> std::ostream & { if (auto sen = std::ostream::sentry(stream)) { stream.setf(std::ios_base::left); stream << "Usage: " << parser.mProgramName << " [options] "; size_t tLongestArgumentLength = parser.get_length_of_longest_argument(); for (const auto &argument : parser.mPositionalArguments) { stream << argument.mNames.front() << " "; } stream << "\n\n"; if (!parser.mDescription.empty()) stream << parser.mDescription << "\n\n"; if (!parser.mPositionalArguments.empty()) stream << "Positional arguments:\n"; for (const auto &mPositionalArgument : parser.mPositionalArguments) { stream.width(tLongestArgumentLength); stream << mPositionalArgument; } if (!parser.mOptionalArguments.empty()) stream << (parser.mPositionalArguments.empty() ? "" : "\n") << "Optional arguments:\n"; for (const auto &mOptionalArgument : parser.mOptionalArguments) { stream.width(tLongestArgumentLength); stream << mOptionalArgument; } if (!parser.mEpilog.empty()) stream << parser.mEpilog << "\n\n"; } return stream; } // Format help message auto help() const -> std::stringstream { std::stringstream out; out << *this; return out; } // Printing the one and only help message // I've stuck with a simple message format, nothing fancy. [[deprecated("Use cout << program; instead. See also help().")]] std::string print_help() { auto out = help(); std::cout << out.rdbuf(); return out.str(); } private: /* * @throws std::runtime_error in case of any invalid argument */ void parse_args_internal(const std::vector &aArguments) { if (mProgramName.empty() && !aArguments.empty()) { mProgramName = aArguments.front(); } auto end = std::end(aArguments); auto positionalArgumentIt = std::begin(mPositionalArguments); for (auto it = std::next(std::begin(aArguments)); it != end;) { const auto &tCurrentArgument = *it; if (Argument::is_positional(tCurrentArgument)) { if (positionalArgumentIt == std::end(mPositionalArguments)) { throw std::runtime_error( "Maximum number of positional arguments exceeded"); } auto tArgument = positionalArgumentIt++; it = tArgument->consume(it, end); continue; } auto tIterator = mArgumentMap.find(tCurrentArgument); if (tIterator != mArgumentMap.end()) { auto tArgument = tIterator->second; // the first optional argument is --help if (tArgument == mOptionalArguments.begin()) { std::cout << *this; std::exit(0); } // the second optional argument is --version else if (tArgument == std::next(mOptionalArguments.begin(), 1)) { std::cout << mVersion << "\n"; std::exit(0); } it = tArgument->consume(std::next(it), end, tIterator->first); } else if (const auto &tCompoundArgument = tCurrentArgument; tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' && tCompoundArgument[1] != '-') { ++it; for (size_t j = 1; j < tCompoundArgument.size(); j++) { auto tHypotheticalArgument = std::string{'-', tCompoundArgument[j]}; auto tIterator2 = mArgumentMap.find(tHypotheticalArgument); if (tIterator2 != mArgumentMap.end()) { auto tArgument = tIterator2->second; it = tArgument->consume(it, end, tIterator2->first); } else { throw std::runtime_error("Unknown argument"); } } } else { throw std::runtime_error("Unknown argument"); } } } /* * @throws std::runtime_error in case of any invalid argument */ void parse_args_validate() { // Check if all arguments are parsed std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap), [](const auto &argPair) { const auto &tArgument = argPair.second; tArgument->validate(); }); } // Used by print_help. size_t get_length_of_longest_argument() const { if (mArgumentMap.empty()) return 0; std::vector argumentLengths(mArgumentMap.size()); std::transform(std::begin(mArgumentMap), std::end(mArgumentMap), std::begin(argumentLengths), [](const auto &argPair) { const auto &tArgument = argPair.second; return tArgument->get_arguments_length(); }); return *std::max_element(std::begin(argumentLengths), std::end(argumentLengths)); } using list_iterator = std::list::iterator; void index_argument(list_iterator argIt) { for (auto &mName : argIt->mNames) mArgumentMap.insert_or_assign(mName, argIt); } std::string mProgramName; std::string mVersion; std::string mDescription; std::string mEpilog; std::list mPositionalArguments; std::list mOptionalArguments; std::map> mArgumentMap; }; } // namespace argparse