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