CPP-AP 2.7.0
Command-line argument parser for C++20
Loading...
Searching...
No Matches
argument_parser.hpp
Go to the documentation of this file.
1// Copyright (c) 2023-2025 Jakub Musiał
2// This file is part of the CPP-AP project (https://github.com/SpectraL519/cpp-ap).
3// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
4
10#pragma once
11
12#include "argument/default.hpp"
13#include "argument/optional.hpp"
16#include "detail/concepts.hpp"
17#include "version.hpp"
18
19#include <algorithm>
20#include <format>
21#include <iostream>
22#include <ranges>
23#include <span>
24
25#ifdef AP_TESTING
26
27namespace ap_testing {
28struct argument_parser_test_fixture;
29} // namespace ap_testing
30
31#endif
32
33namespace ap {
34
35class argument_parser;
36
37namespace detail {
38
41
42} // namespace detail
43
46public:
49
50 argument_parser() = default;
51
54
55 ~argument_parser() = default;
56
62 argument_parser& program_name(std::string_view name) noexcept {
63 this->_program_name.emplace(name);
64 return *this;
65 }
66
73 this->_program_version.emplace(version.str());
74 return *this;
75 }
76
82 argument_parser& program_version(std::string_view version) noexcept {
83 this->_program_version.emplace(version);
84 return *this;
85 }
86
92 argument_parser& program_description(std::string_view description) noexcept {
93 this->_program_description.emplace(description);
94 return *this;
95 }
96
103 argument_parser& verbose(const bool v = true) noexcept {
104 this->_verbose = v;
105 return *this;
106 }
107
114 template <detail::c_range_of<argument::default_positional> AR>
115 argument_parser& default_positional_arguments(const AR& arg_discriminator_range) noexcept {
116 for (const auto arg_discriminator : arg_discriminator_range)
117 detail::add_default_argument(arg_discriminator, *this);
118 return *this;
119 }
120
127 const std::initializer_list<argument::default_positional> arg_discriminator_list
128 ) noexcept {
129 return this->default_positional_arguments<>(arg_discriminator_list);
130 }
131
138 template <detail::c_range_of<argument::default_optional> AR>
139 argument_parser& default_optional_arguments(const AR& arg_discriminator_range) noexcept {
140 for (const auto arg_discriminator : arg_discriminator_range)
141 detail::add_default_argument(arg_discriminator, *this);
142 return *this;
143 }
144
151 const std::initializer_list<argument::default_optional> arg_discriminator_list
152 ) noexcept {
153 return this->default_optional_arguments<>(arg_discriminator_list);
154 }
155
165 template <detail::c_argument_value_type T = std::string>
166 argument::positional<T>& add_positional_argument(const std::string_view primary_name) {
167 this->_verify_arg_name_pattern(primary_name);
168
169 const detail::argument_name arg_name(std::make_optional<std::string>(primary_name));
170 if (this->_is_arg_name_used(arg_name))
172
173 this->_positional_args.emplace_back(std::make_unique<argument::positional<T>>(arg_name));
174 return static_cast<argument::positional<T>&>(*this->_positional_args.back());
175 }
176
187 template <detail::c_argument_value_type T = std::string>
189 const std::string_view primary_name, const std::string_view secondary_name
190 ) {
191 this->_verify_arg_name_pattern(primary_name);
192 this->_verify_arg_name_pattern(secondary_name);
193
194 const detail::argument_name arg_name{
195 std::make_optional<std::string>(primary_name),
196 std::make_optional<std::string>(secondary_name)
197 };
198 if (this->_is_arg_name_used(arg_name))
200
201 this->_positional_args.emplace_back(std::make_unique<argument::positional<T>>(arg_name));
202 return static_cast<argument::positional<T>&>(*this->_positional_args.back());
203 }
204
215 template <detail::c_argument_value_type T = std::string>
217 const std::string_view name,
218 const detail::argument_name_discriminator name_discr = n_primary
219 ) {
220 this->_verify_arg_name_pattern(name);
221
222 const auto arg_name =
223 name_discr == n_primary
224 ? detail::
225 argument_name{std::make_optional<std::string>(name), std::nullopt, this->_flag_prefix_char}
227 std::nullopt, std::make_optional<std::string>(name), this->_flag_prefix_char
228 };
229
230 if (this->_is_arg_name_used(arg_name))
232
233 this->_optional_args.push_back(std::make_unique<argument::optional<T>>(arg_name));
234 return static_cast<argument::optional<T>&>(*this->_optional_args.back());
235 }
236
247 template <detail::c_argument_value_type T = std::string>
249 const std::string_view primary_name, const std::string_view secondary_name
250 ) {
251 this->_verify_arg_name_pattern(primary_name);
252 this->_verify_arg_name_pattern(secondary_name);
253
254 const detail::argument_name arg_name(
255 std::make_optional<std::string>(primary_name),
256 std::make_optional<std::string>(secondary_name),
258 );
259 if (this->_is_arg_name_used(arg_name))
261
262 this->_optional_args.emplace_back(std::make_unique<argument::optional<T>>(arg_name));
263 return static_cast<argument::optional<T>&>(*this->_optional_args.back());
264 }
265
273 template <bool StoreImplicitly = true>
275 const std::string_view name,
276 const detail::argument_name_discriminator name_discr = n_primary
277 ) {
278 return this->add_optional_argument<bool>(name, name_discr)
279 .default_value(not StoreImplicitly)
280 .implicit_value(StoreImplicitly)
281 .nargs(0ull);
282 }
283
291 template <bool StoreImplicitly = true>
293 const std::string_view primary_name, const std::string_view secondary_name
294 ) {
295 return this->add_optional_argument<bool>(primary_name, secondary_name)
296 .default_value(not StoreImplicitly)
297 .implicit_value(StoreImplicitly)
298 .nargs(0ull);
299 }
300
314 void parse_args(int argc, char* argv[]) {
315 this->parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
316 }
317
325 template <detail::c_range_of<std::string, detail::type_validator::convertible> AR>
326 void parse_args(const AR& argv_rng) {
328
329 std::vector<std::string> unknown_args;
330 this->_parse_args_impl(this->_tokenize(argv_rng), unknown_args);
331
332 if (not unknown_args.empty())
334
335 if (not this->_are_required_args_bypassed()) {
336 this->_verify_required_args();
337 this->_verify_nvalues();
338 }
339 }
340
353 void try_parse_args(int argc, char* argv[]) {
354 this->try_parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
355 }
356
368 template <detail::c_range_of<std::string, detail::type_validator::convertible> AR>
369 void try_parse_args(const AR& argv_rng) {
370 try {
371 this->parse_args(argv_rng);
372 }
373 catch (const ap::argument_parser_exception& err) {
374 std::cerr << "[ERROR] : " << err.what() << std::endl << *this << std::endl;
375 std::exit(EXIT_FAILURE);
376 }
377 }
378
397 std::vector<std::string> parse_known_args(int argc, char* argv[]) {
398 return this->parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
399 }
400
414 template <detail::c_range_of<std::string, detail::type_validator::convertible> AR>
415 std::vector<std::string> parse_known_args(const AR& argv_rng) {
417
418 std::vector<std::string> unknown_args;
419 this->_parse_args_impl(this->_tokenize(argv_rng), unknown_args, false);
420
421 if (not this->_are_required_args_bypassed()) {
422 this->_verify_required_args();
423 this->_verify_nvalues();
424 }
425
426 return unknown_args;
427 }
428
442 std::vector<std::string> try_parse_known_args(int argc, char* argv[]) {
443 return this->try_parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
444 }
445
458 template <detail::c_range_of<std::string, detail::type_validator::convertible> AR>
459 std::vector<std::string> try_parse_known_args(const AR& argv_rng) {
460 try {
461 return this->parse_known_args(argv_rng);
462 }
463 catch (const ap::argument_parser_exception& err) {
464 std::cerr << "[ERROR] : " << err.what() << std::endl << *this << std::endl;
465 std::exit(EXIT_FAILURE);
466 }
467 }
468
469 // clang-format off
470
477 [[deprecated("The default help argument now uses the `print_config` on-flag action")]]
478 void handle_help_action() const noexcept {
479 if (this->value<bool>("help")) {
480 std::cout << *this << std::endl;
481 std::exit(EXIT_SUCCESS);
482 }
483 }
484
485 // clang-format on
486
491 [[nodiscard]] bool has_value(std::string_view arg_name) const noexcept {
492 const auto arg_opt = this->_get_argument(arg_name);
493 return arg_opt ? arg_opt->get().has_value() : false;
494 }
495
500 [[nodiscard]] std::size_t count(std::string_view arg_name) const noexcept {
501 const auto arg_opt = this->_get_argument(arg_name);
502 return arg_opt ? arg_opt->get().count() : 0ull;
503 }
504
511 template <detail::c_argument_value_type T = std::string>
512 [[nodiscard]] T value(std::string_view arg_name) const {
513 const auto arg_opt = this->_get_argument(arg_name);
514 if (not arg_opt)
516
517 const auto& arg_value = arg_opt->get().value();
518 try {
519 return std::any_cast<T>(arg_value);
520 }
521 catch (const std::bad_any_cast&) {
522 throw type_error::invalid_value_type<T>(arg_opt->get().name());
523 }
524 }
525
534 template <detail::c_argument_value_type T = std::string, std::convertible_to<T> U>
535 [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const {
536 const auto arg_opt = this->_get_argument(arg_name);
537 if (not arg_opt)
539
540 try {
541 const auto& arg_value = arg_opt->get().value();
542 return std::any_cast<T>(arg_value);
543 }
544 catch (const std::logic_error&) {
545 // positional: no value parsed
546 // optional: no value parsed + no predefined value
547 return T{std::forward<U>(fallback_value)};
548 }
549 catch (const std::bad_any_cast&) {
550 throw type_error::invalid_value_type<T>(arg_opt->get().name());
551 }
552 }
553
560 template <detail::c_argument_value_type T = std::string>
561 [[nodiscard]] std::vector<T> values(std::string_view arg_name) const {
562 const auto arg_opt = this->_get_argument(arg_name);
563 if (not arg_opt)
565
566 const auto& arg = arg_opt->get();
567
568 try {
569 if (not arg.has_parsed_values() and arg.has_value())
570 return std::vector<T>{std::any_cast<T>(arg.value())};
571
572 std::vector<T> values;
573 // TODO: use std::ranges::to after transition to C++23
574 std::ranges::copy(
575 std::views::transform(
576 arg.values(), [](const std::any& value) { return std::any_cast<T>(value); }
577 ),
578 std::back_inserter(values)
579 );
580 return values;
581 }
582 catch (const std::bad_any_cast&) {
583 throw type_error::invalid_value_type<T>(arg.name());
584 }
585 }
586
592 void print_config(const bool verbose, std::ostream& os = std::cout) const noexcept {
593 if (this->_program_name) {
594 os << "Program: " << this->_program_name.value();
595 if (this->_program_version)
596 os << " (" << this->_program_version.value() << ')';
597 os << '\n';
598 }
599
600 if (this->_program_description)
601 os << '\n'
602 << std::string(this->_indent_width, ' ') << this->_program_description.value()
603 << '\n';
604
605 if (not this->_positional_args.empty()) {
606 os << "\nPositional arguments:\n";
607 this->_print(os, this->_positional_args, verbose);
608 }
609
610 if (not this->_optional_args.empty()) {
611 os << "\nOptional arguments:\n";
612 this->_print(os, this->_optional_args, verbose);
613 }
614 }
615
626 friend std::ostream& operator<<(std::ostream& os, const argument_parser& parser) noexcept {
627 parser.print_config(parser._verbose, os);
628 return os;
629 }
630
631#ifdef AP_TESTING
633 friend struct ::ap_testing::argument_parser_test_fixture;
634#endif
635
636private:
637 using arg_ptr_t = std::unique_ptr<detail::argument_base>;
638 using arg_ptr_list_t = std::vector<arg_ptr_t>;
639 using arg_ptr_list_iter_t = typename arg_ptr_list_t::iterator;
641 using arg_opt_t = std::optional<std::reference_wrapper<detail::argument_base>>;
642 using const_arg_opt_t = std::optional<std::reference_wrapper<const detail::argument_base>>;
643
644 using arg_token_list_t = std::vector<detail::argument_token>;
645 using arg_token_list_iterator_t = typename arg_token_list_t::const_iterator;
646
651 void _verify_arg_name_pattern(const std::string_view arg_name) const {
652 if (arg_name.empty())
654 arg_name, "An argument name cannot be empty."
655 );
656
657 if (detail::contains_whitespaces(arg_name))
659 arg_name, "An argument name cannot contain whitespaces."
660 );
661
662 if (arg_name.front() == this->_flag_prefix_char)
664 arg_name,
665 std::format(
666 "An argument name cannot begin with a flag prefix character ({}).",
668 )
669 );
670
671 if (std::isdigit(arg_name.front()))
673 arg_name, "An argument name cannot begin with a digit."
674 );
675 }
676
683 [[nodiscard]] auto _name_match_predicate(
684 const std::string_view arg_name,
686 ) const noexcept {
687 return [=](const arg_ptr_t& arg) { return arg->name().match(arg_name, m_type); };
688 }
689
696 [[nodiscard]] auto _name_match_predicate(
697 const detail::argument_name& arg_name,
699 ) const noexcept {
700 return [&arg_name, m_type](const arg_ptr_t& arg) {
701 return arg->name().match(arg_name, m_type);
702 };
703 }
704
711 [[nodiscard]] bool _is_arg_name_used(
712 const detail::argument_name& arg_name,
714 ) const noexcept {
715 const auto predicate = this->_name_match_predicate(arg_name, m_type);
716
717 if (std::ranges::find_if(this->_positional_args, predicate) != this->_positional_args.end())
718 return true;
719
720 if (std::ranges::find_if(this->_optional_args, predicate) != this->_optional_args.end())
721 return true;
722
723 return false;
724 }
725
733 // step 1
734 const_arg_opt_t non_required_arg = std::nullopt;
735 for (const auto& arg : this->_positional_args) {
736 if (not arg->is_required()) {
737 non_required_arg = std::ref(*arg);
738 continue;
739 }
740
741 if (non_required_arg and arg->is_required())
743 arg->name(), non_required_arg->get().name()
744 );
745 }
746 }
747
754 template <detail::c_sized_range_of<std::string_view, detail::type_validator::convertible> AR>
755 [[nodiscard]] arg_token_list_t _tokenize(const AR& arg_range) {
756 const auto n_args = std::ranges::size(arg_range);
757 if (n_args == 0ull)
758 return arg_token_list_t{};
759
760 arg_token_list_t toks;
761 toks.reserve(n_args);
762 std::ranges::for_each(
763 arg_range, std::bind_front(&argument_parser::_tokenize_arg, this, std::ref(toks))
764 );
765 return toks;
766 }
767
773 void _tokenize_arg(arg_token_list_t& toks, const std::string_view arg_value) {
774 auto tok = this->_build_token(arg_value);
775
776 if (not tok.is_flag_token() or this->_validate_flag_token(tok)) {
777 toks.emplace_back(std::move(tok));
778 return;
779 }
780
781 // invalid flag - check for compound secondary flag
782 const auto compound_toks = this->_try_split_compound_flag(tok);
783 if (not compound_toks.empty()) { // not a valid compound flag
784 toks.insert(toks.end(), compound_toks.begin(), compound_toks.end());
785 return;
786 }
787
788#ifdef AP_UNKNOWN_FLAGS_AS_VALUES
789 toks.emplace_back(detail::argument_token::t_value, std::string(arg_value));
790#else
791 toks.emplace_back(std::move(tok));
792#endif
793 }
794
800 [[nodiscard]] detail::argument_token _build_token(const std::string_view arg_value
801 ) const noexcept {
802 if (detail::contains_whitespaces(arg_value))
803 return {.type = detail::argument_token::t_value, .value = std::string(arg_value)};
804
805 if (arg_value.starts_with(this->_flag_prefix))
806 return {
808 .value = std::string(arg_value.substr(this->_primary_flag_prefix_length))
809 };
810
811 if (arg_value.starts_with(this->_flag_prefix_char))
812 return {
814 .value = std::string(arg_value.substr(this->_secondary_flag_prefix_length))
815 };
816
817 return {.type = detail::argument_token::t_value, .value = std::string(arg_value)};
818 }
819
826 [[nodiscard]] bool _validate_flag_token(detail::argument_token& tok) noexcept {
827 const auto opt_arg_it = this->_find_opt_arg(tok);
828 if (opt_arg_it == this->_optional_args.end())
829 return false;
830
831 tok.arg.emplace(*opt_arg_it);
832 return true;
833 }
834
841 [[nodiscard]] std::vector<detail::argument_token> _try_split_compound_flag(
842 const detail::argument_token& tok
843 ) noexcept {
844 std::vector<detail::argument_token> compound_toks;
845 compound_toks.reserve(tok.value.size());
846
848 return compound_toks;
849
850 for (const char c : tok.value) {
852 detail::argument_token::t_flag_secondary, std::string(1ull, c)
853 };
854 if (not this->_validate_flag_token(ctok)) {
855 compound_toks.clear();
856 return compound_toks;
857 }
858 compound_toks.emplace_back(std::move(ctok));
859 }
860
861 return compound_toks;
862 }
863
876 [[nodiscard]] std::string _unstripped_token_value(const detail::argument_token& tok
877 ) const noexcept {
878 switch (tok.type) {
880 return std::format("{}{}", this->_flag_prefix, tok.value);
882 return std::format("{}{}", this->_flag_prefix_char, tok.value);
883 default:
884 return tok.value;
885 }
886 }
887
895 const arg_token_list_t& arg_tokens,
896 std::vector<std::string>& unknown_args,
897 const bool handle_unknown = true
898 ) {
899 arg_token_list_iterator_t token_it = arg_tokens.begin();
900 this->_parse_positional_args(token_it, arg_tokens.end());
901 this->_parse_optional_args(token_it, arg_tokens.end(), unknown_args, handle_unknown);
902 }
903
910 arg_token_list_iterator_t& token_it, const arg_token_list_iterator_t& tokens_end
911 ) noexcept {
912 for (const auto& pos_arg : this->_positional_args) {
913 if (token_it == tokens_end)
914 return;
915
916 if (token_it->type != detail::argument_token::t_value)
917 return;
918
919 pos_arg->set_value(token_it->value);
920 ++token_it;
921 }
922 }
923
933 const arg_token_list_iterator_t& tokens_end,
934 std::vector<std::string>& unknown_args,
935 const bool handle_unknown = true
936 ) {
937 arg_ptr_opt_t curr_opt_arg;
938
939 while (token_it != tokens_end) {
940 switch (token_it->type) {
942 [[fallthrough]];
944 if (not token_it->is_valid_flag_token()) {
945 if (handle_unknown) {
947 this->_unstripped_token_value(*token_it)
948 );
949 }
950 else {
951 unknown_args.emplace_back(this->_unstripped_token_value(*token_it));
952 curr_opt_arg.reset();
953 break;
954 }
955 }
956
957 if (token_it->arg->get()->mark_used())
958 curr_opt_arg = token_it->arg;
959 else
960 curr_opt_arg.reset();
961
962 break;
963 }
965 if (not curr_opt_arg) {
966 unknown_args.emplace_back(token_it->value);
967 break;
968 }
969
970 if (not curr_opt_arg->get()->set_value(token_it->value))
971 curr_opt_arg.reset();
972
973 break;
974 }
975 }
976
977 ++token_it;
978 }
979 }
980
985 [[nodiscard]] bool _are_required_args_bypassed() const noexcept {
986 // TODO: use std::views::join after the transition to C++23
987 return std::ranges::any_of(
988 this->_positional_args,
989 [](const arg_ptr_t& arg) {
990 return arg->is_used() and arg->bypass_required_enabled();
991 }
992 )
993 or std::ranges::any_of(this->_optional_args, [](const arg_ptr_t& arg) {
994 return arg->is_used() and arg->bypass_required_enabled();
995 });
996 }
997
1003 // TODO: use std::views::join after the transition to C++23
1004 for (const auto& arg : this->_positional_args)
1005 if (arg->is_required() and not arg->has_value())
1007
1008 for (const auto& arg : this->_optional_args)
1009 if (arg->is_required() and not arg->has_value())
1011 }
1012
1017 void _verify_nvalues() const {
1018 // TODO: use std::views::join after the transition to C++23
1019 for (const auto& arg : this->_positional_args)
1020 if (const auto nv_ord = arg->nvalues_ordering(); not std::is_eq(nv_ord))
1021 throw parsing_failure::invalid_nvalues(arg->name(), nv_ord);
1022
1023 for (const auto& arg : this->_optional_args)
1024 if (const auto nv_ord = arg->nvalues_ordering(); not std::is_eq(nv_ord))
1025 throw parsing_failure::invalid_nvalues(arg->name(), nv_ord);
1026 }
1027
1033 arg_opt_t _get_argument(std::string_view arg_name) const noexcept {
1034 const auto predicate = this->_name_match_predicate(arg_name);
1035
1036 if (auto pos_arg_it = std::ranges::find_if(this->_positional_args, predicate);
1037 pos_arg_it != this->_positional_args.end()) {
1038 return std::ref(**pos_arg_it);
1039 }
1040
1041 if (auto opt_arg_it = std::ranges::find_if(this->_optional_args, predicate);
1042 opt_arg_it != this->_optional_args.end()) {
1043 return std::ref(**opt_arg_it);
1044 }
1045
1046 return std::nullopt;
1047 }
1048
1056 ) noexcept {
1057 switch (flag_tok.type) {
1059 return std::ranges::find(this->_optional_args, flag_tok.value, [](const auto& arg_ptr) {
1060 return arg_ptr->name().primary;
1061 });
1063 return std::ranges::find(this->_optional_args, flag_tok.value, [](const auto& arg_ptr) {
1064 return arg_ptr->name().secondary;
1065 });
1066 default:
1067 return this->_optional_args.end();
1068 }
1069 }
1070
1076 void _print(std::ostream& os, const arg_ptr_list_t& args, const bool verbose) const noexcept {
1077 auto visible_args =
1078 std::views::filter(args, [](const auto& arg) { return not arg->is_hidden(); });
1079
1080 if (verbose) {
1081 for (const auto& arg : visible_args)
1082 os << '\n' << arg->desc(verbose).get(this->_indent_width) << '\n';
1083 }
1084 else {
1085 std::vector<detail::argument_descriptor> descriptors;
1086 descriptors.reserve(args.size());
1087
1088 for (const auto& arg : visible_args)
1089 descriptors.emplace_back(arg->desc(verbose));
1090
1091 std::size_t max_arg_name_length = 0ull;
1092 for (const auto& desc : descriptors)
1093 max_arg_name_length = std::max(max_arg_name_length, desc.name.length());
1094
1095 for (const auto& desc : descriptors)
1096 os << '\n' << desc.get_basic(this->_indent_width, max_arg_name_length);
1097
1098 os << '\n';
1099 }
1100 }
1101
1102 std::optional<std::string> _program_name;
1103 std::optional<std::string> _program_version;
1104 std::optional<std::string> _program_description;
1105 bool _verbose = false;
1106
1109
1110 static constexpr uint8_t _primary_flag_prefix_length = 2u;
1111 static constexpr uint8_t _secondary_flag_prefix_length = 1u;
1112 static constexpr char _flag_prefix_char = '-';
1113 static constexpr std::string_view _flag_prefix = "--";
1114 static constexpr uint8_t _indent_width = 2;
1115};
1116
1117namespace detail {
1118
1125 const argument::default_positional arg_discriminator, argument_parser& arg_parser
1126) noexcept {
1127 switch (arg_discriminator) {
1129 arg_parser.add_positional_argument("input")
1131 .help("Input file path");
1132 break;
1133
1135 arg_parser.add_positional_argument("output").help("Output file path");
1136 break;
1137 }
1138}
1139
1146 const argument::default_optional arg_discriminator, argument_parser& arg_parser
1147) noexcept {
1148 switch (arg_discriminator) {
1150 arg_parser.add_flag("help", "h")
1151 .action<action_type::on_flag>(action::print_config(arg_parser, EXIT_SUCCESS))
1152 .nargs(0ull)
1153 .help("Display the help message");
1154 break;
1155
1157 arg_parser.add_optional_argument("input", "i")
1158 .required()
1159 .nargs(1ull)
1161 .help("Input file path");
1162 break;
1163
1165 arg_parser.add_optional_argument("output", "o").required().nargs(1).help("Output file path");
1166 break;
1167
1169 arg_parser.add_optional_argument("input", "i")
1170 .required()
1171 .nargs(ap::nargs::at_least(1ull))
1173 .help("Input files paths");
1174 break;
1175
1177 arg_parser.add_optional_argument("output", "o")
1178 .required()
1179 .nargs(ap::nargs::at_least(1ull))
1180 .help("Output files paths");
1181 break;
1182 }
1183}
1184
1185} // namespace detail
1186
1187} // namespace ap
The optioanl argument class.
Definition optional.hpp:31
The positional argument class.
Main argument parser class.
argument_parser & program_version(const version &version) noexcept
Set the program version.
argument_parser & default_positional_arguments(const AR &arg_discriminator_range) noexcept
Set default positional arguments.
std::vector< std::string > try_parse_known_args(int argc, char *argv[])
Parses the known command-line arguments and exits on error.
argument::optional< bool > & add_flag(const std::string_view name, const detail::argument_name_discriminator name_discr=n_primary)
Adds a boolean flag argument (an optional argument with value_type = bool) to the parser's configurat...
void try_parse_args(const AR &argv_rng)
Parses the command-line arguments and exits on error.
detail::argument_token _build_token(const std::string_view arg_value) const noexcept
Builds an argument token from the given value.
static constexpr uint8_t _indent_width
argument_parser & default_positional_arguments(const std::initializer_list< argument::default_positional > arg_discriminator_list) noexcept
Set default positional arguments.
argument::optional< T > & add_optional_argument(const std::string_view name, const detail::argument_name_discriminator name_discr=n_primary)
Adds a positional argument to the parser's configuration.
typename arg_ptr_list_t::iterator arg_ptr_list_iter_t
void _verify_nvalues() const
Check if the number of argument values is within the specified range.
~argument_parser()=default
std::optional< std::reference_wrapper< detail::argument_base > > arg_opt_t
std::optional< std::string > _program_description
bool _validate_flag_token(detail::argument_token &tok) noexcept
Check if a flag token is valid based on its value.
std::string _unstripped_token_value(const detail::argument_token &tok) const noexcept
Get the unstripped token value (including the flag prefix).
std::vector< arg_ptr_t > arg_ptr_list_t
argument_parser(const argument_parser &)=delete
void _verify_arg_name_pattern(const std::string_view arg_name) const
Verifies the pattern of an argument name and if it's invalid, an error is thrown.
argument_parser & program_version(std::string_view version) noexcept
Set the program version.
void print_config(const bool verbose, std::ostream &os=std::cout) const noexcept
Prints the argument parser's details to an output stream.
bool _is_arg_name_used(const detail::argument_name &arg_name, const detail::argument_name::match_type m_type=detail::argument_name::m_any) const noexcept
Check if an argument name is already used.
argument_parser(argument_parser &&)=default
arg_ptr_list_iter_t _find_opt_arg(const detail::argument_token &flag_tok) noexcept
Find an optional argument based on a flag token.
argument_parser & program_description(std::string_view description) noexcept
Set the program description.
T value(std::string_view arg_name) const
auto _name_match_predicate(const detail::argument_name &arg_name, const detail::argument_name::match_type m_type=detail::argument_name::m_any) const noexcept
Returns a unary predicate function which checks if the given name matches the argument's name.
argument::positional< T > & add_positional_argument(const std::string_view primary_name, const std::string_view secondary_name)
Adds a positional argument to the parser's configuration.
argument_parser & program_name(std::string_view name) noexcept
Set the program name.
static constexpr char _flag_prefix_char
void _validate_argument_configuration() const
Validate whether the definition/configuration of the parser's arguments is correct.
T value_or(std::string_view arg_name, U &&fallback_value) const
bool has_value(std::string_view arg_name) const noexcept
std::optional< std::reference_wrapper< const detail::argument_base > > const_arg_opt_t
argument_parser & operator=(const argument_parser &)=delete
std::vector< detail::argument_token > arg_token_list_t
void _parse_args_impl(const arg_token_list_t &arg_tokens, std::vector< std::string > &unknown_args, const bool handle_unknown=true)
Implementation of parsing command-line arguments.
std::vector< detail::argument_token > _try_split_compound_flag(const detail::argument_token &tok) noexcept
Tries to split a secondary flag token into separate flag token (one for each character of the token's...
std::vector< T > values(std::string_view arg_name) const
detail::uptr_opt_t< detail::argument_base > arg_ptr_opt_t
typename arg_token_list_t::const_iterator arg_token_list_iterator_t
static constexpr uint8_t _secondary_flag_prefix_length
std::vector< std::string > parse_known_args(const AR &argv_rng)
Parses the known command-line arguments.
argument::optional< bool > & add_flag(const std::string_view primary_name, const std::string_view secondary_name)
Adds a boolean flag argument (an optional argument with value_type = bool) to the parser's configurat...
arg_ptr_list_t _optional_args
argument_parser & operator=(argument_parser &&)=default
std::optional< std::string > _program_name
std::vector< std::string > parse_known_args(int argc, char *argv[])
Parses the known command-line arguments.
argument_parser & verbose(const bool v=true) noexcept
Set the verbosity mode.
argument_parser & default_optional_arguments(const AR &arg_discriminator_range) noexcept
Set default optional arguments.
static constexpr std::string_view _flag_prefix
auto _name_match_predicate(const std::string_view arg_name, const detail::argument_name::match_type m_type=detail::argument_name::m_any) const noexcept
Returns a unary predicate function which checks if the given name matches the argument's name.
void try_parse_args(int argc, char *argv[])
Parses the command-line arguments and exits on error.
void handle_help_action() const noexcept
Handles the help argument logic.
arg_opt_t _get_argument(std::string_view arg_name) const noexcept
Get the argument with the specified name.
argument::positional< T > & add_positional_argument(const std::string_view primary_name)
Adds a positional argument to the parser's configuration.
void _parse_positional_args(arg_token_list_iterator_t &token_it, const arg_token_list_iterator_t &tokens_end) noexcept
Parse positional arguments based on command-line input.
std::unique_ptr< detail::argument_base > arg_ptr_t
void _verify_required_args() const
Check if all required positional and optional arguments are used.
void _tokenize_arg(arg_token_list_t &toks, const std::string_view arg_value)
Appends an argument token(s) created from arg_value to the toks vector.
argument_parser & default_optional_arguments(const std::initializer_list< argument::default_optional > arg_discriminator_list) noexcept
Set default optional arguments.
std::vector< std::string > try_parse_known_args(const AR &argv_rng)
Parses known the command-line arguments and exits on error.
static constexpr uint8_t _primary_flag_prefix_length
arg_token_list_t _tokenize(const AR &arg_range)
Converts the command-line arguments into a list of tokens.
std::size_t count(std::string_view arg_name) const noexcept
argument_parser()=default
void parse_args(const AR &argv_rng)
Parses the command-line arguments.
friend std::ostream & operator<<(std::ostream &os, const argument_parser &parser) noexcept
Prints the argument parser's details to an output stream.
std::optional< std::string > _program_version
void parse_args(int argc, char *argv[])
Parses the command-line arguments.
void _print(std::ostream &os, const arg_ptr_list_t &args, const bool verbose) const noexcept
Print the given argument list to an output stream.
argument::optional< T > & add_optional_argument(const std::string_view primary_name, const std::string_view secondary_name)
Adds a positional argument to the parser's configuration.
void _parse_optional_args(arg_token_list_iterator_t &token_it, const arg_token_list_iterator_t &tokens_end, std::vector< std::string > &unknown_args, const bool handle_unknown=true)
Parse optional arguments based on command-line input.
bool _are_required_args_bypassed() const noexcept
Check whether required argument bypassing is enabled.
arg_ptr_list_t _positional_args
Provides the general concept definitions.
Defines the default argument discriminator types.
ap::action_type::on_flag::type print_config(const argument_parser &parser, const std::optional< int > exit_code=std::nullopt, std::ostream &os=std::cout) noexcept
Returns an on-flag action which prints the argument parser's configuration.
detail::callable_type< ap::action_type::observe, std::string > check_file_exists() noexcept
Returns an observe action which checks whether lower_bound file with the given name exists.
default_positional
Enum class representing positional arguments.
Definition default.hpp:17
default_optional
Enum class representing optional arguments.
Definition default.hpp:20
void add_default_argument(const argument::default_positional, argument_parser &) noexcept
Adds a predefined/default positional argument to the parser.
bool contains_whitespaces(std::string_view str) noexcept
Checks whether a string contains any whitespace characters.
argument_name_discriminator
Argument name member discriminator.
std::optional< std::reference_wrapper< std::unique_ptr< T > > > uptr_opt_t
range at_least(const range::count_type n) noexcept
range class builder function. Creates a range [n, inf].
Definition range.hpp:135
Definition utility.hpp:17
An observing value action specifier.
An on-flag action specifier.
Base type for the argument parser functionality errors/exceptions.
Structure holding the argument's name.
match_type
Specifies the type of argument name match.
@ m_any
Matches either the primary or the secondary name.
Structure representing a single command-line argument token.
token_type type
The token's type discrimiator value.
@ t_value
Represents a value argument.
@ t_flag_secondary
Represents the secondary (-) flag argument.
@ t_flag_primary
Represents the primary (–) flag argument.
static invalid_configuration required_after_non_required(const detail::argument_name &required_arg_name, const detail::argument_name &non_required_arg_name) noexcept
static invalid_configuration argument_name_used(const detail::argument_name &arg_name) noexcept
static invalid_configuration invalid_argument_name(const std::string_view arg_name, const std::string_view reason) noexcept
static lookup_failure argument_not_found(const std::string_view &arg_name) noexcept
static parsing_failure invalid_nvalues(const detail::argument_name &arg_name, const std::weak_ordering ordering) noexcept
static parsing_failure unknown_argument(const std::string_view arg_name) noexcept
static parsing_failure required_argument_not_parsed(const detail::argument_name &arg_name) noexcept
static parsing_failure argument_deduction_failure(const std::vector< std::string > &values) noexcept
A helper structure used to represent a program's version.
Definition version.hpp:13
std::string str() const noexcept
Converts the structure into a string in the v{major}.{minor}.{path} format.
Definition version.hpp:19