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