visualizations

Programmatic visualizations
git clone git://git.laack.co/visualizations.git
Log | Files | Refs | README

argparse.hpp (85932B)


      1 /*
      2   __ _ _ __ __ _ _ __   __ _ _ __ ___  ___
      3  / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++
      4 | (_| | | | (_| | |_) | (_| | |  \__ \  __/ http://github.com/p-ranav/argparse
      5  \__,_|_|  \__, | .__/ \__,_|_|  |___/\___|
      6            |___/|_|
      7 
      8 Licensed under the MIT License <http://opensource.org/licenses/MIT>.
      9 SPDX-License-Identifier: MIT
     10 Copyright (c) 2019-2022 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
     11 and other contributors.
     12 
     13 Permission is hereby  granted, free of charge, to any  person obtaining a copy
     14 of this software and associated  documentation files (the "Software"), to deal
     15 in the Software  without restriction, including without  limitation the rights
     16 to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
     17 copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
     18 furnished to do so, subject to the following conditions:
     19 
     20 The above copyright notice and this permission notice shall be included in all
     21 copies or substantial portions of the Software.
     22 
     23 THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
     24 IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
     25 FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
     26 AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
     27 LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     28 OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
     29 SOFTWARE.
     30 */
     31 #pragma once
     32 
     33 #include <cerrno>
     34 
     35 #ifndef ARGPARSE_MODULE_USE_STD_MODULE
     36 #include <algorithm>
     37 #include <any>
     38 #include <array>
     39 #include <set>
     40 #include <charconv>
     41 #include <cstdlib>
     42 #include <functional>
     43 #include <iomanip>
     44 #include <iostream>
     45 #include <iterator>
     46 #include <limits>
     47 #include <list>
     48 #include <map>
     49 #include <numeric>
     50 #include <optional>
     51 #include <sstream>
     52 #include <stdexcept>
     53 #include <string>
     54 #include <string_view>
     55 #include <tuple>
     56 #include <type_traits>
     57 #include <utility>
     58 #include <variant>
     59 #include <vector>
     60 #include <filesystem>
     61 #endif
     62 
     63 #ifndef ARGPARSE_CUSTOM_STRTOF
     64 #define ARGPARSE_CUSTOM_STRTOF strtof
     65 #endif
     66 
     67 #ifndef ARGPARSE_CUSTOM_STRTOD
     68 #define ARGPARSE_CUSTOM_STRTOD strtod
     69 #endif
     70 
     71 #ifndef ARGPARSE_CUSTOM_STRTOLD
     72 #define ARGPARSE_CUSTOM_STRTOLD strtold
     73 #endif
     74 
     75 namespace argparse {
     76 
     77 namespace details { // namespace for helper methods
     78 
     79 template <typename T, typename = void>
     80 struct HasContainerTraits : std::false_type {};
     81 
     82 template <> struct HasContainerTraits<std::string> : std::false_type {};
     83 
     84 template <> struct HasContainerTraits<std::string_view> : std::false_type {};
     85 
     86 template <typename T>
     87 struct HasContainerTraits<
     88     T, std::void_t<typename T::value_type, decltype(std::declval<T>().begin()),
     89                    decltype(std::declval<T>().end()),
     90                    decltype(std::declval<T>().size())>> : std::true_type {};
     91 
     92 template <typename T>
     93 inline constexpr bool IsContainer = HasContainerTraits<T>::value;
     94 
     95 template <typename T, typename = void>
     96 struct HasStreamableTraits : std::false_type {};
     97 
     98 template <typename T>
     99 struct HasStreamableTraits<
    100     T,
    101     std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
    102     : std::true_type {};
    103 
    104 template <typename T>
    105 inline constexpr bool IsStreamable = HasStreamableTraits<T>::value;
    106 
    107 constexpr std::size_t repr_max_container_size = 5;
    108 
    109 template <typename T> std::string repr(T const &val) {
    110   if constexpr (std::is_same_v<T, bool>) {
    111     return val ? "true" : "false";
    112   } else if constexpr (std::is_convertible_v<T, std::string_view>) {
    113     return '"' + std::string{std::string_view{val}} + '"';
    114   } else if constexpr (IsContainer<T>) {
    115     std::stringstream out;
    116     out << "{";
    117     const auto size = val.size();
    118     if (size > 1) {
    119       out << repr(*val.begin());
    120       std::for_each(
    121           std::next(val.begin()),
    122           std::next(
    123               val.begin(),
    124               static_cast<typename T::iterator::difference_type>(
    125                   std::min<std::size_t>(size, repr_max_container_size) - 1)),
    126           [&out](const auto &v) { out << " " << repr(v); });
    127       if (size <= repr_max_container_size) {
    128         out << " ";
    129       } else {
    130         out << "...";
    131       }
    132     }
    133     if (size > 0) {
    134       out << repr(*std::prev(val.end()));
    135     }
    136     out << "}";
    137     return out.str();
    138   } else if constexpr (IsStreamable<T>) {
    139     std::stringstream out;
    140     out << val;
    141     return out.str();
    142   } else {
    143     return "<not representable>";
    144   }
    145 }
    146 
    147 namespace {
    148 
    149 template <typename T> constexpr bool standard_signed_integer = false;
    150 template <> constexpr bool standard_signed_integer<signed char> = true;
    151 template <> constexpr bool standard_signed_integer<short int> = true;
    152 template <> constexpr bool standard_signed_integer<int> = true;
    153 template <> constexpr bool standard_signed_integer<long int> = true;
    154 template <> constexpr bool standard_signed_integer<long long int> = true;
    155 
    156 template <typename T> constexpr bool standard_unsigned_integer = false;
    157 template <> constexpr bool standard_unsigned_integer<unsigned char> = true;
    158 template <> constexpr bool standard_unsigned_integer<unsigned short int> = true;
    159 template <> constexpr bool standard_unsigned_integer<unsigned int> = true;
    160 template <> constexpr bool standard_unsigned_integer<unsigned long int> = true;
    161 template <>
    162 constexpr bool standard_unsigned_integer<unsigned long long int> = true;
    163 
    164 } // namespace
    165 
    166 constexpr int radix_2 = 2;
    167 constexpr int radix_8 = 8;
    168 constexpr int radix_10 = 10;
    169 constexpr int radix_16 = 16;
    170 
    171 template <typename T>
    172 constexpr bool standard_integer =
    173     standard_signed_integer<T> || standard_unsigned_integer<T>;
    174 
    175 template <class F, class Tuple, class Extra, std::size_t... I>
    176 constexpr decltype(auto)
    177 apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x,
    178                     std::index_sequence<I...> /*unused*/) {
    179   return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...,
    180                      std::forward<Extra>(x));
    181 }
    182 
    183 template <class F, class Tuple, class Extra>
    184 constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) {
    185   return details::apply_plus_one_impl(
    186       std::forward<F>(f), std::forward<Tuple>(t), std::forward<Extra>(x),
    187       std::make_index_sequence<
    188           std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    189 }
    190 
    191 constexpr auto pointer_range(std::string_view s) noexcept {
    192   return std::tuple(s.data(), s.data() + s.size());
    193 }
    194 
    195 template <class CharT, class Traits>
    196 constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
    197                            std::basic_string_view<CharT, Traits> s) noexcept {
    198   return s.substr(0, prefix.size()) == prefix;
    199 }
    200 
    201 enum class chars_format {
    202   scientific = 0xf1,
    203   fixed = 0xf2,
    204   hex = 0xf4,
    205   binary = 0xf8,
    206   general = fixed | scientific
    207 };
    208 
    209 struct ConsumeBinaryPrefixResult {
    210   bool is_binary;
    211   std::string_view rest;
    212 };
    213 
    214 constexpr auto consume_binary_prefix(std::string_view s)
    215     -> ConsumeBinaryPrefixResult {
    216   if (starts_with(std::string_view{"0b"}, s) ||
    217       starts_with(std::string_view{"0B"}, s)) {
    218     s.remove_prefix(2);
    219     return {true, s};
    220   }
    221   return {false, s};
    222 }
    223 
    224 struct ConsumeHexPrefixResult {
    225   bool is_hexadecimal;
    226   std::string_view rest;
    227 };
    228 
    229 using namespace std::literals;
    230 
    231 constexpr auto consume_hex_prefix(std::string_view s)
    232     -> ConsumeHexPrefixResult {
    233   if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
    234     s.remove_prefix(2);
    235     return {true, s};
    236   }
    237   return {false, s};
    238 }
    239 
    240 template <class T, auto Param>
    241 inline auto do_from_chars(std::string_view s) -> T {
    242   T x{0};
    243   auto [first, last] = pointer_range(s);
    244   auto [ptr, ec] = std::from_chars(first, last, x, Param);
    245   if (ec == std::errc()) {
    246     if (ptr == last) {
    247       return x;
    248     }
    249     throw std::invalid_argument{"pattern '" + std::string(s) +
    250                                 "' does not match to the end"};
    251   }
    252   if (ec == std::errc::invalid_argument) {
    253     throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"};
    254   }
    255   if (ec == std::errc::result_out_of_range) {
    256     throw std::range_error{"'" + std::string(s) + "' not representable"};
    257   }
    258   return x; // unreachable
    259 }
    260 
    261 template <class T, auto Param = 0> struct parse_number {
    262   auto operator()(std::string_view s) -> T {
    263     return do_from_chars<T, Param>(s);
    264   }
    265 };
    266 
    267 template <class T> struct parse_number<T, radix_2> {
    268   auto operator()(std::string_view s) -> T {
    269     if (auto [ok, rest] = consume_binary_prefix(s); ok) {
    270       return do_from_chars<T, radix_2>(rest);
    271     }
    272     throw std::invalid_argument{"pattern not found"};
    273   }
    274 };
    275 
    276 template <class T> struct parse_number<T, radix_16> {
    277   auto operator()(std::string_view s) -> T {
    278     if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
    279       if (auto [ok, rest] = consume_hex_prefix(s); ok) {
    280         try {
    281           return do_from_chars<T, radix_16>(rest);
    282         } catch (const std::invalid_argument &err) {
    283           throw std::invalid_argument("Failed to parse '" + std::string(s) +
    284                                       "' as hexadecimal: " + err.what());
    285         } catch (const std::range_error &err) {
    286           throw std::range_error("Failed to parse '" + std::string(s) +
    287                                  "' as hexadecimal: " + err.what());
    288         }
    289       }
    290     } else {
    291       // Allow passing hex numbers without prefix
    292       // Shape 'x' already has to be specified
    293       try {
    294         return do_from_chars<T, radix_16>(s);
    295       } catch (const std::invalid_argument &err) {
    296         throw std::invalid_argument("Failed to parse '" + std::string(s) +
    297                                     "' as hexadecimal: " + err.what());
    298       } catch (const std::range_error &err) {
    299         throw std::range_error("Failed to parse '" + std::string(s) +
    300                                "' as hexadecimal: " + err.what());
    301       }
    302     }
    303 
    304     throw std::invalid_argument{"pattern '" + std::string(s) +
    305                                 "' not identified as hexadecimal"};
    306   }
    307 };
    308 
    309 template <class T> struct parse_number<T> {
    310   auto operator()(std::string_view s) -> T {
    311     auto [ok, rest] = consume_hex_prefix(s);
    312     if (ok) {
    313       try {
    314         return do_from_chars<T, radix_16>(rest);
    315       } catch (const std::invalid_argument &err) {
    316         throw std::invalid_argument("Failed to parse '" + std::string(s) +
    317                                     "' as hexadecimal: " + err.what());
    318       } catch (const std::range_error &err) {
    319         throw std::range_error("Failed to parse '" + std::string(s) +
    320                                "' as hexadecimal: " + err.what());
    321       }
    322     }
    323 
    324     auto [ok_binary, rest_binary] = consume_binary_prefix(s);
    325     if (ok_binary) {
    326       try {
    327         return do_from_chars<T, radix_2>(rest_binary);
    328       } catch (const std::invalid_argument &err) {
    329         throw std::invalid_argument("Failed to parse '" + std::string(s) +
    330                                     "' as binary: " + err.what());
    331       } catch (const std::range_error &err) {
    332         throw std::range_error("Failed to parse '" + std::string(s) +
    333                                "' as binary: " + err.what());
    334       }
    335     }
    336 
    337     if (starts_with("0"sv, s)) {
    338       try {
    339         return do_from_chars<T, radix_8>(rest);
    340       } catch (const std::invalid_argument &err) {
    341         throw std::invalid_argument("Failed to parse '" + std::string(s) +
    342                                     "' as octal: " + err.what());
    343       } catch (const std::range_error &err) {
    344         throw std::range_error("Failed to parse '" + std::string(s) +
    345                                "' as octal: " + err.what());
    346       }
    347     }
    348 
    349     try {
    350       return do_from_chars<T, radix_10>(rest);
    351     } catch (const std::invalid_argument &err) {
    352       throw std::invalid_argument("Failed to parse '" + std::string(s) +
    353                                   "' as decimal integer: " + err.what());
    354     } catch (const std::range_error &err) {
    355       throw std::range_error("Failed to parse '" + std::string(s) +
    356                              "' as decimal integer: " + err.what());
    357     }
    358   }
    359 };
    360 
    361 namespace {
    362 
    363 template <class T> inline const auto generic_strtod = nullptr;
    364 template <> inline const auto generic_strtod<float> = ARGPARSE_CUSTOM_STRTOF;
    365 template <> inline const auto generic_strtod<double> = ARGPARSE_CUSTOM_STRTOD;
    366 template <>
    367 inline const auto generic_strtod<long double> = ARGPARSE_CUSTOM_STRTOLD;
    368 
    369 } // namespace
    370 
    371 template <class T> inline auto do_strtod(std::string const &s) -> T {
    372   if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
    373     throw std::invalid_argument{"pattern '" + s + "' not found"};
    374   }
    375 
    376   auto [first, last] = pointer_range(s);
    377   char *ptr;
    378 
    379   errno = 0;
    380   auto x = generic_strtod<T>(first, &ptr);
    381   if (errno == 0) {
    382     if (ptr == last) {
    383       return x;
    384     }
    385     throw std::invalid_argument{"pattern '" + s +
    386                                 "' does not match to the end"};
    387   }
    388   if (errno == ERANGE) {
    389     throw std::range_error{"'" + s + "' not representable"};
    390   }
    391   return x; // unreachable
    392 }
    393 
    394 template <class T> struct parse_number<T, chars_format::general> {
    395   auto operator()(std::string const &s) -> T {
    396     if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
    397       throw std::invalid_argument{
    398           "chars_format::general does not parse hexfloat"};
    399     }
    400     if (auto r = consume_binary_prefix(s); r.is_binary) {
    401       throw std::invalid_argument{
    402           "chars_format::general does not parse binfloat"};
    403     }
    404 
    405     try {
    406       return do_strtod<T>(s);
    407     } catch (const std::invalid_argument &err) {
    408       throw std::invalid_argument("Failed to parse '" + s +
    409                                   "' as number: " + err.what());
    410     } catch (const std::range_error &err) {
    411       throw std::range_error("Failed to parse '" + s +
    412                              "' as number: " + err.what());
    413     }
    414   }
    415 };
    416 
    417 template <class T> struct parse_number<T, chars_format::hex> {
    418   auto operator()(std::string const &s) -> T {
    419     if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
    420       throw std::invalid_argument{"chars_format::hex parses hexfloat"};
    421     }
    422     if (auto r = consume_binary_prefix(s); r.is_binary) {
    423       throw std::invalid_argument{"chars_format::hex does not parse binfloat"};
    424     }
    425 
    426     try {
    427       return do_strtod<T>(s);
    428     } catch (const std::invalid_argument &err) {
    429       throw std::invalid_argument("Failed to parse '" + s +
    430                                   "' as hexadecimal: " + err.what());
    431     } catch (const std::range_error &err) {
    432       throw std::range_error("Failed to parse '" + s +
    433                              "' as hexadecimal: " + err.what());
    434     }
    435   }
    436 };
    437 
    438 template <class T> struct parse_number<T, chars_format::binary> {
    439   auto operator()(std::string const &s) -> T {
    440     if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
    441       throw std::invalid_argument{
    442           "chars_format::binary does not parse hexfloat"};
    443     }
    444     if (auto r = consume_binary_prefix(s); !r.is_binary) {
    445       throw std::invalid_argument{"chars_format::binary parses binfloat"};
    446     }
    447 
    448     return do_strtod<T>(s);
    449   }
    450 };
    451 
    452 template <class T> struct parse_number<T, chars_format::scientific> {
    453   auto operator()(std::string const &s) -> T {
    454     if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
    455       throw std::invalid_argument{
    456           "chars_format::scientific does not parse hexfloat"};
    457     }
    458     if (auto r = consume_binary_prefix(s); r.is_binary) {
    459       throw std::invalid_argument{
    460           "chars_format::scientific does not parse binfloat"};
    461     }
    462     if (s.find_first_of("eE") == std::string::npos) {
    463       throw std::invalid_argument{
    464           "chars_format::scientific requires exponent part"};
    465     }
    466 
    467     try {
    468       return do_strtod<T>(s);
    469     } catch (const std::invalid_argument &err) {
    470       throw std::invalid_argument("Failed to parse '" + s +
    471                                   "' as scientific notation: " + err.what());
    472     } catch (const std::range_error &err) {
    473       throw std::range_error("Failed to parse '" + s +
    474                              "' as scientific notation: " + err.what());
    475     }
    476   }
    477 };
    478 
    479 template <class T> struct parse_number<T, chars_format::fixed> {
    480   auto operator()(std::string const &s) -> T {
    481     if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
    482       throw std::invalid_argument{
    483           "chars_format::fixed does not parse hexfloat"};
    484     }
    485     if (auto r = consume_binary_prefix(s); r.is_binary) {
    486       throw std::invalid_argument{
    487           "chars_format::fixed does not parse binfloat"};
    488     }
    489     if (s.find_first_of("eE") != std::string::npos) {
    490       throw std::invalid_argument{
    491           "chars_format::fixed does not parse exponent part"};
    492     }
    493 
    494     try {
    495       return do_strtod<T>(s);
    496     } catch (const std::invalid_argument &err) {
    497       throw std::invalid_argument("Failed to parse '" + s +
    498                                   "' as fixed notation: " + err.what());
    499     } catch (const std::range_error &err) {
    500       throw std::range_error("Failed to parse '" + s +
    501                              "' as fixed notation: " + err.what());
    502     }
    503   }
    504 };
    505 
    506 template <typename StrIt>
    507 std::string join(StrIt first, StrIt last, const std::string &separator) {
    508   if (first == last) {
    509     return "";
    510   }
    511   std::stringstream value;
    512   value << *first;
    513   ++first;
    514   while (first != last) {
    515     value << separator << *first;
    516     ++first;
    517   }
    518   return value.str();
    519 }
    520 
    521 template <typename T> struct can_invoke_to_string {
    522   template <typename U>
    523   static auto test(int)
    524       -> decltype(std::to_string(std::declval<U>()), std::true_type{});
    525 
    526   template <typename U> static auto test(...) -> std::false_type;
    527 
    528   static constexpr bool value = decltype(test<T>(0))::value;
    529 };
    530 
    531 template <typename T> struct IsChoiceTypeSupported {
    532   using CleanType = typename std::decay<T>::type;
    533   static const bool value = std::is_integral<CleanType>::value ||
    534                             std::is_same<CleanType, std::string>::value ||
    535                             std::is_same<CleanType, std::string_view>::value ||
    536                             std::is_same<CleanType, const char *>::value;
    537 };
    538 
    539 template <typename StringType>
    540 std::size_t get_levenshtein_distance(const StringType &s1,
    541                                      const StringType &s2) {
    542   std::vector<std::vector<std::size_t>> dp(
    543       s1.size() + 1, std::vector<std::size_t>(s2.size() + 1, 0));
    544 
    545   for (std::size_t i = 0; i <= s1.size(); ++i) {
    546     for (std::size_t j = 0; j <= s2.size(); ++j) {
    547       if (i == 0) {
    548         dp[i][j] = j;
    549       } else if (j == 0) {
    550         dp[i][j] = i;
    551       } else if (s1[i - 1] == s2[j - 1]) {
    552         dp[i][j] = dp[i - 1][j - 1];
    553       } else {
    554         dp[i][j] = 1 + std::min<std::size_t>({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
    555       }
    556     }
    557   }
    558 
    559   return dp[s1.size()][s2.size()];
    560 }
    561 
    562 template <typename ValueType>
    563 std::string get_most_similar_string(const std::map<std::string, ValueType> &map,
    564                                     const std::string &input) {
    565   std::string most_similar{};
    566   std::size_t min_distance = (std::numeric_limits<std::size_t>::max)();
    567 
    568   for (const auto &entry : map) {
    569     std::size_t distance = get_levenshtein_distance(entry.first, input);
    570     if (distance < min_distance) {
    571       min_distance = distance;
    572       most_similar = entry.first;
    573     }
    574   }
    575 
    576   return most_similar;
    577 }
    578 
    579 } // namespace details
    580 
    581 enum class nargs_pattern { optional, any, at_least_one };
    582 
    583 enum class default_arguments : unsigned int {
    584   none = 0,
    585   help = 1,
    586   version = 2,
    587   all = help | version,
    588 };
    589 
    590 inline default_arguments operator&(const default_arguments &a,
    591                                    const default_arguments &b) {
    592   return static_cast<default_arguments>(
    593       static_cast<std::underlying_type<default_arguments>::type>(a) &
    594       static_cast<std::underlying_type<default_arguments>::type>(b));
    595 }
    596 
    597 class ArgumentParser;
    598 
    599 class Argument {
    600   friend class ArgumentParser;
    601   friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
    602       -> std::ostream &;
    603 
    604   template <std::size_t N, std::size_t... I>
    605   explicit Argument(std::string_view prefix_chars,
    606                     std::array<std::string_view, N> &&a,
    607                     std::index_sequence<I...> /*unused*/)
    608       : m_accepts_optional_like_value(false),
    609         m_is_optional((is_optional(a[I], prefix_chars) || ...)),
    610         m_is_required(false), m_is_repeatable(false), m_is_used(false),
    611         m_is_hidden(false), m_prefix_chars(prefix_chars) {
    612     ((void)m_names.emplace_back(a[I]), ...);
    613     std::sort(
    614         m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
    615           return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
    616         });
    617   }
    618 
    619 public:
    620   template <std::size_t N>
    621   explicit Argument(std::string_view prefix_chars,
    622                     std::array<std::string_view, N> &&a)
    623       : Argument(prefix_chars, std::move(a), std::make_index_sequence<N>{}) {}
    624 
    625   Argument &help(std::string help_text) {
    626     m_help = std::move(help_text);
    627     return *this;
    628   }
    629 
    630   Argument &metavar(std::string metavar) {
    631     m_metavar = std::move(metavar);
    632     return *this;
    633   }
    634 
    635   template <typename T> Argument &default_value(T &&value) {
    636     m_num_args_range = NArgsRange{0, m_num_args_range.get_max()};
    637     m_default_value_repr = details::repr(value);
    638 
    639     if constexpr (std::is_convertible_v<T, std::string_view>) {
    640       m_default_value_str = std::string{std::string_view{value}};
    641     } else if constexpr (details::can_invoke_to_string<T>::value) {
    642       m_default_value_str = std::to_string(value);
    643     }
    644 
    645     m_default_value = std::forward<T>(value);
    646     return *this;
    647   }
    648 
    649   Argument &default_value(const char *value) {
    650     return default_value(std::string(value));
    651   }
    652 
    653   Argument &required() {
    654     m_is_required = true;
    655     return *this;
    656   }
    657 
    658   Argument &implicit_value(std::any value) {
    659     m_implicit_value = std::move(value);
    660     m_num_args_range = NArgsRange{0, 0};
    661     return *this;
    662   }
    663 
    664   // This is shorthand for:
    665   //   program.add_argument("foo")
    666   //     .default_value(false)
    667   //     .implicit_value(true)
    668   Argument &flag() {
    669     default_value(false);
    670     implicit_value(true);
    671     return *this;
    672   }
    673 
    674   template <class F, class... Args>
    675   auto action(F &&callable, Args &&... bound_args)
    676       -> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
    677                           Argument &> {
    678     using action_type = std::conditional_t<
    679         std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
    680         void_action, valued_action>;
    681     if constexpr (sizeof...(Args) == 0) {
    682       m_actions.emplace_back<action_type>(std::forward<F>(callable));
    683     } else {
    684       m_actions.emplace_back<action_type>(
    685           [f = std::forward<F>(callable),
    686            tup = std::make_tuple(std::forward<Args>(bound_args)...)](
    687               std::string const &opt) mutable {
    688             return details::apply_plus_one(f, tup, opt);
    689           });
    690     }
    691     return *this;
    692   }
    693 
    694   auto &store_into(bool &var) {
    695     if ((!m_default_value.has_value()) && (!m_implicit_value.has_value())) {
    696       flag();
    697     }
    698     if (m_default_value.has_value()) {
    699       var = std::any_cast<bool>(m_default_value);
    700     }
    701     action([&var](const auto & /*unused*/) {
    702       var = true;
    703       return var;
    704     });
    705     return *this;
    706   }
    707 
    708   template <typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
    709   auto &store_into(T &var) {
    710     if (m_default_value.has_value()) {
    711       var = std::any_cast<T>(m_default_value);
    712     }
    713     action([&var](const auto &s) {
    714       var = details::parse_number<T, details::radix_10>()(s);
    715       return var;
    716     });
    717     return *this;
    718   }
    719 
    720   template <typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
    721   auto &store_into(T &var) {
    722     if (m_default_value.has_value()) {
    723       var = std::any_cast<T>(m_default_value);
    724     }
    725     action([&var](const auto &s) {
    726       var = details::parse_number<T, details::chars_format::general>()(s);
    727       return var;
    728     });
    729     return *this;
    730   }
    731 
    732   auto &store_into(std::string &var) {
    733     if (m_default_value.has_value()) {
    734       var = std::any_cast<std::string>(m_default_value);
    735     }
    736     action([&var](const std::string &s) {
    737       var = s;
    738       return var;
    739     });
    740     return *this;
    741   }
    742 
    743   auto &store_into(std::filesystem::path &var) {
    744     if (m_default_value.has_value()) {
    745       var = std::any_cast<std::filesystem::path>(m_default_value);
    746     }
    747     action([&var](const std::string &s) { var = s; });
    748     return *this;
    749   }
    750 
    751   auto &store_into(std::vector<std::string> &var) {
    752     if (m_default_value.has_value()) {
    753       var = std::any_cast<std::vector<std::string>>(m_default_value);
    754     }
    755     action([this, &var](const std::string &s) {
    756       if (!m_is_used) {
    757         var.clear();
    758       }
    759       m_is_used = true;
    760       var.push_back(s);
    761       return var;
    762     });
    763     return *this;
    764   }
    765 
    766   auto &store_into(std::vector<int> &var) {
    767     if (m_default_value.has_value()) {
    768       var = std::any_cast<std::vector<int>>(m_default_value);
    769     }
    770     action([this, &var](const std::string &s) {
    771       if (!m_is_used) {
    772         var.clear();
    773       }
    774       m_is_used = true;
    775       var.push_back(details::parse_number<int, details::radix_10>()(s));
    776       return var;
    777     });
    778     return *this;
    779   }
    780 
    781   auto &store_into(std::set<std::string> &var) {
    782     if (m_default_value.has_value()) {
    783       var = std::any_cast<std::set<std::string>>(m_default_value);
    784     }
    785     action([this, &var](const std::string &s) {
    786       if (!m_is_used) {
    787         var.clear();
    788       }
    789       m_is_used = true;
    790       var.insert(s);
    791       return var;
    792     });
    793     return *this;
    794   }
    795 
    796   auto &store_into(std::set<int> &var) {
    797     if (m_default_value.has_value()) {
    798       var = std::any_cast<std::set<int>>(m_default_value);
    799     }
    800     action([this, &var](const std::string &s) {
    801       if (!m_is_used) {
    802         var.clear();
    803       }
    804       m_is_used = true;
    805       var.insert(details::parse_number<int, details::radix_10>()(s));
    806       return var;
    807     });
    808     return *this;
    809   }
    810 
    811   auto &append() {
    812     m_is_repeatable = true;
    813     return *this;
    814   }
    815 
    816   // Cause the argument to be invisible in usage and help
    817   auto &hidden() {
    818     m_is_hidden = true;
    819     return *this;
    820   }
    821 
    822   template <char Shape, typename T>
    823   auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
    824     static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
    825                   "T should not be cv-qualified");
    826     auto is_one_of = [](char c, auto... x) constexpr {
    827       return ((c == x) || ...);
    828     };
    829 
    830     if constexpr (is_one_of(Shape, 'd') && details::standard_integer<T>) {
    831       action(details::parse_number<T, details::radix_10>());
    832     } else if constexpr (is_one_of(Shape, 'i') &&
    833                          details::standard_integer<T>) {
    834       action(details::parse_number<T>());
    835     } else if constexpr (is_one_of(Shape, 'u') &&
    836                          details::standard_unsigned_integer<T>) {
    837       action(details::parse_number<T, details::radix_10>());
    838     } else if constexpr (is_one_of(Shape, 'b') &&
    839                          details::standard_unsigned_integer<T>) {
    840       action(details::parse_number<T, details::radix_2>());
    841     } else if constexpr (is_one_of(Shape, 'o') &&
    842                          details::standard_unsigned_integer<T>) {
    843       action(details::parse_number<T, details::radix_8>());
    844     } else if constexpr (is_one_of(Shape, 'x', 'X') &&
    845                          details::standard_unsigned_integer<T>) {
    846       action(details::parse_number<T, details::radix_16>());
    847     } else if constexpr (is_one_of(Shape, 'a', 'A') &&
    848                          std::is_floating_point_v<T>) {
    849       action(details::parse_number<T, details::chars_format::hex>());
    850     } else if constexpr (is_one_of(Shape, 'e', 'E') &&
    851                          std::is_floating_point_v<T>) {
    852       action(details::parse_number<T, details::chars_format::scientific>());
    853     } else if constexpr (is_one_of(Shape, 'f', 'F') &&
    854                          std::is_floating_point_v<T>) {
    855       action(details::parse_number<T, details::chars_format::fixed>());
    856     } else if constexpr (is_one_of(Shape, 'g', 'G') &&
    857                          std::is_floating_point_v<T>) {
    858       action(details::parse_number<T, details::chars_format::general>());
    859     } else {
    860       static_assert(alignof(T) == 0, "No scan specification for T");
    861     }
    862 
    863     return *this;
    864   }
    865 
    866   Argument &nargs(std::size_t num_args) {
    867     m_num_args_range = NArgsRange{num_args, num_args};
    868     return *this;
    869   }
    870 
    871   Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
    872     m_num_args_range = NArgsRange{num_args_min, num_args_max};
    873     return *this;
    874   }
    875 
    876   Argument &nargs(nargs_pattern pattern) {
    877     switch (pattern) {
    878     case nargs_pattern::optional:
    879       m_num_args_range = NArgsRange{0, 1};
    880       break;
    881     case nargs_pattern::any:
    882       m_num_args_range =
    883           NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
    884       break;
    885     case nargs_pattern::at_least_one:
    886       m_num_args_range =
    887           NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
    888       break;
    889     }
    890     return *this;
    891   }
    892 
    893   Argument &remaining() {
    894     m_accepts_optional_like_value = true;
    895     return nargs(nargs_pattern::any);
    896   }
    897 
    898   template <typename T> void add_choice(T &&choice) {
    899     static_assert(details::IsChoiceTypeSupported<T>::value,
    900                   "Only string or integer type supported for choice");
    901     static_assert(std::is_convertible_v<T, std::string_view> ||
    902                       details::can_invoke_to_string<T>::value,
    903                   "Choice is not convertible to string_type");
    904     if (!m_choices.has_value()) {
    905       m_choices = std::vector<std::string>{};
    906     }
    907 
    908     if constexpr (std::is_convertible_v<T, std::string_view>) {
    909       m_choices.value().push_back(
    910           std::string{std::string_view{std::forward<T>(choice)}});
    911     } else if constexpr (details::can_invoke_to_string<T>::value) {
    912       m_choices.value().push_back(std::to_string(std::forward<T>(choice)));
    913     }
    914   }
    915 
    916   Argument &choices() {
    917     if (!m_choices.has_value()) {
    918       throw std::runtime_error("Zero choices provided");
    919     }
    920     return *this;
    921   }
    922 
    923   template <typename T, typename... U>
    924   Argument &choices(T &&first, U &&... rest) {
    925     add_choice(std::forward<T>(first));
    926     choices(std::forward<U>(rest)...);
    927     return *this;
    928   }
    929 
    930   void find_default_value_in_choices_or_throw() const {
    931 
    932     const auto &choices = m_choices.value();
    933 
    934     if (m_default_value.has_value()) {
    935       if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
    936           choices.end()) {
    937         // provided arg not in list of allowed choices
    938         // report error
    939 
    940         std::string choices_as_csv =
    941             std::accumulate(choices.begin(), choices.end(), std::string(),
    942                             [](const std::string &a, const std::string &b) {
    943                               return a + (a.empty() ? "" : ", ") + b;
    944                             });
    945 
    946         throw std::runtime_error(
    947             std::string{"Invalid default value "} + m_default_value_repr +
    948             " - allowed options: {" + choices_as_csv + "}");
    949       }
    950     }
    951   }
    952 
    953   template <typename Iterator>
    954   bool is_value_in_choices(Iterator option_it) const {
    955 
    956     const auto &choices = m_choices.value();
    957 
    958     return (std::find(choices.begin(), choices.end(), *option_it) !=
    959             choices.end());
    960   }
    961 
    962   template <typename Iterator>
    963   void throw_invalid_arguments_error(Iterator option_it) const {
    964     const auto &choices = m_choices.value();
    965     const std::string choices_as_csv = std::accumulate(
    966         choices.begin(), choices.end(), std::string(),
    967         [](const std::string &option_a, const std::string &option_b) {
    968           return option_a + (option_a.empty() ? "" : ", ") + option_b;
    969         });
    970 
    971     throw std::runtime_error(std::string{"Invalid argument "} +
    972                              details::repr(*option_it) +
    973                              " - allowed options: {" + choices_as_csv + "}");
    974   }
    975 
    976   /* The dry_run parameter can be set to true to avoid running the actions,
    977    * and setting m_is_used. This may be used by a pre-processing step to do
    978    * a first iteration over arguments.
    979    */
    980   template <typename Iterator>
    981   Iterator consume(Iterator start, Iterator end,
    982                    std::string_view used_name = {}, bool dry_run = false) {
    983     if (!m_is_repeatable && m_is_used) {
    984       throw std::runtime_error(
    985           std::string("Duplicate argument ").append(used_name));
    986     }
    987     m_used_name = used_name;
    988 
    989     std::size_t passed_options = 0;
    990 
    991     if (m_choices.has_value()) {
    992       // Check each value in (start, end) and make sure
    993       // it is in the list of allowed choices/options
    994       const auto max_number_of_args = m_num_args_range.get_max();
    995       const auto min_number_of_args = m_num_args_range.get_min();
    996       for (auto it = start; it != end; ++it) {
    997         if (is_value_in_choices(it)) {
    998           passed_options += 1;
    999           continue;
   1000         }
   1001 
   1002         if ((passed_options >= min_number_of_args) &&
   1003             (passed_options <= max_number_of_args)) {
   1004           break;
   1005         }
   1006 
   1007         throw_invalid_arguments_error(it);
   1008       }
   1009     }
   1010 
   1011     const auto num_args_max =
   1012         (m_choices.has_value()) ? passed_options : m_num_args_range.get_max();
   1013     const auto num_args_min = m_num_args_range.get_min();
   1014     std::size_t dist = 0;
   1015     if (num_args_max == 0) {
   1016       if (!dry_run) {
   1017         m_values.emplace_back(m_implicit_value);
   1018         for(auto &action: m_actions) {
   1019           std::visit([&](const auto &f) { f({}); }, action);
   1020         }
   1021         if(m_actions.empty()){
   1022           std::visit([&](const auto &f) { f({}); }, m_default_action);
   1023         }
   1024         m_is_used = true;
   1025       }
   1026       return start;
   1027     }
   1028     if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
   1029         num_args_min) {
   1030       if (num_args_max < dist) {
   1031         end = std::next(start, static_cast<typename Iterator::difference_type>(
   1032                                    num_args_max));
   1033       }
   1034       if (!m_accepts_optional_like_value) {
   1035         end = std::find_if(
   1036             start, end,
   1037             std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
   1038         dist = static_cast<std::size_t>(std::distance(start, end));
   1039         if (dist < num_args_min) {
   1040           throw std::runtime_error("Too few arguments for '" +
   1041                                    std::string(m_used_name) + "'.");
   1042         }
   1043       }
   1044       struct ActionApply {
   1045         void operator()(valued_action &f) {
   1046           std::transform(first, last, std::back_inserter(self.m_values), f);
   1047         }
   1048 
   1049         void operator()(void_action &f) {
   1050           std::for_each(first, last, f);
   1051           if (!self.m_default_value.has_value()) {
   1052             if (!self.m_accepts_optional_like_value) {
   1053               self.m_values.resize(
   1054                   static_cast<std::size_t>(std::distance(first, last)));
   1055             }
   1056           }
   1057         }
   1058 
   1059         Iterator first, last;
   1060         Argument &self;
   1061       };
   1062       if (!dry_run) {
   1063         for(auto &action: m_actions) {
   1064           std::visit(ActionApply{start, end, *this}, action);
   1065         }
   1066         if(m_actions.empty()){
   1067           std::visit(ActionApply{start, end, *this}, m_default_action);
   1068         }
   1069         m_is_used = true;
   1070       }
   1071       return end;
   1072     }
   1073     if (m_default_value.has_value()) {
   1074       if (!dry_run) {
   1075         m_is_used = true;
   1076       }
   1077       return start;
   1078     }
   1079     throw std::runtime_error("Too few arguments for '" +
   1080                              std::string(m_used_name) + "'.");
   1081   }
   1082 
   1083   /*
   1084    * @throws std::runtime_error if argument values are not valid
   1085    */
   1086   void validate() const {
   1087     if (m_is_optional) {
   1088       // TODO: check if an implicit value was programmed for this argument
   1089       if (!m_is_used && !m_default_value.has_value() && m_is_required) {
   1090         throw_required_arg_not_used_error();
   1091       }
   1092       if (m_is_used && m_is_required && m_values.empty()) {
   1093         throw_required_arg_no_value_provided_error();
   1094       }
   1095     } else {
   1096       if (!m_num_args_range.contains(m_values.size()) &&
   1097           !m_default_value.has_value()) {
   1098         throw_nargs_range_validation_error();
   1099       }
   1100     }
   1101 
   1102     if (m_choices.has_value()) {
   1103       // Make sure the default value (if provided)
   1104       // is in the list of choices
   1105       find_default_value_in_choices_or_throw();
   1106     }
   1107   }
   1108 
   1109   std::string get_names_csv(char separator = ',') const {
   1110     return std::accumulate(
   1111         m_names.begin(), m_names.end(), std::string{""},
   1112         [&](const std::string &result, const std::string &name) {
   1113           return result.empty() ? name : result + separator + name;
   1114         });
   1115   }
   1116 
   1117   std::string get_usage_full() const {
   1118     std::stringstream usage;
   1119 
   1120     usage << get_names_csv('/');
   1121     const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
   1122     if (m_num_args_range.get_max() > 0) {
   1123       usage << " " << metavar;
   1124       if (m_num_args_range.get_max() > 1) {
   1125         usage << "...";
   1126       }
   1127     }
   1128     return usage.str();
   1129   }
   1130 
   1131   std::string get_inline_usage() const {
   1132     std::stringstream usage;
   1133     // Find the longest variant to show in the usage string
   1134     std::string longest_name = m_names.front();
   1135     for (const auto &s : m_names) {
   1136       if (s.size() > longest_name.size()) {
   1137         longest_name = s;
   1138       }
   1139     }
   1140     if (!m_is_required) {
   1141       usage << "[";
   1142     }
   1143     usage << longest_name;
   1144     const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
   1145     if (m_num_args_range.get_max() > 0) {
   1146       usage << " " << metavar;
   1147       if (m_num_args_range.get_max() > 1 &&
   1148           m_metavar.find("> <") == std::string::npos) {
   1149         usage << "...";
   1150       }
   1151     }
   1152     if (!m_is_required) {
   1153       usage << "]";
   1154     }
   1155     if (m_is_repeatable) {
   1156       usage << "...";
   1157     }
   1158     return usage.str();
   1159   }
   1160 
   1161   std::size_t get_arguments_length() const {
   1162 
   1163     std::size_t names_size = std::accumulate(
   1164         std::begin(m_names), std::end(m_names), std::size_t(0),
   1165         [](const auto &sum, const auto &s) { return sum + s.size(); });
   1166 
   1167     if (is_positional(m_names.front(), m_prefix_chars)) {
   1168       // A set metavar means this replaces the names
   1169       if (!m_metavar.empty()) {
   1170         // Indent and metavar
   1171         return 2 + m_metavar.size();
   1172       }
   1173 
   1174       // Indent and space-separated
   1175       return 2 + names_size + (m_names.size() - 1);
   1176     }
   1177     // Is an option - include both names _and_ metavar
   1178     // size = text + (", " between names)
   1179     std::size_t size = names_size + 2 * (m_names.size() - 1);
   1180     if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
   1181       size += m_metavar.size() + 1;
   1182     }
   1183     return size + 2; // indent
   1184   }
   1185 
   1186   friend std::ostream &operator<<(std::ostream &stream,
   1187                                   const Argument &argument) {
   1188     std::stringstream name_stream;
   1189     name_stream << "  "; // indent
   1190     if (argument.is_positional(argument.m_names.front(),
   1191                                argument.m_prefix_chars)) {
   1192       if (!argument.m_metavar.empty()) {
   1193         name_stream << argument.m_metavar;
   1194       } else {
   1195         name_stream << details::join(argument.m_names.begin(),
   1196                                      argument.m_names.end(), " ");
   1197       }
   1198     } else {
   1199       name_stream << details::join(argument.m_names.begin(),
   1200                                    argument.m_names.end(), ", ");
   1201       // If we have a metavar, and one narg - print the metavar
   1202       if (!argument.m_metavar.empty() &&
   1203           argument.m_num_args_range == NArgsRange{1, 1}) {
   1204         name_stream << " " << argument.m_metavar;
   1205       }
   1206       else if (!argument.m_metavar.empty() &&
   1207                argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() &&
   1208                argument.m_metavar.find("> <") != std::string::npos) {
   1209         name_stream << " " << argument.m_metavar;
   1210       }
   1211     }
   1212 
   1213     // align multiline help message
   1214     auto stream_width = stream.width();
   1215     auto name_padding = std::string(name_stream.str().size(), ' ');
   1216     auto pos = std::string::size_type{};
   1217     auto prev = std::string::size_type{};
   1218     auto first_line = true;
   1219     auto hspace = "  "; // minimal space between name and help message
   1220     stream << name_stream.str();
   1221     std::string_view help_view(argument.m_help);
   1222     while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
   1223       auto line = help_view.substr(prev, pos - prev + 1);
   1224       if (first_line) {
   1225         stream << hspace << line;
   1226         first_line = false;
   1227       } else {
   1228         stream.width(stream_width);
   1229         stream << name_padding << hspace << line;
   1230       }
   1231       prev += pos - prev + 1;
   1232     }
   1233     if (first_line) {
   1234       stream << hspace << argument.m_help;
   1235     } else {
   1236       auto leftover = help_view.substr(prev, argument.m_help.size() - prev);
   1237       if (!leftover.empty()) {
   1238         stream.width(stream_width);
   1239         stream << name_padding << hspace << leftover;
   1240       }
   1241     }
   1242 
   1243     // print nargs spec
   1244     if (!argument.m_help.empty()) {
   1245       stream << " ";
   1246     }
   1247     stream << argument.m_num_args_range;
   1248 
   1249     bool add_space = false;
   1250     if (argument.m_default_value.has_value() &&
   1251         argument.m_num_args_range != NArgsRange{0, 0}) {
   1252       stream << "[default: " << argument.m_default_value_repr << "]";
   1253       add_space = true;
   1254     } else if (argument.m_is_required) {
   1255       stream << "[required]";
   1256       add_space = true;
   1257     }
   1258     if (argument.m_is_repeatable) {
   1259       if (add_space) {
   1260         stream << " ";
   1261       }
   1262       stream << "[may be repeated]";
   1263     }
   1264     stream << "\n";
   1265     return stream;
   1266   }
   1267 
   1268   template <typename T> bool operator!=(const T &rhs) const {
   1269     return !(*this == rhs);
   1270   }
   1271 
   1272   /*
   1273    * Compare to an argument value of known type
   1274    * @throws std::logic_error in case of incompatible types
   1275    */
   1276   template <typename T> bool operator==(const T &rhs) const {
   1277     if constexpr (!details::IsContainer<T>) {
   1278       return get<T>() == rhs;
   1279     } else {
   1280       using ValueType = typename T::value_type;
   1281       auto lhs = get<T>();
   1282       return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
   1283                         std::end(rhs), [](const auto &a, const auto &b) {
   1284                           return std::any_cast<const ValueType &>(a) == b;
   1285                         });
   1286     }
   1287   }
   1288 
   1289   /*
   1290    * positional:
   1291    *    _empty_
   1292    *    '-'
   1293    *    '-' decimal-literal
   1294    *    !'-' anything
   1295    */
   1296   static bool is_positional(std::string_view name,
   1297                             std::string_view prefix_chars) {
   1298     auto first = lookahead(name);
   1299 
   1300     if (first == eof) {
   1301       return true;
   1302     }
   1303     if (prefix_chars.find(static_cast<char>(first)) !=
   1304                           std::string_view::npos) {
   1305       name.remove_prefix(1);
   1306       if (name.empty()) {
   1307         return true;
   1308       }
   1309       return is_decimal_literal(name);
   1310     }
   1311     return true;
   1312   }
   1313 
   1314 private:
   1315   class NArgsRange {
   1316     std::size_t m_min;
   1317     std::size_t m_max;
   1318 
   1319   public:
   1320     NArgsRange(std::size_t minimum, std::size_t maximum)
   1321         : m_min(minimum), m_max(maximum) {
   1322       if (minimum > maximum) {
   1323         throw std::logic_error("Range of number of arguments is invalid");
   1324       }
   1325     }
   1326 
   1327     bool contains(std::size_t value) const {
   1328       return value >= m_min && value <= m_max;
   1329     }
   1330 
   1331     bool is_exact() const { return m_min == m_max; }
   1332 
   1333     bool is_right_bounded() const {
   1334       return m_max < (std::numeric_limits<std::size_t>::max)();
   1335     }
   1336 
   1337     std::size_t get_min() const { return m_min; }
   1338 
   1339     std::size_t get_max() const { return m_max; }
   1340 
   1341     // Print help message
   1342     friend auto operator<<(std::ostream &stream, const NArgsRange &range)
   1343         -> std::ostream & {
   1344       if (range.m_min == range.m_max) {
   1345         if (range.m_min != 0 && range.m_min != 1) {
   1346           stream << "[nargs: " << range.m_min << "] ";
   1347         }
   1348       } else {
   1349         if (range.m_max == (std::numeric_limits<std::size_t>::max)()) {
   1350           stream << "[nargs: " << range.m_min << " or more] ";
   1351         } else {
   1352           stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
   1353         }
   1354       }
   1355       return stream;
   1356     }
   1357 
   1358     bool operator==(const NArgsRange &rhs) const {
   1359       return rhs.m_min == m_min && rhs.m_max == m_max;
   1360     }
   1361 
   1362     bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
   1363   };
   1364 
   1365   void throw_nargs_range_validation_error() const {
   1366     std::stringstream stream;
   1367     if (!m_used_name.empty()) {
   1368       stream << m_used_name << ": ";
   1369     } else {
   1370       stream << m_names.front() << ": ";
   1371     }
   1372     if (m_num_args_range.is_exact()) {
   1373       stream << m_num_args_range.get_min();
   1374     } else if (m_num_args_range.is_right_bounded()) {
   1375       stream << m_num_args_range.get_min() << " to "
   1376              << m_num_args_range.get_max();
   1377     } else {
   1378       stream << m_num_args_range.get_min() << " or more";
   1379     }
   1380     stream << " argument(s) expected. " << m_values.size() << " provided.";
   1381     throw std::runtime_error(stream.str());
   1382   }
   1383 
   1384   void throw_required_arg_not_used_error() const {
   1385     std::stringstream stream;
   1386     stream << m_names.front() << ": required.";
   1387     throw std::runtime_error(stream.str());
   1388   }
   1389 
   1390   void throw_required_arg_no_value_provided_error() const {
   1391     std::stringstream stream;
   1392     stream << m_used_name << ": no value provided.";
   1393     throw std::runtime_error(stream.str());
   1394   }
   1395 
   1396   static constexpr int eof = std::char_traits<char>::eof();
   1397 
   1398   static auto lookahead(std::string_view s) -> int {
   1399     if (s.empty()) {
   1400       return eof;
   1401     }
   1402     return static_cast<int>(static_cast<unsigned char>(s[0]));
   1403   }
   1404 
   1405   /*
   1406    * decimal-literal:
   1407    *    '0'
   1408    *    nonzero-digit digit-sequence_opt
   1409    *    integer-part fractional-part
   1410    *    fractional-part
   1411    *    integer-part '.' exponent-part_opt
   1412    *    integer-part exponent-part
   1413    *
   1414    * integer-part:
   1415    *    digit-sequence
   1416    *
   1417    * fractional-part:
   1418    *    '.' post-decimal-point
   1419    *
   1420    * post-decimal-point:
   1421    *    digit-sequence exponent-part_opt
   1422    *
   1423    * exponent-part:
   1424    *    'e' post-e
   1425    *    'E' post-e
   1426    *
   1427    * post-e:
   1428    *    sign_opt digit-sequence
   1429    *
   1430    * sign: one of
   1431    *    '+' '-'
   1432    */
   1433   static bool is_decimal_literal(std::string_view s) {
   1434     auto is_digit = [](auto c) constexpr {
   1435       switch (c) {
   1436       case '0':
   1437       case '1':
   1438       case '2':
   1439       case '3':
   1440       case '4':
   1441       case '5':
   1442       case '6':
   1443       case '7':
   1444       case '8':
   1445       case '9':
   1446         return true;
   1447       default:
   1448         return false;
   1449       }
   1450     };
   1451 
   1452     // precondition: we have consumed or will consume at least one digit
   1453     auto consume_digits = [=](std::string_view sd) {
   1454       // NOLINTNEXTLINE(readability-qualified-auto)
   1455       auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit);
   1456       return sd.substr(static_cast<std::size_t>(it - std::begin(sd)));
   1457     };
   1458 
   1459     switch (lookahead(s)) {
   1460     case '0': {
   1461       s.remove_prefix(1);
   1462       if (s.empty()) {
   1463         return true;
   1464       }
   1465       goto integer_part;
   1466     }
   1467     case '1':
   1468     case '2':
   1469     case '3':
   1470     case '4':
   1471     case '5':
   1472     case '6':
   1473     case '7':
   1474     case '8':
   1475     case '9': {
   1476       s = consume_digits(s);
   1477       if (s.empty()) {
   1478         return true;
   1479       }
   1480       goto integer_part_consumed;
   1481     }
   1482     case '.': {
   1483       s.remove_prefix(1);
   1484       goto post_decimal_point;
   1485     }
   1486     default:
   1487       return false;
   1488     }
   1489 
   1490   integer_part:
   1491     s = consume_digits(s);
   1492   integer_part_consumed:
   1493     switch (lookahead(s)) {
   1494     case '.': {
   1495       s.remove_prefix(1);
   1496       if (is_digit(lookahead(s))) {
   1497         goto post_decimal_point;
   1498       } else {
   1499         goto exponent_part_opt;
   1500       }
   1501     }
   1502     case 'e':
   1503     case 'E': {
   1504       s.remove_prefix(1);
   1505       goto post_e;
   1506     }
   1507     default:
   1508       return false;
   1509     }
   1510 
   1511   post_decimal_point:
   1512     if (is_digit(lookahead(s))) {
   1513       s = consume_digits(s);
   1514       goto exponent_part_opt;
   1515     }
   1516     return false;
   1517 
   1518   exponent_part_opt:
   1519     switch (lookahead(s)) {
   1520     case eof:
   1521       return true;
   1522     case 'e':
   1523     case 'E': {
   1524       s.remove_prefix(1);
   1525       goto post_e;
   1526     }
   1527     default:
   1528       return false;
   1529     }
   1530 
   1531   post_e:
   1532     switch (lookahead(s)) {
   1533     case '-':
   1534     case '+':
   1535       s.remove_prefix(1);
   1536     }
   1537     if (is_digit(lookahead(s))) {
   1538       s = consume_digits(s);
   1539       return s.empty();
   1540     }
   1541     return false;
   1542   }
   1543 
   1544   static bool is_optional(std::string_view name,
   1545                           std::string_view prefix_chars) {
   1546     return !is_positional(name, prefix_chars);
   1547   }
   1548 
   1549   /*
   1550    * Get argument value given a type
   1551    * @throws std::logic_error in case of incompatible types
   1552    */
   1553   template <typename T> T get() const {
   1554     if (!m_values.empty()) {
   1555       if constexpr (details::IsContainer<T>) {
   1556         return any_cast_container<T>(m_values);
   1557       } else {
   1558         return std::any_cast<T>(m_values.front());
   1559       }
   1560     }
   1561     if (m_default_value.has_value()) {
   1562       return std::any_cast<T>(m_default_value);
   1563     }
   1564     if constexpr (details::IsContainer<T>) {
   1565       if (!m_accepts_optional_like_value) {
   1566         return any_cast_container<T>(m_values);
   1567       }
   1568     }
   1569 
   1570     throw std::logic_error("No value provided for '" + m_names.back() + "'.");
   1571   }
   1572 
   1573   /*
   1574    * Get argument value given a type.
   1575    * @pre The object has no default value.
   1576    * @returns The stored value if any, std::nullopt otherwise.
   1577    */
   1578   template <typename T> auto present() const -> std::optional<T> {
   1579     if (m_default_value.has_value()) {
   1580       throw std::logic_error("Argument with default value always presents");
   1581     }
   1582     if (m_values.empty()) {
   1583       return std::nullopt;
   1584     }
   1585     if constexpr (details::IsContainer<T>) {
   1586       return any_cast_container<T>(m_values);
   1587     }
   1588     return std::any_cast<T>(m_values.front());
   1589   }
   1590 
   1591   template <typename T>
   1592   static auto any_cast_container(const std::vector<std::any> &operand) -> T {
   1593     using ValueType = typename T::value_type;
   1594 
   1595     T result;
   1596     std::transform(
   1597         std::begin(operand), std::end(operand), std::back_inserter(result),
   1598         [](const auto &value) { return std::any_cast<ValueType>(value); });
   1599     return result;
   1600   }
   1601 
   1602   void set_usage_newline_counter(int i) { m_usage_newline_counter = i; }
   1603 
   1604   void set_group_idx(std::size_t i) { m_group_idx = i; }
   1605 
   1606   std::vector<std::string> m_names;
   1607   std::string_view m_used_name;
   1608   std::string m_help;
   1609   std::string m_metavar;
   1610   std::any m_default_value;
   1611   std::string m_default_value_repr;
   1612   std::optional<std::string>
   1613       m_default_value_str; // used for checking default_value against choices
   1614   std::any m_implicit_value;
   1615   std::optional<std::vector<std::string>> m_choices{std::nullopt};
   1616   using valued_action = std::function<std::any(const std::string &)>;
   1617   using void_action = std::function<void(const std::string &)>;
   1618   std::vector<std::variant<valued_action, void_action>> m_actions;
   1619   std::variant<valued_action, void_action> m_default_action{
   1620     std::in_place_type<valued_action>,
   1621     [](const std::string &value) { return value; }};
   1622   std::vector<std::any> m_values;
   1623   NArgsRange m_num_args_range{1, 1};
   1624   // Bit field of bool values. Set default value in ctor.
   1625   bool m_accepts_optional_like_value : 1;
   1626   bool m_is_optional : 1;
   1627   bool m_is_required : 1;
   1628   bool m_is_repeatable : 1;
   1629   bool m_is_used : 1;
   1630   bool m_is_hidden : 1;            // if set, does not appear in usage or help
   1631   std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
   1632   int m_usage_newline_counter = 0;
   1633   std::size_t m_group_idx = 0;
   1634 };
   1635 
   1636 class ArgumentParser {
   1637 public:
   1638   explicit ArgumentParser(std::string program_name = {},
   1639                           std::string version = "1.0",
   1640                           default_arguments add_args = default_arguments::all,
   1641                           bool exit_on_default_arguments = true,
   1642                           std::ostream &os = std::cout)
   1643       : m_program_name(std::move(program_name)), m_version(std::move(version)),
   1644         m_exit_on_default_arguments(exit_on_default_arguments),
   1645         m_parser_path(m_program_name) {
   1646     if ((add_args & default_arguments::help) == default_arguments::help) {
   1647       add_argument("-h", "--help")
   1648           .action([&](const auto & /*unused*/) {
   1649             os << help().str();
   1650             if (m_exit_on_default_arguments) {
   1651               std::exit(0);
   1652             }
   1653           })
   1654           .default_value(false)
   1655           .help("shows help message and exits")
   1656           .implicit_value(true)
   1657           .nargs(0);
   1658     }
   1659     if ((add_args & default_arguments::version) == default_arguments::version) {
   1660       add_argument("-v", "--version")
   1661           .action([&](const auto & /*unused*/) {
   1662             os << m_version << std::endl;
   1663             if (m_exit_on_default_arguments) {
   1664               std::exit(0);
   1665             }
   1666           })
   1667           .default_value(false)
   1668           .help("prints version information and exits")
   1669           .implicit_value(true)
   1670           .nargs(0);
   1671     }
   1672   }
   1673 
   1674   ~ArgumentParser() = default;
   1675 
   1676   // ArgumentParser is meant to be used in a single function.
   1677   // Setup everything and parse arguments in one place.
   1678   //
   1679   // ArgumentParser internally uses std::string_views,
   1680   // references, iterators, etc.
   1681   // Many of these elements become invalidated after a copy or move.
   1682   ArgumentParser(const ArgumentParser &other) = delete;
   1683   ArgumentParser &operator=(const ArgumentParser &other) = delete;
   1684   ArgumentParser(ArgumentParser &&) noexcept = delete;
   1685   ArgumentParser &operator=(ArgumentParser &&) = delete;
   1686 
   1687   explicit operator bool() const {
   1688     auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
   1689                                 [](auto &it) { return it.second->m_is_used; });
   1690     auto subparser_used =
   1691         std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
   1692                     [](auto &it) { return it.second; });
   1693 
   1694     return m_is_parsed && (arg_used || subparser_used);
   1695   }
   1696 
   1697   // Parameter packing
   1698   // Call add_argument with variadic number of string arguments
   1699   template <typename... Targs> Argument &add_argument(Targs... f_args) {
   1700     using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
   1701     auto argument =
   1702         m_optional_arguments.emplace(std::cend(m_optional_arguments),
   1703                                      m_prefix_chars, array_of_sv{f_args...});
   1704 
   1705     if (!argument->m_is_optional) {
   1706       m_positional_arguments.splice(std::cend(m_positional_arguments),
   1707                                     m_optional_arguments, argument);
   1708     }
   1709     argument->set_usage_newline_counter(m_usage_newline_counter);
   1710     argument->set_group_idx(m_group_names.size());
   1711 
   1712     index_argument(argument);
   1713     return *argument;
   1714   }
   1715 
   1716   class MutuallyExclusiveGroup {
   1717     friend class ArgumentParser;
   1718 
   1719   public:
   1720     MutuallyExclusiveGroup() = delete;
   1721 
   1722     explicit MutuallyExclusiveGroup(ArgumentParser &parent,
   1723                                     bool required = false)
   1724         : m_parent(parent), m_required(required), m_elements({}) {}
   1725 
   1726     MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
   1727     MutuallyExclusiveGroup &
   1728     operator=(const MutuallyExclusiveGroup &other) = delete;
   1729 
   1730     MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
   1731         : m_parent(other.m_parent), m_required(other.m_required),
   1732           m_elements(std::move(other.m_elements)) {
   1733       other.m_elements.clear();
   1734     }
   1735 
   1736     template <typename... Targs> Argument &add_argument(Targs... f_args) {
   1737       auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
   1738       m_elements.push_back(&argument);
   1739       argument.set_usage_newline_counter(m_parent.m_usage_newline_counter);
   1740       argument.set_group_idx(m_parent.m_group_names.size());
   1741       return argument;
   1742     }
   1743 
   1744   private:
   1745     ArgumentParser &m_parent;
   1746     bool m_required{false};
   1747     std::vector<Argument *> m_elements{};
   1748   };
   1749 
   1750   MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
   1751     m_mutually_exclusive_groups.emplace_back(*this, required);
   1752     return m_mutually_exclusive_groups.back();
   1753   }
   1754 
   1755   // Parameter packed add_parents method
   1756   // Accepts a variadic number of ArgumentParser objects
   1757   template <typename... Targs>
   1758   ArgumentParser &add_parents(const Targs &... f_args) {
   1759     for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
   1760       for (const auto &argument : parent_parser.m_positional_arguments) {
   1761         auto it = m_positional_arguments.insert(
   1762             std::cend(m_positional_arguments), argument);
   1763         index_argument(it);
   1764       }
   1765       for (const auto &argument : parent_parser.m_optional_arguments) {
   1766         auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
   1767                                               argument);
   1768         index_argument(it);
   1769       }
   1770     }
   1771     return *this;
   1772   }
   1773 
   1774   // Ask for the next optional arguments to be displayed on a separate
   1775   // line in usage() output. Only effective if set_usage_max_line_width() is
   1776   // also used.
   1777   ArgumentParser &add_usage_newline() {
   1778     ++m_usage_newline_counter;
   1779     return *this;
   1780   }
   1781 
   1782   // Ask for the next optional arguments to be displayed in a separate section
   1783   // in usage() and help (<< *this) output.
   1784   // For usage(), this is only effective if set_usage_max_line_width() is
   1785   // also used.
   1786   ArgumentParser &add_group(std::string group_name) {
   1787     m_group_names.emplace_back(std::move(group_name));
   1788     return *this;
   1789   }
   1790 
   1791   ArgumentParser &add_description(std::string description) {
   1792     m_description = std::move(description);
   1793     return *this;
   1794   }
   1795 
   1796   ArgumentParser &add_epilog(std::string epilog) {
   1797     m_epilog = std::move(epilog);
   1798     return *this;
   1799   }
   1800 
   1801   // Add a un-documented/hidden alias for an argument.
   1802   // Ideally we'd want this to be a method of Argument, but Argument
   1803   // does not own its owing ArgumentParser.
   1804   ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) {
   1805     for (auto it = m_optional_arguments.begin();
   1806          it != m_optional_arguments.end(); ++it) {
   1807       if (&(*it) == &arg) {
   1808         m_argument_map.insert_or_assign(std::string(alias), it);
   1809         return *this;
   1810       }
   1811     }
   1812     throw std::logic_error(
   1813         "Argument is not an optional argument of this parser");
   1814   }
   1815 
   1816   /* Getter for arguments and subparsers.
   1817    * @throws std::logic_error in case of an invalid argument or subparser name
   1818    */
   1819   template <typename T = Argument> T &at(std::string_view name) {
   1820     if constexpr (std::is_same_v<T, Argument>) {
   1821       return (*this)[name];
   1822     } else {
   1823       std::string str_name(name);
   1824       auto subparser_it = m_subparser_map.find(str_name);
   1825       if (subparser_it != m_subparser_map.end()) {
   1826         return subparser_it->second->get();
   1827       }
   1828       throw std::logic_error("No such subparser: " + str_name);
   1829     }
   1830   }
   1831 
   1832   ArgumentParser &set_prefix_chars(std::string prefix_chars) {
   1833     m_prefix_chars = std::move(prefix_chars);
   1834     return *this;
   1835   }
   1836 
   1837   ArgumentParser &set_assign_chars(std::string assign_chars) {
   1838     m_assign_chars = std::move(assign_chars);
   1839     return *this;
   1840   }
   1841 
   1842   /* Call parse_args_internal - which does all the work
   1843    * Then, validate the parsed arguments
   1844    * This variant is used mainly for testing
   1845    * @throws std::runtime_error in case of any invalid argument
   1846    */
   1847   void parse_args(const std::vector<std::string> &arguments) {
   1848     parse_args_internal(arguments);
   1849     // Check if all arguments are parsed
   1850     for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
   1851       argument->validate();
   1852     }
   1853 
   1854     // Check each mutually exclusive group and make sure
   1855     // there are no constraint violations
   1856     for (const auto &group : m_mutually_exclusive_groups) {
   1857       auto mutex_argument_used{false};
   1858       Argument *mutex_argument_it{nullptr};
   1859       for (Argument *arg : group.m_elements) {
   1860         if (!mutex_argument_used && arg->m_is_used) {
   1861           mutex_argument_used = true;
   1862           mutex_argument_it = arg;
   1863         } else if (mutex_argument_used && arg->m_is_used) {
   1864           // Violation
   1865           throw std::runtime_error("Argument '" + arg->get_usage_full() +
   1866                                    "' not allowed with '" +
   1867                                    mutex_argument_it->get_usage_full() + "'");
   1868         }
   1869       }
   1870 
   1871       if (!mutex_argument_used && group.m_required) {
   1872         // at least one argument from the group is
   1873         // required
   1874         std::string argument_names{};
   1875         std::size_t i = 0;
   1876         std::size_t size = group.m_elements.size();
   1877         for (Argument *arg : group.m_elements) {
   1878           if (i + 1 == size) {
   1879             // last
   1880             argument_names += std::string("'") + arg->get_usage_full() + std::string("' ");
   1881           } else {
   1882             argument_names += std::string("'") + arg->get_usage_full() + std::string("' or ");
   1883           }
   1884           i += 1;
   1885         }
   1886         throw std::runtime_error("One of the arguments " + argument_names +
   1887                                  "is required");
   1888       }
   1889     }
   1890   }
   1891 
   1892   /* Call parse_known_args_internal - which does all the work
   1893    * Then, validate the parsed arguments
   1894    * This variant is used mainly for testing
   1895    * @throws std::runtime_error in case of any invalid argument
   1896    */
   1897   std::vector<std::string>
   1898   parse_known_args(const std::vector<std::string> &arguments) {
   1899     auto unknown_arguments = parse_known_args_internal(arguments);
   1900     // Check if all arguments are parsed
   1901     for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
   1902       argument->validate();
   1903     }
   1904     return unknown_arguments;
   1905   }
   1906 
   1907   /* Main entry point for parsing command-line arguments using this
   1908    * ArgumentParser
   1909    * @throws std::runtime_error in case of any invalid argument
   1910    */
   1911   // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
   1912   void parse_args(int argc, const char *const argv[]) {
   1913     parse_args({argv, argv + argc});
   1914   }
   1915 
   1916   /* Main entry point for parsing command-line arguments using this
   1917    * ArgumentParser
   1918    * @throws std::runtime_error in case of any invalid argument
   1919    */
   1920   // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
   1921   auto parse_known_args(int argc, const char *const argv[]) {
   1922     return parse_known_args({argv, argv + argc});
   1923   }
   1924 
   1925   /* Getter for options with default values.
   1926    * @throws std::logic_error if parse_args() has not been previously called
   1927    * @throws std::logic_error if there is no such option
   1928    * @throws std::logic_error if the option has no value
   1929    * @throws std::bad_any_cast if the option is not of type T
   1930    */
   1931   template <typename T = std::string> T get(std::string_view arg_name) const {
   1932     if (!m_is_parsed) {
   1933       throw std::logic_error("Nothing parsed, no arguments are available.");
   1934     }
   1935     return (*this)[arg_name].get<T>();
   1936   }
   1937 
   1938   /* Getter for options without default values.
   1939    * @pre The option has no default value.
   1940    * @throws std::logic_error if there is no such option
   1941    * @throws std::bad_any_cast if the option is not of type T
   1942    */
   1943   template <typename T = std::string>
   1944   auto present(std::string_view arg_name) const -> std::optional<T> {
   1945     return (*this)[arg_name].present<T>();
   1946   }
   1947 
   1948   /* Getter that returns true for user-supplied options. Returns false if not
   1949    * user-supplied, even with a default value.
   1950    */
   1951   auto is_used(std::string_view arg_name) const {
   1952     return (*this)[arg_name].m_is_used;
   1953   }
   1954 
   1955   /* Getter that returns true if a subcommand is used.
   1956    */
   1957   auto is_subcommand_used(std::string_view subcommand_name) const {
   1958     return m_subparser_used.at(std::string(subcommand_name));
   1959   }
   1960 
   1961   /* Getter that returns true if a subcommand is used.
   1962    */
   1963   auto is_subcommand_used(const ArgumentParser &subparser) const {
   1964     return is_subcommand_used(subparser.m_program_name);
   1965   }
   1966 
   1967   /* Indexing operator. Return a reference to an Argument object
   1968    * Used in conjunction with Argument.operator== e.g., parser["foo"] == true
   1969    * @throws std::logic_error in case of an invalid argument name
   1970    */
   1971   Argument &operator[](std::string_view arg_name) const {
   1972     std::string name(arg_name);
   1973     auto it = m_argument_map.find(name);
   1974     if (it != m_argument_map.end()) {
   1975       return *(it->second);
   1976     }
   1977     if (!is_valid_prefix_char(arg_name.front())) {
   1978       const auto legal_prefix_char = get_any_valid_prefix_char();
   1979       const auto prefix = std::string(1, legal_prefix_char);
   1980 
   1981       // "-" + arg_name
   1982       name = prefix + name;
   1983       it = m_argument_map.find(name);
   1984       if (it != m_argument_map.end()) {
   1985         return *(it->second);
   1986       }
   1987       // "--" + arg_name
   1988       name = prefix + name;
   1989       it = m_argument_map.find(name);
   1990       if (it != m_argument_map.end()) {
   1991         return *(it->second);
   1992       }
   1993     }
   1994     throw std::logic_error("No such argument: " + std::string(arg_name));
   1995   }
   1996 
   1997   // Print help message
   1998   friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
   1999       -> std::ostream & {
   2000     stream.setf(std::ios_base::left);
   2001 
   2002     auto longest_arg_length = parser.get_length_of_longest_argument();
   2003 
   2004     stream << parser.usage() << "\n\n";
   2005 
   2006     if (!parser.m_description.empty()) {
   2007       stream << parser.m_description << "\n\n";
   2008     }
   2009 
   2010     const bool has_visible_positional_args = std::find_if(
   2011       parser.m_positional_arguments.begin(),
   2012       parser.m_positional_arguments.end(),
   2013       [](const auto &argument) {
   2014       return !argument.m_is_hidden; }) !=
   2015       parser.m_positional_arguments.end();
   2016     if (has_visible_positional_args) {
   2017       stream << "Positional arguments:\n";
   2018     }
   2019 
   2020     for (const auto &argument : parser.m_positional_arguments) {
   2021       if (!argument.m_is_hidden) {
   2022         stream.width(static_cast<std::streamsize>(longest_arg_length));
   2023         stream << argument;
   2024       }
   2025     }
   2026 
   2027     if (!parser.m_optional_arguments.empty()) {
   2028       stream << (!has_visible_positional_args ? "" : "\n")
   2029              << "Optional arguments:\n";
   2030     }
   2031 
   2032     for (const auto &argument : parser.m_optional_arguments) {
   2033       if (argument.m_group_idx == 0 && !argument.m_is_hidden) {
   2034         stream.width(static_cast<std::streamsize>(longest_arg_length));
   2035         stream << argument;
   2036       }
   2037     }
   2038 
   2039     for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) {
   2040       stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
   2041       for (const auto &argument : parser.m_optional_arguments) {
   2042         if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) {
   2043           stream.width(static_cast<std::streamsize>(longest_arg_length));
   2044           stream << argument;
   2045         }
   2046       }
   2047     }
   2048 
   2049     bool has_visible_subcommands = std::any_of(
   2050         parser.m_subparser_map.begin(), parser.m_subparser_map.end(),
   2051         [](auto &p) { return !p.second->get().m_suppress; });
   2052 
   2053     if (has_visible_subcommands) {
   2054       stream << (parser.m_positional_arguments.empty()
   2055                      ? (parser.m_optional_arguments.empty() ? "" : "\n")
   2056                      : "\n")
   2057              << "Subcommands:\n";
   2058       for (const auto &[command, subparser] : parser.m_subparser_map) {
   2059         if (subparser->get().m_suppress) {
   2060           continue;
   2061         }
   2062 
   2063         stream << std::setw(2) << " ";
   2064         stream << std::setw(static_cast<int>(longest_arg_length - 2))
   2065                << command;
   2066         stream << " " << subparser->get().m_description << "\n";
   2067       }
   2068     }
   2069 
   2070     if (!parser.m_epilog.empty()) {
   2071       stream << '\n';
   2072       stream << parser.m_epilog << "\n\n";
   2073     }
   2074 
   2075     return stream;
   2076   }
   2077 
   2078   // Format help message
   2079   auto help() const -> std::stringstream {
   2080     std::stringstream out;
   2081     out << *this;
   2082     return out;
   2083   }
   2084 
   2085   // Sets the maximum width for a line of the Usage message
   2086   ArgumentParser &set_usage_max_line_width(size_t w) {
   2087     this->m_usage_max_line_width = w;
   2088     return *this;
   2089   }
   2090 
   2091   // Asks to display arguments of mutually exclusive group on separate lines in
   2092   // the Usage message
   2093   ArgumentParser &set_usage_break_on_mutex() {
   2094     this->m_usage_break_on_mutex = true;
   2095     return *this;
   2096   }
   2097 
   2098   // Format usage part of help only
   2099   auto usage() const -> std::string {
   2100     std::stringstream stream;
   2101 
   2102     std::string curline("Usage: ");
   2103     curline += this->m_parser_path;
   2104     const bool multiline_usage =
   2105         this->m_usage_max_line_width < (std::numeric_limits<std::size_t>::max)();
   2106     const size_t indent_size = curline.size();
   2107 
   2108     const auto deal_with_options_of_group = [&](std::size_t group_idx) {
   2109       bool found_options = false;
   2110       // Add any options inline here
   2111       const MutuallyExclusiveGroup *cur_mutex = nullptr;
   2112       int usage_newline_counter = -1;
   2113       for (const auto &argument : this->m_optional_arguments) {
   2114         if (argument.m_is_hidden) {
   2115           continue;
   2116         }
   2117         if (multiline_usage) {
   2118           if (argument.m_group_idx != group_idx) {
   2119             continue;
   2120           }
   2121           if (usage_newline_counter != argument.m_usage_newline_counter) {
   2122             if (usage_newline_counter >= 0) {
   2123               if (curline.size() > indent_size) {
   2124                 stream << curline << std::endl;
   2125                 curline = std::string(indent_size, ' ');
   2126               }
   2127             }
   2128             usage_newline_counter = argument.m_usage_newline_counter;
   2129           }
   2130         }
   2131         found_options = true;
   2132         const std::string arg_inline_usage = argument.get_inline_usage();
   2133         const MutuallyExclusiveGroup *arg_mutex =
   2134             get_belonging_mutex(&argument);
   2135         if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) {
   2136           curline += ']';
   2137           if (this->m_usage_break_on_mutex) {
   2138             stream << curline << std::endl;
   2139             curline = std::string(indent_size, ' ');
   2140           }
   2141         } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) {
   2142           if ((this->m_usage_break_on_mutex && curline.size() > indent_size) ||
   2143               curline.size() + 3 + arg_inline_usage.size() >
   2144                   this->m_usage_max_line_width) {
   2145             stream << curline << std::endl;
   2146             curline = std::string(indent_size, ' ');
   2147           }
   2148           curline += " [";
   2149         } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) {
   2150           if (cur_mutex != arg_mutex) {
   2151             curline += ']';
   2152             if (this->m_usage_break_on_mutex ||
   2153                 curline.size() + 3 + arg_inline_usage.size() >
   2154                     this->m_usage_max_line_width) {
   2155               stream << curline << std::endl;
   2156               curline = std::string(indent_size, ' ');
   2157             }
   2158             curline += " [";
   2159           } else {
   2160             curline += '|';
   2161           }
   2162         }
   2163         cur_mutex = arg_mutex;
   2164         if (curline.size() != indent_size &&
   2165             curline.size() + 1 + arg_inline_usage.size() >
   2166             this->m_usage_max_line_width) {
   2167           stream << curline << std::endl;
   2168           curline = std::string(indent_size, ' ');
   2169           curline += " ";
   2170         } else if (cur_mutex == nullptr) {
   2171           curline += " ";
   2172         }
   2173         curline += arg_inline_usage;
   2174       }
   2175       if (cur_mutex != nullptr) {
   2176         curline += ']';
   2177       }
   2178       return found_options;
   2179     };
   2180 
   2181     const bool found_options = deal_with_options_of_group(0);
   2182 
   2183     if (found_options && multiline_usage &&
   2184         !this->m_positional_arguments.empty()) {
   2185       stream << curline << std::endl;
   2186       curline = std::string(indent_size, ' ');
   2187     }
   2188     // Put positional arguments after the optionals
   2189     for (const auto &argument : this->m_positional_arguments) {
   2190       if (argument.m_is_hidden) {
   2191         continue;
   2192       }
   2193       const std::string pos_arg = !argument.m_metavar.empty()
   2194                                       ? argument.m_metavar
   2195                                       : argument.m_names.front();
   2196       if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) {
   2197         stream << curline << std::endl;
   2198         curline = std::string(indent_size, ' ');
   2199       }
   2200       curline += " ";
   2201       if (argument.m_num_args_range.get_min() == 0 &&
   2202           !argument.m_num_args_range.is_right_bounded()) {
   2203         curline += "[";
   2204         curline += pos_arg;
   2205         curline += "]...";
   2206       } else if (argument.m_num_args_range.get_min() == 1 &&
   2207                  !argument.m_num_args_range.is_right_bounded()) {
   2208         curline += pos_arg;
   2209         curline += "...";
   2210       } else {
   2211         curline += pos_arg;
   2212       }
   2213     }
   2214 
   2215     if (multiline_usage) {
   2216       // Display options of other groups
   2217       for (std::size_t i = 0; i < m_group_names.size(); ++i) {
   2218         stream << curline << std::endl << std::endl;
   2219         stream << m_group_names[i] << ":" << std::endl;
   2220         curline = std::string(indent_size, ' ');
   2221         deal_with_options_of_group(i + 1);
   2222       }
   2223     }
   2224 
   2225     stream << curline;
   2226 
   2227     // Put subcommands after positional arguments
   2228     if (!m_subparser_map.empty()) {
   2229       stream << " {";
   2230       std::size_t i{0};
   2231       for (const auto &[command, subparser] : m_subparser_map) {
   2232         if (subparser->get().m_suppress) {
   2233           continue;
   2234         }
   2235 
   2236         if (i == 0) {
   2237           stream << command;
   2238         } else {
   2239           stream << "," << command;
   2240         }
   2241         ++i;
   2242       }
   2243       stream << "}";
   2244     }
   2245 
   2246     return stream.str();
   2247   }
   2248 
   2249   // Printing the one and only help message
   2250   // I've stuck with a simple message format, nothing fancy.
   2251   [[deprecated("Use cout << program; instead.  See also help().")]] std::string
   2252   print_help() const {
   2253     auto out = help();
   2254     std::cout << out.rdbuf();
   2255     return out.str();
   2256   }
   2257 
   2258   void add_subparser(ArgumentParser &parser) {
   2259     parser.m_parser_path = m_program_name + " " + parser.m_program_name;
   2260     auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
   2261     m_subparser_map.insert_or_assign(parser.m_program_name, it);
   2262     m_subparser_used.insert_or_assign(parser.m_program_name, false);
   2263   }
   2264 
   2265   void set_suppress(bool suppress) { m_suppress = suppress; }
   2266 
   2267 protected:
   2268   const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const {
   2269     for (const auto &mutex : m_mutually_exclusive_groups) {
   2270       if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) !=
   2271           mutex.m_elements.end()) {
   2272         return &mutex;
   2273       }
   2274     }
   2275     return nullptr;
   2276   }
   2277 
   2278   bool is_valid_prefix_char(char c) const {
   2279     return m_prefix_chars.find(c) != std::string::npos;
   2280   }
   2281 
   2282   char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
   2283 
   2284   /*
   2285    * Pre-process this argument list. Anything starting with "--", that
   2286    * contains an =, where the prefix before the = has an entry in the
   2287    * options table, should be split.
   2288    */
   2289   std::vector<std::string>
   2290   preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
   2291     std::vector<std::string> arguments{};
   2292     for (const auto &arg : raw_arguments) {
   2293       
   2294       const auto argument_starts_with_prefix_chars =
   2295           [this](const std::string &a) -> bool {
   2296         if (!a.empty()) {
   2297 
   2298           const auto legal_prefix = [this](char c) -> bool {
   2299             return m_prefix_chars.find(c) != std::string::npos;
   2300           };
   2301 
   2302           // Windows-style
   2303           // if '/' is a legal prefix char
   2304           // then allow single '/' followed by argument name, followed by an
   2305           // assign char, e.g., ':' e.g., 'test.exe /A:Foo'
   2306           const auto windows_style = legal_prefix('/');
   2307 
   2308           if (windows_style) {
   2309             if (legal_prefix(a[0])) {
   2310               return true;
   2311             }
   2312           } else {
   2313             // Slash '/' is not a legal prefix char
   2314             // For all other characters, only support long arguments
   2315             // i.e., the argument must start with 2 prefix chars, e.g,
   2316             // '--foo' e,g, './test --foo=Bar -DARG=yes'
   2317             if (a.size() > 1) {
   2318               return (legal_prefix(a[0]) && legal_prefix(a[1]));
   2319             }
   2320           }
   2321         }
   2322         return false;
   2323       };
   2324 
   2325       // Check that:
   2326       // - We don't have an argument named exactly this
   2327       // - The argument starts with a prefix char, e.g., "--"
   2328       // - The argument contains an assign char, e.g., "="
   2329       auto assign_char_pos = arg.find_first_of(m_assign_chars);
   2330 
   2331       if (m_argument_map.find(arg) == m_argument_map.end() &&
   2332           argument_starts_with_prefix_chars(arg) &&
   2333           assign_char_pos != std::string::npos) {
   2334         // Get the name of the potential option, and check it exists
   2335         std::string opt_name = arg.substr(0, assign_char_pos);
   2336         if (m_argument_map.find(opt_name) != m_argument_map.end()) {
   2337           // This is the name of an option! Split it into two parts
   2338           arguments.push_back(std::move(opt_name));
   2339           arguments.push_back(arg.substr(assign_char_pos + 1));
   2340           continue;
   2341         }
   2342       }
   2343       // If we've fallen through to here, then it's a standard argument
   2344       arguments.push_back(arg);
   2345     }
   2346     return arguments;
   2347   }
   2348 
   2349   /*
   2350    * @throws std::runtime_error in case of any invalid argument
   2351    */
   2352   void parse_args_internal(const std::vector<std::string> &raw_arguments) {
   2353     auto arguments = preprocess_arguments(raw_arguments);
   2354     if (m_program_name.empty() && !arguments.empty()) {
   2355       m_program_name = arguments.front();
   2356     }
   2357     auto end = std::end(arguments);
   2358     auto positional_argument_it = std::begin(m_positional_arguments);
   2359     for (auto it = std::next(std::begin(arguments)); it != end;) {
   2360       const auto &current_argument = *it;
   2361       if (Argument::is_positional(current_argument, m_prefix_chars)) {
   2362         if (positional_argument_it == std::end(m_positional_arguments)) {
   2363 
   2364           // Check sub-parsers
   2365           auto subparser_it = m_subparser_map.find(current_argument);
   2366           if (subparser_it != m_subparser_map.end()) {
   2367 
   2368             // build list of remaining args
   2369             const auto unprocessed_arguments =
   2370                 std::vector<std::string>(it, end);
   2371 
   2372             // invoke subparser
   2373             m_is_parsed = true;
   2374             m_subparser_used[current_argument] = true;
   2375             return subparser_it->second->get().parse_args(
   2376                 unprocessed_arguments);
   2377           }
   2378 
   2379           if (m_positional_arguments.empty()) {
   2380 
   2381             // Ask the user if they argument they provided was a typo
   2382             // for some sub-parser,
   2383             // e.g., user provided `git totes` instead of `git notes`
   2384             if (!m_subparser_map.empty()) {
   2385               throw std::runtime_error(
   2386                   "Failed to parse '" + current_argument + "', did you mean '" +
   2387                   std::string{details::get_most_similar_string(
   2388                       m_subparser_map, current_argument)} +
   2389                   "'");
   2390             }
   2391 
   2392             // Ask the user if they meant to use a specific optional argument
   2393             if (!m_optional_arguments.empty()) {
   2394               for (const auto &opt : m_optional_arguments) {
   2395                 if (!opt.m_implicit_value.has_value()) {
   2396                   // not a flag, requires a value
   2397                   if (!opt.m_is_used) {
   2398                     throw std::runtime_error(
   2399                         "Zero positional arguments expected, did you mean " +
   2400                         opt.get_usage_full());
   2401                   }
   2402                 }
   2403               }
   2404 
   2405               throw std::runtime_error("Zero positional arguments expected");
   2406             } else {
   2407               throw std::runtime_error("Zero positional arguments expected");
   2408             }
   2409           } else {
   2410             throw std::runtime_error("Maximum number of positional arguments "
   2411                                      "exceeded, failed to parse '" +
   2412                                      current_argument + "'");
   2413           }
   2414         }
   2415         auto argument = positional_argument_it++;
   2416 
   2417         // Deal with the situation of <positional_arg1>... <positional_arg2>
   2418         if (argument->m_num_args_range.get_min() == 1 &&
   2419             argument->m_num_args_range.get_max() == (std::numeric_limits<std::size_t>::max)() &&
   2420             positional_argument_it != std::end(m_positional_arguments) &&
   2421             std::next(positional_argument_it) == std::end(m_positional_arguments) &&
   2422             positional_argument_it->m_num_args_range.get_min() == 1 &&
   2423             positional_argument_it->m_num_args_range.get_max() == 1 ) {
   2424           if (std::next(it) != end) {
   2425             positional_argument_it->consume(std::prev(end), end);
   2426             end = std::prev(end);
   2427           } else {
   2428             throw std::runtime_error("Missing " + positional_argument_it->m_names.front());
   2429           }
   2430         }
   2431 
   2432         it = argument->consume(it, end);
   2433         continue;
   2434       }
   2435 
   2436       auto arg_map_it = m_argument_map.find(current_argument);
   2437       if (arg_map_it != m_argument_map.end()) {
   2438         auto argument = arg_map_it->second;
   2439         it = argument->consume(std::next(it), end, arg_map_it->first);
   2440       } else if (const auto &compound_arg = current_argument;
   2441                  compound_arg.size() > 1 &&
   2442                  is_valid_prefix_char(compound_arg[0]) &&
   2443                  !is_valid_prefix_char(compound_arg[1])) {
   2444         ++it;
   2445         for (std::size_t j = 1; j < compound_arg.size(); j++) {
   2446           auto hypothetical_arg = std::string{'-', compound_arg[j]};
   2447           auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
   2448           if (arg_map_it2 != m_argument_map.end()) {
   2449             auto argument = arg_map_it2->second;
   2450             it = argument->consume(it, end, arg_map_it2->first);
   2451           } else {
   2452             throw std::runtime_error("Unknown argument: " + current_argument);
   2453           }
   2454         }
   2455       } else {
   2456         throw std::runtime_error("Unknown argument: " + current_argument);
   2457       }
   2458     }
   2459     m_is_parsed = true;
   2460   }
   2461 
   2462   /*
   2463    * Like parse_args_internal but collects unused args into a vector<string>
   2464    */
   2465   std::vector<std::string>
   2466   parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
   2467     auto arguments = preprocess_arguments(raw_arguments);
   2468 
   2469     std::vector<std::string> unknown_arguments{};
   2470 
   2471     if (m_program_name.empty() && !arguments.empty()) {
   2472       m_program_name = arguments.front();
   2473     }
   2474     auto end = std::end(arguments);
   2475     auto positional_argument_it = std::begin(m_positional_arguments);
   2476     for (auto it = std::next(std::begin(arguments)); it != end;) {
   2477       const auto &current_argument = *it;
   2478       if (Argument::is_positional(current_argument, m_prefix_chars)) {
   2479         if (positional_argument_it == std::end(m_positional_arguments)) {
   2480 
   2481           // Check sub-parsers
   2482           auto subparser_it = m_subparser_map.find(current_argument);
   2483           if (subparser_it != m_subparser_map.end()) {
   2484 
   2485             // build list of remaining args
   2486             const auto unprocessed_arguments =
   2487                 std::vector<std::string>(it, end);
   2488 
   2489             // invoke subparser
   2490             m_is_parsed = true;
   2491             m_subparser_used[current_argument] = true;
   2492             return subparser_it->second->get().parse_known_args_internal(
   2493                 unprocessed_arguments);
   2494           }
   2495 
   2496           // save current argument as unknown and go to next argument
   2497           unknown_arguments.push_back(current_argument);
   2498           ++it;
   2499         } else {
   2500           // current argument is the value of a positional argument
   2501           // consume it
   2502           auto argument = positional_argument_it++;
   2503           it = argument->consume(it, end);
   2504         }
   2505         continue;
   2506       }
   2507 
   2508       auto arg_map_it = m_argument_map.find(current_argument);
   2509       if (arg_map_it != m_argument_map.end()) {
   2510         auto argument = arg_map_it->second;
   2511         it = argument->consume(std::next(it), end, arg_map_it->first);
   2512       } else if (const auto &compound_arg = current_argument;
   2513                  compound_arg.size() > 1 &&
   2514                  is_valid_prefix_char(compound_arg[0]) &&
   2515                  !is_valid_prefix_char(compound_arg[1])) {
   2516         ++it;
   2517         for (std::size_t j = 1; j < compound_arg.size(); j++) {
   2518           auto hypothetical_arg = std::string{'-', compound_arg[j]};
   2519           auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
   2520           if (arg_map_it2 != m_argument_map.end()) {
   2521             auto argument = arg_map_it2->second;
   2522             it = argument->consume(it, end, arg_map_it2->first);
   2523           } else {
   2524             unknown_arguments.push_back(current_argument);
   2525             break;
   2526           }
   2527         }
   2528       } else {
   2529         // current argument is an optional-like argument that is unknown
   2530         // save it and move to next argument
   2531         unknown_arguments.push_back(current_argument);
   2532         ++it;
   2533       }
   2534     }
   2535     m_is_parsed = true;
   2536     return unknown_arguments;
   2537   }
   2538 
   2539   // Used by print_help.
   2540   std::size_t get_length_of_longest_argument() const {
   2541     if (m_argument_map.empty()) {
   2542       return 0;
   2543     }
   2544     std::size_t max_size = 0;
   2545     for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
   2546       max_size =
   2547           std::max<std::size_t>(max_size, argument->get_arguments_length());
   2548     }
   2549     for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
   2550       max_size = std::max<std::size_t>(max_size, command.size());
   2551     }
   2552     return max_size;
   2553   }
   2554 
   2555   using argument_it = std::list<Argument>::iterator;
   2556   using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
   2557   using argument_parser_it =
   2558       std::list<std::reference_wrapper<ArgumentParser>>::iterator;
   2559 
   2560   void index_argument(argument_it it) {
   2561     for (const auto &name : std::as_const(it->m_names)) {
   2562       m_argument_map.insert_or_assign(name, it);
   2563     }
   2564   }
   2565 
   2566   std::string m_program_name;
   2567   std::string m_version;
   2568   std::string m_description;
   2569   std::string m_epilog;
   2570   bool m_exit_on_default_arguments = true;
   2571   std::string m_prefix_chars{"-"};
   2572   std::string m_assign_chars{"="};
   2573   bool m_is_parsed = false;
   2574   std::list<Argument> m_positional_arguments;
   2575   std::list<Argument> m_optional_arguments;
   2576   std::map<std::string, argument_it> m_argument_map;
   2577   std::string m_parser_path;
   2578   std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
   2579   std::map<std::string, argument_parser_it> m_subparser_map;
   2580   std::map<std::string, bool> m_subparser_used;
   2581   std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
   2582   bool m_suppress = false;
   2583   std::size_t m_usage_max_line_width = (std::numeric_limits<std::size_t>::max)();
   2584   bool m_usage_break_on_mutex = false;
   2585   int m_usage_newline_counter = 0;
   2586   std::vector<std::string> m_group_names;
   2587 };
   2588 
   2589 } // namespace argparse