LCOV - code coverage report
Current view: top level - apps/argparse - argparse.hpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 622 986 63.1 %
Date: 2024-05-14 23:54:21 Functions: 318 348 91.4 %

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

Generated by: LCOV version 1.14