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 89415 : template <typename T> std::string repr(T const &val) {
109 : if constexpr (std::is_same_v<T, bool>) {
110 88059 : 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 2304 : std::stringstream out;
139 1152 : out << val;
140 2304 : 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 6581 : constexpr auto pointer_range(std::string_view s) noexcept {
191 6581 : return std::tuple(s.data(), s.data() + s.size());
192 : }
193 :
194 : template <class CharT, class Traits>
195 25206 : constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
196 : std::basic_string_view<CharT, Traits> s) noexcept {
197 25206 : 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 6162 : constexpr auto consume_binary_prefix(std::string_view s)
214 : -> ConsumeBinaryPrefixResult {
215 12324 : if (starts_with(std::string_view{"0b"}, s) ||
216 12324 : starts_with(std::string_view{"0B"}, s)) {
217 0 : s.remove_prefix(2);
218 0 : return {true, s};
219 : }
220 6162 : 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 6162 : constexpr auto consume_hex_prefix(std::string_view s)
231 : -> ConsumeHexPrefixResult {
232 6162 : if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
233 0 : s.remove_prefix(2);
234 0 : return {true, s};
235 : }
236 6162 : return {false, s};
237 : }
238 :
239 : template <class T, auto Param>
240 977 : inline auto do_from_chars(std::string_view s) -> T {
241 977 : T x{0};
242 977 : auto [first, last] = pointer_range(s);
243 977 : auto [ptr, ec] = std::from_chars(first, last, x, Param);
244 977 : if (ec == std::errc()) {
245 977 : if (ptr == last) {
246 977 : 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 419 : auto operator()(std::string_view s) -> T {
262 419 : 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 558 : auto operator()(std::string_view s) -> T {
310 558 : auto [ok, rest] = consume_hex_prefix(s);
311 558 : 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 558 : auto [ok_binary, rest_binary] = consume_binary_prefix(s);
324 558 : 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 558 : if (starts_with("0"sv, s)) {
337 : try {
338 10 : 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 548 : 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 5604 : template <class T> inline auto do_strtod(std::string const &s) -> T {
371 5604 : if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
372 0 : throw std::invalid_argument{"pattern '" + s + "' not found"};
373 : }
374 :
375 5604 : auto [first, last] = pointer_range(s);
376 : char *ptr;
377 :
378 5604 : errno = 0;
379 5604 : auto x = generic_strtod<T>(first, &ptr);
380 5604 : if (errno == 0) {
381 5604 : if (ptr == last) {
382 5599 : return x;
383 : }
384 : throw std::invalid_argument{"pattern '" + s +
385 5 : "' 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 5604 : auto operator()(std::string const &s) -> T {
395 5604 : 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 5604 : 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 5604 : return do_strtod<T>(s);
406 10 : } catch (const std::invalid_argument &err) {
407 : throw std::invalid_argument("Failed to parse '" + s +
408 5 : "' 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 10844 : inline default_arguments operator&(const default_arguments &a,
590 : const default_arguments &b) {
591 : return static_cast<default_arguments>(
592 10844 : static_cast<std::underlying_type<default_arguments>::type>(a) &
593 10844 : 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 230132 : 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 230132 : m_is_optional((is_optional(a[I], prefix_chars) || ...)),
609 : m_is_required(false), m_is_repeatable(false), m_is_used(false),
610 238325 : m_is_hidden(false), m_prefix_chars(prefix_chars) {
611 230132 : ((void)m_names.emplace_back(a[I]), ...);
612 230132 : std::sort(
613 16386 : m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
614 16386 : return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
615 : });
616 230132 : }
617 :
618 : public:
619 : template <std::size_t N>
620 230132 : explicit Argument(std::string_view prefix_chars,
621 : std::array<std::string_view, N> &&a)
622 230132 : : Argument(prefix_chars, std::move(a), std::make_index_sequence<N>{}) {}
623 :
624 227714 : Argument &help(std::string help_text) {
625 227714 : m_help = std::move(help_text);
626 227714 : return *this;
627 : }
628 :
629 150759 : Argument &metavar(std::string metavar) {
630 150759 : m_metavar = std::move(metavar);
631 150759 : return *this;
632 : }
633 :
634 89415 : template <typename T> Argument &default_value(T &&value) {
635 89415 : m_num_args_range = NArgsRange{0, m_num_args_range.get_max()};
636 89415 : 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 89211 : m_default_value_str = std::to_string(value);
642 : }
643 :
644 89415 : m_default_value = std::forward<T>(value);
645 89415 : return *this;
646 : }
647 :
648 204 : Argument &default_value(const char *value) {
649 204 : return default_value(std::string(value));
650 : }
651 :
652 : Argument &required() {
653 : m_is_required = true;
654 : return *this;
655 : }
656 :
657 88059 : Argument &implicit_value(std::any value) {
658 88059 : m_implicit_value = std::move(value);
659 88059 : m_num_args_range = NArgsRange{0, 0};
660 88059 : return *this;
661 : }
662 :
663 : // This is shorthand for:
664 : // program.add_argument("foo")
665 : // .default_value(false)
666 : // .implicit_value(true)
667 87097 : Argument &flag() {
668 87097 : default_value(false);
669 87097 : implicit_value(true);
670 87097 : return *this;
671 : }
672 :
673 : template <class F, class... Args>
674 216362 : 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 216362 : m_actions.emplace_back<action_type>(std::forward<F>(callable));
682 : } else {
683 : m_actions.emplace_back<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 216362 : return *this;
691 : }
692 :
693 35375 : auto &store_into(bool &var) {
694 35375 : flag();
695 35375 : if (m_default_value.has_value()) {
696 35375 : var = std::any_cast<bool>(m_default_value);
697 : }
698 35375 : action([&var](const auto & /*unused*/) { var = true; });
699 35375 : return *this;
700 : }
701 :
702 : template <typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
703 5805 : auto &store_into(T &var) {
704 5805 : if (m_default_value.has_value()) {
705 : try
706 : {
707 218 : var = std::any_cast<T>(m_default_value);
708 : }
709 22 : catch (...)
710 : {
711 22 : var = static_cast<T>(std::any_cast<int>(m_default_value));
712 : }
713 : }
714 5886 : action([&var](const auto &s) {
715 147 : var = details::parse_number<T, details::radix_10>()(s);
716 : });
717 5805 : return *this;
718 : }
719 :
720 13959 : auto &store_into(double &var) {
721 13959 : if (m_default_value.has_value()) {
722 : try
723 : {
724 934 : var = std::any_cast<double>(m_default_value);
725 : }
726 110 : catch (...)
727 : {
728 110 : var = std::any_cast<int>(m_default_value);
729 : }
730 : }
731 224 : action([&var](const auto &s) {
732 224 : var = details::parse_number<double, details::chars_format::general>()(s);
733 14178 : });
734 13959 : return *this;
735 : }
736 :
737 30260 : auto &store_into(std::string &var) {
738 30260 : if (m_default_value.has_value()) {
739 57 : var = std::any_cast<std::string>(m_default_value);
740 : }
741 33896 : action([&var](const std::string &s) { var = s; });
742 30260 : return *this;
743 : }
744 :
745 452 : auto &store_into(std::vector<std::string> &var) {
746 452 : if (m_default_value.has_value()) {
747 0 : var = std::any_cast<std::vector<std::string>>(m_default_value);
748 : }
749 645 : action([this, &var](const std::string &s) {
750 220 : if (!m_is_used) {
751 205 : var.clear();
752 : }
753 220 : m_is_used = true;
754 220 : var.push_back(s);
755 452 : });
756 452 : return *this;
757 : }
758 :
759 1981 : auto &store_into(std::vector<int> &var) {
760 1981 : 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 2253 : });
770 1981 : return *this;
771 : }
772 :
773 30 : auto &store_into(std::set<std::string> &var) {
774 30 : 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 30 : });
784 30 : 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 40439 : auto &append() {
802 40439 : m_is_repeatable = true;
803 40439 : return *this;
804 : }
805 :
806 : // Cause the argument to be invisible in usage and help
807 11200 : auto &hidden() {
808 11200 : m_is_hidden = true;
809 11200 : return *this;
810 : }
811 :
812 : template <char Shape, typename T>
813 28771 : 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 2463 : 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 26308 : 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 28771 : return *this;
854 : }
855 :
856 19093 : Argument &nargs(std::size_t num_args) {
857 19093 : m_num_args_range = NArgsRange{num_args, num_args};
858 19093 : return *this;
859 : }
860 :
861 2964 : Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
862 2964 : m_num_args_range = NArgsRange{num_args_min, num_args_max};
863 2964 : return *this;
864 : }
865 :
866 1169 : Argument &nargs(nargs_pattern pattern) {
867 1169 : switch (pattern) {
868 105 : case nargs_pattern::optional:
869 105 : m_num_args_range = NArgsRange{0, 1};
870 105 : break;
871 943 : case nargs_pattern::any:
872 943 : m_num_args_range =
873 943 : NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
874 943 : break;
875 121 : case nargs_pattern::at_least_one:
876 121 : m_num_args_range =
877 121 : NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
878 121 : break;
879 : }
880 1169 : return *this;
881 : }
882 :
883 913 : Argument &remaining() {
884 913 : m_accepts_optional_like_value = true;
885 913 : return nargs(nargs_pattern::any);
886 : }
887 :
888 4326 : 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 4326 : if (!m_choices.has_value()) {
895 1369 : m_choices = std::vector<std::string>{};
896 : }
897 :
898 : if constexpr (std::is_convertible_v<T, std::string_view>) {
899 8652 : m_choices.value().push_back(
900 4326 : 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 4326 : }
905 :
906 1369 : Argument &choices() {
907 1369 : if (!m_choices.has_value()) {
908 0 : throw std::runtime_error("Zero choices provided");
909 : }
910 1369 : return *this;
911 : }
912 :
913 : template <typename T, typename... U>
914 4326 : Argument &choices(T &&first, U &&... rest) {
915 4326 : add_choice(std::forward<T>(first));
916 4326 : choices(std::forward<U>(rest)...);
917 4326 : return *this;
918 : }
919 :
920 1343 : void find_default_value_in_choices_or_throw() const {
921 :
922 1343 : const auto &choices = m_choices.value();
923 :
924 1343 : 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 1343 : }
942 :
943 : template <typename Iterator>
944 188 : void find_value_in_choices_or_throw(Iterator it) const {
945 :
946 188 : const auto &choices = m_choices.value();
947 :
948 188 : 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 188 : }
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 23679 : Iterator consume(Iterator start, Iterator end,
970 : std::string_view used_name = {}, bool dry_run = false) {
971 23679 : if (!m_is_repeatable && m_is_used) {
972 : throw std::runtime_error(
973 0 : std::string("Duplicate argument ").append(used_name));
974 : }
975 23679 : m_used_name = used_name;
976 :
977 23679 : 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 188 : std::size_t i = 0;
981 188 : auto max_number_of_args = m_num_args_range.get_max();
982 376 : for (auto it = start; it != end; ++it) {
983 270 : if (i == max_number_of_args) {
984 82 : break;
985 : }
986 188 : find_value_in_choices_or_throw(it);
987 188 : i += 1;
988 : }
989 : }
990 :
991 23679 : const auto num_args_max = m_num_args_range.get_max();
992 23679 : const auto num_args_min = m_num_args_range.get_min();
993 23679 : std::size_t dist = 0;
994 23679 : if (num_args_max == 0) {
995 1791 : if (!dry_run) {
996 836 : m_values.emplace_back(m_implicit_value);
997 1644 : for(auto &action: m_actions) {
998 1684 : std::visit([&](const auto &f) { f({}); }, action);
999 : }
1000 802 : if(m_actions.empty()){
1001 0 : std::visit([&](const auto &f) { f({}); }, m_default_action);
1002 : }
1003 802 : m_is_used = true;
1004 : }
1005 1757 : return start;
1006 : }
1007 21888 : if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
1008 : num_args_min) {
1009 21887 : if (num_args_max < dist) {
1010 12645 : end = std::next(start, static_cast<typename Iterator::difference_type>(
1011 : num_args_max));
1012 : }
1013 21887 : if (!m_accepts_optional_like_value) {
1014 21780 : end = std::find_if(
1015 : start, end,
1016 21780 : std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
1017 21780 : dist = static_cast<std::size_t>(std::distance(start, end));
1018 21780 : if (dist < num_args_min) {
1019 : throw std::runtime_error("Too few arguments for '" +
1020 0 : std::string(m_used_name) + "'.");
1021 : }
1022 : }
1023 :
1024 : struct ActionApply {
1025 2128 : void operator()(valued_action &f) {
1026 2128 : std::transform(first, last, std::back_inserter(self.m_values), f);
1027 2128 : }
1028 :
1029 8269 : void operator()(void_action &f) {
1030 8269 : std::for_each(first, last, f);
1031 8238 : if (!self.m_default_value.has_value()) {
1032 8086 : if (!self.m_accepts_optional_like_value) {
1033 16154 : self.m_values.resize(
1034 8077 : static_cast<std::size_t>(std::distance(first, last)));
1035 : }
1036 : }
1037 8238 : }
1038 :
1039 : Iterator first, last;
1040 : Argument &self;
1041 : };
1042 21887 : if (!dry_run) {
1043 20313 : for(auto &action: m_actions) {
1044 10130 : std::visit(ActionApply{start, end, *this}, action);
1045 : }
1046 10183 : if(m_actions.empty()){
1047 267 : std::visit(ActionApply{start, end, *this}, m_default_action);
1048 : }
1049 10183 : m_is_used = true;
1050 : }
1051 21856 : return end;
1052 : }
1053 1 : if (m_default_value.has_value()) {
1054 0 : if (!dry_run) {
1055 0 : m_is_used = true;
1056 : }
1057 0 : return start;
1058 : }
1059 : throw std::runtime_error("Too few arguments for '" +
1060 1 : std::string(m_used_name) + "'.");
1061 : }
1062 :
1063 : /*
1064 : * @throws std::runtime_error if argument values are not valid
1065 : */
1066 235443 : void validate() const {
1067 235443 : if (m_is_optional) {
1068 : // TODO: check if an implicit value was programmed for this argument
1069 233162 : if (!m_is_used && !m_default_value.has_value() && m_is_required) {
1070 0 : throw_required_arg_not_used_error();
1071 : }
1072 233162 : if (m_is_used && m_is_required && m_values.empty()) {
1073 0 : throw_required_arg_no_value_provided_error();
1074 : }
1075 : } else {
1076 2286 : if (!m_num_args_range.contains(m_values.size()) &&
1077 5 : !m_default_value.has_value()) {
1078 5 : throw_nargs_range_validation_error();
1079 : }
1080 : }
1081 :
1082 235438 : if (m_choices.has_value()) {
1083 : // Make sure the default value (if provided)
1084 : // is in the list of choices
1085 1343 : find_default_value_in_choices_or_throw();
1086 : }
1087 235438 : }
1088 :
1089 2 : std::string get_names_csv(char separator = ',') const {
1090 : return std::accumulate(
1091 4 : m_names.begin(), m_names.end(), std::string{""},
1092 2 : [&](const std::string &result, const std::string &name) {
1093 4 : return result.empty() ? name : result + separator + name;
1094 6 : });
1095 : }
1096 :
1097 2 : std::string get_usage_full() const {
1098 4 : std::stringstream usage;
1099 :
1100 2 : usage << get_names_csv('/');
1101 4 : const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
1102 2 : if (m_num_args_range.get_max() > 0) {
1103 2 : usage << " " << metavar;
1104 2 : if (m_num_args_range.get_max() > 1) {
1105 0 : usage << "...";
1106 : }
1107 : }
1108 4 : return usage.str();
1109 : }
1110 :
1111 499 : std::string get_inline_usage() const {
1112 998 : std::stringstream usage;
1113 : // Find the longest variant to show in the usage string
1114 998 : std::string longest_name = m_names.front();
1115 1032 : for (const auto &s : m_names) {
1116 533 : if (s.size() > longest_name.size()) {
1117 34 : longest_name = s;
1118 : }
1119 : }
1120 499 : if (!m_is_required) {
1121 499 : usage << "[";
1122 : }
1123 499 : usage << longest_name;
1124 998 : const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
1125 499 : if (m_num_args_range.get_max() > 0) {
1126 317 : usage << " " << metavar;
1127 354 : if (m_num_args_range.get_max() > 1 &&
1128 37 : m_metavar.find("> <") == std::string::npos) {
1129 0 : usage << "...";
1130 : }
1131 : }
1132 499 : if (!m_is_required) {
1133 499 : usage << "]";
1134 : }
1135 499 : if (m_is_repeatable) {
1136 78 : usage << "...";
1137 : }
1138 998 : return usage.str();
1139 : }
1140 :
1141 0 : std::size_t get_arguments_length() const {
1142 :
1143 0 : std::size_t names_size = std::accumulate(
1144 0 : std::begin(m_names), std::end(m_names), std::size_t(0),
1145 0 : [](const auto &sum, const auto &s) { return sum + s.size(); });
1146 :
1147 0 : if (is_positional(m_names.front(), m_prefix_chars)) {
1148 : // A set metavar means this replaces the names
1149 0 : if (!m_metavar.empty()) {
1150 : // Indent and metavar
1151 0 : return 2 + m_metavar.size();
1152 : }
1153 :
1154 : // Indent and space-separated
1155 0 : return 2 + names_size + (m_names.size() - 1);
1156 : }
1157 : // Is an option - include both names _and_ metavar
1158 : // size = text + (", " between names)
1159 0 : std::size_t size = names_size + 2 * (m_names.size() - 1);
1160 0 : if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
1161 0 : size += m_metavar.size() + 1;
1162 : }
1163 0 : return size + 2; // indent
1164 : }
1165 :
1166 0 : friend std::ostream &operator<<(std::ostream &stream,
1167 : const Argument &argument) {
1168 0 : std::stringstream name_stream;
1169 0 : name_stream << " "; // indent
1170 0 : if (argument.is_positional(argument.m_names.front(),
1171 : argument.m_prefix_chars)) {
1172 0 : if (!argument.m_metavar.empty()) {
1173 0 : name_stream << argument.m_metavar;
1174 : } else {
1175 0 : name_stream << details::join(argument.m_names.begin(),
1176 0 : argument.m_names.end(), " ");
1177 : }
1178 : } else {
1179 0 : name_stream << details::join(argument.m_names.begin(),
1180 0 : argument.m_names.end(), ", ");
1181 : // If we have a metavar, and one narg - print the metavar
1182 0 : if (!argument.m_metavar.empty() &&
1183 0 : argument.m_num_args_range == NArgsRange{1, 1}) {
1184 0 : name_stream << " " << argument.m_metavar;
1185 : }
1186 0 : else if (!argument.m_metavar.empty() &&
1187 0 : argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() &&
1188 0 : argument.m_metavar.find("> <") != std::string::npos) {
1189 0 : name_stream << " " << argument.m_metavar;
1190 : }
1191 : }
1192 :
1193 : // align multiline help message
1194 0 : auto stream_width = stream.width();
1195 0 : auto name_padding = std::string(name_stream.str().size(), ' ');
1196 0 : auto pos = std::string::size_type{};
1197 0 : auto prev = std::string::size_type{};
1198 0 : auto first_line = true;
1199 0 : auto hspace = " "; // minimal space between name and help message
1200 0 : stream << name_stream.str();
1201 0 : std::string_view help_view(argument.m_help);
1202 0 : while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
1203 0 : auto line = help_view.substr(prev, pos - prev + 1);
1204 0 : if (first_line) {
1205 0 : stream << hspace << line;
1206 0 : first_line = false;
1207 : } else {
1208 0 : stream.width(stream_width);
1209 0 : stream << name_padding << hspace << line;
1210 : }
1211 0 : prev += pos - prev + 1;
1212 : }
1213 0 : if (first_line) {
1214 0 : stream << hspace << argument.m_help;
1215 : } else {
1216 0 : auto leftover = help_view.substr(prev, argument.m_help.size() - prev);
1217 0 : if (!leftover.empty()) {
1218 0 : stream.width(stream_width);
1219 0 : stream << name_padding << hspace << leftover;
1220 : }
1221 : }
1222 :
1223 : // print nargs spec
1224 0 : if (!argument.m_help.empty()) {
1225 0 : stream << " ";
1226 : }
1227 0 : stream << argument.m_num_args_range;
1228 :
1229 0 : bool add_space = false;
1230 0 : if (argument.m_default_value.has_value() &&
1231 0 : argument.m_num_args_range != NArgsRange{0, 0}) {
1232 0 : stream << "[default: " << argument.m_default_value_repr << "]";
1233 0 : add_space = true;
1234 0 : } else if (argument.m_is_required) {
1235 0 : stream << "[required]";
1236 0 : add_space = true;
1237 : }
1238 0 : if (argument.m_is_repeatable) {
1239 0 : if (add_space) {
1240 0 : stream << " ";
1241 : }
1242 0 : stream << "[may be repeated]";
1243 : }
1244 0 : stream << "\n";
1245 0 : return stream;
1246 : }
1247 :
1248 : template <typename T> bool operator!=(const T &rhs) const {
1249 : return !(*this == rhs);
1250 : }
1251 :
1252 : /*
1253 : * Compare to an argument value of known type
1254 : * @throws std::logic_error in case of incompatible types
1255 : */
1256 : template <typename T> bool operator==(const T &rhs) const {
1257 : if constexpr (!details::IsContainer<T>) {
1258 : return get<T>() == rhs;
1259 : } else {
1260 : using ValueType = typename T::value_type;
1261 : auto lhs = get<T>();
1262 : return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
1263 : std::end(rhs), [](const auto &a, const auto &b) {
1264 : return std::any_cast<const ValueType &>(a) == b;
1265 : });
1266 : }
1267 : }
1268 :
1269 : /*
1270 : * positional:
1271 : * _empty_
1272 : * '-'
1273 : * '-' decimal-literal
1274 : * !'-' anything
1275 : */
1276 286062 : static bool is_positional(std::string_view name,
1277 : std::string_view prefix_chars) {
1278 286062 : auto first = lookahead(name);
1279 :
1280 286062 : if (first == eof) {
1281 3 : return true;
1282 : }
1283 286059 : if (prefix_chars.find(static_cast<char>(first)) !=
1284 : std::string_view::npos) {
1285 249054 : name.remove_prefix(1);
1286 249054 : if (name.empty()) {
1287 0 : return true;
1288 : }
1289 249054 : return is_decimal_literal(name);
1290 : }
1291 37005 : return true;
1292 : }
1293 :
1294 : private:
1295 : class NArgsRange {
1296 : std::size_t m_min;
1297 : std::size_t m_max;
1298 :
1299 : public:
1300 430832 : NArgsRange(std::size_t minimum, std::size_t maximum)
1301 430832 : : m_min(minimum), m_max(maximum) {
1302 430832 : if (minimum > maximum) {
1303 0 : throw std::logic_error("Range of number of arguments is invalid");
1304 : }
1305 430832 : }
1306 :
1307 2281 : bool contains(std::size_t value) const {
1308 2281 : return value >= m_min && value <= m_max;
1309 : }
1310 :
1311 5 : bool is_exact() const { return m_min == m_max; }
1312 :
1313 25 : bool is_right_bounded() const {
1314 25 : return m_max < (std::numeric_limits<std::size_t>::max)();
1315 : }
1316 :
1317 25144 : std::size_t get_min() const { return m_min; }
1318 :
1319 115407 : std::size_t get_max() const { return m_max; }
1320 :
1321 : // Print help message
1322 0 : friend auto operator<<(std::ostream &stream, const NArgsRange &range)
1323 : -> std::ostream & {
1324 0 : if (range.m_min == range.m_max) {
1325 0 : if (range.m_min != 0 && range.m_min != 1) {
1326 0 : stream << "[nargs: " << range.m_min << "] ";
1327 : }
1328 : } else {
1329 0 : if (range.m_max == (std::numeric_limits<std::size_t>::max)()) {
1330 0 : stream << "[nargs: " << range.m_min << " or more] ";
1331 : } else {
1332 0 : stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
1333 : }
1334 : }
1335 0 : return stream;
1336 : }
1337 :
1338 0 : bool operator==(const NArgsRange &rhs) const {
1339 0 : return rhs.m_min == m_min && rhs.m_max == m_max;
1340 : }
1341 :
1342 0 : bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
1343 : };
1344 :
1345 5 : void throw_nargs_range_validation_error() const {
1346 10 : std::stringstream stream;
1347 5 : if (!m_used_name.empty()) {
1348 0 : stream << m_used_name << ": ";
1349 : } else {
1350 5 : stream << m_names.front() << ": ";
1351 : }
1352 5 : if (m_num_args_range.is_exact()) {
1353 5 : stream << m_num_args_range.get_min();
1354 0 : } else if (m_num_args_range.is_right_bounded()) {
1355 0 : stream << m_num_args_range.get_min() << " to "
1356 0 : << m_num_args_range.get_max();
1357 : } else {
1358 0 : stream << m_num_args_range.get_min() << " or more";
1359 : }
1360 5 : stream << " argument(s) expected. " << m_values.size() << " provided.";
1361 5 : throw std::runtime_error(stream.str());
1362 : }
1363 :
1364 0 : void throw_required_arg_not_used_error() const {
1365 0 : std::stringstream stream;
1366 0 : stream << m_names.front() << ": required.";
1367 0 : throw std::runtime_error(stream.str());
1368 : }
1369 :
1370 0 : void throw_required_arg_no_value_provided_error() const {
1371 0 : std::stringstream stream;
1372 0 : stream << m_used_name << ": no value provided.";
1373 0 : throw std::runtime_error(stream.str());
1374 : }
1375 :
1376 : static constexpr int eof = std::char_traits<char>::eof();
1377 :
1378 537874 : static auto lookahead(std::string_view s) -> int {
1379 537874 : if (s.empty()) {
1380 671 : return eof;
1381 : }
1382 537203 : return static_cast<int>(static_cast<unsigned char>(s[0]));
1383 : }
1384 :
1385 : /*
1386 : * decimal-literal:
1387 : * '0'
1388 : * nonzero-digit digit-sequence_opt
1389 : * integer-part fractional-part
1390 : * fractional-part
1391 : * integer-part '.' exponent-part_opt
1392 : * integer-part exponent-part
1393 : *
1394 : * integer-part:
1395 : * digit-sequence
1396 : *
1397 : * fractional-part:
1398 : * '.' post-decimal-point
1399 : *
1400 : * post-decimal-point:
1401 : * digit-sequence exponent-part_opt
1402 : *
1403 : * exponent-part:
1404 : * 'e' post-e
1405 : * 'E' post-e
1406 : *
1407 : * post-e:
1408 : * sign_opt digit-sequence
1409 : *
1410 : * sign: one of
1411 : * '+' '-'
1412 : */
1413 249054 : static bool is_decimal_literal(std::string_view s) {
1414 249054 : if (s == "inf") {
1415 2 : return true;
1416 : }
1417 :
1418 13079 : auto is_digit = [](auto c) constexpr {
1419 13079 : switch (c) {
1420 12339 : case '0':
1421 : case '1':
1422 : case '2':
1423 : case '3':
1424 : case '4':
1425 : case '5':
1426 : case '6':
1427 : case '7':
1428 : case '8':
1429 : case '9':
1430 12339 : return true;
1431 740 : default:
1432 740 : return false;
1433 : }
1434 : };
1435 :
1436 : // precondition: we have consumed or will consume at least one digit
1437 1619 : auto consume_digits = [=](std::string_view sd) {
1438 : // NOLINTNEXTLINE(readability-qualified-auto)
1439 1619 : auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit);
1440 1619 : return sd.substr(static_cast<std::size_t>(it - std::begin(sd)));
1441 : };
1442 :
1443 249052 : switch (lookahead(s)) {
1444 48 : case '0': {
1445 48 : s.remove_prefix(1);
1446 48 : if (s.empty()) {
1447 0 : return true;
1448 : }
1449 48 : goto integer_part;
1450 : }
1451 895 : case '1':
1452 : case '2':
1453 : case '3':
1454 : case '4':
1455 : case '5':
1456 : case '6':
1457 : case '7':
1458 : case '8':
1459 : case '9': {
1460 895 : s = consume_digits(s);
1461 895 : if (s.empty()) {
1462 207 : return true;
1463 : }
1464 688 : goto integer_part_consumed;
1465 : }
1466 0 : case '.': {
1467 0 : s.remove_prefix(1);
1468 0 : goto post_decimal_point;
1469 : }
1470 248109 : default:
1471 248109 : return false;
1472 : }
1473 :
1474 48 : integer_part:
1475 48 : s = consume_digits(s);
1476 736 : integer_part_consumed:
1477 736 : switch (lookahead(s)) {
1478 672 : case '.': {
1479 672 : s.remove_prefix(1);
1480 672 : if (is_digit(lookahead(s))) {
1481 672 : goto post_decimal_point;
1482 : } else {
1483 0 : goto exponent_part_opt;
1484 : }
1485 : }
1486 0 : case 'e':
1487 : case 'E': {
1488 0 : s.remove_prefix(1);
1489 0 : goto post_e;
1490 : }
1491 64 : default:
1492 64 : return false;
1493 : }
1494 :
1495 672 : post_decimal_point:
1496 672 : if (is_digit(lookahead(s))) {
1497 672 : s = consume_digits(s);
1498 672 : goto exponent_part_opt;
1499 : }
1500 0 : return false;
1501 :
1502 672 : exponent_part_opt:
1503 672 : switch (lookahead(s)) {
1504 668 : case eof:
1505 668 : return true;
1506 4 : case 'e':
1507 : case 'E': {
1508 4 : s.remove_prefix(1);
1509 4 : goto post_e;
1510 : }
1511 0 : default:
1512 0 : return false;
1513 : }
1514 :
1515 4 : post_e:
1516 4 : switch (lookahead(s)) {
1517 4 : case '-':
1518 : case '+':
1519 4 : s.remove_prefix(1);
1520 : }
1521 4 : if (is_digit(lookahead(s))) {
1522 4 : s = consume_digits(s);
1523 4 : return s.empty();
1524 : }
1525 0 : return false;
1526 : }
1527 :
1528 260765 : static bool is_optional(std::string_view name,
1529 : std::string_view prefix_chars) {
1530 260765 : return !is_positional(name, prefix_chars);
1531 : }
1532 :
1533 : /*
1534 : * Get argument value given a type
1535 : * @throws std::logic_error in case of incompatible types
1536 : */
1537 4 : template <typename T> T get() const {
1538 4 : if (!m_values.empty()) {
1539 : if constexpr (details::IsContainer<T>) {
1540 : return any_cast_container<T>(m_values);
1541 : } else {
1542 4 : return std::any_cast<T>(m_values.front());
1543 : }
1544 : }
1545 0 : if (m_default_value.has_value()) {
1546 0 : return std::any_cast<T>(m_default_value);
1547 : }
1548 : if constexpr (details::IsContainer<T>) {
1549 : if (!m_accepts_optional_like_value) {
1550 : return any_cast_container<T>(m_values);
1551 : }
1552 : }
1553 :
1554 0 : throw std::logic_error("No value provided for '" + m_names.back() + "'.");
1555 : }
1556 :
1557 : /*
1558 : * Get argument value given a type.
1559 : * @pre The object has no default value.
1560 : * @returns The stored value if any, std::nullopt otherwise.
1561 : */
1562 19134 : template <typename T> auto present() const -> std::optional<T> {
1563 19134 : if (m_default_value.has_value()) {
1564 0 : throw std::logic_error("Argument with default value always presents");
1565 : }
1566 19134 : if (m_values.empty()) {
1567 17204 : return std::nullopt;
1568 : }
1569 : if constexpr (details::IsContainer<T>) {
1570 1930 : return any_cast_container<T>(m_values);
1571 : }
1572 : return std::any_cast<T>(m_values.front());
1573 : }
1574 :
1575 : template <typename T>
1576 1930 : static auto any_cast_container(const std::vector<std::any> &operand) -> T {
1577 : using ValueType = typename T::value_type;
1578 :
1579 1930 : T result;
1580 1930 : std::transform(
1581 : std::begin(operand), std::end(operand), std::back_inserter(result),
1582 6221 : [](const auto &value) { return std::any_cast<ValueType>(value); });
1583 1930 : return result;
1584 : }
1585 :
1586 265220 : void set_usage_newline_counter(int i) { m_usage_newline_counter = i; }
1587 :
1588 265220 : void set_group_idx(std::size_t i) { m_group_idx = i; }
1589 :
1590 : std::vector<std::string> m_names;
1591 : std::string_view m_used_name;
1592 : std::string m_help;
1593 : std::string m_metavar;
1594 : std::any m_default_value;
1595 : std::string m_default_value_repr;
1596 : std::optional<std::string>
1597 : m_default_value_str; // used for checking default_value against choices
1598 : std::any m_implicit_value;
1599 : std::optional<std::vector<std::string>> m_choices{std::nullopt};
1600 : using valued_action = std::function<std::any(const std::string &)>;
1601 : using void_action = std::function<void(const std::string &)>;
1602 : std::vector<std::variant<valued_action, void_action>> m_actions;
1603 : std::variant<valued_action, void_action> m_default_action{
1604 : std::in_place_type<valued_action>,
1605 498 : [](const std::string &value) { return value; }};
1606 : std::vector<std::any> m_values;
1607 : NArgsRange m_num_args_range{1, 1};
1608 : // Bit field of bool values. Set default value in ctor.
1609 : bool m_accepts_optional_like_value : 1;
1610 : bool m_is_optional : 1;
1611 : bool m_is_required : 1;
1612 : bool m_is_repeatable : 1;
1613 : bool m_is_used : 1;
1614 : bool m_is_hidden : 1; // if set, does not appear in usage or help
1615 : std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
1616 : int m_usage_newline_counter = 0;
1617 : std::size_t m_group_idx = 0;
1618 : };
1619 :
1620 : class ArgumentParser {
1621 : public:
1622 5422 : explicit ArgumentParser(std::string program_name = {},
1623 : std::string version = "1.0",
1624 : default_arguments add_args = default_arguments::all,
1625 : bool exit_on_default_arguments = true,
1626 : std::ostream &os = std::cout)
1627 10844 : : m_program_name(std::move(program_name)), m_version(std::move(version)),
1628 : m_exit_on_default_arguments(exit_on_default_arguments),
1629 5422 : m_parser_path(m_program_name) {
1630 5422 : if ((add_args & default_arguments::help) == default_arguments::help) {
1631 0 : add_argument("-h", "--help")
1632 0 : .action([&](const auto & /*unused*/) {
1633 0 : os << help().str();
1634 0 : if (m_exit_on_default_arguments) {
1635 0 : std::exit(0);
1636 : }
1637 0 : })
1638 0 : .default_value(false)
1639 0 : .help("shows help message and exits")
1640 0 : .implicit_value(true)
1641 0 : .nargs(0);
1642 : }
1643 5422 : if ((add_args & default_arguments::version) == default_arguments::version) {
1644 0 : add_argument("-v", "--version")
1645 0 : .action([&](const auto & /*unused*/) {
1646 0 : os << m_version << std::endl;
1647 0 : if (m_exit_on_default_arguments) {
1648 0 : std::exit(0);
1649 : }
1650 0 : })
1651 0 : .default_value(false)
1652 0 : .help("prints version information and exits")
1653 0 : .implicit_value(true)
1654 0 : .nargs(0);
1655 : }
1656 5422 : }
1657 :
1658 5263 : ~ArgumentParser() = default;
1659 :
1660 : // ArgumentParser is meant to be used in a single function.
1661 : // Setup everything and parse arguments in one place.
1662 : //
1663 : // ArgumentParser internally uses std::string_views,
1664 : // references, iterators, etc.
1665 : // Many of these elements become invalidated after a copy or move.
1666 : ArgumentParser(const ArgumentParser &other) = delete;
1667 : ArgumentParser &operator=(const ArgumentParser &other) = delete;
1668 : ArgumentParser(ArgumentParser &&) noexcept = delete;
1669 : ArgumentParser &operator=(ArgumentParser &&) = delete;
1670 :
1671 : explicit operator bool() const {
1672 : auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
1673 : [](auto &it) { return it.second->m_is_used; });
1674 : auto subparser_used =
1675 : std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
1676 : [](auto &it) { return it.second; });
1677 :
1678 : return m_is_parsed && (arg_used || subparser_used);
1679 : }
1680 :
1681 : // Parameter packing
1682 : // Call add_argument with variadic number of string arguments
1683 230132 : template <typename... Targs> Argument &add_argument(Targs... f_args) {
1684 : using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
1685 460264 : auto argument =
1686 230132 : m_optional_arguments.emplace(std::cend(m_optional_arguments),
1687 230132 : m_prefix_chars, array_of_sv{f_args...});
1688 :
1689 230132 : if (!argument->m_is_optional) {
1690 5560 : m_positional_arguments.splice(std::cend(m_positional_arguments),
1691 2780 : m_optional_arguments, argument);
1692 : }
1693 230132 : argument->set_usage_newline_counter(m_usage_newline_counter);
1694 230132 : argument->set_group_idx(m_group_names.size());
1695 :
1696 230132 : index_argument(argument);
1697 460264 : return *argument;
1698 : }
1699 :
1700 : class MutuallyExclusiveGroup {
1701 : friend class ArgumentParser;
1702 :
1703 : public:
1704 : MutuallyExclusiveGroup() = delete;
1705 :
1706 15528 : explicit MutuallyExclusiveGroup(ArgumentParser &parent,
1707 : bool required = false)
1708 15528 : : m_parent(parent), m_required(required), m_elements({}) {}
1709 :
1710 : MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
1711 : MutuallyExclusiveGroup &
1712 : operator=(const MutuallyExclusiveGroup &other) = delete;
1713 :
1714 11738 : MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
1715 11738 : : m_parent(other.m_parent), m_required(other.m_required),
1716 11738 : m_elements(std::move(other.m_elements)) {
1717 11738 : other.m_elements.clear();
1718 11738 : }
1719 :
1720 35088 : template <typename... Targs> Argument &add_argument(Targs... f_args) {
1721 35088 : auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
1722 35088 : m_elements.push_back(&argument);
1723 35088 : argument.set_usage_newline_counter(m_parent.m_usage_newline_counter);
1724 35088 : argument.set_group_idx(m_parent.m_group_names.size());
1725 35088 : return argument;
1726 : }
1727 :
1728 : private:
1729 : ArgumentParser &m_parent;
1730 : bool m_required{false};
1731 : std::vector<Argument *> m_elements{};
1732 : };
1733 :
1734 15528 : MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
1735 15528 : m_mutually_exclusive_groups.emplace_back(*this, required);
1736 15528 : return m_mutually_exclusive_groups.back();
1737 : }
1738 :
1739 : // Parameter packed add_parents method
1740 : // Accepts a variadic number of ArgumentParser objects
1741 : template <typename... Targs>
1742 : ArgumentParser &add_parents(const Targs &... f_args) {
1743 : for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
1744 : for (const auto &argument : parent_parser.m_positional_arguments) {
1745 : auto it = m_positional_arguments.insert(
1746 : std::cend(m_positional_arguments), argument);
1747 : index_argument(it);
1748 : }
1749 : for (const auto &argument : parent_parser.m_optional_arguments) {
1750 : auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
1751 : argument);
1752 : index_argument(it);
1753 : }
1754 : }
1755 : return *this;
1756 : }
1757 :
1758 : // Ask for the next optional arguments to be displayed on a separate
1759 : // line in usage() output. Only effective if set_usage_max_line_width() is
1760 : // also used.
1761 8227 : ArgumentParser &add_usage_newline() {
1762 8227 : ++m_usage_newline_counter;
1763 8227 : return *this;
1764 : }
1765 :
1766 : // Ask for the next optional arguments to be displayed in a separate section
1767 : // in usage() and help (<< *this) output.
1768 : // For usage(), this is only effective if set_usage_max_line_width() is
1769 : // also used.
1770 3148 : ArgumentParser &add_group(std::string group_name) {
1771 3148 : m_group_names.emplace_back(std::move(group_name));
1772 3148 : return *this;
1773 : }
1774 :
1775 5422 : ArgumentParser &add_description(std::string description) {
1776 5422 : m_description = std::move(description);
1777 5422 : return *this;
1778 : }
1779 :
1780 4819 : ArgumentParser &add_epilog(std::string epilog) {
1781 4819 : m_epilog = std::move(epilog);
1782 4819 : return *this;
1783 : }
1784 :
1785 : // Add a un-documented/hidden alias for an argument.
1786 : // Ideally we'd want this to be a method of Argument, but Argument
1787 : // does not own its owing ArgumentParser.
1788 6965 : ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) {
1789 80548 : for (auto it = m_optional_arguments.begin();
1790 154131 : it != m_optional_arguments.end(); ++it) {
1791 80548 : if (&(*it) == &arg) {
1792 6965 : m_argument_map.insert_or_assign(std::string(alias), it);
1793 6965 : return *this;
1794 : }
1795 : }
1796 : throw std::logic_error(
1797 0 : "Argument is not an optional argument of this parser");
1798 : }
1799 :
1800 : /* Getter for arguments and subparsers.
1801 : * @throws std::logic_error in case of an invalid argument or subparser name
1802 : */
1803 : template <typename T = Argument> T &at(std::string_view name) {
1804 : if constexpr (std::is_same_v<T, Argument>) {
1805 : return (*this)[name];
1806 : } else {
1807 : std::string str_name(name);
1808 : auto subparser_it = m_subparser_map.find(str_name);
1809 : if (subparser_it != m_subparser_map.end()) {
1810 : return subparser_it->second->get();
1811 : }
1812 : throw std::logic_error("No such subparser: " + str_name);
1813 : }
1814 : }
1815 :
1816 : ArgumentParser &set_prefix_chars(std::string prefix_chars) {
1817 : m_prefix_chars = std::move(prefix_chars);
1818 : return *this;
1819 : }
1820 :
1821 : ArgumentParser &set_assign_chars(std::string assign_chars) {
1822 : m_assign_chars = std::move(assign_chars);
1823 : return *this;
1824 : }
1825 :
1826 : /* Call parse_args_internal - which does all the work
1827 : * Then, validate the parsed arguments
1828 : * This variant is used mainly for testing
1829 : * @throws std::runtime_error in case of any invalid argument
1830 : */
1831 4808 : void parse_args(const std::vector<std::string> &arguments) {
1832 4808 : parse_args_internal(arguments);
1833 : // Check if all arguments are parsed
1834 240181 : for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1835 235443 : argument->validate();
1836 : }
1837 :
1838 : // Check each mutually exclusive group and make sure
1839 : // there are no constraint violations
1840 20048 : for (const auto &group : m_mutually_exclusive_groups) {
1841 15311 : auto mutex_argument_used{false};
1842 15311 : Argument *mutex_argument_it{nullptr};
1843 49882 : for (Argument *arg : group.m_elements) {
1844 34572 : if (!mutex_argument_used && arg->m_is_used) {
1845 518 : mutex_argument_used = true;
1846 518 : mutex_argument_it = arg;
1847 34054 : } else if (mutex_argument_used && arg->m_is_used) {
1848 : // Violation
1849 2 : throw std::runtime_error("Argument '" + arg->get_usage_full() +
1850 2 : "' not allowed with '" +
1851 3 : mutex_argument_it->get_usage_full() + "'");
1852 : }
1853 : }
1854 :
1855 15310 : if (!mutex_argument_used && group.m_required) {
1856 : // at least one argument from the group is
1857 : // required
1858 0 : std::string argument_names{};
1859 0 : std::size_t i = 0;
1860 0 : std::size_t size = group.m_elements.size();
1861 0 : for (Argument *arg : group.m_elements) {
1862 0 : if (i + 1 == size) {
1863 : // last
1864 0 : argument_names += "'" + arg->get_usage_full() + "' ";
1865 : } else {
1866 0 : argument_names += "'" + arg->get_usage_full() + "' or ";
1867 : }
1868 0 : i += 1;
1869 : }
1870 0 : throw std::runtime_error("One of the arguments " + argument_names +
1871 0 : "is required");
1872 : }
1873 : }
1874 4737 : }
1875 :
1876 : /* Call parse_known_args_internal - which does all the work
1877 : * Then, validate the parsed arguments
1878 : * This variant is used mainly for testing
1879 : * @throws std::runtime_error in case of any invalid argument
1880 : */
1881 : std::vector<std::string>
1882 : parse_known_args(const std::vector<std::string> &arguments) {
1883 : auto unknown_arguments = parse_known_args_internal(arguments);
1884 : // Check if all arguments are parsed
1885 : for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1886 : argument->validate();
1887 : }
1888 : return unknown_arguments;
1889 : }
1890 :
1891 : /* Main entry point for parsing command-line arguments using this
1892 : * ArgumentParser
1893 : * @throws std::runtime_error in case of any invalid argument
1894 : */
1895 : // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
1896 : void parse_args(int argc, const char *const argv[]) {
1897 : parse_args({argv, argv + argc});
1898 : }
1899 :
1900 : /* Main entry point for parsing command-line arguments using this
1901 : * ArgumentParser
1902 : * @throws std::runtime_error in case of any invalid argument
1903 : */
1904 : // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
1905 : auto parse_known_args(int argc, const char *const argv[]) {
1906 : return parse_known_args({argv, argv + argc});
1907 : }
1908 :
1909 : /* Getter for options with default values.
1910 : * @throws std::logic_error if parse_args() has not been previously called
1911 : * @throws std::logic_error if there is no such option
1912 : * @throws std::logic_error if the option has no value
1913 : * @throws std::bad_any_cast if the option is not of type T
1914 : */
1915 4 : template <typename T = std::string> T get(std::string_view arg_name) const {
1916 4 : if (!m_is_parsed) {
1917 0 : throw std::logic_error("Nothing parsed, no arguments are available.");
1918 : }
1919 4 : return (*this)[arg_name].get<T>();
1920 : }
1921 :
1922 : /* Getter for options without default values.
1923 : * @pre The option has no default value.
1924 : * @throws std::logic_error if there is no such option
1925 : * @throws std::bad_any_cast if the option is not of type T
1926 : */
1927 : template <typename T = std::string>
1928 19134 : auto present(std::string_view arg_name) const -> std::optional<T> {
1929 19134 : return (*this)[arg_name].present<T>();
1930 : }
1931 :
1932 : /* Getter that returns true for user-supplied options. Returns false if not
1933 : * user-supplied, even with a default value.
1934 : */
1935 4632 : auto is_used(std::string_view arg_name) const {
1936 4632 : return (*this)[arg_name].m_is_used;
1937 : }
1938 :
1939 : /* Getter that returns true if a subcommand is used.
1940 : */
1941 30 : auto is_subcommand_used(std::string_view subcommand_name) const {
1942 30 : return m_subparser_used.at(std::string(subcommand_name));
1943 : }
1944 :
1945 : /* Getter that returns true if a subcommand is used.
1946 : */
1947 : auto is_subcommand_used(const ArgumentParser &subparser) const {
1948 : return is_subcommand_used(subparser.m_program_name);
1949 : }
1950 :
1951 : /* Indexing operator. Return a reference to an Argument object
1952 : * Used in conjunction with Argument.operator== e.g., parser["foo"] == true
1953 : * @throws std::logic_error in case of an invalid argument name
1954 : */
1955 23770 : Argument &operator[](std::string_view arg_name) const {
1956 47540 : std::string name(arg_name);
1957 23770 : auto it = m_argument_map.find(name);
1958 23770 : if (it != m_argument_map.end()) {
1959 23750 : return *(it->second);
1960 : }
1961 20 : if (!is_valid_prefix_char(arg_name.front())) {
1962 0 : const auto legal_prefix_char = get_any_valid_prefix_char();
1963 0 : const auto prefix = std::string(1, legal_prefix_char);
1964 :
1965 : // "-" + arg_name
1966 0 : name = prefix + name;
1967 0 : it = m_argument_map.find(name);
1968 0 : if (it != m_argument_map.end()) {
1969 0 : return *(it->second);
1970 : }
1971 : // "--" + arg_name
1972 0 : name = prefix + name;
1973 0 : it = m_argument_map.find(name);
1974 0 : if (it != m_argument_map.end()) {
1975 0 : return *(it->second);
1976 : }
1977 : }
1978 20 : throw std::logic_error("No such argument: " + std::string(arg_name));
1979 : }
1980 :
1981 : // Print help message
1982 0 : friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
1983 : -> std::ostream & {
1984 0 : stream.setf(std::ios_base::left);
1985 :
1986 0 : auto longest_arg_length = parser.get_length_of_longest_argument();
1987 :
1988 0 : stream << parser.usage() << "\n\n";
1989 :
1990 0 : if (!parser.m_description.empty()) {
1991 0 : stream << parser.m_description << "\n\n";
1992 : }
1993 :
1994 0 : const bool has_visible_positional_args = std::find_if(
1995 : parser.m_positional_arguments.begin(),
1996 : parser.m_positional_arguments.end(),
1997 0 : [](const auto &argument) {
1998 0 : return !argument.m_is_hidden; }) !=
1999 0 : parser.m_positional_arguments.end();
2000 0 : if (has_visible_positional_args) {
2001 0 : stream << "Positional arguments:\n";
2002 : }
2003 :
2004 0 : for (const auto &argument : parser.m_positional_arguments) {
2005 0 : if (!argument.m_is_hidden) {
2006 0 : stream.width(static_cast<std::streamsize>(longest_arg_length));
2007 0 : stream << argument;
2008 : }
2009 : }
2010 :
2011 0 : if (!parser.m_optional_arguments.empty()) {
2012 : stream << (!has_visible_positional_args ? "" : "\n")
2013 0 : << "Optional arguments:\n";
2014 : }
2015 :
2016 0 : for (const auto &argument : parser.m_optional_arguments) {
2017 0 : if (argument.m_group_idx == 0 && !argument.m_is_hidden) {
2018 0 : stream.width(static_cast<std::streamsize>(longest_arg_length));
2019 0 : stream << argument;
2020 : }
2021 : }
2022 :
2023 0 : for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) {
2024 0 : stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
2025 0 : for (const auto &argument : parser.m_optional_arguments) {
2026 0 : if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) {
2027 0 : stream.width(static_cast<std::streamsize>(longest_arg_length));
2028 0 : stream << argument;
2029 : }
2030 : }
2031 : }
2032 :
2033 0 : bool has_visible_subcommands = std::any_of(
2034 : parser.m_subparser_map.begin(), parser.m_subparser_map.end(),
2035 0 : [](auto &p) { return !p.second->get().m_suppress; });
2036 :
2037 0 : if (has_visible_subcommands) {
2038 0 : stream << (parser.m_positional_arguments.empty()
2039 0 : ? (parser.m_optional_arguments.empty() ? "" : "\n")
2040 : : "\n")
2041 0 : << "Subcommands:\n";
2042 0 : for (const auto &[command, subparser] : parser.m_subparser_map) {
2043 0 : if (subparser->get().m_suppress) {
2044 0 : continue;
2045 : }
2046 :
2047 0 : stream << std::setw(2) << " ";
2048 0 : if (longest_arg_length >= 2) {
2049 0 : stream << std::setw(static_cast<int>(longest_arg_length - 2))
2050 0 : << command;
2051 : }
2052 0 : stream << " " << subparser->get().m_description << "\n";
2053 : }
2054 : }
2055 :
2056 0 : if (!parser.m_epilog.empty()) {
2057 0 : stream << '\n';
2058 0 : stream << parser.m_epilog << "\n\n";
2059 : }
2060 :
2061 0 : return stream;
2062 : }
2063 :
2064 : // Format help message
2065 0 : auto help() const -> std::stringstream {
2066 0 : std::stringstream out;
2067 0 : out << *this;
2068 0 : return out;
2069 : }
2070 :
2071 : // Sets the maximum width for a line of the Usage message
2072 5422 : ArgumentParser &set_usage_max_line_width(size_t w) {
2073 5422 : this->m_usage_max_line_width = w;
2074 5422 : return *this;
2075 : }
2076 :
2077 : // Asks to display arguments of mutually exclusive group on separate lines in
2078 : // the Usage message
2079 5422 : ArgumentParser &set_usage_break_on_mutex() {
2080 5422 : this->m_usage_break_on_mutex = true;
2081 5422 : return *this;
2082 : }
2083 :
2084 : // Format usage part of help only
2085 16 : auto usage() const -> std::string {
2086 32 : std::stringstream stream;
2087 :
2088 32 : std::string curline("Usage: ");
2089 16 : curline += this->m_parser_path;
2090 : const bool multiline_usage =
2091 16 : this->m_usage_max_line_width < std::numeric_limits<std::size_t>::max();
2092 16 : const size_t indent_size = curline.size();
2093 :
2094 21 : const auto deal_with_options_of_group = [&](std::size_t group_idx) {
2095 21 : bool found_options = false;
2096 : // Add any options inline here
2097 21 : const MutuallyExclusiveGroup *cur_mutex = nullptr;
2098 21 : int usage_newline_counter = -1;
2099 922 : for (const auto &argument : this->m_optional_arguments) {
2100 901 : if (argument.m_is_hidden) {
2101 402 : continue;
2102 : }
2103 839 : if (multiline_usage) {
2104 839 : if (argument.m_group_idx != group_idx) {
2105 340 : continue;
2106 : }
2107 499 : if (usage_newline_counter != argument.m_usage_newline_counter) {
2108 35 : if (usage_newline_counter >= 0) {
2109 14 : if (curline.size() > indent_size) {
2110 14 : stream << curline << std::endl;
2111 14 : curline = std::string(indent_size, ' ');
2112 : }
2113 : }
2114 35 : usage_newline_counter = argument.m_usage_newline_counter;
2115 : }
2116 : }
2117 499 : found_options = true;
2118 998 : const std::string arg_inline_usage = argument.get_inline_usage();
2119 : const MutuallyExclusiveGroup *arg_mutex =
2120 499 : get_belonging_mutex(&argument);
2121 499 : if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) {
2122 26 : curline += ']';
2123 26 : if (this->m_usage_break_on_mutex) {
2124 26 : stream << curline << std::endl;
2125 26 : curline = std::string(indent_size, ' ');
2126 : }
2127 473 : } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) {
2128 28 : if ((this->m_usage_break_on_mutex && curline.size() > indent_size) ||
2129 1 : curline.size() + 3 + arg_inline_usage.size() >
2130 1 : this->m_usage_max_line_width) {
2131 26 : stream << curline << std::endl;
2132 26 : curline = std::string(indent_size, ' ');
2133 : }
2134 27 : curline += " [";
2135 446 : } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) {
2136 42 : if (cur_mutex != arg_mutex) {
2137 4 : curline += ']';
2138 4 : if (this->m_usage_break_on_mutex ||
2139 0 : curline.size() + 3 + arg_inline_usage.size() >
2140 0 : this->m_usage_max_line_width) {
2141 4 : stream << curline << std::endl;
2142 4 : curline = std::string(indent_size, ' ');
2143 : }
2144 4 : curline += " [";
2145 : } else {
2146 38 : curline += '|';
2147 : }
2148 : }
2149 499 : cur_mutex = arg_mutex;
2150 499 : if (curline.size() + 1 + arg_inline_usage.size() >
2151 499 : this->m_usage_max_line_width) {
2152 88 : stream << curline << std::endl;
2153 88 : curline = std::string(indent_size, ' ');
2154 88 : curline += " ";
2155 411 : } else if (cur_mutex == nullptr) {
2156 346 : curline += " ";
2157 : }
2158 499 : curline += arg_inline_usage;
2159 : }
2160 21 : if (cur_mutex != nullptr) {
2161 1 : curline += ']';
2162 : }
2163 21 : return found_options;
2164 16 : };
2165 :
2166 16 : const bool found_options = deal_with_options_of_group(0);
2167 :
2168 32 : if (found_options && multiline_usage &&
2169 16 : !this->m_positional_arguments.empty()) {
2170 12 : stream << curline << std::endl;
2171 12 : curline = std::string(indent_size, ' ');
2172 : }
2173 : // Put positional arguments after the optionals
2174 41 : for (const auto &argument : this->m_positional_arguments) {
2175 25 : if (argument.m_is_hidden) {
2176 0 : continue;
2177 : }
2178 25 : const std::string pos_arg = !argument.m_metavar.empty()
2179 : ? argument.m_metavar
2180 50 : : argument.m_names.front();
2181 25 : if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) {
2182 0 : stream << curline << std::endl;
2183 0 : curline = std::string(indent_size, ' ');
2184 : }
2185 25 : curline += " ";
2186 28 : if (argument.m_num_args_range.get_min() == 0 &&
2187 3 : !argument.m_num_args_range.is_right_bounded()) {
2188 3 : curline += "[";
2189 3 : curline += pos_arg;
2190 3 : curline += "]...";
2191 44 : } else if (argument.m_num_args_range.get_min() == 1 &&
2192 22 : !argument.m_num_args_range.is_right_bounded()) {
2193 2 : curline += pos_arg;
2194 2 : curline += "...";
2195 : } else {
2196 20 : curline += pos_arg;
2197 : }
2198 : }
2199 :
2200 16 : if (multiline_usage) {
2201 : // Display options of other groups
2202 21 : for (std::size_t i = 0; i < m_group_names.size(); ++i) {
2203 5 : stream << curline << std::endl << std::endl;
2204 5 : stream << m_group_names[i] << ":" << std::endl;
2205 5 : curline = std::string(indent_size, ' ');
2206 5 : deal_with_options_of_group(i + 1);
2207 : }
2208 : }
2209 :
2210 16 : stream << curline;
2211 :
2212 : // Put subcommands after positional arguments
2213 16 : if (!m_subparser_map.empty()) {
2214 4 : stream << " {";
2215 4 : std::size_t i{0};
2216 20 : for (const auto &[command, subparser] : m_subparser_map) {
2217 16 : if (subparser->get().m_suppress) {
2218 0 : continue;
2219 : }
2220 :
2221 16 : if (i == 0) {
2222 4 : stream << command;
2223 : } else {
2224 12 : stream << "," << command;
2225 : }
2226 16 : ++i;
2227 : }
2228 4 : stream << "}";
2229 : }
2230 :
2231 32 : return stream.str();
2232 : }
2233 :
2234 : // Printing the one and only help message
2235 : // I've stuck with a simple message format, nothing fancy.
2236 : [[deprecated("Use cout << program; instead. See also help().")]] std::string
2237 : print_help() const {
2238 : auto out = help();
2239 : std::cout << out.rdbuf();
2240 : return out.str();
2241 : }
2242 :
2243 603 : void add_subparser(ArgumentParser &parser) {
2244 603 : parser.m_parser_path = m_program_name + " " + parser.m_program_name;
2245 603 : auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
2246 603 : m_subparser_map.insert_or_assign(parser.m_program_name, it);
2247 603 : m_subparser_used.insert_or_assign(parser.m_program_name, false);
2248 603 : }
2249 :
2250 : void set_suppress(bool suppress) { m_suppress = suppress; }
2251 :
2252 : protected:
2253 499 : const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const {
2254 1840 : for (const auto &mutex : m_mutually_exclusive_groups) {
2255 1410 : if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) !=
2256 2820 : mutex.m_elements.end()) {
2257 69 : return &mutex;
2258 : }
2259 : }
2260 430 : return nullptr;
2261 : }
2262 :
2263 20 : bool is_valid_prefix_char(char c) const {
2264 20 : return m_prefix_chars.find(c) != std::string::npos;
2265 : }
2266 :
2267 0 : char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
2268 :
2269 : /*
2270 : * Pre-process this argument list. Anything starting with "--", that
2271 : * contains an =, where the prefix before the = has an entry in the
2272 : * options table, should be split.
2273 : */
2274 : std::vector<std::string>
2275 10589 : preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
2276 10589 : std::vector<std::string> arguments{};
2277 73733 : for (const auto &arg : raw_arguments) {
2278 :
2279 : const auto argument_starts_with_prefix_chars =
2280 84941 : [this](const std::string &a) -> bool {
2281 42472 : if (!a.empty()) {
2282 :
2283 81089 : const auto legal_prefix = [this](char c) -> bool {
2284 81089 : return m_prefix_chars.find(c) != std::string::npos;
2285 42469 : };
2286 :
2287 : // Windows-style
2288 : // if '/' is a legal prefix char
2289 : // then allow single '/' followed by argument name, followed by an
2290 : // assign char, e.g., ':' e.g., 'test.exe /A:Foo'
2291 42469 : const auto windows_style = legal_prefix('/');
2292 :
2293 42469 : if (windows_style) {
2294 0 : if (legal_prefix(a[0])) {
2295 37228 : return true;
2296 : }
2297 : } else {
2298 : // Slash '/' is not a legal prefix char
2299 : // For all other characters, only support long arguments
2300 : // i.e., the argument must start with 2 prefix chars, e.g,
2301 : // '--foo' e,g, './test --foo=Bar -DARG=yes'
2302 42469 : if (a.size() > 1) {
2303 37228 : return (legal_prefix(a[0]) && legal_prefix(a[1]));
2304 : }
2305 : }
2306 : }
2307 5244 : return false;
2308 63144 : };
2309 :
2310 : // Check that:
2311 : // - We don't have an argument named exactly this
2312 : // - The argument starts with a prefix char, e.g., "--"
2313 : // - The argument contains an assign char, e.g., "="
2314 63144 : auto assign_char_pos = arg.find_first_of(m_assign_chars);
2315 :
2316 105616 : if (m_argument_map.find(arg) == m_argument_map.end() &&
2317 105616 : argument_starts_with_prefix_chars(arg) &&
2318 : assign_char_pos != std::string::npos) {
2319 : // Get the name of the potential option, and check it exists
2320 5 : std::string opt_name = arg.substr(0, assign_char_pos);
2321 5 : if (m_argument_map.find(opt_name) != m_argument_map.end()) {
2322 : // This is the name of an option! Split it into two parts
2323 5 : arguments.push_back(std::move(opt_name));
2324 5 : arguments.push_back(arg.substr(assign_char_pos + 1));
2325 5 : continue;
2326 : }
2327 : }
2328 : // If we've fallen through to here, then it's a standard argument
2329 63139 : arguments.push_back(arg);
2330 : }
2331 10589 : return arguments;
2332 : }
2333 :
2334 : /*
2335 : * @throws std::runtime_error in case of any invalid argument
2336 : */
2337 4808 : void parse_args_internal(const std::vector<std::string> &raw_arguments) {
2338 4839 : auto arguments = preprocess_arguments(raw_arguments);
2339 4808 : if (m_program_name.empty() && !arguments.empty()) {
2340 0 : m_program_name = arguments.front();
2341 : }
2342 4808 : auto end = std::end(arguments);
2343 4808 : auto positional_argument_it = std::begin(m_positional_arguments);
2344 15693 : for (auto it = std::next(std::begin(arguments)); it != end;) {
2345 10950 : const auto ¤t_argument = *it;
2346 10950 : if (Argument::is_positional(current_argument, m_prefix_chars)) {
2347 1313 : if (positional_argument_it == std::end(m_positional_arguments)) {
2348 :
2349 : // Check sub-parsers
2350 0 : auto subparser_it = m_subparser_map.find(current_argument);
2351 0 : if (subparser_it != m_subparser_map.end()) {
2352 :
2353 : // build list of remaining args
2354 : const auto unprocessed_arguments =
2355 0 : std::vector<std::string>(it, end);
2356 :
2357 : // invoke subparser
2358 0 : m_is_parsed = true;
2359 0 : m_subparser_used[current_argument] = true;
2360 0 : return subparser_it->second->get().parse_args(
2361 0 : unprocessed_arguments);
2362 : }
2363 :
2364 0 : if (m_positional_arguments.empty()) {
2365 :
2366 : // Ask the user if they argument they provided was a typo
2367 : // for some sub-parser,
2368 : // e.g., user provided `git totes` instead of `git notes`
2369 0 : if (!m_subparser_map.empty()) {
2370 : throw std::runtime_error(
2371 0 : "Failed to parse '" + current_argument + "', did you mean '" +
2372 0 : std::string{details::get_most_similar_string(
2373 0 : m_subparser_map, current_argument)} +
2374 0 : "'");
2375 : }
2376 :
2377 : // Ask the user if they meant to use a specific optional argument
2378 0 : if (!m_optional_arguments.empty()) {
2379 0 : for (const auto &opt : m_optional_arguments) {
2380 0 : if (!opt.m_implicit_value.has_value()) {
2381 : // not a flag, requires a value
2382 0 : if (!opt.m_is_used) {
2383 : throw std::runtime_error(
2384 0 : "Zero positional arguments expected, did you mean " +
2385 0 : opt.get_usage_full());
2386 : }
2387 : }
2388 : }
2389 :
2390 0 : throw std::runtime_error("Zero positional arguments expected");
2391 : } else {
2392 0 : throw std::runtime_error("Zero positional arguments expected");
2393 : }
2394 : } else {
2395 : throw std::runtime_error("Maximum number of positional arguments "
2396 0 : "exceeded, failed to parse '" +
2397 0 : current_argument + "'");
2398 : }
2399 : }
2400 1313 : auto argument = positional_argument_it++;
2401 :
2402 : // Deal with the situation of <positional_arg1>... <positional_arg2>
2403 2518 : if (argument->m_num_args_range.get_min() == 1 &&
2404 1318 : argument->m_num_args_range.get_max() == (std::numeric_limits<std::size_t>::max)() &&
2405 213 : positional_argument_it != std::end(m_positional_arguments) &&
2406 200 : std::next(positional_argument_it) == std::end(m_positional_arguments) &&
2407 2618 : positional_argument_it->m_num_args_range.get_min() == 1 &&
2408 100 : positional_argument_it->m_num_args_range.get_max() == 1 ) {
2409 100 : if (std::next(it) != end) {
2410 100 : positional_argument_it->consume(std::prev(end), end);
2411 100 : end = std::prev(end);
2412 : } else {
2413 0 : throw std::runtime_error("Missing " + positional_argument_it->m_names.front());
2414 : }
2415 : }
2416 :
2417 1313 : it = argument->consume(it, end);
2418 1313 : continue;
2419 : }
2420 :
2421 9637 : auto arg_map_it = m_argument_map.find(current_argument);
2422 9637 : if (arg_map_it != m_argument_map.end()) {
2423 9637 : auto argument = arg_map_it->second;
2424 9637 : it = argument->consume(std::next(it), end, arg_map_it->first);
2425 0 : } else if (const auto &compound_arg = current_argument;
2426 0 : compound_arg.size() > 1 &&
2427 0 : is_valid_prefix_char(compound_arg[0]) &&
2428 0 : !is_valid_prefix_char(compound_arg[1])) {
2429 0 : ++it;
2430 0 : for (std::size_t j = 1; j < compound_arg.size(); j++) {
2431 0 : auto hypothetical_arg = std::string{'-', compound_arg[j]};
2432 0 : auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
2433 0 : if (arg_map_it2 != m_argument_map.end()) {
2434 0 : auto argument = arg_map_it2->second;
2435 0 : it = argument->consume(it, end, arg_map_it2->first);
2436 : } else {
2437 0 : throw std::runtime_error("Unknown argument: " + current_argument);
2438 : }
2439 : }
2440 : } else {
2441 0 : throw std::runtime_error("Unknown argument: " + current_argument);
2442 : }
2443 : }
2444 4743 : m_is_parsed = true;
2445 : }
2446 :
2447 : /*
2448 : * Like parse_args_internal but collects unused args into a vector<string>
2449 : */
2450 : std::vector<std::string>
2451 : parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
2452 : auto arguments = preprocess_arguments(raw_arguments);
2453 :
2454 : std::vector<std::string> unknown_arguments{};
2455 :
2456 : if (m_program_name.empty() && !arguments.empty()) {
2457 : m_program_name = arguments.front();
2458 : }
2459 : auto end = std::end(arguments);
2460 : auto positional_argument_it = std::begin(m_positional_arguments);
2461 : for (auto it = std::next(std::begin(arguments)); it != end;) {
2462 : const auto ¤t_argument = *it;
2463 : if (Argument::is_positional(current_argument, m_prefix_chars)) {
2464 : if (positional_argument_it == std::end(m_positional_arguments)) {
2465 :
2466 : // Check sub-parsers
2467 : auto subparser_it = m_subparser_map.find(current_argument);
2468 : if (subparser_it != m_subparser_map.end()) {
2469 :
2470 : // build list of remaining args
2471 : const auto unprocessed_arguments =
2472 : std::vector<std::string>(it, end);
2473 :
2474 : // invoke subparser
2475 : m_is_parsed = true;
2476 : m_subparser_used[current_argument] = true;
2477 : return subparser_it->second->get().parse_known_args_internal(
2478 : unprocessed_arguments);
2479 : }
2480 :
2481 : // save current argument as unknown and go to next argument
2482 : unknown_arguments.push_back(current_argument);
2483 : ++it;
2484 : } else {
2485 : // current argument is the value of a positional argument
2486 : // consume it
2487 : auto argument = positional_argument_it++;
2488 : it = argument->consume(it, end);
2489 : }
2490 : continue;
2491 : }
2492 :
2493 : auto arg_map_it = m_argument_map.find(current_argument);
2494 : if (arg_map_it != m_argument_map.end()) {
2495 : auto argument = arg_map_it->second;
2496 : it = argument->consume(std::next(it), end, arg_map_it->first);
2497 : } else if (const auto &compound_arg = current_argument;
2498 : compound_arg.size() > 1 &&
2499 : is_valid_prefix_char(compound_arg[0]) &&
2500 : !is_valid_prefix_char(compound_arg[1])) {
2501 : ++it;
2502 : for (std::size_t j = 1; j < compound_arg.size(); j++) {
2503 : auto hypothetical_arg = std::string{'-', compound_arg[j]};
2504 : auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
2505 : if (arg_map_it2 != m_argument_map.end()) {
2506 : auto argument = arg_map_it2->second;
2507 : it = argument->consume(it, end, arg_map_it2->first);
2508 : } else {
2509 : unknown_arguments.push_back(current_argument);
2510 : break;
2511 : }
2512 : }
2513 : } else {
2514 : // current argument is an optional-like argument that is unknown
2515 : // save it and move to next argument
2516 : unknown_arguments.push_back(current_argument);
2517 : ++it;
2518 : }
2519 : }
2520 : m_is_parsed = true;
2521 : return unknown_arguments;
2522 : }
2523 :
2524 : // Used by print_help.
2525 0 : std::size_t get_length_of_longest_argument() const {
2526 0 : if (m_argument_map.empty()) {
2527 0 : return 0;
2528 : }
2529 0 : std::size_t max_size = 0;
2530 0 : for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
2531 0 : max_size =
2532 0 : std::max<std::size_t>(max_size, argument->get_arguments_length());
2533 : }
2534 0 : for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
2535 0 : max_size = std::max<std::size_t>(max_size, command.size());
2536 : }
2537 0 : return max_size;
2538 : }
2539 :
2540 : using argument_it = std::list<Argument>::iterator;
2541 : using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
2542 : using argument_parser_it =
2543 : std::list<std::reference_wrapper<ArgumentParser>>::iterator;
2544 :
2545 230132 : void index_argument(argument_it it) {
2546 468457 : for (const auto &name : std::as_const(it->m_names)) {
2547 238325 : m_argument_map.insert_or_assign(name, it);
2548 : }
2549 230132 : }
2550 :
2551 : std::string m_program_name;
2552 : std::string m_version;
2553 : std::string m_description;
2554 : std::string m_epilog;
2555 : bool m_exit_on_default_arguments = true;
2556 : std::string m_prefix_chars{"-"};
2557 : std::string m_assign_chars{"="};
2558 : bool m_is_parsed = false;
2559 : std::list<Argument> m_positional_arguments;
2560 : std::list<Argument> m_optional_arguments;
2561 : std::map<std::string, argument_it> m_argument_map;
2562 : std::string m_parser_path;
2563 : std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
2564 : std::map<std::string, argument_parser_it> m_subparser_map;
2565 : std::map<std::string, bool> m_subparser_used;
2566 : std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
2567 : bool m_suppress = false;
2568 : std::size_t m_usage_max_line_width = std::numeric_limits<std::size_t>::max();
2569 : bool m_usage_break_on_mutex = false;
2570 : int m_usage_newline_counter = 0;
2571 : std::vector<std::string> m_group_names;
2572 : };
2573 :
2574 : } // namespace argparse
|