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 ¤t_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 ¤t_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
|