78template <
typename T,
typename =
void>
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 {};
94template <
typename T,
typename =
void>
100 std::
void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
104inline constexpr bool IsStreamable = HasStreamableTraits<T>::value;
106constexpr std::size_t repr_max_container_size = 5;
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;
116 const auto size = val.size();
118 out << repr(*val.begin());
120 std::next(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) {
133 out << repr(*std::prev(val.end()));
137 } else if constexpr (IsStreamable<T>) {
138 std::stringstream out;
142 return "<not representable>";
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;
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;
161constexpr bool standard_unsigned_integer<unsigned long long int> = true;
165constexpr int radix_2 = 2;
166constexpr int radix_8 = 8;
167constexpr int radix_10 = 10;
168constexpr int radix_16 = 16;
171constexpr bool standard_integer =
172 standard_signed_integer<T> || standard_unsigned_integer<T>;
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...> ) {
178 return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...,
179 std::forward<Extra>(x));
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>>>{});
190constexpr auto pointer_range(std::string_view s) noexcept {
191 return std::tuple(s.data(), s.data() + s.size());
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;
200enum class chars_format {
205 general = fixed | scientific
208struct ConsumeBinaryPrefixResult {
210 std::string_view rest;
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)) {
223struct ConsumeHexPrefixResult {
225 std::string_view rest;
228using namespace std::literals;
230constexpr auto consume_hex_prefix(std::string_view s)
231 -> ConsumeHexPrefixResult {
232 if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
239template <class T, auto Param>
240inline auto do_from_chars(std::string_view s) -> T {
242 auto [first, last] = pointer_range(s);
243 auto [ptr, ec] = std::from_chars(first, last, x, Param);
244 if (ec == std::errc()) {
248 throw std::invalid_argument{"pattern '" + std::string(s) +
249 "' does not match to the end"};
251 if (ec == std::errc::invalid_argument) {
252 throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"};
254 if (ec == std::errc::result_out_of_range) {
255 throw std::range_error{"'" + std::string(s) + "' not representable"};
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);
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);
271 throw std::invalid_argument{"pattern not found"};
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) {
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());
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());
303 throw std::invalid_argument{"pattern '" + std::string(s) +
304 "' not identified as hexadecimal"};
308template <class T> struct parse_number<T> {
309 auto operator()(std::string_view s) -> T {
310 auto [ok, rest] = consume_hex_prefix(s);
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());
323 auto [ok_binary, rest_binary] = consume_binary_prefix(s);
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());
336 if (starts_with("0"sv, s)) {
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());
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());
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;
366inline const auto generic_strtod<long double> = WPI_CUSTOM_STRTOLD;
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"};
375 auto [first, last] = pointer_range(s);
379 auto x = generic_strtod<T>(first, &ptr);
384 throw std::invalid_argument{"pattern '" + s +
385 "' does not match to the end"};
387 if (errno == ERANGE) {
388 throw std::range_error{"'" + s + "' not representable"};
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"};
399 if (auto r = consume_binary_prefix(s); r.is_binary) {
400 throw std::invalid_argument{
401 "chars_format::general does not parse binfloat"};
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());
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"};
421 if (auto r = consume_binary_prefix(s); r.is_binary) {
422 throw std::invalid_argument{"chars_format::hex does not parse binfloat"};
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());
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"};
443 if (auto r = consume_binary_prefix(s); !r.is_binary) {
444 throw std::invalid_argument{"chars_format::binary parses binfloat"};
447 return do_strtod<T>(s);
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"};
457 if (auto r = consume_binary_prefix(s); r.is_binary) {
458 throw std::invalid_argument{
459 "chars_format::scientific does not parse binfloat"};
461 if (s.find_first_of("eE") == std::string::npos) {
462 throw std::invalid_argument{
463 "chars_format::scientific requires exponent part"};
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());
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"};
484 if (auto r = consume_binary_prefix(s); r.is_binary) {
485 throw std::invalid_argument{
486 "chars_format::fixed does not parse binfloat"};
488 if (s.find_first_of("eE") != std::string::npos) {
489 throw std::invalid_argument{
490 "chars_format::fixed does not parse exponent part"};
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());
505template <typename StrIt>
506std::string join(StrIt first, StrIt last, const std::string &separator) {
510 std::stringstream value;
513 while (first != last) {
514 value << separator << *first;
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{});
525 template <typename U> static auto test(...) -> std::false_type;
527 static constexpr bool value = decltype(test<T>(0))::value;
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;
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));
544 for (std::size_t i = 0; i <= s1.size(); ++i) {
545 for (std::size_t j = 0; j <= s2.size(); ++j) {
550 } else if (s1[i - 1] == s2[j - 1]) {
551 dp[i][j] = dp[i - 1][j - 1];
553 dp[i][j] = 1 + std::min<std::size_t>({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
558 return dp[s1.size()][s2.size()];
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)();
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;
599 friend class ArgumentParser;
600 friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
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...> )
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]), ...);
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();
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>{}) {}
624 Argument &help(std::string help_text) {
625 m_help = std::move(help_text);
629 Argument &metavar(std::string metavar) {
630 m_metavar = std::move(metavar);
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);
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);
644 m_default_value = std::forward<T>(value);
648 Argument &default_value(const char *value) {
649 return default_value(std::string(value));
652 Argument &required() {
653 m_is_required = true;
657 Argument &implicit_value(std::any value) {
658 m_implicit_value = std::move(value);
659 m_num_args_range = NArgsRange{0, 0};
668 default_value(false);
669 implicit_value(true);
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>,
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));
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);
693 auto &store_into(bool &var) {
695 if (m_default_value.has_value()) {
696 var = std::any_cast<bool>(m_default_value);
698 action([&var](const auto & ) { var = true; });
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);
707 action([&var](const auto &s) {
708 var = details::parse_number<T, details::radix_10>()(s);
713 auto &store_into(double &var) {
714 if (m_default_value.has_value()) {
715 var = std::any_cast<double>(m_default_value);
717 action([&var](const auto &s) {
718 var = details::parse_number<double, details::chars_format::general>()(s);
723 auto &store_into(std::string &var) {
724 if (m_default_value.has_value()) {
725 var = std::any_cast<std::string>(m_default_value);
727 action([&var](const std::string &s) { var = s; });
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);
735 action([this, &var](const std::string &s) {
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);
749 action([this, &var](const std::string &s) {
754 var.push_back(details::parse_number<int, details::radix_10>()(s));
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);
763 action([this, &var](const std::string &s) {
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);
777 action([this, &var](const std::string &s) {
782 var.insert(details::parse_number<int, details::radix_10>()(s));
788 m_is_repeatable = true;
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) || ...);
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>());
836 static_assert(alignof(T) == 0, "No scan specification for T");
842 Argument &nargs(std::size_t num_args) {
843 m_num_args_range = NArgsRange{num_args, num_args};
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};
852 Argument &nargs(nargs_pattern pattern) {
854 case nargs_pattern::optional:
855 m_num_args_range = NArgsRange{0, 1};
857 case nargs_pattern::any:
859 NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
861 case nargs_pattern::at_least_one:
863 NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
869 Argument &remaining() {
870 m_accepts_optional_like_value = true;
871 return nargs(nargs_pattern::any);
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>{};
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)));
892 Argument &choices() {
893 if (!m_choices.has_value()) {
894 throw std::runtime_error("Zero choices provided");
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)...);
906 void find_default_value_in_choices_or_throw() const {
908 const auto &choices = m_choices.value();
910 if (m_default_value.has_value()) {
911 if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
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;
922 throw std::runtime_error(
923 std::string{"Invalid default value "} + m_default_value_repr +
924 " - allowed options: {" + choices_as_csv + "}");
929 template <typename Iterator>
930 void find_value_in_choices_or_throw(Iterator it) const {
932 const auto &choices = m_choices.value();
934 if (std::find(choices.begin(), choices.end(), *it) == choices.end()) {
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;
944 throw std::runtime_error(std::string{"Invalid argument "} +
945 details::repr(*it) + " - allowed options: {" +
946 choices_as_csv + "}");
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));
961 m_used_name = used_name;
963 if (m_choices.has_value()) {
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) {
972 find_value_in_choices_or_throw(it);
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) {
982 m_values.emplace_back(m_implicit_value);
983 std::visit([](const auto &f) { f({}); }, m_action);
988 if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
990 if (num_args_max < dist) {
991 end = std::next(start, static_cast<typename Iterator::difference_type>(
994 if (!m_accepts_optional_like_value) {
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) + "'.");
1005 struct ActionApply {
1006 void operator()(valued_action &f) {
1007 std::transform(first, last, std::back_inserter(self.m_values), f);
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)));
1020 Iterator first, last;
1024 std::visit(ActionApply{start, end, *this}, m_action);
1029 if (m_default_value.has_value()) {
1035 throw std::runtime_error("Too few arguments for '" +
1036 std::string(m_used_name) + "'.");
1042 void validate() const {
1043 if (m_is_optional) {
1045 if (!m_is_used && !m_default_value.has_value() && m_is_required) {
1046 throw_required_arg_not_used_error();
1048 if (m_is_used && m_is_required && m_values.empty()) {
1049 throw_required_arg_no_value_provided_error();
1052 if (!m_num_args_range.contains(m_values.size()) &&
1053 !m_default_value.has_value()) {
1054 throw_nargs_range_validation_error();
1058 if (m_choices.has_value()) {
1061 find_default_value_in_choices_or_throw();
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;
1073 std::string get_usage_full() const {
1074 std::stringstream usage;
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) {
1087 std::string get_inline_usage() const {
1088 std::stringstream usage;
1090 std::string longest_name = m_names.front();
1091 for (const auto &s : m_names) {
1092 if (s.size() > longest_name.size()) {
1096 if (!m_is_required) {
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) {
1108 if (!m_is_required) {
1111 if (m_is_repeatable) {
1117 std::size_t get_arguments_length() const {
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(); });
1123 if (is_positional(m_names.front(), m_prefix_chars)) {
1125 if (!m_metavar.empty()) {
1127 return 2 + m_metavar.size();
1131 return 2 + names_size + (m_names.size() - 1);
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;
1142 friend std::ostream &operator<<(std::ostream &stream,
1143 const Argument &argument) {
1144 std::stringstream name_stream;
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;
1151 name_stream << details::join(argument.m_names.begin(),
1152 argument.m_names.end(), " ");
1155 name_stream << details::join(argument.m_names.begin(),
1156 argument.m_names.end(), ", ");
1158 if (!argument.m_metavar.empty() &&
1159 argument.m_num_args_range == NArgsRange{1, 1}) {
1160 name_stream << " " << argument.m_metavar;
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;
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;
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);
1181 stream << hspace << line;
1184 stream.width(stream_width);
1185 stream << name_padding << hspace << line;
1187 prev += pos - prev + 1;
1190 stream << hspace << argument.m_help;
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;
1200 if (!argument.m_help.empty()) {
1203 stream << argument.m_num_args_range;
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 << "]";
1210 } else if (argument.m_is_required) {
1211 stream << "[required]";
1214 if (argument.m_is_repeatable) {
1218 stream << "[may be repeated]";
1224 template <typename T> bool operator!=(const T &rhs) const {
1225 return !(*this == rhs);
1232 template <typename T> bool operator==(const T &rhs) const {
1233 if constexpr (!details::IsContainer<T>) {
1234 return get<T>() == rhs;
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;
1252 static bool is_positional(std::string_view name,
1253 std::string_view prefix_chars) {
1254 auto first = lookahead(name);
1259 if (prefix_chars.find(static_cast<char>(first)) !=
1260 std::string_view::npos) {
1261 name.remove_prefix(1);
1265 return is_decimal_literal(name);
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");
1283 bool contains(std::size_t value) const {
1284 return value >= m_min && value <= m_max;
1287 bool is_exact() const { return m_min == m_max; }
1289 bool is_right_bounded() const {
1290 return m_max < (std::numeric_limits<std::size_t>::max)();
1293 std::size_t get_min() const { return m_min; }
1295 std::size_t get_max() const { return m_max; }
1298 friend auto operator<<(std::ostream &stream, const NArgsRange &range)
1300 if (range.m_min == range.m_max) {
1301 if (range.m_min != 0 && range.m_min != 1) {
1302 stream << "[nargs: " << range.m_min << "] ";
1305 if (range.m_max == (std::numeric_limits<std::size_t>::max)()) {
1306 stream << "[nargs: " << range.m_min << " or more] ";
1308 stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
1314 bool operator==(const NArgsRange &rhs) const {
1315 return rhs.m_min == m_min && rhs.m_max == m_max;
1318 bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
1321 void throw_nargs_range_validation_error() const {
1322 std::stringstream stream;
1323 if (!m_used_name.empty()) {
1324 stream << m_used_name << ": ";
1326 stream << m_names.front() << ": ";
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();
1334 stream << m_num_args_range.get_min() << " or more";
1336 stream << " argument(s) expected. " << m_values.size() << " provided.";
1337 throw std::runtime_error(stream.str());
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());
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());
1352 static constexpr int eof = std::char_traits<char>::eof();
1354 static auto lookahead(std::string_view s) -> int {
1358 return static_cast<int>(static_cast<unsigned char>(s[0]));
1389 static bool is_decimal_literal(std::string_view s) {
1390 auto is_digit = [](auto c) constexpr {
1409 auto consume_digits = [=](std::string_view sd) {
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)));
1415 switch (lookahead(s)) {
1432 s = consume_digits(s);
1436 goto integer_part_consumed;
1440 goto post_decimal_point;
1447 s = consume_digits(s);
1448 integer_part_consumed:
1449 switch (lookahead(s)) {
1452 if (is_digit(lookahead(s))) {
1453 goto post_decimal_point;
1455 goto exponent_part_opt;
1468 if (is_digit(lookahead(s))) {
1469 s = consume_digits(s);
1470 goto exponent_part_opt;
1475 switch (lookahead(s)) {
1488 switch (lookahead(s)) {
1493 if (is_digit(lookahead(s))) {
1494 s = consume_digits(s);
1500 static bool is_optional(std::string_view name,
1501 std::string_view prefix_chars) {
1502 return !is_positional(name, prefix_chars);
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);
1514 return std::any_cast<T>(m_values.front());
1517 if (m_default_value.has_value()) {
1518 return std::any_cast<T>(m_default_value);
1520 if constexpr (details::IsContainer<T>) {
1521 if (!m_accepts_optional_like_value) {
1522 return any_cast_container<T>(m_values);
1526 throw std::logic_error("No value provided for '" + m_names.back() + "'.");
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");
1538 if (m_values.empty()) {
1539 return std::nullopt;
1541 if constexpr (details::IsContainer<T>) {
1542 return any_cast_container<T>(m_values);
1544 return std::any_cast<T>(m_values.front());
1547 template <typename T>
1548 static auto any_cast_container(const std::vector<std::any> &operand) -> T {
1549 using ValueType = typename T::value_type;
1553 std::begin(operand), std::end(operand), std::back_inserter(result),
1554 [](const auto &value) { return std::any_cast<ValueType>(value); });
1558 void set_usage_newline_counter(int i) { m_usage_newline_counter = i; }
1560 void set_group_idx(std::size_t i) { m_group_idx = i; }
1562 std::vector<std::string> m_names;
1563 std::string_view m_used_name;
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;
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};
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;
1585 bool m_is_hidden : 1;
1586 std::string_view m_prefix_chars;
1587 int m_usage_newline_counter = 0;
1588 std::size_t m_group_idx = 0;
1591class ArgumentParser {
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 & ) {
1605 if (m_exit_on_default_arguments) {
1609 .default_value(false)
1610 .help("shows help message and exits")
1611 .implicit_value(true)
1614 if ((add_args & default_arguments::version) == default_arguments::version) {
1615 add_argument("-v", "--version")
1616 .action([&](const auto & ) {
1617 os << m_version << std::endl;
1618 if (m_exit_on_default_arguments) {
1622 .default_value(false)
1623 .help("prints version information and exits")
1624 .implicit_value(true)
1629 ~ArgumentParser() = default;
1637 ArgumentParser(const ArgumentParser &other) = delete;
1638 ArgumentParser &operator=(const ArgumentParser &other) = delete;
1639 ArgumentParser(ArgumentParser &&) noexcept = delete;
1640 ArgumentParser &operator=(ArgumentParser &&) = delete;
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; });
1649 return m_is_parsed && (arg_used || subparser_used);
1654 template <typename... Targs> Argument &add_argument(Targs... f_args) {
1655 using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
1657 m_optional_arguments.emplace(std::cend(m_optional_arguments),
1658 m_prefix_chars, array_of_sv{f_args...});
1660 if (!argument->m_is_optional) {
1661 m_positional_arguments.splice(std::cend(m_positional_arguments),
1662 m_optional_arguments, argument);
1664 argument->set_usage_newline_counter(m_usage_newline_counter);
1665 argument->set_group_idx(m_group_names.size());
1667 index_argument(argument);
1671 class MutuallyExclusiveGroup {
1672 friend class ArgumentParser;
1675 MutuallyExclusiveGroup() = delete;
1677 explicit MutuallyExclusiveGroup(ArgumentParser &parent,
1678 bool required = false)
1679 : m_parent(parent), m_required(required), m_elements({}) {}
1681 MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
1682 MutuallyExclusiveGroup &
1683 operator=(const MutuallyExclusiveGroup &other) = delete;
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();
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());
1700 ArgumentParser &m_parent;
1701 bool m_required{false};
1702 std::vector<Argument *> m_elements{};
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();
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);
1720 for (const auto &argument : parent_parser.m_optional_arguments) {
1721 auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
1732 ArgumentParser &add_usage_newline() {
1733 ++m_usage_newline_counter;
1741 ArgumentParser &add_group(std::string group_name) {
1742 m_group_names.emplace_back(std::move(group_name));
1746 ArgumentParser &add_description(std::string description) {
1747 m_description = std::move(description);
1751 ArgumentParser &add_epilog(std::string epilog) {
1752 m_epilog = std::move(epilog);
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);
1767 throw std::logic_error(
1768 "Argument is not an optional argument of this parser");
1774 template <typename T = Argument> T &at(std::string_view name) {
1775 if constexpr (std::is_same_v<T, Argument>) {
1776 return (*this)[name];
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();
1783 throw std::logic_error("No such subparser: " + str_name);
1787 ArgumentParser &set_prefix_chars(std::string prefix_chars) {
1788 m_prefix_chars = std::move(prefix_chars);
1792 ArgumentParser &set_assign_chars(std::string assign_chars) {
1793 m_assign_chars = std::move(assign_chars);
1802 void parse_args(const std::vector<std::string> &arguments) {
1803 parse_args_internal(arguments);
1805 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1806 argument->validate();
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) {
1820 throw std::runtime_error("Argument '" + arg->get_usage_full() +
1821 "' not allowed with '" +
1822 mutex_argument_it->get_usage_full() + "'");
1826 if (!mutex_argument_used && group.m_required) {
1829 std::string argument_names{};
1831 std::size_t size = group.m_elements.size();
1832 for (Argument *arg : group.m_elements) {
1833 if (i + 1 == size) {
1835 argument_names += std::string("'") + arg->get_usage_full() + std::string("' ");
1837 argument_names += std::string("'") + arg->get_usage_full() + std::string("' or ");
1841 throw std::runtime_error("One of the arguments " + argument_names +
1852 std::vector<std::string>
1853 parse_known_args(const std::vector<std::string> &arguments) {
1854 auto unknown_arguments = parse_known_args_internal(arguments);
1856 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
1857 argument->validate();
1859 return unknown_arguments;
1867 void parse_args(int argc, const char *const argv[]) {
1868 parse_args({argv, argv + argc});
1876 auto parse_known_args(int argc, const char *const argv[]) {
1877 return parse_known_args({argv, argv + argc});
1886 template <typename T = std::string> T get(std::string_view arg_name) const {
1888 throw std::logic_error("Nothing parsed, no arguments are available.");
1890 return (*this)[arg_name].get<T>();
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>();
1906 auto is_used(std::string_view arg_name) const {
1907 return (*this)[arg_name].m_is_used;
1912 auto is_subcommand_used(std::string_view subcommand_name) const {
1913 return m_subparser_used.at(std::string(subcommand_name));
1918 auto is_subcommand_used(const ArgumentParser &subparser) const {
1919 return is_subcommand_used(subparser.m_program_name);
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);
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);
1937 name = prefix + name;
1938 it = m_argument_map.find(name);
1939 if (it != m_argument_map.end()) {
1940 return *(it->second);
1943 name = prefix + name;
1944 it = m_argument_map.find(name);
1945 if (it != m_argument_map.end()) {
1946 return *(it->second);
1949 throw std::logic_error("No such argument: " + std::string(arg_name));
1953 friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
1955 stream.setf(std::ios_base::left);
1957 auto longest_arg_length = parser.get_length_of_longest_argument();
1959 stream << parser.usage() << "\n\n";
1961 if (!parser.m_description.empty()) {
1962 stream << parser.m_description << "\n\n";
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";
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));
1982 if (!parser.m_optional_arguments.empty()) {
1983 stream << (!has_visible_positional_args ? "" : "\n")
1984 << "Optional arguments:\n";
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));
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));
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; });
2008 if (has_visible_subcommands) {
2009 stream << (parser.m_positional_arguments.empty()
2010 ? (parser.m_optional_arguments.empty() ? "" : "\n")
2012 << "Subcommands:\n";
2013 for (const auto &[command, subparser] : parser.m_subparser_map) {
2014 if (subparser->get().m_suppress) {
2018 stream << std::setw(2) << " ";
2019 stream << std::setw(static_cast<int>(longest_arg_length - 2))
2021 stream << " " << subparser->get().m_description << "\n";
2025 if (!parser.m_epilog.empty()) {
2027 stream << parser.m_epilog << "\n\n";
2034 auto help() const -> std::stringstream {
2035 std::stringstream out;
2041 ArgumentParser &set_usage_max_line_width(size_t w) {
2042 this->m_usage_max_line_width = w;
2048 ArgumentParser &set_usage_break_on_mutex() {
2049 this->m_usage_break_on_mutex = true;
2054 auto usage() const -> std::string {
2055 std::stringstream stream;
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();
2063 const auto deal_with_options_of_group = [&](std::size_t group_idx) {
2064 bool found_options = false;
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) {
2072 if (multiline_usage) {
2073 if (argument.m_group_idx != group_idx) {
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, ' ');
2083 usage_newline_counter = argument.m_usage_newline_counter;
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)) {
2092 if (this->m_usage_break_on_mutex) {
2093 stream << curline << std::endl;
2094 curline = std::string(indent_size, ' ');
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, ' ');
2104 } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) {
2105 if (cur_mutex != arg_mutex) {
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, ' ');
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, ' ');
2124 } else if (cur_mutex == nullptr) {
2127 curline += arg_inline_usage;
2129 if (cur_mutex != nullptr) {
2132 return found_options;
2135 const bool found_options = deal_with_options_of_group(0);
2137 if (found_options && multiline_usage &&
2138 !this->m_positional_arguments.empty()) {
2139 stream << curline << std::endl;
2140 curline = std::string(indent_size, ' ');
2143 for (const auto &argument : this->m_positional_arguments) {
2144 if (argument.m_is_hidden) {
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, ' ');
2155 if (argument.m_num_args_range.get_min() == 0 &&
2156 !argument.m_num_args_range.is_right_bounded()) {
2160 } else if (argument.m_num_args_range.get_min() == 1 &&
2161 !argument.m_num_args_range.is_right_bounded()) {
2169 if (multiline_usage) {
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);
2182 if (!m_subparser_map.empty()) {
2185 for (const auto &[command, subparser] : m_subparser_map) {
2186 if (subparser->get().m_suppress) {
2193 stream << "," << command;
2200 return stream.str();
2205 [[deprecated("Use cout << program; instead. See also help().")]] std::string
2206 print_help() const {
2208 std::cout << out.rdbuf();
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);
2219 void set_suppress(bool suppress) { m_suppress = suppress; }
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()) {
2232 bool is_valid_prefix_char(char c) const {
2233 return m_prefix_chars.find(c) != std::string::npos;
2236 char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
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) {
2248 const auto argument_starts_with_prefix_chars =
2249 [this](const std::string &a) -> bool {
2252 const auto legal_prefix = [this](char c) -> bool {
2253 return m_prefix_chars.find(c) != std::string::npos;
2260 const auto windows_style = legal_prefix('/');
2262 if (windows_style) {
2263 if (legal_prefix(a[0])) {
2272 return (legal_prefix(a[0]) && legal_prefix(a[1]));
2283 auto assign_char_pos = arg.find_first_of(m_assign_chars);
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) {
2289 std::string opt_name = arg.substr(0, assign_char_pos);
2290 if (m_argument_map.find(opt_name) != m_argument_map.end()) {
2292 arguments.push_back(std::move(opt_name));
2293 arguments.push_back(arg.substr(assign_char_pos + 1));
2298 arguments.push_back(arg);
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();
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 ¤t_argument = *it;
2315 if (Argument::is_positional(current_argument, m_prefix_chars)) {
2316 if (positional_argument_it == std::end(m_positional_arguments)) {
2319 auto subparser_it = m_subparser_map.find(current_argument);
2320 if (subparser_it != m_subparser_map.end()) {
2323 const auto unprocessed_arguments =
2324 std::vector<std::string>(it, end);
2328 m_subparser_used[current_argument] = true;
2329 return subparser_it->second->get().parse_args(
2330 unprocessed_arguments);
2333 if (m_positional_arguments.empty()) {
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)} +
2347 if (!m_optional_arguments.empty()) {
2348 for (const auto &opt : m_optional_arguments) {
2349 if (!opt.m_implicit_value.has_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());
2359 throw std::runtime_error("Zero positional arguments expected");
2361 throw std::runtime_error("Zero positional arguments expected");
2364 throw std::runtime_error("Maximum number of positional arguments "
2365 "exceeded, failed to parse '" +
2366 current_argument + "'");
2369 auto argument = positional_argument_it++;
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);
2382 throw std::runtime_error("Missing " + positional_argument_it->m_names.front());
2386 it = argument->consume(it, end);
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])) {
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);
2406 throw std::runtime_error("Unknown argument: " + current_argument);
2410 throw std::runtime_error("Unknown argument: " + current_argument);
2419 std::vector<std::string>
2420 parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
2421 auto arguments = preprocess_arguments(raw_arguments);
2423 std::vector<std::string> unknown_arguments{};
2425 if (m_program_name.empty() && !arguments.empty()) {
2426 m_program_name = arguments.front();
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 ¤t_argument = *it;
2432 if (Argument::is_positional(current_argument, m_prefix_chars)) {
2433 if (positional_argument_it == std::end(m_positional_arguments)) {
2436 auto subparser_it = m_subparser_map.find(current_argument);
2437 if (subparser_it != m_subparser_map.end()) {
2440 const auto unprocessed_arguments =
2441 std::vector<std::string>(it, end);
2445 m_subparser_used[current_argument] = true;
2446 return subparser_it->second->get().parse_known_args_internal(
2447 unprocessed_arguments);
2451 unknown_arguments.push_back(current_argument);
2456 auto argument = positional_argument_it++;
2457 it = argument->consume(it, end);
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])) {
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);
2478 unknown_arguments.push_back(current_argument);
2485 unknown_arguments.push_back(current_argument);
2490 return unknown_arguments;
2494 std::size_t get_length_of_longest_argument() const {
2495 if (m_argument_map.empty()) {
2498 std::size_t max_size = 0;
2499 for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
2501 std::max<std::size_t>(max_size, argument->get_arguments_length());
2503 for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
2504 max_size = std::max<std::size_t>(max_size, command.size());
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;
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);
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;