File indexing completed on 2025-01-18 09:27:05
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031 #pragma once
0032
0033 #ifndef ARGPARSE_MODULE_USE_STD_MODULE
0034 #include <algorithm>
0035 #include <any>
0036 #include <array>
0037 #include <cerrno>
0038 #include <charconv>
0039 #include <cstdlib>
0040 #include <functional>
0041 #include <iomanip>
0042 #include <iostream>
0043 #include <iterator>
0044 #include <limits>
0045 #include <list>
0046 #include <map>
0047 #include <numeric>
0048 #include <optional>
0049 #include <sstream>
0050 #include <stdexcept>
0051 #include <string>
0052 #include <string_view>
0053 #include <tuple>
0054 #include <type_traits>
0055 #include <utility>
0056 #include <variant>
0057 #include <vector>
0058 #endif
0059
0060 namespace argparse {
0061
0062 namespace details {
0063
0064 template <typename T, typename = void>
0065 struct HasContainerTraits : std::false_type {};
0066
0067 template <> struct HasContainerTraits<std::string> : std::false_type {};
0068
0069 template <> struct HasContainerTraits<std::string_view> : std::false_type {};
0070
0071 template <typename T>
0072 struct HasContainerTraits<
0073 T, std::void_t<typename T::value_type, decltype(std::declval<T>().begin()),
0074 decltype(std::declval<T>().end()),
0075 decltype(std::declval<T>().size())>> : std::true_type {};
0076
0077 template <typename T>
0078 inline constexpr bool IsContainer = HasContainerTraits<T>::value;
0079
0080 template <typename T, typename = void>
0081 struct HasStreamableTraits : std::false_type {};
0082
0083 template <typename T>
0084 struct HasStreamableTraits<
0085 T,
0086 std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
0087 : std::true_type {};
0088
0089 template <typename T>
0090 inline constexpr bool IsStreamable = HasStreamableTraits<T>::value;
0091
0092 constexpr std::size_t repr_max_container_size = 5;
0093
0094 template <typename T> std::string repr(T const &val) {
0095 if constexpr (std::is_same_v<T, bool>) {
0096 return val ? "true" : "false";
0097 } else if constexpr (std::is_convertible_v<T, std::string_view>) {
0098 return '"' + std::string{std::string_view{val}} + '"';
0099 } else if constexpr (IsContainer<T>) {
0100 std::stringstream out;
0101 out << "{";
0102 const auto size = val.size();
0103 if (size > 1) {
0104 out << repr(*val.begin());
0105 std::for_each(
0106 std::next(val.begin()),
0107 std::next(
0108 val.begin(),
0109 static_cast<typename T::iterator::difference_type>(
0110 std::min<std::size_t>(size, repr_max_container_size) - 1)),
0111 [&out](const auto &v) { out << " " << repr(v); });
0112 if (size <= repr_max_container_size) {
0113 out << " ";
0114 } else {
0115 out << "...";
0116 }
0117 }
0118 if (size > 0) {
0119 out << repr(*std::prev(val.end()));
0120 }
0121 out << "}";
0122 return out.str();
0123 } else if constexpr (IsStreamable<T>) {
0124 std::stringstream out;
0125 out << val;
0126 return out.str();
0127 } else {
0128 return "<not representable>";
0129 }
0130 }
0131
0132 namespace {
0133
0134 template <typename T> constexpr bool standard_signed_integer = false;
0135 template <> constexpr bool standard_signed_integer<signed char> = true;
0136 template <> constexpr bool standard_signed_integer<short int> = true;
0137 template <> constexpr bool standard_signed_integer<int> = true;
0138 template <> constexpr bool standard_signed_integer<long int> = true;
0139 template <> constexpr bool standard_signed_integer<long long int> = true;
0140
0141 template <typename T> constexpr bool standard_unsigned_integer = false;
0142 template <> constexpr bool standard_unsigned_integer<unsigned char> = true;
0143 template <> constexpr bool standard_unsigned_integer<unsigned short int> = true;
0144 template <> constexpr bool standard_unsigned_integer<unsigned int> = true;
0145 template <> constexpr bool standard_unsigned_integer<unsigned long int> = true;
0146 template <>
0147 constexpr bool standard_unsigned_integer<unsigned long long int> = true;
0148
0149 }
0150
0151 constexpr int radix_2 = 2;
0152 constexpr int radix_8 = 8;
0153 constexpr int radix_10 = 10;
0154 constexpr int radix_16 = 16;
0155
0156 template <typename T>
0157 constexpr bool standard_integer =
0158 standard_signed_integer<T> || standard_unsigned_integer<T>;
0159
0160 template <class F, class Tuple, class Extra, std::size_t... I>
0161 constexpr decltype(auto)
0162 apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x,
0163 std::index_sequence<I...> ) {
0164 return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...,
0165 std::forward<Extra>(x));
0166 }
0167
0168 template <class F, class Tuple, class Extra>
0169 constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) {
0170 return details::apply_plus_one_impl(
0171 std::forward<F>(f), std::forward<Tuple>(t), std::forward<Extra>(x),
0172 std::make_index_sequence<
0173 std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
0174 }
0175
0176 constexpr auto pointer_range(std::string_view s) noexcept {
0177 return std::tuple(s.data(), s.data() + s.size());
0178 }
0179
0180 template <class CharT, class Traits>
0181 constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
0182 std::basic_string_view<CharT, Traits> s) noexcept {
0183 return s.substr(0, prefix.size()) == prefix;
0184 }
0185
0186 enum class chars_format {
0187 scientific = 0xf1,
0188 fixed = 0xf2,
0189 hex = 0xf4,
0190 binary = 0xf8,
0191 general = fixed | scientific
0192 };
0193
0194 struct ConsumeBinaryPrefixResult {
0195 bool is_binary;
0196 std::string_view rest;
0197 };
0198
0199 constexpr auto consume_binary_prefix(std::string_view s)
0200 -> ConsumeBinaryPrefixResult {
0201 if (starts_with(std::string_view{"0b"}, s) ||
0202 starts_with(std::string_view{"0B"}, s)) {
0203 s.remove_prefix(2);
0204 return {true, s};
0205 }
0206 return {false, s};
0207 }
0208
0209 struct ConsumeHexPrefixResult {
0210 bool is_hexadecimal;
0211 std::string_view rest;
0212 };
0213
0214 using namespace std::literals;
0215
0216 constexpr auto consume_hex_prefix(std::string_view s)
0217 -> ConsumeHexPrefixResult {
0218 if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
0219 s.remove_prefix(2);
0220 return {true, s};
0221 }
0222 return {false, s};
0223 }
0224
0225 template <class T, auto Param>
0226 inline auto do_from_chars(std::string_view s) -> T {
0227 T x;
0228 auto [first, last] = pointer_range(s);
0229 auto [ptr, ec] = std::from_chars(first, last, x, Param);
0230 if (ec == std::errc()) {
0231 if (ptr == last) {
0232 return x;
0233 }
0234 throw std::invalid_argument{"pattern '" + std::string(s) +
0235 "' does not match to the end"};
0236 }
0237 if (ec == std::errc::invalid_argument) {
0238 throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"};
0239 }
0240 if (ec == std::errc::result_out_of_range) {
0241 throw std::range_error{"'" + std::string(s) + "' not representable"};
0242 }
0243 return x;
0244 }
0245
0246 template <class T, auto Param = 0> struct parse_number {
0247 auto operator()(std::string_view s) -> T {
0248 return do_from_chars<T, Param>(s);
0249 }
0250 };
0251
0252 template <class T> struct parse_number<T, radix_2> {
0253 auto operator()(std::string_view s) -> T {
0254 if (auto [ok, rest] = consume_binary_prefix(s); ok) {
0255 return do_from_chars<T, radix_2>(rest);
0256 }
0257 throw std::invalid_argument{"pattern not found"};
0258 }
0259 };
0260
0261 template <class T> struct parse_number<T, radix_16> {
0262 auto operator()(std::string_view s) -> T {
0263 if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
0264 if (auto [ok, rest] = consume_hex_prefix(s); ok) {
0265 try {
0266 return do_from_chars<T, radix_16>(rest);
0267 } catch (const std::invalid_argument &err) {
0268 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0269 "' as hexadecimal: " + err.what());
0270 } catch (const std::range_error &err) {
0271 throw std::range_error("Failed to parse '" + std::string(s) +
0272 "' as hexadecimal: " + err.what());
0273 }
0274 }
0275 } else {
0276
0277
0278 try {
0279 return do_from_chars<T, radix_16>(s);
0280 } catch (const std::invalid_argument &err) {
0281 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0282 "' as hexadecimal: " + err.what());
0283 } catch (const std::range_error &err) {
0284 throw std::range_error("Failed to parse '" + std::string(s) +
0285 "' as hexadecimal: " + err.what());
0286 }
0287 }
0288
0289 throw std::invalid_argument{"pattern '" + std::string(s) +
0290 "' not identified as hexadecimal"};
0291 }
0292 };
0293
0294 template <class T> struct parse_number<T> {
0295 auto operator()(std::string_view s) -> T {
0296 auto [ok, rest] = consume_hex_prefix(s);
0297 if (ok) {
0298 try {
0299 return do_from_chars<T, radix_16>(rest);
0300 } catch (const std::invalid_argument &err) {
0301 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0302 "' as hexadecimal: " + err.what());
0303 } catch (const std::range_error &err) {
0304 throw std::range_error("Failed to parse '" + std::string(s) +
0305 "' as hexadecimal: " + err.what());
0306 }
0307 }
0308
0309 auto [ok_binary, rest_binary] = consume_binary_prefix(s);
0310 if (ok_binary) {
0311 try {
0312 return do_from_chars<T, radix_2>(rest_binary);
0313 } catch (const std::invalid_argument &err) {
0314 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0315 "' as binary: " + err.what());
0316 } catch (const std::range_error &err) {
0317 throw std::range_error("Failed to parse '" + std::string(s) +
0318 "' as binary: " + err.what());
0319 }
0320 }
0321
0322 if (starts_with("0"sv, s)) {
0323 try {
0324 return do_from_chars<T, radix_8>(rest);
0325 } catch (const std::invalid_argument &err) {
0326 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0327 "' as octal: " + err.what());
0328 } catch (const std::range_error &err) {
0329 throw std::range_error("Failed to parse '" + std::string(s) +
0330 "' as octal: " + err.what());
0331 }
0332 }
0333
0334 try {
0335 return do_from_chars<T, radix_10>(rest);
0336 } catch (const std::invalid_argument &err) {
0337 throw std::invalid_argument("Failed to parse '" + std::string(s) +
0338 "' as decimal integer: " + err.what());
0339 } catch (const std::range_error &err) {
0340 throw std::range_error("Failed to parse '" + std::string(s) +
0341 "' as decimal integer: " + err.what());
0342 }
0343 }
0344 };
0345
0346 namespace {
0347
0348 template <class T> inline const auto generic_strtod = nullptr;
0349 template <> inline const auto generic_strtod<float> = strtof;
0350 template <> inline const auto generic_strtod<double> = strtod;
0351 template <> inline const auto generic_strtod<long double> = strtold;
0352
0353 }
0354
0355 template <class T> inline auto do_strtod(std::string const &s) -> T {
0356 if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
0357 throw std::invalid_argument{"pattern '" + s + "' not found"};
0358 }
0359
0360 auto [first, last] = pointer_range(s);
0361 char *ptr;
0362
0363 errno = 0;
0364 auto x = generic_strtod<T>(first, &ptr);
0365 if (errno == 0) {
0366 if (ptr == last) {
0367 return x;
0368 }
0369 throw std::invalid_argument{"pattern '" + s +
0370 "' does not match to the end"};
0371 }
0372 if (errno == ERANGE) {
0373 throw std::range_error{"'" + s + "' not representable"};
0374 }
0375 return x;
0376 }
0377
0378 template <class T> struct parse_number<T, chars_format::general> {
0379 auto operator()(std::string const &s) -> T {
0380 if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
0381 throw std::invalid_argument{
0382 "chars_format::general does not parse hexfloat"};
0383 }
0384 if (auto r = consume_binary_prefix(s); r.is_binary) {
0385 throw std::invalid_argument{
0386 "chars_format::general does not parse binfloat"};
0387 }
0388
0389 try {
0390 return do_strtod<T>(s);
0391 } catch (const std::invalid_argument &err) {
0392 throw std::invalid_argument("Failed to parse '" + s +
0393 "' as number: " + err.what());
0394 } catch (const std::range_error &err) {
0395 throw std::range_error("Failed to parse '" + s +
0396 "' as number: " + err.what());
0397 }
0398 }
0399 };
0400
0401 template <class T> struct parse_number<T, chars_format::hex> {
0402 auto operator()(std::string const &s) -> T {
0403 if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
0404 throw std::invalid_argument{"chars_format::hex parses hexfloat"};
0405 }
0406 if (auto r = consume_binary_prefix(s); r.is_binary) {
0407 throw std::invalid_argument{"chars_format::hex does not parse binfloat"};
0408 }
0409
0410 try {
0411 return do_strtod<T>(s);
0412 } catch (const std::invalid_argument &err) {
0413 throw std::invalid_argument("Failed to parse '" + s +
0414 "' as hexadecimal: " + err.what());
0415 } catch (const std::range_error &err) {
0416 throw std::range_error("Failed to parse '" + s +
0417 "' as hexadecimal: " + err.what());
0418 }
0419 }
0420 };
0421
0422 template <class T> struct parse_number<T, chars_format::binary> {
0423 auto operator()(std::string const &s) -> T {
0424 if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
0425 throw std::invalid_argument{
0426 "chars_format::binary does not parse hexfloat"};
0427 }
0428 if (auto r = consume_binary_prefix(s); !r.is_binary) {
0429 throw std::invalid_argument{"chars_format::binary parses binfloat"};
0430 }
0431
0432 return do_strtod<T>(s);
0433 }
0434 };
0435
0436 template <class T> struct parse_number<T, chars_format::scientific> {
0437 auto operator()(std::string const &s) -> T {
0438 if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
0439 throw std::invalid_argument{
0440 "chars_format::scientific does not parse hexfloat"};
0441 }
0442 if (auto r = consume_binary_prefix(s); r.is_binary) {
0443 throw std::invalid_argument{
0444 "chars_format::scientific does not parse binfloat"};
0445 }
0446 if (s.find_first_of("eE") == std::string::npos) {
0447 throw std::invalid_argument{
0448 "chars_format::scientific requires exponent part"};
0449 }
0450
0451 try {
0452 return do_strtod<T>(s);
0453 } catch (const std::invalid_argument &err) {
0454 throw std::invalid_argument("Failed to parse '" + s +
0455 "' as scientific notation: " + err.what());
0456 } catch (const std::range_error &err) {
0457 throw std::range_error("Failed to parse '" + s +
0458 "' as scientific notation: " + err.what());
0459 }
0460 }
0461 };
0462
0463 template <class T> struct parse_number<T, chars_format::fixed> {
0464 auto operator()(std::string const &s) -> T {
0465 if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
0466 throw std::invalid_argument{
0467 "chars_format::fixed does not parse hexfloat"};
0468 }
0469 if (auto r = consume_binary_prefix(s); r.is_binary) {
0470 throw std::invalid_argument{
0471 "chars_format::fixed does not parse binfloat"};
0472 }
0473 if (s.find_first_of("eE") != std::string::npos) {
0474 throw std::invalid_argument{
0475 "chars_format::fixed does not parse exponent part"};
0476 }
0477
0478 try {
0479 return do_strtod<T>(s);
0480 } catch (const std::invalid_argument &err) {
0481 throw std::invalid_argument("Failed to parse '" + s +
0482 "' as fixed notation: " + err.what());
0483 } catch (const std::range_error &err) {
0484 throw std::range_error("Failed to parse '" + s +
0485 "' as fixed notation: " + err.what());
0486 }
0487 }
0488 };
0489
0490 template <typename StrIt>
0491 std::string join(StrIt first, StrIt last, const std::string &separator) {
0492 if (first == last) {
0493 return "";
0494 }
0495 std::stringstream value;
0496 value << *first;
0497 ++first;
0498 while (first != last) {
0499 value << separator << *first;
0500 ++first;
0501 }
0502 return value.str();
0503 }
0504
0505 template <typename T> struct can_invoke_to_string {
0506 template <typename U>
0507 static auto test(int)
0508 -> decltype(std::to_string(std::declval<U>()), std::true_type{});
0509
0510 template <typename U> static auto test(...) -> std::false_type;
0511
0512 static constexpr bool value = decltype(test<T>(0))::value;
0513 };
0514
0515 template <typename T> struct IsChoiceTypeSupported {
0516 using CleanType = typename std::decay<T>::type;
0517 static const bool value = std::is_integral<CleanType>::value ||
0518 std::is_same<CleanType, std::string>::value ||
0519 std::is_same<CleanType, std::string_view>::value ||
0520 std::is_same<CleanType, const char *>::value;
0521 };
0522
0523 template <typename StringType>
0524 std::size_t get_levenshtein_distance(const StringType &s1,
0525 const StringType &s2) {
0526 std::vector<std::vector<std::size_t>> dp(
0527 s1.size() + 1, std::vector<std::size_t>(s2.size() + 1, 0));
0528
0529 for (std::size_t i = 0; i <= s1.size(); ++i) {
0530 for (std::size_t j = 0; j <= s2.size(); ++j) {
0531 if (i == 0) {
0532 dp[i][j] = j;
0533 } else if (j == 0) {
0534 dp[i][j] = i;
0535 } else if (s1[i - 1] == s2[j - 1]) {
0536 dp[i][j] = dp[i - 1][j - 1];
0537 } else {
0538 dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
0539 }
0540 }
0541 }
0542
0543 return dp[s1.size()][s2.size()];
0544 }
0545
0546 template <typename ValueType>
0547 std::string_view
0548 get_most_similar_string(const std::map<std::string_view, ValueType> &map,
0549 const std::string_view input) {
0550 std::string_view most_similar{};
0551 std::size_t min_distance = std::numeric_limits<std::size_t>::max();
0552
0553 for (const auto &entry : map) {
0554 std::size_t distance = get_levenshtein_distance(entry.first, input);
0555 if (distance < min_distance) {
0556 min_distance = distance;
0557 most_similar = entry.first;
0558 }
0559 }
0560
0561 return most_similar;
0562 }
0563
0564 }
0565
0566 enum class nargs_pattern { optional, any, at_least_one };
0567
0568 enum class default_arguments : unsigned int {
0569 none = 0,
0570 help = 1,
0571 version = 2,
0572 all = help | version,
0573 };
0574
0575 inline default_arguments operator&(const default_arguments &a,
0576 const default_arguments &b) {
0577 return static_cast<default_arguments>(
0578 static_cast<std::underlying_type<default_arguments>::type>(a) &
0579 static_cast<std::underlying_type<default_arguments>::type>(b));
0580 }
0581
0582 class ArgumentParser;
0583
0584 class Argument {
0585 friend class ArgumentParser;
0586 friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
0587 -> std::ostream &;
0588
0589 template <std::size_t N, std::size_t... I>
0590 explicit Argument(std::string_view prefix_chars,
0591 std::array<std::string_view, N> &&a,
0592 std::index_sequence<I...> )
0593 : m_accepts_optional_like_value(false),
0594 m_is_optional((is_optional(a[I], prefix_chars) || ...)),
0595 m_is_required(false), m_is_repeatable(false), m_is_used(false),
0596 m_prefix_chars(prefix_chars) {
0597 ((void)m_names.emplace_back(a[I]), ...);
0598 std::sort(
0599 m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
0600 return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
0601 });
0602 }
0603
0604 public:
0605 template <std::size_t N>
0606 explicit Argument(std::string_view prefix_chars,
0607 std::array<std::string_view, N> &&a)
0608 : Argument(prefix_chars, std::move(a), std::make_index_sequence<N>{}) {}
0609
0610 Argument &help(std::string help_text) {
0611 m_help = std::move(help_text);
0612 return *this;
0613 }
0614
0615 Argument &metavar(std::string metavar) {
0616 m_metavar = std::move(metavar);
0617 return *this;
0618 }
0619
0620 template <typename T> Argument &default_value(T &&value) {
0621 m_num_args_range = NArgsRange{0, m_num_args_range.get_max()};
0622 m_default_value_repr = details::repr(value);
0623
0624 if constexpr (std::is_convertible_v<T, std::string_view>) {
0625 m_default_value_str = std::string{std::string_view{value}};
0626 } else if constexpr (details::can_invoke_to_string<T>::value) {
0627 m_default_value_str = std::to_string(value);
0628 }
0629
0630 m_default_value = std::forward<T>(value);
0631 return *this;
0632 }
0633
0634 Argument &default_value(const char *value) {
0635 return default_value(std::string(value));
0636 }
0637
0638 Argument &required() {
0639 m_is_required = true;
0640 return *this;
0641 }
0642
0643 Argument &implicit_value(std::any value) {
0644 m_implicit_value = std::move(value);
0645 m_num_args_range = NArgsRange{0, 0};
0646 return *this;
0647 }
0648
0649
0650
0651
0652
0653 Argument &flag() {
0654 default_value(false);
0655 implicit_value(true);
0656 return *this;
0657 }
0658
0659 template <class F, class... Args>
0660 auto action(F &&callable, Args &&... bound_args)
0661 -> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
0662 Argument &> {
0663 using action_type = std::conditional_t<
0664 std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
0665 void_action, valued_action>;
0666 if constexpr (sizeof...(Args) == 0) {
0667 m_action.emplace<action_type>(std::forward<F>(callable));
0668 } else {
0669 m_action.emplace<action_type>(
0670 [f = std::forward<F>(callable),
0671 tup = std::make_tuple(std::forward<Args>(bound_args)...)](
0672 std::string const &opt) mutable {
0673 return details::apply_plus_one(f, tup, opt);
0674 });
0675 }
0676 return *this;
0677 }
0678
0679 auto &append() {
0680 m_is_repeatable = true;
0681 return *this;
0682 }
0683
0684 template <char Shape, typename T>
0685 auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
0686 static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
0687 "T should not be cv-qualified");
0688 auto is_one_of = [](char c, auto... x) constexpr {
0689 return ((c == x) || ...);
0690 };
0691
0692 if constexpr (is_one_of(Shape, 'd') && details::standard_integer<T>) {
0693 action(details::parse_number<T, details::radix_10>());
0694 } else if constexpr (is_one_of(Shape, 'i') &&
0695 details::standard_integer<T>) {
0696 action(details::parse_number<T>());
0697 } else if constexpr (is_one_of(Shape, 'u') &&
0698 details::standard_unsigned_integer<T>) {
0699 action(details::parse_number<T, details::radix_10>());
0700 } else if constexpr (is_one_of(Shape, 'b') &&
0701 details::standard_unsigned_integer<T>) {
0702 action(details::parse_number<T, details::radix_2>());
0703 } else if constexpr (is_one_of(Shape, 'o') &&
0704 details::standard_unsigned_integer<T>) {
0705 action(details::parse_number<T, details::radix_8>());
0706 } else if constexpr (is_one_of(Shape, 'x', 'X') &&
0707 details::standard_unsigned_integer<T>) {
0708 action(details::parse_number<T, details::radix_16>());
0709 } else if constexpr (is_one_of(Shape, 'a', 'A') &&
0710 std::is_floating_point_v<T>) {
0711 action(details::parse_number<T, details::chars_format::hex>());
0712 } else if constexpr (is_one_of(Shape, 'e', 'E') &&
0713 std::is_floating_point_v<T>) {
0714 action(details::parse_number<T, details::chars_format::scientific>());
0715 } else if constexpr (is_one_of(Shape, 'f', 'F') &&
0716 std::is_floating_point_v<T>) {
0717 action(details::parse_number<T, details::chars_format::fixed>());
0718 } else if constexpr (is_one_of(Shape, 'g', 'G') &&
0719 std::is_floating_point_v<T>) {
0720 action(details::parse_number<T, details::chars_format::general>());
0721 } else {
0722 static_assert(alignof(T) == 0, "No scan specification for T");
0723 }
0724
0725 return *this;
0726 }
0727
0728 Argument &nargs(std::size_t num_args) {
0729 m_num_args_range = NArgsRange{num_args, num_args};
0730 return *this;
0731 }
0732
0733 Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
0734 m_num_args_range = NArgsRange{num_args_min, num_args_max};
0735 return *this;
0736 }
0737
0738 Argument &nargs(nargs_pattern pattern) {
0739 switch (pattern) {
0740 case nargs_pattern::optional:
0741 m_num_args_range = NArgsRange{0, 1};
0742 break;
0743 case nargs_pattern::any:
0744 m_num_args_range =
0745 NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
0746 break;
0747 case nargs_pattern::at_least_one:
0748 m_num_args_range =
0749 NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
0750 break;
0751 }
0752 return *this;
0753 }
0754
0755 Argument &remaining() {
0756 m_accepts_optional_like_value = true;
0757 return nargs(nargs_pattern::any);
0758 }
0759
0760 template <typename T> void add_choice(T &&choice) {
0761 static_assert(details::IsChoiceTypeSupported<T>::value,
0762 "Only string or integer type supported for choice");
0763 static_assert(std::is_convertible_v<T, std::string_view> ||
0764 details::can_invoke_to_string<T>::value,
0765 "Choice is not convertible to string_type");
0766 if (!m_choices.has_value()) {
0767 m_choices = std::vector<std::string>{};
0768 }
0769
0770 if constexpr (std::is_convertible_v<T, std::string_view>) {
0771 m_choices.value().push_back(
0772 std::string{std::string_view{std::forward<T>(choice)}});
0773 } else if constexpr (details::can_invoke_to_string<T>::value) {
0774 m_choices.value().push_back(std::to_string(std::forward<T>(choice)));
0775 }
0776 }
0777
0778 Argument &choices() {
0779 if (!m_choices.has_value()) {
0780 throw std::runtime_error("Zero choices provided");
0781 }
0782 return *this;
0783 }
0784
0785 template <typename T, typename... U>
0786 Argument &choices(T &&first, U &&... rest) {
0787 add_choice(std::forward<T>(first));
0788 choices(std::forward<U>(rest)...);
0789 return *this;
0790 }
0791
0792 void find_default_value_in_choices_or_throw() const {
0793
0794 const auto &choices = m_choices.value();
0795
0796 if (m_default_value.has_value()) {
0797 if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
0798 choices.end()) {
0799
0800
0801
0802 std::string choices_as_csv =
0803 std::accumulate(choices.begin(), choices.end(), std::string(),
0804 [](const std::string &a, const std::string &b) {
0805 return a + (a.empty() ? "" : ", ") + b;
0806 });
0807
0808 throw std::runtime_error(
0809 std::string{"Invalid default value "} + m_default_value_repr +
0810 " - allowed options: {" + choices_as_csv + "}");
0811 }
0812 }
0813 }
0814
0815 template <typename Iterator>
0816 void find_value_in_choices_or_throw(Iterator it) const {
0817
0818 const auto &choices = m_choices.value();
0819
0820 if (std::find(choices.begin(), choices.end(), *it) == choices.end()) {
0821
0822
0823
0824 std::string choices_as_csv =
0825 std::accumulate(choices.begin(), choices.end(), std::string(),
0826 [](const std::string &a, const std::string &b) {
0827 return a + (a.empty() ? "" : ", ") + b;
0828 });
0829
0830 throw std::runtime_error(std::string{"Invalid argument "} +
0831 details::repr(*it) + " - allowed options: {" +
0832 choices_as_csv + "}");
0833 }
0834 }
0835
0836 template <typename Iterator>
0837 Iterator consume(Iterator start, Iterator end,
0838 std::string_view used_name = {}) {
0839 if (!m_is_repeatable && m_is_used) {
0840 throw std::runtime_error("Duplicate argument");
0841 }
0842 m_is_used = true;
0843 m_used_name = used_name;
0844
0845 if (m_choices.has_value()) {
0846
0847
0848 std::size_t i = 0;
0849 auto max_number_of_args = m_num_args_range.get_max();
0850 for (auto it = start; it != end; ++it) {
0851 if (i == max_number_of_args) {
0852 break;
0853 }
0854 find_value_in_choices_or_throw(it);
0855 i += 1;
0856 }
0857 }
0858
0859 const auto num_args_max = m_num_args_range.get_max();
0860 const auto num_args_min = m_num_args_range.get_min();
0861 std::size_t dist = 0;
0862 if (num_args_max == 0) {
0863 m_values.emplace_back(m_implicit_value);
0864 std::visit([](const auto &f) { f({}); }, m_action);
0865 return start;
0866 }
0867 if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
0868 num_args_min) {
0869 if (num_args_max < dist) {
0870 end = std::next(start, static_cast<typename Iterator::difference_type>(
0871 num_args_max));
0872 }
0873 if (!m_accepts_optional_like_value) {
0874 end = std::find_if(
0875 start, end,
0876 std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
0877 dist = static_cast<std::size_t>(std::distance(start, end));
0878 if (dist < num_args_min) {
0879 throw std::runtime_error("Too few arguments");
0880 }
0881 }
0882
0883 struct ActionApply {
0884 void operator()(valued_action &f) {
0885 std::transform(first, last, std::back_inserter(self.m_values), f);
0886 }
0887
0888 void operator()(void_action &f) {
0889 std::for_each(first, last, f);
0890 if (!self.m_default_value.has_value()) {
0891 if (!self.m_accepts_optional_like_value) {
0892 self.m_values.resize(
0893 static_cast<std::size_t>(std::distance(first, last)));
0894 }
0895 }
0896 }
0897
0898 Iterator first, last;
0899 Argument &self;
0900 };
0901 std::visit(ActionApply{start, end, *this}, m_action);
0902 return end;
0903 }
0904 if (m_default_value.has_value()) {
0905 return start;
0906 }
0907 throw std::runtime_error("Too few arguments for '" +
0908 std::string(m_used_name) + "'.");
0909 }
0910
0911
0912
0913
0914 void validate() const {
0915 if (m_is_optional) {
0916
0917 if (!m_is_used && !m_default_value.has_value() && m_is_required) {
0918 throw_required_arg_not_used_error();
0919 }
0920 if (m_is_used && m_is_required && m_values.empty()) {
0921 throw_required_arg_no_value_provided_error();
0922 }
0923 } else {
0924 if (!m_num_args_range.contains(m_values.size()) &&
0925 !m_default_value.has_value()) {
0926 throw_nargs_range_validation_error();
0927 }
0928 }
0929
0930 if (m_choices.has_value()) {
0931
0932
0933 find_default_value_in_choices_or_throw();
0934 }
0935 }
0936
0937 std::string get_names_csv(char separator = ',') const {
0938 return std::accumulate(
0939 m_names.begin(), m_names.end(), std::string{""},
0940 [&](const std::string &result, const std::string &name) {
0941 return result.empty() ? name : result + separator + name;
0942 });
0943 }
0944
0945 std::string get_usage_full() const {
0946 std::stringstream usage;
0947
0948 usage << get_names_csv('/');
0949 const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
0950 if (m_num_args_range.get_max() > 0) {
0951 usage << " " << metavar;
0952 if (m_num_args_range.get_max() > 1) {
0953 usage << "...";
0954 }
0955 }
0956 return usage.str();
0957 }
0958
0959 std::string get_inline_usage() const {
0960 std::stringstream usage;
0961
0962 std::string longest_name = m_names.front();
0963 for (const auto &s : m_names) {
0964 if (s.size() > longest_name.size()) {
0965 longest_name = s;
0966 }
0967 }
0968 if (!m_is_required) {
0969 usage << "[";
0970 }
0971 usage << longest_name;
0972 const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
0973 if (m_num_args_range.get_max() > 0) {
0974 usage << " " << metavar;
0975 if (m_num_args_range.get_max() > 1) {
0976 usage << "...";
0977 }
0978 }
0979 if (!m_is_required) {
0980 usage << "]";
0981 }
0982 return usage.str();
0983 }
0984
0985 std::size_t get_arguments_length() const {
0986
0987 std::size_t names_size = std::accumulate(
0988 std::begin(m_names), std::end(m_names), std::size_t(0),
0989 [](const auto &sum, const auto &s) { return sum + s.size(); });
0990
0991 if (is_positional(m_names.front(), m_prefix_chars)) {
0992
0993 if (!m_metavar.empty()) {
0994
0995 return 2 + m_metavar.size();
0996 }
0997
0998
0999 return 2 + names_size + (m_names.size() - 1);
1000 }
1001
1002
1003 std::size_t size = names_size + 2 * (m_names.size() - 1);
1004 if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
1005 size += m_metavar.size() + 1;
1006 }
1007 return size + 2;
1008 }
1009
1010 friend std::ostream &operator<<(std::ostream &stream,
1011 const Argument &argument) {
1012 std::stringstream name_stream;
1013 name_stream << " ";
1014 if (argument.is_positional(argument.m_names.front(),
1015 argument.m_prefix_chars)) {
1016 if (!argument.m_metavar.empty()) {
1017 name_stream << argument.m_metavar;
1018 } else {
1019 name_stream << details::join(argument.m_names.begin(),
1020 argument.m_names.end(), " ");
1021 }
1022 } else {
1023 name_stream << details::join(argument.m_names.begin(),
1024 argument.m_names.end(), ", ");
1025
1026 if (!argument.m_metavar.empty() &&
1027 argument.m_num_args_range == NArgsRange{1, 1}) {
1028 name_stream << " " << argument.m_metavar;
1029 }
1030 }
1031
1032
1033 auto stream_width = stream.width();
1034 auto name_padding = std::string(name_stream.str().size(), ' ');
1035 auto pos = std::string::size_type{};
1036 auto prev = std::string::size_type{};
1037 auto first_line = true;
1038 auto hspace = " ";
1039 stream << name_stream.str();
1040 std::string_view help_view(argument.m_help);
1041 while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
1042 auto line = help_view.substr(prev, pos - prev + 1);
1043 if (first_line) {
1044 stream << hspace << line;
1045 first_line = false;
1046 } else {
1047 stream.width(stream_width);
1048 stream << name_padding << hspace << line;
1049 }
1050 prev += pos - prev + 1;
1051 }
1052 if (first_line) {
1053 stream << hspace << argument.m_help;
1054 } else {
1055 auto leftover = help_view.substr(prev, argument.m_help.size() - prev);
1056 if (!leftover.empty()) {
1057 stream.width(stream_width);
1058 stream << name_padding << hspace << leftover;
1059 }
1060 }
1061
1062
1063 if (!argument.m_help.empty()) {
1064 stream << " ";
1065 }
1066 stream << argument.m_num_args_range;
1067
1068 if (argument.m_default_value.has_value() &&
1069 argument.m_num_args_range != NArgsRange{0, 0}) {
1070 stream << "[default: " << argument.m_default_value_repr << "]";
1071 } else if (argument.m_is_required) {
1072 stream << "[required]";
1073 }
1074 stream << "\n";
1075 return stream;
1076 }
1077
1078 template <typename T> bool operator!=(const T &rhs) const {
1079 return !(*this == rhs);
1080 }
1081
1082
1083
1084
1085
1086 template <typename T> bool operator==(const T &rhs) const {
1087 if constexpr (!details::IsContainer<T>) {
1088 return get<T>() == rhs;
1089 } else {
1090 using ValueType = typename T::value_type;
1091 auto lhs = get<T>();
1092 return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
1093 std::end(rhs), [](const auto &a, const auto &b) {
1094 return std::any_cast<const ValueType &>(a) == b;
1095 });
1096 }
1097 }
1098
1099 private:
1100 class NArgsRange {
1101 std::size_t m_min;
1102 std::size_t m_max;
1103
1104 public:
1105 NArgsRange(std::size_t minimum, std::size_t maximum)
1106 : m_min(minimum), m_max(maximum) {
1107 if (minimum > maximum) {
1108 throw std::logic_error("Range of number of arguments is invalid");
1109 }
1110 }
1111
1112 bool contains(std::size_t value) const {
1113 return value >= m_min && value <= m_max;
1114 }
1115
1116 bool is_exact() const { return m_min == m_max; }
1117
1118 bool is_right_bounded() const {
1119 return m_max < (std::numeric_limits<std::size_t>::max)();
1120 }
1121
1122 std::size_t get_min() const { return m_min; }
1123
1124 std::size_t get_max() const { return m_max; }
1125
1126
1127 friend auto operator<<(std::ostream &stream, const NArgsRange &range)
1128 -> std::ostream & {
1129 if (range.m_min == range.m_max) {
1130 if (range.m_min != 0 && range.m_min != 1) {
1131 stream << "[nargs: " << range.m_min << "] ";
1132 }
1133 } else {
1134 if (range.m_max == (std::numeric_limits<std::size_t>::max)()) {
1135 stream << "[nargs: " << range.m_min << " or more] ";
1136 } else {
1137 stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
1138 }
1139 }
1140 return stream;
1141 }
1142
1143 bool operator==(const NArgsRange &rhs) const {
1144 return rhs.m_min == m_min && rhs.m_max == m_max;
1145 }
1146
1147 bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
1148 };
1149
1150 void throw_nargs_range_validation_error() const {
1151 std::stringstream stream;
1152 if (!m_used_name.empty()) {
1153 stream << m_used_name << ": ";
1154 } else {
1155 stream << m_names.front() << ": ";
1156 }
1157 if (m_num_args_range.is_exact()) {
1158 stream << m_num_args_range.get_min();
1159 } else if (m_num_args_range.is_right_bounded()) {
1160 stream << m_num_args_range.get_min() << " to "
1161 << m_num_args_range.get_max();
1162 } else {
1163 stream << m_num_args_range.get_min() << " or more";
1164 }
1165 stream << " argument(s) expected. " << m_values.size() << " provided.";
1166 throw std::runtime_error(stream.str());
1167 }
1168
1169 void throw_required_arg_not_used_error() const {
1170 std::stringstream stream;
1171 stream << m_names.front() << ": required.";
1172 throw std::runtime_error(stream.str());
1173 }
1174
1175 void throw_required_arg_no_value_provided_error() const {
1176 std::stringstream stream;
1177 stream << m_used_name << ": no value provided.";
1178 throw std::runtime_error(stream.str());
1179 }
1180
1181 static constexpr int eof = std::char_traits<char>::eof();
1182
1183 static auto lookahead(std::string_view s) -> int {
1184 if (s.empty()) {
1185 return eof;
1186 }
1187 return static_cast<int>(static_cast<unsigned char>(s[0]));
1188 }
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218 static bool is_decimal_literal(std::string_view s) {
1219 auto is_digit = [](auto c) constexpr {
1220 switch (c) {
1221 case '0':
1222 case '1':
1223 case '2':
1224 case '3':
1225 case '4':
1226 case '5':
1227 case '6':
1228 case '7':
1229 case '8':
1230 case '9':
1231 return true;
1232 default:
1233 return false;
1234 }
1235 };
1236
1237
1238 auto consume_digits = [=](std::string_view sd) {
1239
1240 auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit);
1241 return sd.substr(static_cast<std::size_t>(it - std::begin(sd)));
1242 };
1243
1244 switch (lookahead(s)) {
1245 case '0': {
1246 s.remove_prefix(1);
1247 if (s.empty()) {
1248 return true;
1249 }
1250 goto integer_part;
1251 }
1252 case '1':
1253 case '2':
1254 case '3':
1255 case '4':
1256 case '5':
1257 case '6':
1258 case '7':
1259 case '8':
1260 case '9': {
1261 s = consume_digits(s);
1262 if (s.empty()) {
1263 return true;
1264 }
1265 goto integer_part_consumed;
1266 }
1267 case '.': {
1268 s.remove_prefix(1);
1269 goto post_decimal_point;
1270 }
1271 default:
1272 return false;
1273 }
1274
1275 integer_part:
1276 s = consume_digits(s);
1277 integer_part_consumed:
1278 switch (lookahead(s)) {
1279 case '.': {
1280 s.remove_prefix(1);
1281 if (is_digit(lookahead(s))) {
1282 goto post_decimal_point;
1283 } else {
1284 goto exponent_part_opt;
1285 }
1286 }
1287 case 'e':
1288 case 'E': {
1289 s.remove_prefix(1);
1290 goto post_e;
1291 }
1292 default:
1293 return false;
1294 }
1295
1296 post_decimal_point:
1297 if (is_digit(lookahead(s))) {
1298 s = consume_digits(s);
1299 goto exponent_part_opt;
1300 }
1301 return false;
1302
1303 exponent_part_opt:
1304 switch (lookahead(s)) {
1305 case eof:
1306 return true;
1307 case 'e':
1308 case 'E': {
1309 s.remove_prefix(1);
1310 goto post_e;
1311 }
1312 default:
1313 return false;
1314 }
1315
1316 post_e:
1317 switch (lookahead(s)) {
1318 case '-':
1319 case '+':
1320 s.remove_prefix(1);
1321 }
1322 if (is_digit(lookahead(s))) {
1323 s = consume_digits(s);
1324 return s.empty();
1325 }
1326 return false;
1327 }
1328
1329 static bool is_optional(std::string_view name,
1330 std::string_view prefix_chars) {
1331 return !is_positional(name, prefix_chars);
1332 }
1333
1334
1335
1336
1337
1338
1339
1340
1341 static bool is_positional(std::string_view name,
1342 std::string_view prefix_chars) {
1343 auto first = lookahead(name);
1344
1345 if (first == eof) {
1346 return true;
1347 } else if (prefix_chars.find(static_cast<char>(first)) !=
1348 std::string_view::npos) {
1349 name.remove_prefix(1);
1350 if (name.empty()) {
1351 return true;
1352 }
1353 return is_decimal_literal(name);
1354 }
1355 return true;
1356 }
1357
1358
1359
1360
1361
1362 template <typename T> T get() const {
1363 if (!m_values.empty()) {
1364 if constexpr (details::IsContainer<T>) {
1365 return any_cast_container<T>(m_values);
1366 } else {
1367 return std::any_cast<T>(m_values.front());
1368 }
1369 }
1370 if (m_default_value.has_value()) {
1371 return std::any_cast<T>(m_default_value);
1372 }
1373 if constexpr (details::IsContainer<T>) {
1374 if (!m_accepts_optional_like_value) {
1375 return any_cast_container<T>(m_values);
1376 }
1377 }
1378
1379 throw std::logic_error("No value provided for '" + m_names.back() + "'.");
1380 }
1381
1382
1383
1384
1385
1386
1387 template <typename T> auto present() const -> std::optional<T> {
1388 if (m_default_value.has_value()) {
1389 throw std::logic_error("Argument with default value always presents");
1390 }
1391 if (m_values.empty()) {
1392 return std::nullopt;
1393 }
1394 if constexpr (details::IsContainer<T>) {
1395 return any_cast_container<T>(m_values);
1396 }
1397 return std::any_cast<T>(m_values.front());
1398 }
1399
1400 template <typename T>
1401 static auto any_cast_container(const std::vector<std::any> &operand) -> T {
1402 using ValueType = typename T::value_type;
1403
1404 T result;
1405 std::transform(
1406 std::begin(operand), std::end(operand), std::back_inserter(result),
1407 [](const auto &value) { return std::any_cast<ValueType>(value); });
1408 return result;
1409 }
1410
1411 std::vector<std::string> m_names;
1412 std::string_view m_used_name;
1413 std::string m_help;
1414 std::string m_metavar;
1415 std::any m_default_value;
1416 std::string m_default_value_repr;
1417 std::optional<std::string>
1418 m_default_value_str;
1419 std::any m_implicit_value;
1420 std::optional<std::vector<std::string>> m_choices{std::nullopt};
1421 using valued_action = std::function<std::any(const std::string &)>;
1422 using void_action = std::function<void(const std::string &)>;
1423 std::variant<valued_action, void_action> m_action{
1424 std::in_place_type<valued_action>,
1425 [](const std::string &value) { return value; }};
1426 std::vector<std::any> m_values;
1427 NArgsRange m_num_args_range{1, 1};
1428
1429 bool m_accepts_optional_like_value : 1;
1430 bool m_is_optional : 1;
1431 bool m_is_required : 1;
1432 bool m_is_repeatable : 1;
1433 bool m_is_used : 1;
1434 std::string_view m_prefix_chars;
1435 };
1436
1437 class ArgumentParser {
1438 public:
1439 explicit ArgumentParser(std::string program_name = {},
1440 std::string version = "1.0",
1441 default_arguments add_args = default_arguments::all,
1442 bool exit_on_default_arguments = true,
1443 std::ostream &os = std::cout)
1444 : m_program_name(std::move(program_name)), m_version(std::move(version)),
1445 m_exit_on_default_arguments(exit_on_default_arguments),
1446 m_parser_path(m_program_name) {
1447 if ((add_args & default_arguments::help) == default_arguments::help) {
1448 add_argument("-h", "--help")
1449 .action([&](const auto & ) {
1450 os << help().str();
1451 if (m_exit_on_default_arguments) {
1452 std::exit(0);
1453 }
1454 })
1455 .default_value(false)
1456 .help("shows help message and exits")
1457 .implicit_value(true)
1458 .nargs(0);
1459 }
1460 if ((add_args & default_arguments::version) == default_arguments::version) {
1461 add_argument("-v", "--version")
1462 .action([&](const auto & ) {
1463 os << m_version << std::endl;
1464 if (m_exit_on_default_arguments) {
1465 std::exit(0);
1466 }
1467 })
1468 .default_value(false)
1469 .help("prints version information and exits")
1470 .implicit_value(true)
1471 .nargs(0);
1472 }
1473 }
1474
1475 ~ArgumentParser() = default;
1476
1477
1478
1479
1480
1481
1482
1483 ArgumentParser(const ArgumentParser &other) = delete;
1484 ArgumentParser &operator=(const ArgumentParser &other) = delete;
1485 ArgumentParser(ArgumentParser &&) noexcept = delete;
1486 ArgumentParser &operator=(ArgumentParser &&) = delete;
1487
1488 explicit operator bool() const {
1489 auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
1490 [](auto &it) { return it.second->m_is_used; });
1491 auto subparser_used =
1492 std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
1493 [](auto &it) { return it.second; });
1494
1495 return m_is_parsed && (arg_used || subparser_used);
1496 }
1497
1498
1499
1500 template <typename... Targs> Argument &add_argument(Targs... f_args) {
1501 using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
1502 auto argument =
1503 m_optional_arguments.emplace(std::cend(m_optional_arguments),
1504 m_prefix_chars, array_of_sv{f_args...});
1505
1506 if (!argument->m_is_optional) {
1507 m_positional_arguments.splice(std::cend(m_positional_arguments),
1508 m_optional_arguments, argument);
1509 }
1510
1511 index_argument(argument);
1512 return *argument;
1513 }
1514
1515 class MutuallyExclusiveGroup {
1516 friend class ArgumentParser;
1517
1518 public:
1519 MutuallyExclusiveGroup() = delete;
1520
1521 explicit MutuallyExclusiveGroup(ArgumentParser &parent,
1522 bool required = false)
1523 : m_parent(parent), m_required(required), m_elements({}) {}
1524
1525 MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
1526 MutuallyExclusiveGroup &
1527 operator=(const MutuallyExclusiveGroup &other) = delete;
1528
1529 MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
1530 : m_parent(other.m_parent), m_required(other.m_required),
1531 m_elements(std::move(other.m_elements)) {
1532 other.m_elements.clear();
1533 }
1534
1535 template <typename... Targs> Argument &add_argument(Targs... f_args) {
1536 auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
1537 m_elements.push_back(&argument);
1538 return argument;
1539 }
1540
1541 private:
1542 ArgumentParser &m_parent;
1543 bool m_required{false};
1544 std::vector<Argument *> m_elements{};
1545 };
1546
1547 MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
1548 m_mutually_exclusive_groups.emplace_back(*this, required);
1549 return m_mutually_exclusive_groups.back();
1550 }
1551
1552
1553
1554 template <typename... Targs>
1555 ArgumentParser &add_parents(const Targs &... f_args) {
1556 for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
1557 for (const auto &argument : parent_parser.m_positional_arguments) {
1558 auto it = m_positional_arguments.insert(
1559 std::cend(m_positional_arguments), argument);
1560 index_argument(it);
1561 }
1562 for (const auto &argument : parent_parser.m_optional_arguments) {
1563 auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
1564 argument);
1565 index_argument(it);
1566 }
1567 }
1568 return *this;
1569 }
1570
1571 ArgumentParser &add_description(std::string description) {
1572 m_description = std::move(description);
1573 return *this;
1574 }
1575
1576 ArgumentParser &add_epilog(std::string epilog) {
1577 m_epilog = std::move(epilog);
1578 return *this;
1579 }
1580
1581
1582
1583
1584 template <typename T = Argument> T &at(std::string_view name) {
1585 if constexpr (std::is_same_v<T, Argument>) {
1586 return (*this)[name];
1587 } else {
1588 auto subparser_it = m_subparser_map.find(name);
1589 if (subparser_it != m_subparser_map.end()) {
1590 return subparser_it->second->get();
1591 }
1592 throw std::logic_error("No such subparser: " + std::string(name));
1593 }
1594 }
1595
1596 ArgumentParser &set_prefix_chars(std::string prefix_chars) {
1597 m_prefix_chars = std::move(prefix_chars);
1598 return *this;
1599 }
1600
1601 ArgumentParser &set_assign_chars(std::string assign_chars) {
1602 m_assign_chars = std::move(assign_chars);
1603 return *this;
1604 }
1605
1606
1607
1608
1609
1610
1611 void parse_args(const std::vector<std::string> &arguments) {
1612 parse_args_internal(arguments);
1613
1614 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1615 argument->validate();
1616 }
1617
1618
1619
1620 for (const auto &group : m_mutually_exclusive_groups) {
1621 auto mutex_argument_used{false};
1622 Argument *mutex_argument_it{nullptr};
1623 for (Argument *arg : group.m_elements) {
1624 if (!mutex_argument_used && arg->m_is_used) {
1625 mutex_argument_used = true;
1626 mutex_argument_it = arg;
1627 } else if (mutex_argument_used && arg->m_is_used) {
1628
1629 throw std::runtime_error("Argument '" + arg->get_usage_full() +
1630 "' not allowed with '" +
1631 mutex_argument_it->get_usage_full() + "'");
1632 }
1633 }
1634
1635 if (!mutex_argument_used && group.m_required) {
1636
1637
1638 std::string argument_names{};
1639 std::size_t i = 0;
1640 std::size_t size = group.m_elements.size();
1641 for (Argument *arg : group.m_elements) {
1642 if (i + 1 == size) {
1643
1644 argument_names += "'" + arg->get_usage_full() + "' ";
1645 } else {
1646 argument_names += "'" + arg->get_usage_full() + "' or ";
1647 }
1648 i += 1;
1649 }
1650 throw std::runtime_error("One of the arguments " + argument_names +
1651 "is required");
1652 }
1653 }
1654 }
1655
1656
1657
1658
1659
1660
1661 std::vector<std::string>
1662 parse_known_args(const std::vector<std::string> &arguments) {
1663 auto unknown_arguments = parse_known_args_internal(arguments);
1664
1665 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1666 argument->validate();
1667 }
1668 return unknown_arguments;
1669 }
1670
1671
1672
1673
1674
1675
1676 void parse_args(int argc, const char *const argv[]) {
1677 parse_args({argv, argv + argc});
1678 }
1679
1680
1681
1682
1683
1684
1685 auto parse_known_args(int argc, const char *const argv[]) {
1686 return parse_known_args({argv, argv + argc});
1687 }
1688
1689
1690
1691
1692
1693
1694
1695 template <typename T = std::string> T get(std::string_view arg_name) const {
1696 if (!m_is_parsed) {
1697 throw std::logic_error("Nothing parsed, no arguments are available.");
1698 }
1699 return (*this)[arg_name].get<T>();
1700 }
1701
1702
1703
1704
1705
1706
1707 template <typename T = std::string>
1708 auto present(std::string_view arg_name) const -> std::optional<T> {
1709 return (*this)[arg_name].present<T>();
1710 }
1711
1712
1713
1714
1715 auto is_used(std::string_view arg_name) const {
1716 return (*this)[arg_name].m_is_used;
1717 }
1718
1719
1720
1721 auto is_subcommand_used(std::string_view subcommand_name) const {
1722 return m_subparser_used.at(subcommand_name);
1723 }
1724
1725
1726
1727 auto is_subcommand_used(const ArgumentParser &subparser) const {
1728 return is_subcommand_used(subparser.m_program_name);
1729 }
1730
1731
1732
1733
1734
1735 Argument &operator[](std::string_view arg_name) const {
1736 auto it = m_argument_map.find(arg_name);
1737 if (it != m_argument_map.end()) {
1738 return *(it->second);
1739 }
1740 if (!is_valid_prefix_char(arg_name.front())) {
1741 std::string name(arg_name);
1742 const auto legal_prefix_char = get_any_valid_prefix_char();
1743 const auto prefix = std::string(1, legal_prefix_char);
1744
1745
1746 name = prefix + name;
1747 it = m_argument_map.find(name);
1748 if (it != m_argument_map.end()) {
1749 return *(it->second);
1750 }
1751
1752 name = prefix + name;
1753 it = m_argument_map.find(name);
1754 if (it != m_argument_map.end()) {
1755 return *(it->second);
1756 }
1757 }
1758 throw std::logic_error("No such argument: " + std::string(arg_name));
1759 }
1760
1761
1762 friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
1763 -> std::ostream & {
1764 stream.setf(std::ios_base::left);
1765
1766 auto longest_arg_length = parser.get_length_of_longest_argument();
1767
1768 stream << parser.usage() << "\n\n";
1769
1770 if (!parser.m_description.empty()) {
1771 stream << parser.m_description << "\n\n";
1772 }
1773
1774 if (!parser.m_positional_arguments.empty()) {
1775 stream << "Positional arguments:\n";
1776 }
1777
1778 for (const auto &argument : parser.m_positional_arguments) {
1779 stream.width(static_cast<std::streamsize>(longest_arg_length));
1780 stream << argument;
1781 }
1782
1783 if (!parser.m_optional_arguments.empty()) {
1784 stream << (parser.m_positional_arguments.empty() ? "" : "\n")
1785 << "Optional arguments:\n";
1786 }
1787
1788 for (const auto &argument : parser.m_optional_arguments) {
1789 stream.width(static_cast<std::streamsize>(longest_arg_length));
1790 stream << argument;
1791 }
1792
1793 bool has_visible_subcommands = std::any_of(
1794 parser.m_subparser_map.begin(), parser.m_subparser_map.end(),
1795 [](auto &p) { return !p.second->get().m_suppress; });
1796
1797 if (has_visible_subcommands) {
1798 stream << (parser.m_positional_arguments.empty()
1799 ? (parser.m_optional_arguments.empty() ? "" : "\n")
1800 : "\n")
1801 << "Subcommands:\n";
1802 for (const auto &[command, subparser] : parser.m_subparser_map) {
1803 if (subparser->get().m_suppress) {
1804 continue;
1805 }
1806
1807 stream << std::setw(2) << " ";
1808 stream << std::setw(static_cast<int>(longest_arg_length - 2))
1809 << command;
1810 stream << " " << subparser->get().m_description << "\n";
1811 }
1812 }
1813
1814 if (!parser.m_epilog.empty()) {
1815 stream << '\n';
1816 stream << parser.m_epilog << "\n\n";
1817 }
1818
1819 return stream;
1820 }
1821
1822
1823 auto help() const -> std::stringstream {
1824 std::stringstream out;
1825 out << *this;
1826 return out;
1827 }
1828
1829
1830 auto usage() const -> std::string {
1831 std::stringstream stream;
1832
1833 stream << "Usage: " << this->m_program_name;
1834
1835
1836 for (const auto &argument : this->m_optional_arguments) {
1837 stream << " " << argument.get_inline_usage();
1838 }
1839
1840 for (const auto &argument : this->m_positional_arguments) {
1841 if (!argument.m_metavar.empty()) {
1842 stream << " " << argument.m_metavar;
1843 } else {
1844 stream << " " << argument.m_names.front();
1845 }
1846 }
1847
1848 if (!m_subparser_map.empty()) {
1849 stream << " {";
1850 std::size_t i{0};
1851 for (const auto &[command, subparser] : m_subparser_map) {
1852 if (subparser->get().m_suppress) {
1853 continue;
1854 }
1855
1856 if (i == 0) {
1857 stream << command;
1858 } else {
1859 stream << "," << command;
1860 }
1861 ++i;
1862 }
1863 stream << "}";
1864 }
1865
1866 return stream.str();
1867 }
1868
1869
1870
1871 [[deprecated("Use cout << program; instead. See also help().")]] std::string
1872 print_help() const {
1873 auto out = help();
1874 std::cout << out.rdbuf();
1875 return out.str();
1876 }
1877
1878 void add_subparser(ArgumentParser &parser) {
1879 parser.m_parser_path = m_program_name + " " + parser.m_program_name;
1880 auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
1881 m_subparser_map.insert_or_assign(parser.m_program_name, it);
1882 m_subparser_used.insert_or_assign(parser.m_program_name, false);
1883 }
1884
1885 void set_suppress(bool suppress) { m_suppress = suppress; }
1886
1887 private:
1888 bool is_valid_prefix_char(char c) const {
1889 return m_prefix_chars.find(c) != std::string::npos;
1890 }
1891
1892 char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
1893
1894
1895
1896
1897
1898
1899 std::vector<std::string>
1900 preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
1901 std::vector<std::string> arguments{};
1902 for (const auto &arg : raw_arguments) {
1903
1904 const auto argument_starts_with_prefix_chars =
1905 [this](const std::string &a) -> bool {
1906 if (!a.empty()) {
1907
1908 const auto legal_prefix = [this](char c) -> bool {
1909 return m_prefix_chars.find(c) != std::string::npos;
1910 };
1911
1912
1913
1914
1915
1916 const auto windows_style = legal_prefix('/');
1917
1918 if (windows_style) {
1919 if (legal_prefix(a[0])) {
1920 return true;
1921 }
1922 } else {
1923
1924
1925
1926
1927 if (a.size() > 1) {
1928 return (legal_prefix(a[0]) && legal_prefix(a[1]));
1929 }
1930 }
1931 }
1932 return false;
1933 };
1934
1935
1936
1937
1938
1939 auto assign_char_pos = arg.find_first_of(m_assign_chars);
1940
1941 if (m_argument_map.find(arg) == m_argument_map.end() &&
1942 argument_starts_with_prefix_chars(arg) &&
1943 assign_char_pos != std::string::npos) {
1944
1945 std::string opt_name = arg.substr(0, assign_char_pos);
1946 if (m_argument_map.find(opt_name) != m_argument_map.end()) {
1947
1948 arguments.push_back(std::move(opt_name));
1949 arguments.push_back(arg.substr(assign_char_pos + 1));
1950 continue;
1951 }
1952 }
1953
1954 arguments.push_back(arg);
1955 }
1956 return arguments;
1957 }
1958
1959
1960
1961
1962 void parse_args_internal(const std::vector<std::string> &raw_arguments) {
1963 auto arguments = preprocess_arguments(raw_arguments);
1964 if (m_program_name.empty() && !arguments.empty()) {
1965 m_program_name = arguments.front();
1966 }
1967 auto end = std::end(arguments);
1968 auto positional_argument_it = std::begin(m_positional_arguments);
1969 for (auto it = std::next(std::begin(arguments)); it != end;) {
1970 const auto ¤t_argument = *it;
1971 if (Argument::is_positional(current_argument, m_prefix_chars)) {
1972 if (positional_argument_it == std::end(m_positional_arguments)) {
1973
1974 std::string_view maybe_command = current_argument;
1975
1976
1977 auto subparser_it = m_subparser_map.find(maybe_command);
1978 if (subparser_it != m_subparser_map.end()) {
1979
1980
1981 const auto unprocessed_arguments =
1982 std::vector<std::string>(it, end);
1983
1984
1985 m_is_parsed = true;
1986 m_subparser_used[maybe_command] = true;
1987 return subparser_it->second->get().parse_args(
1988 unprocessed_arguments);
1989 }
1990
1991 if (m_positional_arguments.empty()) {
1992
1993
1994
1995
1996 if (!m_subparser_map.empty()) {
1997 throw std::runtime_error(
1998 "Failed to parse '" + current_argument + "', did you mean '" +
1999 std::string{details::get_most_similar_string(
2000 m_subparser_map, current_argument)} +
2001 "'");
2002 }
2003
2004
2005 if (!m_optional_arguments.empty()) {
2006 for (const auto &opt : m_optional_arguments) {
2007 if (!opt.m_implicit_value.has_value()) {
2008
2009 if (!opt.m_is_used) {
2010 throw std::runtime_error(
2011 "Zero positional arguments expected, did you mean " +
2012 opt.get_usage_full());
2013 }
2014 }
2015 }
2016
2017 throw std::runtime_error("Zero positional arguments expected");
2018 } else {
2019 throw std::runtime_error("Zero positional arguments expected");
2020 }
2021 } else {
2022 throw std::runtime_error("Maximum number of positional arguments "
2023 "exceeded, failed to parse '" +
2024 current_argument + "'");
2025 }
2026 }
2027 auto argument = positional_argument_it++;
2028 it = argument->consume(it, end);
2029 continue;
2030 }
2031
2032 auto arg_map_it = m_argument_map.find(current_argument);
2033 if (arg_map_it != m_argument_map.end()) {
2034 auto argument = arg_map_it->second;
2035 it = argument->consume(std::next(it), end, arg_map_it->first);
2036 } else if (const auto &compound_arg = current_argument;
2037 compound_arg.size() > 1 &&
2038 is_valid_prefix_char(compound_arg[0]) &&
2039 !is_valid_prefix_char(compound_arg[1])) {
2040 ++it;
2041 for (std::size_t j = 1; j < compound_arg.size(); j++) {
2042 auto hypothetical_arg = std::string{'-', compound_arg[j]};
2043 auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
2044 if (arg_map_it2 != m_argument_map.end()) {
2045 auto argument = arg_map_it2->second;
2046 it = argument->consume(it, end, arg_map_it2->first);
2047 } else {
2048 throw std::runtime_error("Unknown argument: " + current_argument);
2049 }
2050 }
2051 } else {
2052 throw std::runtime_error("Unknown argument: " + current_argument);
2053 }
2054 }
2055 m_is_parsed = true;
2056 }
2057
2058
2059
2060
2061 std::vector<std::string>
2062 parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
2063 auto arguments = preprocess_arguments(raw_arguments);
2064
2065 std::vector<std::string> unknown_arguments{};
2066
2067 if (m_program_name.empty() && !arguments.empty()) {
2068 m_program_name = arguments.front();
2069 }
2070 auto end = std::end(arguments);
2071 auto positional_argument_it = std::begin(m_positional_arguments);
2072 for (auto it = std::next(std::begin(arguments)); it != end;) {
2073 const auto ¤t_argument = *it;
2074 if (Argument::is_positional(current_argument, m_prefix_chars)) {
2075 if (positional_argument_it == std::end(m_positional_arguments)) {
2076
2077 std::string_view maybe_command = current_argument;
2078
2079
2080 auto subparser_it = m_subparser_map.find(maybe_command);
2081 if (subparser_it != m_subparser_map.end()) {
2082
2083
2084 const auto unprocessed_arguments =
2085 std::vector<std::string>(it, end);
2086
2087
2088 m_is_parsed = true;
2089 m_subparser_used[maybe_command] = true;
2090 return subparser_it->second->get().parse_known_args_internal(
2091 unprocessed_arguments);
2092 }
2093
2094
2095 unknown_arguments.push_back(current_argument);
2096 ++it;
2097 } else {
2098
2099
2100 auto argument = positional_argument_it++;
2101 it = argument->consume(it, end);
2102 }
2103 continue;
2104 }
2105
2106 auto arg_map_it = m_argument_map.find(current_argument);
2107 if (arg_map_it != m_argument_map.end()) {
2108 auto argument = arg_map_it->second;
2109 it = argument->consume(std::next(it), end, arg_map_it->first);
2110 } else if (const auto &compound_arg = current_argument;
2111 compound_arg.size() > 1 &&
2112 is_valid_prefix_char(compound_arg[0]) &&
2113 !is_valid_prefix_char(compound_arg[1])) {
2114 ++it;
2115 for (std::size_t j = 1; j < compound_arg.size(); j++) {
2116 auto hypothetical_arg = std::string{'-', compound_arg[j]};
2117 auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
2118 if (arg_map_it2 != m_argument_map.end()) {
2119 auto argument = arg_map_it2->second;
2120 it = argument->consume(it, end, arg_map_it2->first);
2121 } else {
2122 unknown_arguments.push_back(current_argument);
2123 break;
2124 }
2125 }
2126 } else {
2127
2128
2129 unknown_arguments.push_back(current_argument);
2130 ++it;
2131 }
2132 }
2133 m_is_parsed = true;
2134 return unknown_arguments;
2135 }
2136
2137
2138 std::size_t get_length_of_longest_argument() const {
2139 if (m_argument_map.empty()) {
2140 return 0;
2141 }
2142 std::size_t max_size = 0;
2143 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
2144 max_size =
2145 std::max<std::size_t>(max_size, argument->get_arguments_length());
2146 }
2147 for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
2148 max_size = std::max<std::size_t>(max_size, command.size());
2149 }
2150 return max_size;
2151 }
2152
2153 using argument_it = std::list<Argument>::iterator;
2154 using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
2155 using argument_parser_it =
2156 std::list<std::reference_wrapper<ArgumentParser>>::iterator;
2157
2158 void index_argument(argument_it it) {
2159 for (const auto &name : std::as_const(it->m_names)) {
2160 m_argument_map.insert_or_assign(name, it);
2161 }
2162 }
2163
2164 std::string m_program_name;
2165 std::string m_version;
2166 std::string m_description;
2167 std::string m_epilog;
2168 bool m_exit_on_default_arguments = true;
2169 std::string m_prefix_chars{"-"};
2170 std::string m_assign_chars{"="};
2171 bool m_is_parsed = false;
2172 std::list<Argument> m_positional_arguments;
2173 std::list<Argument> m_optional_arguments;
2174 std::map<std::string_view, argument_it> m_argument_map;
2175 std::string m_parser_path;
2176 std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
2177 std::map<std::string_view, argument_parser_it> m_subparser_map;
2178 std::map<std::string_view, bool> m_subparser_used;
2179 std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
2180 bool m_suppress = false;
2181 };
2182
2183 }