CPP-AP 3.0.1
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.hpp"
13#include "argument_group.hpp"
15#include "types.hpp"
16
17#include <algorithm>
18#include <format>
19#include <iostream>
20#include <ranges>
21#include <span>
22
23#ifdef AP_TESTING
24
25namespace ap_testing {
26struct argument_parser_test_fixture;
27} // namespace ap_testing
28
29#endif
30
31namespace ap {
32
33class argument_parser;
34
36enum class default_argument : std::uint8_t {
46 p_input,
47
57
67 o_help,
68
82
93 o_input,
94
104 o_output,
105
117
128};
129
131enum class unknown_policy : std::uint8_t {
132 fail,
133 warn,
134 ignore,
135 as_values
136};
137
138namespace detail {
139
141
142} // namespace detail
143
180public:
183
186
187 argument_parser(const std::string_view name) : argument_parser(name, "") {}
188
189 ~argument_parser() = default;
190
197 this->_program_version.emplace(version.str());
198 return *this;
199 }
200
208 throw invalid_configuration("The program version cannot contain whitespace characters!"
209 );
210
211 this->_program_version.emplace(version);
212 return *this;
213 }
214
220 argument_parser& program_description(std::string_view description) noexcept {
221 this->_program_description.emplace(description);
222 return *this;
223 }
224
231 argument_parser& verbose(const bool v = true) noexcept {
232 this->_verbose = v;
233 return *this;
234 }
235
243 this->_unknown_policy = policy;
244 return *this;
245 }
246
254 template <util::c_range_of<default_argument> AR>
260
267 const std::initializer_list<default_argument>& arg_discriminators
268 ) noexcept {
270 }
271
278 const std::same_as<default_argument> auto... arg_discriminators
279 ) noexcept {
281 return *this;
282 }
283
291 template <util::c_argument_value_type T = std::string>
293 return this->add_positional_argument<T>(this->_gr_positional_args, name);
294 }
295
303 template <util::c_argument_value_type T = std::string>
305 argument_group& group, const std::string_view name
306 ) {
307 this->_validate_group(group);
308 this->_verify_arg_name_pattern(name);
309
310 const detail::argument_name arg_name(std::make_optional<std::string>(name));
311 if (this->_is_arg_name_used(arg_name))
313
314 auto& new_arg_ptr =
315 this->_positional_args.emplace_back(std::make_shared<positional_argument<T>>(arg_name));
316 group._add_argument(new_arg_ptr);
317 return static_cast<positional_argument<T>&>(*new_arg_ptr);
318 }
319
328 template <util::c_argument_value_type T = std::string>
330 const std::string_view name,
332 ) {
333 return this->add_optional_argument<T>(this->_gr_optional_args, name, name_discr);
334 }
335
344 template <util::c_argument_value_type T = std::string>
346 const std::string_view primary_name, const std::string_view secondary_name
347 ) {
348 return this->add_optional_argument<T>(
349 this->_gr_optional_args, primary_name, secondary_name
350 );
351 }
352
362 template <util::c_argument_value_type T = std::string>
365 const std::string_view name,
367 ) {
368 this->_validate_group(group);
369 this->_verify_arg_name_pattern(name);
370
371 const auto arg_name =
372 name_discr == n_primary
373 ? detail::
374 argument_name{std::make_optional<std::string>(name), std::nullopt, this->_flag_prefix_char}
376 std::nullopt, std::make_optional<std::string>(name), this->_flag_prefix_char
377 };
378
379 if (this->_is_arg_name_used(arg_name))
381
382 auto& new_arg_ptr =
383 this->_optional_args.emplace_back(std::make_shared<optional_argument<T>>(arg_name));
384 group._add_argument(new_arg_ptr);
385 return static_cast<optional_argument<T>&>(*new_arg_ptr);
386 }
387
397 template <util::c_argument_value_type T = std::string>
400 const std::string_view primary_name,
401 const std::string_view secondary_name
402 ) {
403 this->_validate_group(group);
404 this->_verify_arg_name_pattern(primary_name);
405 this->_verify_arg_name_pattern(secondary_name);
406
408 std::make_optional<std::string>(primary_name),
409 std::make_optional<std::string>(secondary_name),
411 );
412 if (this->_is_arg_name_used(arg_name))
414
415 auto& new_arg_ptr =
416 this->_optional_args.emplace_back(std::make_shared<optional_argument<T>>(arg_name));
417 group._add_argument(new_arg_ptr);
418 return static_cast<optional_argument<T>&>(*new_arg_ptr);
419 }
420
429 template <bool StoreImplicitly = true>
431 const std::string_view name,
433 ) {
434 return this->add_optional_argument<bool>(name, name_discr)
435 .default_values(not StoreImplicitly)
436 .implicit_values(StoreImplicitly)
437 .nargs(0ull);
438 }
439
448 template <bool StoreImplicitly = true>
450 const std::string_view primary_name, const std::string_view secondary_name
451 ) {
452 return this->add_optional_argument<bool>(primary_name, secondary_name)
453 .default_values(not StoreImplicitly)
454 .implicit_values(StoreImplicitly)
455 .nargs(0ull);
456 }
457
467 template <bool StoreImplicitly = true>
470 const std::string_view name,
472 ) {
473 return this->add_optional_argument<bool>(group, name, name_discr)
474 .default_values(not StoreImplicitly)
475 .implicit_values(StoreImplicitly)
476 .nargs(0ull);
477 }
478
488 template <bool StoreImplicitly = true>
491 const std::string_view primary_name,
492 const std::string_view secondary_name
493 ) {
495 .default_values(not StoreImplicitly)
496 .implicit_values(StoreImplicitly)
497 .nargs(0ull);
498 }
499
505 argument_group& add_group(const std::string_view name) noexcept {
506 return *this->_argument_groups.emplace_back(argument_group::create(*this, name));
507 }
508
514 argument_parser& add_subparser(const std::string_view name) {
515 const auto subparser_it = std::ranges::find(
516 this->_subparsers, name, [](const auto& subparser) { return subparser->_name; }
517 );
518 if (subparser_it != this->_subparsers.end())
519 throw std::logic_error(std::format(
520 "A subparser with the given name () already exists in parser '{}'",
521 (*subparser_it)->_name,
523 ));
524
525 return *this->_subparsers.emplace_back(
526 std::unique_ptr<argument_parser>(new argument_parser(name, this->_program_name))
527 );
528 }
529
543 void parse_args(int argc, char* argv[]) {
544 this->parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
545 }
546
555 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
556 void parse_args(const AR& argv_rng) {
557 parsing_state state(*this);
558 this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state);
559
560 if (not state.unknown_args.empty())
561 throw parsing_failure(std::format(
562 "Failed to deduce the argument for values [{}]", util::join(state.unknown_args)
563 ));
564 }
565
578 void try_parse_args(int argc, char* argv[]) {
579 this->try_parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
580 }
581
594 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
596 try {
597 this->parse_args(argv_rng);
598 }
599 catch (const ap::argument_parser_exception& err) {
600 std::cerr << "[ap::error] " << err.what() << std::endl
601 << this->resolved_parser() << std::endl;
602 std::exit(EXIT_FAILURE);
603 }
604 }
605
624 std::vector<std::string> parse_known_args(int argc, char* argv[]) {
625 return this->parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
626 }
627
642 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
643 std::vector<std::string> parse_known_args(const AR& argv_rng) {
644 parsing_state state(*this, true);
645 this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state);
646 return std::move(state.unknown_args);
647 }
648
662 std::vector<std::string> try_parse_known_args(int argc, char* argv[]) {
663 return this->try_parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
664 }
665
679 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
680 std::vector<std::string> try_parse_known_args(const AR& argv_rng) {
681 try {
682 return this->parse_known_args(argv_rng);
683 }
684 catch (const ap::argument_parser_exception& err) {
685 std::cerr << "[ap::error] " << err.what() << std::endl
686 << this->resolved_parser() << std::endl;
687 std::exit(EXIT_FAILURE);
688 }
689 }
690
692 [[nodiscard]] std::string_view name() const noexcept {
693 return this->_name;
694 }
695
707 [[nodiscard]] std::string_view program_name() const noexcept {
708 return this->_program_name;
709 }
710
717 return this->_invoked;
718 }
719
725 return this->_finalized;
726 }
727
733 const auto used_subparser_it = std::ranges::find_if(
734 this->_subparsers, [](const auto& subparser) { return subparser->_invoked; }
735 );
736 if (used_subparser_it == this->_subparsers.end())
737 return *this;
738 return (*used_subparser_it)->resolved_parser();
739 }
740
746 [[nodiscard]] bool is_used(std::string_view arg_name) const noexcept {
747 const auto arg = this->_get_argument(arg_name);
748 return arg ? arg->is_used() : false;
749 }
750
756 [[nodiscard]] bool has_value(std::string_view arg_name) const noexcept {
757 const auto arg = this->_get_argument(arg_name);
758 return arg ? arg->has_value() : false;
759 }
760
766 [[nodiscard]] std::size_t count(std::string_view arg_name) const noexcept {
767 const auto arg = this->_get_argument(arg_name);
768 return arg ? arg->count() : 0ull;
769 }
770
778 template <util::c_argument_value_type T = std::string>
779 [[nodiscard]] T value(std::string_view arg_name) const {
780 const auto arg = this->_get_argument(arg_name);
781 if (not arg)
783
784 const auto& arg_value = arg->value();
785 try {
786 return std::any_cast<T>(arg_value);
787 }
788 catch (const std::bad_any_cast&) {
789 throw type_error::invalid_value_type<T>(arg->name());
790 }
791 }
792
802 template <util::c_argument_value_type T = std::string, std::convertible_to<T> U>
803 [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const {
804 const auto arg = this->_get_argument(arg_name);
805 if (not arg)
807
808 try {
809 const auto& arg_value = arg->value();
810 return std::any_cast<T>(arg_value);
811 }
812 catch (const std::logic_error&) {
813 // positional: no value parsed
814 // optional: no value parsed + no predefined value
815 return T{std::forward<U>(fallback_value)};
816 }
817 catch (const std::bad_any_cast&) {
818 throw type_error::invalid_value_type<T>(arg->name());
819 }
820 }
821
829 template <util::c_argument_value_type T = std::string>
830 [[nodiscard]] std::vector<T> values(std::string_view arg_name) const {
831 const auto arg = this->_get_argument(arg_name);
832 if (not arg)
834
835 try {
836 std::vector<T> values;
837 // TODO: use std::ranges::to after transition to C++23
838 std::ranges::copy(
839 util::any_range_cast_view<T>(arg->values()), std::back_inserter(values)
840 );
841 return values;
842 }
843 catch (const std::bad_any_cast&) {
844 throw type_error::invalid_value_type<T>(arg->name());
845 }
846 }
847
853 void print_help(const bool verbose, std::ostream& os = std::cout) const noexcept {
854 os << "Program: " << this->_program_name;
855 if (this->_program_version)
856 os << " (" << this->_program_version.value() << ')';
857 os << '\n';
858
859 if (this->_program_description)
860 os << '\n'
861 << std::string(this->_indent_width, ' ') << this->_program_description.value()
862 << '\n';
863
864 this->_print_subparsers(os);
865 for (const auto& group : this->_argument_groups)
866 this->_print_group(os, *group, verbose);
867 }
868
876 void print_version(std::ostream& os = std::cout) const noexcept {
877 os << this->_program_name << " : version " << this->_program_version.value_or("unspecified")
878 << std::endl;
879 }
880
891 friend std::ostream& operator<<(std::ostream& os, const argument_parser& parser) noexcept {
893 return os;
894 }
895
896#ifdef AP_TESTING
898 friend struct ::ap_testing::argument_parser_test_fixture;
899#endif
900
901private:
902 using arg_ptr_t = std::shared_ptr<detail::argument_base>;
903 using arg_ptr_vec_t = std::vector<arg_ptr_t>;
904 using arg_ptr_vec_iter_t = typename arg_ptr_vec_t::iterator;
905
906 using arg_group_ptr_t = std::unique_ptr<argument_group>;
907 using arg_group_ptr_vec_t = std::vector<arg_group_ptr_t>;
908
909 using arg_parser_ptr_t = std::unique_ptr<argument_parser>;
910 using arg_parser_ptr_vec_t = std::vector<arg_parser_ptr_t>;
911
912 using arg_token_vec_t = std::vector<detail::argument_token>;
913 using arg_token_vec_iter_t = typename arg_token_vec_t::const_iterator;
914
936
937 argument_parser(const std::string_view name, const std::string_view parent_name)
938 : _name(name),
940 std::format("{}{}{}", parent_name, std::string(not parent_name.empty(), ' '), name)
941 ),
942 _gr_positional_args(add_group("Positional Arguments")),
943 _gr_optional_args(add_group("Optional Arguments")) {
944 if (name.empty())
945 throw invalid_configuration("The program name cannot be empty!");
946
948 throw invalid_configuration("The program name cannot contain whitespace characters!");
949 }
950
955 void _verify_arg_name_pattern(const std::string_view arg_name) const {
956 if (arg_name.empty())
958 arg_name, "An argument name cannot be empty."
959 );
960
963 arg_name, "An argument name cannot contain whitespaces."
964 );
965
966 if (arg_name.front() == this->_flag_prefix_char)
968 arg_name,
969 std::format(
970 "An argument name cannot begin with a flag prefix character ({}).",
972 )
973 );
974
975 if (std::isdigit(arg_name.front()))
977 arg_name, "An argument name cannot begin with a digit."
978 );
979 }
980
988 const std::string_view arg_name,
990 ) const noexcept {
991 return [=](const arg_ptr_t& arg) { return arg->name().match(arg_name, m_type); };
992 }
993
1003 ) const noexcept {
1004 return [&arg_name, m_type](const arg_ptr_t& arg) {
1005 return arg->name().match(arg_name, m_type);
1006 };
1007 }
1008
1018 ) const noexcept {
1019 const auto predicate = this->_name_match_predicate(arg_name, m_type);
1020
1021 if (std::ranges::find_if(this->_positional_args, predicate) != this->_positional_args.end())
1022 return true;
1023
1024 if (std::ranges::find_if(this->_optional_args, predicate) != this->_optional_args.end())
1025 return true;
1026
1027 return false;
1028 }
1029
1036 if (group._parser != this)
1037 throw std::logic_error(std::format(
1038 "An argument group '{}' does not belong to the given parser.", group._name
1039 ));
1040 }
1041
1051 template <util::c_forward_iterator_of<std::string, util::type_validator::convertible> AIt>
1053 this->_invoked = true;
1054
1055 if (args_begin != args_end) {
1056 // try to match a subparser
1057 const auto subparser_it =
1058 std::ranges::find(this->_subparsers, *args_begin, [](const auto& subparser) {
1059 return subparser->_name;
1060 });
1061 if (subparser_it != this->_subparsers.end()) {
1062 auto& subparser = **subparser_it;
1063 state.set_parser(subparser);
1065 return;
1066 }
1067 }
1068
1069 // process command-line arguments within the current parser
1071 for (const auto& tok : this->_tokenize(args_begin, args_end, state))
1072 this->_parse_token(tok, state);
1073 this->_verify_final_state();
1074 this->_finalized = true;
1075 }
1076
1084 // step 1
1085 arg_ptr_t non_required_arg = nullptr;
1086 for (const auto& arg : this->_positional_args) {
1087 if (not arg->is_required()) {
1088 non_required_arg = arg;
1089 continue;
1090 }
1091
1092 if (non_required_arg and arg->is_required())
1093 throw invalid_configuration(std::format(
1094 "Required positional argument [{}] cannot be defined after a non-required "
1095 "positional argument [{}].",
1096 arg->name().str(),
1097 non_required_arg->name().str()
1098 ));
1099 }
1100 }
1101
1111 template <util::c_forward_iterator_of<std::string, util::type_validator::convertible> AIt>
1114 ) {
1116 toks.reserve(static_cast<std::size_t>(std::ranges::distance(args_begin, args_end)));
1117 std::ranges::for_each(args_begin, args_end, [&](const auto& arg_value) {
1118 this->_tokenize_arg(arg_value, toks, state);
1119 });
1120 return toks;
1121 }
1122
1130 const std::string_view arg_value, arg_token_vec_t& toks, const parsing_state& state
1131 ) {
1133 .type = this->_deduce_token_type(arg_value), .value = std::string(arg_value)
1134 };
1135
1136 if (not tok.is_flag_token() or this->_validate_flag_token(tok)) {
1137 toks.emplace_back(std::move(tok));
1138 return;
1139 }
1140
1141 // not a value token -> flag token
1142 // flag token could not be validated -> unknown flag
1143 if (state.parse_known_only) { // do nothing (will be handled during parsing)
1144 toks.emplace_back(std::move(tok));
1145 return;
1146 }
1147
1148 switch (this->_unknown_policy) {
1152 std::cerr << "[ap::warning] Unknown argument '" << tok.value << "' will be ignored."
1153 << std::endl;
1154 [[fallthrough]];
1156 return;
1159 toks.emplace_back(std::move(tok));
1160 break;
1161 }
1162 }
1163
1174 const std::string_view arg_value
1175 ) const noexcept {
1178
1179 if (arg_value.starts_with(this->_flag_prefix))
1181
1182 if (arg_value.starts_with(this->_flag_prefix_char))
1184
1186 }
1187
1195 const auto opt_arg_it = this->_find_opt_arg(tok);
1196 if (opt_arg_it == this->_optional_args.end())
1197 return this->_validate_compound_flag_token(tok);
1198
1199 tok.args.emplace_back(*opt_arg_it);
1200 return true;
1201 }
1202
1212 return false;
1213
1214 const auto actual_tok_value = this->_strip_flag_prefix(tok);
1215 tok.args.reserve(actual_tok_value.size());
1216
1217 for (const char c : actual_tok_value) {
1218 const auto opt_arg_it = std::ranges::find_if(
1219 this->_optional_args,
1221 std::string_view(&c, 1ull), detail::argument_name::m_secondary
1222 )
1223 );
1224
1225 if (opt_arg_it == this->_optional_args.end()) {
1226 tok.args.clear();
1227 return false;
1228 }
1229
1230 tok.args.emplace_back(*opt_arg_it);
1231 }
1232
1234 return true;
1235 }
1236
1244 ) noexcept {
1245 if (not flag_tok.is_flag_token())
1246 return this->_optional_args.end();
1247
1248 const auto actual_tok_value = this->_strip_flag_prefix(flag_tok);
1249 const auto match_type =
1253
1254 return std::ranges::find_if(
1255 this->_optional_args, this->_name_match_predicate(actual_tok_value, match_type)
1256 );
1257 }
1258
1265 ) const noexcept {
1266 switch (tok.type) {
1268 return std::string_view(tok.value).substr(this->_primary_flag_prefix_length);
1270 return std::string_view(tok.value).substr(this->_secondary_flag_prefix_length);
1271 default:
1272 return tok.value;
1273 }
1274 }
1275
1283 if (state.curr_arg and state.curr_arg->is_greedy()) {
1284 this->_set_argument_value(tok.value, state);
1285 return;
1286 }
1287
1288 if (tok.is_flag_token())
1289 this->_parse_flag_token(tok, state);
1290 else
1291 this->_parse_value_token(tok, state);
1292 }
1293
1301 if (not tok.is_valid_flag_token()) {
1302 if (state.parse_known_only) {
1303 state.curr_arg.reset();
1304 state.unknown_args.emplace_back(tok.value);
1305 return;
1306 }
1307 else {
1308 // should never happen as unknown flags are filtered out during tokenization
1310 }
1311 }
1312
1313 for (const auto& arg : tok.args) {
1314 if (arg->mark_used())
1315 state.curr_arg = arg;
1316 else
1317 state.curr_arg.reset();
1318 }
1319 }
1320
1328 if (not state.curr_arg) {
1329 if (state.curr_pos_arg_it == this->_positional_args.end()) {
1330 state.unknown_args.emplace_back(tok.value);
1331 return;
1332 }
1333
1334 state.curr_arg = *state.curr_pos_arg_it;
1335 }
1336
1337 this->_set_argument_value(tok.value, state);
1338 }
1339
1346 void _set_argument_value(const std::string_view value, parsing_state& state) noexcept {
1347 if (state.curr_arg->set_value(std::string(value)))
1348 return; // argument still accepts values
1349
1350 // advance to the next positional argument if possible
1351 if (state.curr_arg->is_positional()
1352 and state.curr_pos_arg_it != this->_positional_args.end())
1353 ++state.curr_pos_arg_it;
1354
1355 state.curr_arg.reset();
1356 }
1357
1362 void _verify_final_state() const {
1363 const auto [supress_group_checks, suppress_arg_checks] = this->_are_checks_suppressed();
1364 for (const auto& group : this->_argument_groups)
1365 this->_verify_group_requirements(*group, supress_group_checks, suppress_arg_checks);
1366 }
1367
1374 [[nodiscard]] std::pair<bool, bool> _are_checks_suppressed() const noexcept {
1375 bool suppress_group_checks = false;
1376 bool suppress_arg_checks = false;
1377
1378 auto check_arg = [&](const arg_ptr_t& arg) {
1379 if (arg->is_used()) {
1380 if (arg->suppresses_group_checks())
1381 suppress_group_checks = true;
1382 if (arg->suppresses_arg_checks())
1383 suppress_arg_checks = true;
1384 }
1385 };
1386
1387 // TODO: use std::views::join after the transition to C++23
1388 std::ranges::for_each(this->_positional_args, check_arg);
1389 std::ranges::for_each(this->_optional_args, check_arg);
1390 return {suppress_group_checks, suppress_arg_checks};
1391 }
1392
1400 const argument_group& group,
1401 const bool suppress_group_checks,
1402 const bool suppress_arg_checks
1403 ) const {
1404 if (group._arguments.empty())
1405 return;
1406
1407 if (not suppress_group_checks) {
1408 const auto n_used_args = static_cast<std::size_t>(std::ranges::count_if(
1409 group._arguments, [](const auto& arg) { return arg->is_used(); }
1410 ));
1411
1412 if (group._mutually_exclusive) {
1413 if (n_used_args > 1ull)
1414 throw parsing_failure(std::format(
1415 "At most one argument from the mutually exclusive group '{}' can be used",
1416 group._name
1417 ));
1418
1419 const auto used_arg_it = std::ranges::find_if(
1420 group._arguments, [](const auto& arg) { return arg->is_used(); }
1421 );
1422
1423 if (used_arg_it != group._arguments.end()) {
1424 // only the one used argument has to be validated
1425 this->_verify_argument_requirements(*used_arg_it, suppress_arg_checks);
1426 return;
1427 }
1428 }
1429
1430 if (group._required and n_used_args == 0ull)
1431 throw parsing_failure(std::format(
1432 "At least one argument from the required group '{}' must be used", group._name
1433 ));
1434 }
1435
1436 // all arguments in the group have to be validated
1437 for (const auto& arg : group._arguments)
1438 this->_verify_argument_requirements(arg, suppress_arg_checks);
1439 }
1440
1447 void _verify_argument_requirements(const arg_ptr_t& arg, const bool suppress_arg_checks) const {
1448 if (suppress_arg_checks)
1449 return;
1450
1451 if (arg->is_required() and not arg->has_value())
1452 throw parsing_failure(
1453 std::format("No values parsed for a required argument [{}]", arg->name().str())
1454 );
1455 if (const auto nv_ord = arg->nvalues_ordering(); not std::is_eq(nv_ord))
1457 }
1458
1464 arg_ptr_t _get_argument(std::string_view arg_name) const noexcept {
1465 const auto predicate = this->_name_match_predicate(arg_name);
1466
1467 if (auto pos_arg_it = std::ranges::find_if(this->_positional_args, predicate);
1468 pos_arg_it != this->_positional_args.end()) {
1469 return *pos_arg_it;
1470 }
1471
1472 if (auto opt_arg_it = std::ranges::find_if(this->_optional_args, predicate);
1473 opt_arg_it != this->_optional_args.end()) {
1474 return *opt_arg_it;
1475 }
1476
1477 return nullptr;
1478 }
1479
1480 void _print_subparsers(std::ostream& os) const noexcept {
1481 if (this->_subparsers.empty())
1482 return;
1483
1484 os << "\nCommands:\n";
1485
1486 std::vector<detail::help_builder> builders;
1487 builders.reserve(this->_subparsers.size());
1488
1489 for (const auto& subparser : this->_subparsers)
1490 builders.emplace_back(subparser->_name, subparser->_program_description);
1491
1492 std::size_t max_subparser_name_length = 0ull;
1493 for (const auto& bld : builders)
1495
1496 for (const auto& bld : builders)
1497 os << '\n' << bld.get_basic(this->_indent_width, max_subparser_name_length);
1498
1499 os << '\n';
1500 }
1501
1509 void _print_group(std::ostream& os, const argument_group& group, const bool verbose)
1510 const noexcept {
1511 auto visible_args = std::views::filter(group._arguments, [](const auto& arg) {
1512 return not arg->is_hidden();
1513 });
1514
1515 if (std::ranges::empty(visible_args))
1516 return;
1517
1518 os << '\n' << group._name << ":";
1519
1520 std::vector<std::string> group_attrs;
1521 if (group._required)
1522 group_attrs.emplace_back("required");
1523 if (group._mutually_exclusive)
1524 group_attrs.emplace_back("mutually exclusive");
1525 if (not group_attrs.empty())
1526 os << " (" << util::join(group_attrs) << ')';
1527
1528 os << '\n';
1529
1530 if (verbose) {
1531 for (const auto& arg : visible_args)
1532 os << '\n' << arg->help_builder(verbose).get(this->_indent_width) << '\n';
1533 }
1534 else {
1535 std::vector<detail::help_builder> builders;
1536 builders.reserve(group._arguments.size());
1537
1538 for (const auto& arg : visible_args)
1539 builders.emplace_back(arg->help_builder(verbose));
1540
1541 std::size_t max_arg_name_length = 0ull;
1542 for (const auto& bld : builders)
1543 max_arg_name_length = std::max(max_arg_name_length, bld.name.length());
1544
1545 for (const auto& bld : builders)
1546 os << '\n' << bld.get_basic(this->_indent_width, max_arg_name_length);
1547
1548 os << '\n';
1549 }
1550 }
1551
1552 std::string _name;
1553 std::string
1555 std::optional<std::string> _program_version;
1556 std::optional<std::string> _program_description;
1557 bool _verbose = false;
1559
1566
1568 false;
1569 bool _finalized = false;
1570
1571 static constexpr std::uint8_t _primary_flag_prefix_length = 2u;
1572 static constexpr std::uint8_t _secondary_flag_prefix_length = 1u;
1573 static constexpr char _flag_prefix_char = '-';
1574 static constexpr std::string_view _flag_prefix = "--";
1575 static constexpr std::uint8_t _indent_width = 2;
1576};
1577
1578namespace detail {
1579
1586 const default_argument arg_discriminator, argument_parser& arg_parser
1587) noexcept {
1588 switch (arg_discriminator) {
1590 arg_parser.add_positional_argument("input")
1592 .help("Input file path");
1593 break;
1594
1596 arg_parser.add_positional_argument("output").help("Output file path");
1597 break;
1598
1600 arg_parser.add_optional_argument<none_type>("help", "h")
1601 .action<action_type::on_flag>(action::print_help(arg_parser, EXIT_SUCCESS))
1602 .help("Display the help message");
1603 break;
1604
1606 arg_parser.add_optional_argument<none_type>("version", "v")
1607 .action<action_type::on_flag>([&arg_parser]() {
1608 arg_parser.print_version();
1609 std::exit(EXIT_SUCCESS);
1610 })
1611 .help("Dsiplay program version info");
1612 break;
1613
1615 arg_parser.add_optional_argument("input", "i")
1616 .nargs(1ull)
1618 .help("Input file path");
1619 break;
1620
1622 arg_parser.add_optional_argument("output", "o").nargs(1ull).help("Output file path");
1623 break;
1624
1626 arg_parser.add_optional_argument("input", "i")
1627 .nargs(ap::nargs::at_least(1ull))
1629 .help("Input files paths");
1630 break;
1631
1633 arg_parser.add_optional_argument("output", "o")
1634 .nargs(ap::nargs::at_least(1ull))
1635 .help("Output files paths");
1636 break;
1637 }
1638}
1639
1640} // namespace detail
1641
1642} // namespace ap
Represents a group of arguments.
static std::unique_ptr< argument_group > create(argument_parser &parser, std::string_view name)
Factory method to create an argument group.
The main argument parser class.
std::vector< arg_ptr_t > arg_ptr_vec_t
std::string _program_name
The name of the program in the format "<parent-parser-names>... <program-name>".
argument_parser & program_version(const version &version) noexcept
Set the program version.
optional_argument< T > & add_optional_argument(const std::string_view name, const detail::argument_name_discriminator name_discr=n_primary)
Adds an optional argument to the parser's configuration.
std::vector< std::string > try_parse_known_args(int argc, char *argv[])
Parses the known command-line arguments and exits on error.
void _verify_group_requirements(const argument_group &group, const bool suppress_group_checks, const bool suppress_arg_checks) const
Verifies whether the requirements of the given argument group are satisfied.
void try_parse_args(const AR &argv_rng)
Parses the command-line arguments and exits on error.
void _validate_group(const argument_group &group)
Check if the given group belongs to the parser.
std::string_view program_name() const noexcept
void _parse_flag_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument flag token.
bool finalized() const noexcept
Check whether the parser has finalized parsing its own arguments.
static constexpr std::uint8_t _indent_width
optional_argument< T > & add_optional_argument(const std::string_view primary_name, const std::string_view secondary_name)
Adds an optional argument to the parser's configuration.
~argument_parser()=default
std::shared_ptr< detail::argument_base > arg_ptr_t
arg_group_ptr_vec_t _argument_groups
The list of argument groups.
arg_ptr_vec_t _optional_args
The list of optional arguments.
optional_argument< bool > & add_flag(argument_group &group, 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...
argument_parser & program_version(std::string_view version)
Set the program version.
arg_token_vec_t _tokenize(AIt args_begin, const AIt args_end, const parsing_state &state)
Converts the command-line arguments into a list of tokens.
std::optional< std::string > _program_description
The description of the program.
bool _validate_flag_token(detail::argument_token &tok) noexcept
Check if a flag token is valid based on its value.
std::pair< bool, bool > _are_checks_suppressed() const noexcept
Check whether required argument group checks or argument checks suppressing is enabled.
void print_help(const bool verbose, std::ostream &os=std::cout) const noexcept
Prints the argument parser's help message to an output stream.
optional_argument< 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...
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.
void _parse_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument token.
std::string _name
The name of the parser.
std::string_view name() const noexcept
Returns the parser's name.
void _verify_argument_requirements(const arg_ptr_t &arg, const bool suppress_arg_checks) const
Verifies whether the requirements of the given argument are satisfied.
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.
std::unique_ptr< argument_group > arg_group_ptr_t
arg_ptr_vec_t _positional_args
The list of positional arguments.
arg_parser_ptr_vec_t _subparsers
The list of subparsers.
argument_parser & program_description(std::string_view description) noexcept
Set the program description.
T value(std::string_view arg_name) const
Get the value of the given argument.
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.
bool _invoked
A flag indicating whether the parser has been invoked to parse arguments.
std::string_view _strip_flag_prefix(const detail::argument_token &tok) const noexcept
Removes the flag prefix from a flag token's value.
optional_argument< 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...
static constexpr char _flag_prefix_char
void _validate_argument_configuration() const
Validate whether the definition/configuration of the parser's arguments is correct.
void _parse_args_impl(AIt args_begin, const AIt args_end, parsing_state &state)
Implementation of parsing command-line arguments.
T value_or(std::string_view arg_name, U &&fallback_value) const
Get the value of the given argument, if it has any, or a fallback value, if not.
void _verify_final_state() const
Verifies the correctness of the parsed command-line arguments.
bool has_value(std::string_view arg_name) const noexcept
Check if the given argument has a value.
bool _verbose
Verbosity flag.
argument_group & _gr_optional_args
The optional argument group.
positional_argument< T > & add_positional_argument(argument_group &group, const std::string_view name)
Adds a positional argument to the parser's configuration and binds it to the given group.
arg_ptr_t _get_argument(std::string_view arg_name) const noexcept
Get the argument with the specified name.
argument_parser(argument_parser &&)=delete
argument_parser & resolved_parser() noexcept
Returns the deepest invoked parser.
argument_group & add_group(const std::string_view name) noexcept
Adds an argument group with the given name to the parser's configuration.
argument_parser & operator=(const argument_parser &)=delete
optional_argument< T > & add_optional_argument(argument_group &group, const std::string_view name, const detail::argument_name_discriminator name_discr=n_primary)
Adds an optional argument to the parser's configuration and binds it to the given group.
std::vector< T > values(std::string_view arg_name) const
Get all values of the given argument.
void _parse_value_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument value token.
void _set_argument_value(const std::string_view value, parsing_state &state) noexcept
Set the value for the currently processed argument.
std::vector< std::string > parse_known_args(const AR &argv_rng)
Parses the known command-line arguments.
argument_parser & default_arguments(const AR &arg_discriminators) noexcept
Add default arguments to the argument parser.
bool invoked() const noexcept
Check whether this parser was invoked.
optional_argument< T > & add_optional_argument(argument_group &group, const std::string_view primary_name, const std::string_view secondary_name)
Adds an optional argument to the parser's configuration and binds it to the given group.
void _print_group(std::ostream &os, const argument_group &group, const bool verbose) const noexcept
Print the given argument list to an output stream.
static constexpr std::uint8_t _primary_flag_prefix_length
std::unique_ptr< argument_parser > arg_parser_ptr_t
std::vector< arg_group_ptr_t > arg_group_ptr_vec_t
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.
static constexpr std::string_view _flag_prefix
typename arg_ptr_vec_t::iterator arg_ptr_vec_iter_t
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.
std::vector< arg_parser_ptr_t > arg_parser_ptr_vec_t
argument_parser(const std::string_view name, const std::string_view parent_name)
unknown_policy _unknown_policy
Policy for unknown arguments.
void try_parse_args(int argc, char *argv[])
Parses the command-line arguments and exits on error.
positional_argument< T > & add_positional_argument(const std::string_view name)
Adds a positional argument to the parser's configuration.
void print_version(std::ostream &os=std::cout) const noexcept
Prints the argument parser's version info to an output stream.
std::vector< detail::argument_token > arg_token_vec_t
arg_ptr_vec_iter_t _find_opt_arg(const detail::argument_token &flag_tok) noexcept
Find an optional argument based on a flag token.
optional_argument< bool > & add_flag(argument_group &group, 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...
argument_parser & add_subparser(const std::string_view name)
Adds an subparser with the given name to the parser's configuration.
argument_parser & default_arguments(const std::initializer_list< default_argument > &arg_discriminators) noexcept
Add default arguments to the argument parser.
typename arg_token_vec_t::const_iterator arg_token_vec_iter_t
bool is_used(std::string_view arg_name) const noexcept
Check if a specific argument was used in the command-line.
bool _validate_compound_flag_token(detail::argument_token &tok) noexcept
Check if a flag token is a valid compound argument flag based on its value.
std::vector< std::string > try_parse_known_args(const AR &argv_rng)
Parses known the command-line arguments and exits on error.
argument_parser & unknown_arguments_policy(const unknown_policy policy) noexcept
Set the unknown argument flags handling policy.
argument_parser(const std::string_view name)
argument_parser & default_arguments(const std::same_as< default_argument > auto... arg_discriminators) noexcept
Add default arguments to the argument parser.
std::size_t count(std::string_view arg_name) const noexcept
Get the given argument's usage count.
void _print_subparsers(std::ostream &os) const noexcept
detail::argument_token::token_type _deduce_token_type(const std::string_view arg_value) const noexcept
Returns the most appropriate initial token type based on a command-line argument's value.
bool _finalized
A flag indicating whether the parsing process has been finalized.
static constexpr std::uint8_t _secondary_flag_prefix_length
void _tokenize_arg(const std::string_view arg_value, arg_token_vec_t &toks, const parsing_state &state)
Appends an argument token(s) created from arg_value to the toks vector.
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
The version of the program.
argument_parser & operator=(argument_parser &&)=delete
void parse_args(int argc, char *argv[])
Parses the command-line arguments.
argument_group & _gr_positional_args
The positional argument group.
Represents a command-line argument, either positional or optional.
Definition argument.hpp:58
std::string join(const R &range, const std::string_view delimiter=", ")
Joins elements of a range into a single string with a delimiter.
Definition string.hpp:49
ap::action_type::on_flag::type print_help(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 help message.
util::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.
argument_name_discriminator
Argument name member discriminator.
void add_default_argument(const default_argument, argument_parser &) noexcept
Adds a predefined/default positional argument to the parser.
constexpr range at_least(const count_type n) noexcept
range class builder function. Creates a range [n, inf).
Definition range.hpp:128
bool contains_whitespaces(std::string_view str) noexcept
Checks whether a string contains any whitespace characters.
Definition string.hpp:34
unknown_policy
The enumeration of policies for handling unknown arguments.
@ warn
Issue a warning when an unknown argument is encountered.
@ as_values
Treat unknown arguments as positional values.
@ ignore
Ignore unknown arguments.
@ fail
Throw an exception when an unknown argument is encountered.
default_argument
The enumeration of default arguments provided by the library.
@ p_output
A positional argument representing a single output file path. Equivalent to:
@ o_help
An optional argument representing the program's help flag. Equivalent to:
@ p_input
A positional argument representing a single input file path. Equivalent to:
@ o_input
A positional argument representing multiple input file paths. Equivalent to:
@ o_multi_input
A positional argument representing multiple input file paths. Equivalent to:
@ o_version
An optional argument representing the program's version flag. Equivalent to:
@ o_output
A positional argument representing multiple output file paths. Equivalent to:
@ o_multi_output
A positional argument representing multiple output file paths. Equivalent to:
An observing value action specifier.
Definition types.hpp:24
A collection of values used during the parsing process.
void set_parser(argument_parser &parser)
Update the parser-specific parameters of the state object.
parsing_state(argument_parser &parser, const bool parse_known_only=false)
std::vector< std::string > unknown_args
A vector of unknown argument values.
const bool parse_known_only
A flag indicating whether only known arguments should be parsed.
arg_ptr_vec_iter_t curr_pos_arg_it
An iterator pointing to the next positional argument to be processed.
arg_ptr_t curr_arg
The currently processed argument.
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.
@ m_primary
Matches only the primary name.
@ m_secondary
Matches only the secondary name.
Structure representing a single command-line argument token.
std::string value
The actual token's value.
token_type
The token type discriminator.
@ t_flag_compound
Represents a compound flag argument (secondary flag matching multiple arguments).
@ t_value
Represents a value argument.
@ t_flag_secondary
Represents a secondary (-) flag argument.
@ t_flag_primary
Represents a primary (–) flag argument.
Exception type used for invalid configuration of an argument parser or its arguments.
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
A type representing the absence of a value. This type is used for arguments that should not store any...
Definition types.hpp:20
Exception type used for errors encountered during the argument parsing operation.
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
A helper structure used to represent a program's version.
Definition types.hpp:23
std::string str() const noexcept
Converts the structure into a string in the v{major}.{minor}.{path} format.
Definition types.hpp:29