CPP-ARGON 4.0.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-2026 Jakub Musiał
2// This file is part of the CPP-ARGON project (https://github.com/SpectraL519/cpp-argon).
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 "argon/argument.hpp"
15#include "argon/types.hpp"
16#include "argon/util/ranges.hpp"
17
18#include <algorithm>
19#include <format>
20#include <iostream>
21#include <ranges>
22#include <span>
23
24#ifdef AP_TESTING
25
26namespace argon_testing {
27struct argument_parser_test_fixture;
28} // namespace argon_testing
29
30#endif
31
32namespace argon {
33
34class argument_parser;
35
37enum class default_argument : std::uint8_t {
47 p_input,
48
58
68 o_help,
69
83
94 o_input,
95
105 o_output,
106
118
129};
130
132enum class unknown_policy : std::uint8_t {
133 fail,
134 warn,
135 ignore,
136 as_values
137};
138
139namespace detail {
140
142
143} // namespace detail
144
181public:
184
187
188 argument_parser(const std::string_view name) : argument_parser(name, "") {}
189
190 ~argument_parser() = default;
191
198 this->_program_version.emplace(version.str());
199 return *this;
200 }
201
209 throw invalid_configuration("The program version cannot contain whitespace characters!"
210 );
211
212 this->_program_version.emplace(version);
213 return *this;
214 }
215
221 argument_parser& program_description(std::string_view description) noexcept {
222 this->_program_description.emplace(description);
223 return *this;
224 }
225
232 argument_parser& verbose(const bool v = true) noexcept {
233 this->_verbose = v;
234 return *this;
235 }
236
244 this->_unknown_policy = policy;
245 return *this;
246 }
247
255 template <util::c_range_of<default_argument> AR>
261
268 const std::initializer_list<default_argument>& arg_discriminators
269 ) noexcept {
271 }
272
279 const std::same_as<default_argument> auto... arg_discriminators
280 ) noexcept {
282 return *this;
283 }
284
292 template <util::c_argument_value_type T = std::string>
294 return this->add_positional_argument<T>(this->_gr_positional_args, name);
295 }
296
304 template <util::c_argument_value_type T = std::string>
306 argument_group& group, const std::string_view name
307 ) {
308 this->_validate_group(group);
309 this->_verify_arg_name_pattern(name);
310
311 const detail::argument_name arg_name(std::make_optional<std::string>(name));
312 if (this->_is_arg_name_used(arg_name))
314
315 auto& new_arg_ptr =
316 this->_positional_args.emplace_back(std::make_shared<positional_argument<T>>(arg_name));
317 group._add_argument(new_arg_ptr);
318 return static_cast<positional_argument<T>&>(*new_arg_ptr);
319 }
320
329 template <util::c_argument_value_type T = std::string>
331 const std::string_view name,
333 ) {
334 return this->add_optional_argument<T>(this->_gr_optional_args, name, name_discr);
335 }
336
345 template <util::c_argument_value_type T = std::string>
347 const std::string_view primary_name, const std::string_view secondary_name
348 ) {
349 return this->add_optional_argument<T>(
350 this->_gr_optional_args, primary_name, secondary_name
351 );
352 }
353
363 template <util::c_argument_value_type T = std::string>
366 const std::string_view name,
368 ) {
369 this->_validate_group(group);
370 this->_verify_arg_name_pattern(name);
371
372 const auto arg_name =
373 name_discr == n_primary
374 ? detail::
375 argument_name{std::make_optional<std::string>(name), std::nullopt, this->_flag_prefix_char}
377 std::nullopt, std::make_optional<std::string>(name), this->_flag_prefix_char
378 };
379
380 if (this->_is_arg_name_used(arg_name))
382
383 auto& new_arg_ptr =
384 this->_optional_args.emplace_back(std::make_shared<optional_argument<T>>(arg_name));
385 group._add_argument(new_arg_ptr);
386 return static_cast<optional_argument<T>&>(*new_arg_ptr);
387 }
388
398 template <util::c_argument_value_type T = std::string>
401 const std::string_view primary_name,
402 const std::string_view secondary_name
403 ) {
404 this->_validate_group(group);
405 this->_verify_arg_name_pattern(primary_name);
406 this->_verify_arg_name_pattern(secondary_name);
407
409 std::make_optional<std::string>(primary_name),
410 std::make_optional<std::string>(secondary_name),
412 );
413 if (this->_is_arg_name_used(arg_name))
415
416 auto& new_arg_ptr =
417 this->_optional_args.emplace_back(std::make_shared<optional_argument<T>>(arg_name));
418 group._add_argument(new_arg_ptr);
419 return static_cast<optional_argument<T>&>(*new_arg_ptr);
420 }
421
430 template <bool StoreImplicitly = true>
432 const std::string_view name,
434 ) {
435 return this->add_optional_argument<bool>(name, name_discr)
436 .default_values(not StoreImplicitly)
437 .implicit_values(StoreImplicitly)
438 .nargs(0ull);
439 }
440
449 template <bool StoreImplicitly = true>
451 const std::string_view primary_name, const std::string_view secondary_name
452 ) {
453 return this->add_optional_argument<bool>(primary_name, secondary_name)
454 .default_values(not StoreImplicitly)
455 .implicit_values(StoreImplicitly)
456 .nargs(0ull);
457 }
458
468 template <bool StoreImplicitly = true>
471 const std::string_view name,
473 ) {
474 return this->add_optional_argument<bool>(group, name, name_discr)
475 .default_values(not StoreImplicitly)
476 .implicit_values(StoreImplicitly)
477 .nargs(0ull);
478 }
479
489 template <bool StoreImplicitly = true>
492 const std::string_view primary_name,
493 const std::string_view secondary_name
494 ) {
496 .default_values(not StoreImplicitly)
497 .implicit_values(StoreImplicitly)
498 .nargs(0ull);
499 }
500
506 argument_group& add_group(const std::string_view name) noexcept {
507 return *this->_argument_groups.emplace_back(argument_group::create(*this, name));
508 }
509
515 argument_parser& add_subparser(const std::string_view name) {
516 const auto subparser_it = std::ranges::find(
517 this->_subparsers, name, [](const auto& subparser) { return subparser->_name; }
518 );
519 if (subparser_it != this->_subparsers.end())
520 throw std::logic_error(std::format(
521 "A subparser with the given name () already exists in parser '{}'",
522 (*subparser_it)->_name,
524 ));
525
526 return *this->_subparsers.emplace_back(
527 std::unique_ptr<argument_parser>(new argument_parser(name, this->_program_name))
528 );
529 }
530
544 void parse_args(int argc, char* argv[]) {
545 this->parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
546 }
547
556 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
557 void parse_args(const AR& argv_rng) {
558 parsing_state state(*this);
559 this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state);
560
561 if (not state.unknown_args.empty())
562 throw parsing_failure(std::format(
563 "Failed to deduce the argument for values [{}]", util::join(state.unknown_args)
564 ));
565 }
566
579 void try_parse_args(int argc, char* argv[]) {
580 this->try_parse_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
581 }
582
595 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
597 try {
598 this->parse_args(argv_rng);
599 }
601 std::cerr << "[argon::error] " << err.what() << std::endl
602 << this->resolved_parser() << std::endl;
603 std::exit(EXIT_FAILURE);
604 }
605 }
606
625 std::vector<std::string> parse_known_args(int argc, char* argv[]) {
626 return this->parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
627 }
628
643 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
644 std::vector<std::string> parse_known_args(const AR& argv_rng) {
645 parsing_state state(*this, true);
646 this->_parse_args_impl(std::ranges::begin(argv_rng), std::ranges::end(argv_rng), state);
647 return std::move(state.unknown_args);
648 }
649
663 std::vector<std::string> try_parse_known_args(int argc, char* argv[]) {
664 return this->try_parse_known_args(std::span(argv + 1, static_cast<std::size_t>(argc - 1)));
665 }
666
680 template <util::c_forward_range_of<std::string, util::type_validator::convertible> AR>
681 std::vector<std::string> try_parse_known_args(const AR& argv_rng) {
682 try {
683 return this->parse_known_args(argv_rng);
684 }
686 std::cerr << "[argon::error] " << err.what() << std::endl
687 << this->resolved_parser() << std::endl;
688 std::exit(EXIT_FAILURE);
689 }
690 }
691
693 [[nodiscard]] std::string_view name() const noexcept {
694 return this->_name;
695 }
696
708 [[nodiscard]] std::string_view program_name() const noexcept {
709 return this->_program_name;
710 }
711
718 return this->_invoked;
719 }
720
726 return this->_finalized;
727 }
728
734 const auto used_subparser_it = std::ranges::find_if(
735 this->_subparsers, [](const auto& subparser) { return subparser->_invoked; }
736 );
737 if (used_subparser_it == this->_subparsers.end())
738 return *this;
739 return (*used_subparser_it)->resolved_parser();
740 }
741
747 [[nodiscard]] bool is_used(std::string_view arg_name) const noexcept {
748 const auto arg = this->_get_argument(arg_name);
749 return arg ? arg->is_used() : false;
750 }
751
757 [[nodiscard]] bool has_value(std::string_view arg_name) const noexcept {
758 const auto arg = this->_get_argument(arg_name);
759 return arg ? arg->has_value() : false;
760 }
761
767 [[nodiscard]] std::size_t count(std::string_view arg_name) const noexcept {
768 const auto arg = this->_get_argument(arg_name);
769 return arg ? arg->count() : 0ull;
770 }
771
779 template <util::c_argument_value_type T = std::string>
780 [[nodiscard]] T value(std::string_view arg_name) const {
781 const auto arg = this->_get_argument(arg_name);
782 if (not arg)
784
785 const auto& arg_value = arg->value();
786 try {
787 return std::any_cast<T>(arg_value);
788 }
789 catch (const std::bad_any_cast&) {
790 throw type_error::invalid_value_type<T>(arg->name());
791 }
792 }
793
803 template <util::c_argument_value_type T = std::string, std::convertible_to<T> U>
804 [[nodiscard]] T value_or(std::string_view arg_name, U&& fallback_value) const {
805 const auto arg = this->_get_argument(arg_name);
806 if (not arg)
808
809 try {
810 const auto& arg_value = arg->value();
811 return std::any_cast<T>(arg_value);
812 }
813 catch (const std::logic_error&) {
814 // positional: no value parsed
815 // optional: no value parsed + no predefined value
816 return T{std::forward<U>(fallback_value)};
817 }
818 catch (const std::bad_any_cast&) {
819 throw type_error::invalid_value_type<T>(arg->name());
820 }
821 }
822
831 template <util::c_argument_value_type T = std::string>
832 [[nodiscard]] std::vector<T> values(std::string_view arg_name) const {
833 const auto arg = this->_get_argument(arg_name);
834 if (not arg)
836
837 try {
838 std::vector<T> values;
839 std::ranges::copy(
840 util::any_range_cast_view<T>(arg->values()), std::back_inserter(values)
841 );
842 return values;
843 }
844 catch (const std::bad_any_cast&) {
845 throw type_error::invalid_value_type<T>(arg->name());
846 }
847 }
848
854 void print_help(const bool verbose, std::ostream& os = std::cout) const noexcept {
855 os << "Program: " << this->_program_name;
856 if (this->_program_version)
857 os << " (" << this->_program_version.value() << ')';
858 os << '\n';
859
860 if (this->_program_description)
861 os << '\n'
862 << std::string(this->_indent_width, ' ') << this->_program_description.value()
863 << '\n';
864
865 this->_print_subparsers(os);
866 for (const auto& group : this->_argument_groups)
867 this->_print_group(os, *group, verbose);
868 }
869
877 void print_version(std::ostream& os = std::cout) const noexcept {
878 os << this->_program_name << " : version " << this->_program_version.value_or("unspecified")
879 << std::endl;
880 }
881
892 friend std::ostream& operator<<(std::ostream& os, const argument_parser& parser) noexcept {
894 return os;
895 }
896
897#ifdef AP_TESTING
899 friend struct ::argon_testing::argument_parser_test_fixture;
900#endif
901
902private:
903 using arg_ptr_t = std::shared_ptr<detail::argument_base>;
904 using arg_ptr_vec_t = std::vector<arg_ptr_t>;
905 using arg_ptr_vec_iter_t = typename arg_ptr_vec_t::iterator;
906
907 using arg_group_ptr_t = std::unique_ptr<argument_group>;
908 using arg_group_ptr_vec_t = std::vector<arg_group_ptr_t>;
909
910 using arg_parser_ptr_t = std::unique_ptr<argument_parser>;
911 using arg_parser_ptr_vec_t = std::vector<arg_parser_ptr_t>;
912
913 using arg_token_vec_t = std::vector<detail::argument_token>;
914 using arg_token_vec_iter_t = typename arg_token_vec_t::const_iterator;
915
937
938 argument_parser(const std::string_view name, const std::string_view parent_name)
939 : _name(name),
941 std::format("{}{}{}", parent_name, std::string(not parent_name.empty(), ' '), name)
942 ),
943 _gr_positional_args(add_group("Positional Arguments")),
944 _gr_optional_args(add_group("Optional Arguments")) {
945 if (name.empty())
946 throw invalid_configuration("The program name cannot be empty!");
947
949 throw invalid_configuration("The program name cannot contain whitespace characters!");
950 }
951
956 void _verify_arg_name_pattern(const std::string_view arg_name) const {
957 if (arg_name.empty())
959 arg_name, "An argument name cannot be empty."
960 );
961
964 arg_name, "An argument name cannot contain whitespaces."
965 );
966
967 if (arg_name.front() == this->_flag_prefix_char)
969 arg_name,
970 std::format(
971 "An argument name cannot begin with a flag prefix character ({}).",
973 )
974 );
975
976 if (std::isdigit(arg_name.front()))
978 arg_name, "An argument name cannot begin with a digit."
979 );
980 }
981
989 const std::string_view arg_name,
991 ) const noexcept {
992 return [=](const arg_ptr_t& arg) { return arg->name().match(arg_name, m_type); };
993 }
994
1004 ) const noexcept {
1005 return [&arg_name, m_type](const arg_ptr_t& arg) {
1006 return arg->name().match(arg_name, m_type);
1007 };
1008 }
1009
1019 ) const noexcept {
1020 const auto predicate = this->_name_match_predicate(arg_name, m_type);
1021
1022 if (std::ranges::find_if(this->_positional_args, predicate) != this->_positional_args.end())
1023 return true;
1024
1025 if (std::ranges::find_if(this->_optional_args, predicate) != this->_optional_args.end())
1026 return true;
1027
1028 return false;
1029 }
1030
1037 if (group._parser != this)
1038 throw std::logic_error(std::format(
1039 "An argument group '{}' does not belong to the given parser.", group._name
1040 ));
1041 }
1042
1052 template <util::c_forward_iterator_of<std::string, util::type_validator::convertible> AIt>
1054 this->_invoked = true;
1055
1056 if (args_begin != args_end) {
1057 // try to match a subparser
1058 const auto subparser_it =
1059 std::ranges::find(this->_subparsers, *args_begin, [](const auto& subparser) {
1060 return subparser->_name;
1061 });
1062 if (subparser_it != this->_subparsers.end()) {
1063 auto& subparser = **subparser_it;
1064 state.set_parser(subparser);
1066 return;
1067 }
1068 }
1069
1070 // process command-line arguments within the current parser
1072 for (const auto& tok : this->_tokenize(args_begin, args_end, state))
1073 this->_parse_token(tok, state);
1074 this->_verify_final_state();
1075 this->_finalized = true;
1076 }
1077
1085 // step 1
1086 arg_ptr_t non_required_arg = nullptr;
1087 for (const auto& arg : this->_positional_args) {
1088 if (not arg->is_required()) {
1089 non_required_arg = arg;
1090 continue;
1091 }
1092
1093 if (non_required_arg and arg->is_required())
1094 throw invalid_configuration(std::format(
1095 "Required positional argument [{}] cannot be defined after a non-required "
1096 "positional argument [{}].",
1097 arg->name().str(),
1098 non_required_arg->name().str()
1099 ));
1100 }
1101 }
1102
1112 template <util::c_forward_iterator_of<std::string, util::type_validator::convertible> AIt>
1115 ) {
1117 toks.reserve(static_cast<std::size_t>(std::ranges::distance(args_begin, args_end)));
1118 std::ranges::for_each(args_begin, args_end, [&](const auto& arg_value) {
1119 this->_tokenize_arg(arg_value, toks, state);
1120 });
1121 return toks;
1122 }
1123
1131 const std::string_view arg_value, arg_token_vec_t& toks, const parsing_state& state
1132 ) {
1134 .type = this->_deduce_token_type(arg_value), .value = std::string(arg_value)
1135 };
1136
1137 if (not tok.is_flag_token() or this->_validate_flag_token(tok)) {
1138 toks.emplace_back(std::move(tok));
1139 return;
1140 }
1141
1142 // not a value token -> flag token
1143 // flag token could not be validated -> unknown flag
1144 if (state.parse_known_only) { // do nothing (will be handled during parsing)
1145 toks.emplace_back(std::move(tok));
1146 return;
1147 }
1148
1149 switch (this->_unknown_policy) {
1153 std::cerr << "[argon::warning] Unknown argument '" << tok.value << "' will be ignored."
1154 << std::endl;
1155 [[fallthrough]];
1157 return;
1160 toks.emplace_back(std::move(tok));
1161 break;
1162 }
1163 }
1164
1175 const std::string_view arg_value
1176 ) const noexcept {
1179
1180 if (arg_value.starts_with(this->_flag_prefix))
1182
1183 if (arg_value.starts_with(this->_flag_prefix_char))
1185
1187 }
1188
1196 const auto opt_arg_it = this->_find_opt_arg(tok);
1197 if (opt_arg_it == this->_optional_args.end())
1198 return this->_validate_compound_flag_token(tok);
1199
1200 tok.args.emplace_back(*opt_arg_it);
1201 return true;
1202 }
1203
1213 return false;
1214
1215 const auto actual_tok_value = this->_strip_flag_prefix(tok);
1216 tok.args.reserve(actual_tok_value.size());
1217
1218 for (const char c : actual_tok_value) {
1219 const auto opt_arg_it = std::ranges::find_if(
1220 this->_optional_args,
1222 std::string_view(&c, 1ull), detail::argument_name::m_secondary
1223 )
1224 );
1225
1226 if (opt_arg_it == this->_optional_args.end()) {
1227 tok.args.clear();
1228 return false;
1229 }
1230
1231 tok.args.emplace_back(*opt_arg_it);
1232 }
1233
1235 return true;
1236 }
1237
1245 ) noexcept {
1246 if (not flag_tok.is_flag_token())
1247 return this->_optional_args.end();
1248
1249 const auto actual_tok_value = this->_strip_flag_prefix(flag_tok);
1250 const auto match_type =
1254
1255 return std::ranges::find_if(
1256 this->_optional_args, this->_name_match_predicate(actual_tok_value, match_type)
1257 );
1258 }
1259
1266 ) const noexcept {
1267 switch (tok.type) {
1269 return std::string_view(tok.value).substr(this->_primary_flag_prefix_length);
1271 return std::string_view(tok.value).substr(this->_secondary_flag_prefix_length);
1272 default:
1273 return tok.value;
1274 }
1275 }
1276
1284 if (state.curr_arg and state.curr_arg->is_greedy()) {
1285 this->_set_argument_value(tok.value, state);
1286 return;
1287 }
1288
1289 if (tok.is_flag_token())
1290 this->_parse_flag_token(tok, state);
1291 else
1292 this->_parse_value_token(tok, state);
1293 }
1294
1302 if (not tok.is_valid_flag_token()) {
1303 if (state.parse_known_only) {
1304 state.curr_arg.reset();
1305 state.unknown_args.emplace_back(tok.value);
1306 return;
1307 }
1308 else {
1309 // should never happen as unknown flags are filtered out during tokenization
1311 }
1312 }
1313
1314 for (const auto& arg : tok.args) {
1315 if (arg->mark_used())
1316 state.curr_arg = arg;
1317 else
1318 state.curr_arg.reset();
1319 }
1320 }
1321
1329 if (not state.curr_arg) {
1330 if (state.curr_pos_arg_it == this->_positional_args.end()) {
1331 state.unknown_args.emplace_back(tok.value);
1332 return;
1333 }
1334
1335 state.curr_arg = *state.curr_pos_arg_it;
1336 }
1337
1338 this->_set_argument_value(tok.value, state);
1339 }
1340
1347 void _set_argument_value(const std::string_view value, parsing_state& state) noexcept {
1348 if (state.curr_arg->set_value(std::string(value)))
1349 return; // argument still accepts values
1350
1351 // advance to the next positional argument if possible
1352 if (state.curr_arg->is_positional()
1353 and state.curr_pos_arg_it != this->_positional_args.end())
1354 ++state.curr_pos_arg_it;
1355
1356 state.curr_arg.reset();
1357 }
1358
1363 void _verify_final_state() const {
1364 const auto [supress_group_checks, suppress_arg_checks] = this->_are_checks_suppressed();
1365 for (const auto& group : this->_argument_groups)
1366 this->_verify_group_requirements(*group, supress_group_checks, suppress_arg_checks);
1367 }
1368
1375 [[nodiscard]] std::pair<bool, bool> _are_checks_suppressed() const noexcept {
1376 bool suppress_group_checks = false;
1377 bool suppress_arg_checks = false;
1378
1379 auto check_arg = [&](const arg_ptr_t& arg) {
1380 if (arg->is_used()) {
1381 if (arg->suppresses_group_checks())
1382 suppress_group_checks = true;
1383 if (arg->suppresses_arg_checks())
1384 suppress_arg_checks = true;
1385 }
1386 };
1387
1388 // TODO: use std::views::concat after the transition to C++26
1389 std::ranges::for_each(this->_positional_args, check_arg);
1390 std::ranges::for_each(this->_optional_args, check_arg);
1391 return {suppress_group_checks, suppress_arg_checks};
1392 }
1393
1401 const argument_group& group,
1402 const bool suppress_group_checks,
1403 const bool suppress_arg_checks
1404 ) const {
1405 if (group._arguments.empty())
1406 return;
1407
1408 if (not suppress_group_checks) {
1409 const auto n_used_args = static_cast<std::size_t>(std::ranges::count_if(
1410 group._arguments, [](const auto& arg) { return arg->is_used(); }
1411 ));
1412
1413 if (group._mutually_exclusive) {
1414 if (n_used_args > 1ull)
1415 throw parsing_failure(std::format(
1416 "At most one argument from the mutually exclusive group '{}' can be used",
1417 group._name
1418 ));
1419
1420 const auto used_arg_it = std::ranges::find_if(
1421 group._arguments, [](const auto& arg) { return arg->is_used(); }
1422 );
1423
1424 if (used_arg_it != group._arguments.end()) {
1425 // only the one used argument has to be validated
1426 this->_verify_argument_requirements(*used_arg_it, suppress_arg_checks);
1427 return;
1428 }
1429 }
1430
1431 if (group._required and n_used_args == 0ull)
1432 throw parsing_failure(std::format(
1433 "At least one argument from the required group '{}' must be used", group._name
1434 ));
1435 }
1436
1437 // all arguments in the group have to be validated
1438 for (const auto& arg : group._arguments)
1439 this->_verify_argument_requirements(arg, suppress_arg_checks);
1440 }
1441
1448 void _verify_argument_requirements(const arg_ptr_t& arg, const bool suppress_arg_checks) const {
1449 if (suppress_arg_checks)
1450 return;
1451
1452 if (arg->is_required() and not arg->has_value())
1453 throw parsing_failure(
1454 std::format("No values parsed for a required argument [{}]", arg->name().str())
1455 );
1456 if (const auto nv_ord = arg->nvalues_ordering(); not std::is_eq(nv_ord))
1458 }
1459
1465 arg_ptr_t _get_argument(std::string_view arg_name) const noexcept {
1466 const auto predicate = this->_name_match_predicate(arg_name);
1467
1468 if (auto pos_arg_it = std::ranges::find_if(this->_positional_args, predicate);
1469 pos_arg_it != this->_positional_args.end()) {
1470 return *pos_arg_it;
1471 }
1472
1473 if (auto opt_arg_it = std::ranges::find_if(this->_optional_args, predicate);
1474 opt_arg_it != this->_optional_args.end()) {
1475 return *opt_arg_it;
1476 }
1477
1478 return nullptr;
1479 }
1480
1481 void _print_subparsers(std::ostream& os) const noexcept {
1482 if (this->_subparsers.empty())
1483 return;
1484
1485 os << "\nCommands:\n";
1486
1487 std::vector<detail::help_builder> builders;
1488 builders.reserve(this->_subparsers.size());
1489
1490 for (const auto& subparser : this->_subparsers)
1491 builders.emplace_back(subparser->_name, subparser->_program_description);
1492
1493 std::size_t max_subparser_name_length = 0ull;
1494 for (const auto& bld : builders)
1496
1497 for (const auto& bld : builders)
1498 os << '\n' << bld.get_basic(this->_indent_width, max_subparser_name_length);
1499
1500 os << '\n';
1501 }
1502
1510 void _print_group(std::ostream& os, const argument_group& group, const bool verbose)
1511 const noexcept {
1512 if (group._hidden)
1513 return;
1514
1515 auto visible_args = std::views::filter(group._arguments, [](const auto& arg) {
1516 return not arg->is_hidden();
1517 });
1518
1519 if (std::ranges::empty(visible_args))
1520 return;
1521
1522 os << '\n' << group._name << ':';
1523
1524 std::vector<std::string_view> group_attrs;
1525 if (group._required)
1526 group_attrs.emplace_back("required");
1527 if (group._mutually_exclusive)
1528 group_attrs.emplace_back("mutually exclusive");
1529 if (not group_attrs.empty())
1530 os << " (" << util::join(group_attrs) << ')';
1531 os << '\n';
1532
1533 if (verbose) {
1534 for (const auto& arg : visible_args)
1535 os << '\n' << arg->help_builder(verbose).get(this->_indent_width) << '\n';
1536 }
1537 else {
1538 std::vector<detail::help_builder> builders;
1539 builders.reserve(group._arguments.size());
1540
1541 for (const auto& arg : visible_args)
1542 builders.emplace_back(arg->help_builder(verbose));
1543
1544 std::size_t max_arg_name_length = 0ull;
1545 for (const auto& bld : builders)
1546 max_arg_name_length = std::max(max_arg_name_length, bld.name.length());
1547
1548 for (const auto& bld : builders)
1549 os << '\n' << bld.get_basic(this->_indent_width, max_arg_name_length);
1550
1551 os << '\n';
1552 }
1553 }
1554
1555 std::string _name;
1556 std::string
1558 std::optional<std::string> _program_version;
1559 std::optional<std::string> _program_description;
1560 bool _verbose = false;
1562
1569
1571 false;
1572 bool _finalized = false;
1573
1574 static constexpr std::uint8_t _primary_flag_prefix_length = 2u;
1575 static constexpr std::uint8_t _secondary_flag_prefix_length = 1u;
1576 static constexpr char _flag_prefix_char = '-';
1577 static constexpr std::string_view _flag_prefix = "--";
1578 static constexpr std::uint8_t _indent_width = 2;
1579};
1580
1581namespace detail {
1582
1589 const default_argument arg_discriminator, argument_parser& arg_parser
1590) noexcept {
1591 switch (arg_discriminator) {
1593 arg_parser.add_positional_argument("input")
1595 .help("Input file path");
1596 break;
1597
1599 arg_parser.add_positional_argument("output").help("Output file path");
1600 break;
1601
1603 arg_parser.add_optional_argument<none_type>("help", "h")
1604 .action<action_type::on_flag>(action::print_help(arg_parser, EXIT_SUCCESS))
1605 .help("Display the help message");
1606 break;
1607
1609 arg_parser.add_optional_argument<none_type>("version", "v")
1610 .action<action_type::on_flag>([&arg_parser]() {
1611 arg_parser.print_version();
1612 std::exit(EXIT_SUCCESS);
1613 })
1614 .help("Dsiplay program version info");
1615 break;
1616
1618 arg_parser.add_optional_argument("input", "i")
1619 .nargs(1ull)
1621 .help("Input file path");
1622 break;
1623
1625 arg_parser.add_optional_argument("output", "o").nargs(1ull).help("Output file path");
1626 break;
1627
1629 arg_parser.add_optional_argument("input", "i")
1630 .nargs(argon::nargs::at_least(1ull))
1632 .help("Input files paths");
1633 break;
1634
1636 arg_parser.add_optional_argument("output", "o")
1637 .nargs(argon::nargs::at_least(1ull))
1638 .help("Output files paths");
1639 break;
1640 }
1641}
1642
1643} // namespace detail
1644
1645} // namespace argon
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< detail::argument_token > arg_token_vec_t
void print_help(const bool verbose, std::ostream &os=std::cout) const noexcept
Prints the argument parser's help message to an output stream.
argument_group & add_group(const std::string_view name) noexcept
Adds an argument group with the given name 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.
arg_ptr_vec_t _positional_args
The list of positional arguments.
void _parse_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument token.
static constexpr char _flag_prefix_char
argument_parser(const std::string_view name, const std::string_view parent_name)
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.
argument_parser & operator=(argument_parser &&)=delete
std::vector< arg_group_ptr_t > arg_group_ptr_vec_t
std::string_view program_name() const noexcept
std::vector< arg_ptr_t > arg_ptr_vec_t
bool has_value(std::string_view arg_name) const noexcept
Check if the given argument has a value.
void _set_argument_value(const std::string_view value, parsing_state &state) noexcept
Set the value for the currently processed argument.
std::string _name
The name of the parser.
static constexpr std::string_view _flag_prefix
typename arg_token_vec_t::const_iterator arg_token_vec_iter_t
std::optional< std::string > _program_description
The description of the program.
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.
argument_parser & unknown_arguments_policy(const unknown_policy policy) noexcept
Set the unknown argument flags handling policy.
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.
arg_parser_ptr_vec_t _subparsers
The list of subparsers.
argument_group & _gr_optional_args
The optional argument group.
argument_parser & program_description(std::string_view description) noexcept
Set the program description.
void _parse_flag_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument flag token.
bool _verbose
Verbosity flag.
argument_parser & default_arguments(const AR &arg_discriminators) noexcept
Add default arguments to the argument parser.
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...
arg_group_ptr_vec_t _argument_groups
The list of argument groups.
std::pair< bool, bool > _are_checks_suppressed() const noexcept
Check whether required argument group checks or argument checks suppressing is enabled.
void parse_args(int argc, char *argv[])
Parses the command-line arguments.
void parse_args(const AR &argv_rng)
Parses the command-line arguments.
std::vector< std::string > parse_known_args(int argc, char *argv[])
Parses the known command-line arguments.
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.
std::vector< T > values(std::string_view arg_name) const
Get all values of the given argument.
bool _finalized
A flag indicating whether the parsing process has been finalized.
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.
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.
typename arg_ptr_vec_t::iterator arg_ptr_vec_iter_t
std::optional< std::string > _program_version
The version of the program.
argument_parser & default_arguments(const std::initializer_list< default_argument > &arg_discriminators) noexcept
Add default arguments to the argument parser.
std::string_view name() const noexcept
Returns the parser's name.
arg_ptr_vec_iter_t _find_opt_arg(const detail::argument_token &flag_tok) noexcept
Find an optional argument based on a flag token.
void try_parse_args(const AR &argv_rng)
Parses the command-line arguments and exits on error.
static constexpr std::uint8_t _primary_flag_prefix_length
void _parse_args_impl(AIt args_begin, const AIt args_end, parsing_state &state)
Implementation of parsing command-line arguments.
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.
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 _validate_group(const argument_group &group)
Check if the given group belongs to the parser.
static constexpr std::uint8_t _indent_width
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.
bool _validate_flag_token(detail::argument_token &tok) noexcept
Check if a flag token is valid 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.
std::string _program_name
The name of the program in the format "<parent-parser-names>... <program-name>".
void _print_subparsers(std::ostream &os) const noexcept
argument_parser & add_subparser(const std::string_view name)
Adds an subparser with the given name to the parser's configuration.
T value(std::string_view arg_name) const
Get the value of the given argument.
void _print_group(std::ostream &os, const argument_group &group, const bool verbose) const noexcept
Print the given argument list to an output stream.
argument_parser & operator=(const argument_parser &)=delete
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 & verbose(const bool v=true) noexcept
Set the verbosity mode.
std::vector< std::string > try_parse_known_args(int argc, char *argv[])
Parses the known command-line arguments and exits on error.
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.
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.
std::vector< arg_parser_ptr_t > arg_parser_ptr_vec_t
void _verify_final_state() const
Verifies the correctness of the parsed command-line arguments.
void _parse_value_token(const detail::argument_token &tok, parsing_state &state)
Parse a single command-line argument value token.
argument_parser(argument_parser &&)=delete
argument_parser & program_version(const version &version) noexcept
Set the program version.
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 finalized() const noexcept
Check whether the parser has finalized parsing its own arguments.
bool _invoked
A flag indicating whether the parser has been invoked to parse arguments.
static constexpr std::uint8_t _secondary_flag_prefix_length
unknown_policy _unknown_policy
Policy for unknown arguments.
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::size_t count(std::string_view arg_name) const noexcept
Get the given argument's usage count.
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...
void _validate_argument_configuration() const
Validate whether the definition/configuration of the parser's arguments is correct.
argument_parser(const argument_parser &)=delete
positional_argument< T > & add_positional_argument(const std::string_view name)
Adds a positional argument to the parser's configuration.
argument_parser & resolved_parser() noexcept
Returns the deepest invoked parser.
arg_ptr_vec_t _optional_args
The list of optional arguments.
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.
argument_parser & default_arguments(const std::same_as< default_argument > auto... arg_discriminators) noexcept
Add default arguments to the argument parser.
argument_group & _gr_positional_args
The positional argument group.
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...
bool is_used(std::string_view arg_name) const noexcept
Check if a specific argument was used in the command-line.
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.
std::unique_ptr< argument_group > arg_group_ptr_t
void try_parse_args(int argc, char *argv[])
Parses the command-line arguments and exits on error.
argument_parser & program_version(std::string_view version)
Set the program version.
friend std::ostream & operator<<(std::ostream &os, const argument_parser &parser) noexcept
Prints the argument parser's details to an output stream.
std::vector< std::string > parse_known_args(const AR &argv_rng)
Parses the known command-line arguments.
arg_ptr_t _get_argument(std::string_view arg_name) const noexcept
Get the argument with the specified name.
std::string_view _strip_flag_prefix(const detail::argument_token &tok) const noexcept
Removes the flag prefix from a flag token's value.
std::unique_ptr< argument_parser > arg_parser_ptr_t
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.
std::shared_ptr< detail::argument_base > arg_ptr_t
argument_parser(const std::string_view name)
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:48
argon::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< argon::action_type::observe, std::string > check_file_exists() noexcept
Returns an observe action which checks whether lower_bound file with the given name exists.
void add_default_argument(const default_argument, argument_parser &) noexcept
Adds a predefined/default positional argument to the parser.
argument_name_discriminator
Argument name member discriminator.
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
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:
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.
Provides common ranges utility functions.
An observing value action specifier.
Definition types.hpp:24
A collection of values used during the parsing process.
arg_ptr_t curr_arg
The currently processed argument.
std::vector< std::string > unknown_args
A vector of unknown argument values.
parsing_state(argument_parser &parser, const bool parse_known_only=false)
void set_parser(argument_parser &parser)
Update the parser-specific parameters of the state object.
arg_ptr_vec_iter_t curr_pos_arg_it
An iterator pointing to the next positional argument to be processed.
const bool parse_known_only
A flag indicating whether only known arguments should be parsed.
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.
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.
std::string value
The actual token's value.
Exception type used for invalid configuration of an argument parser or its arguments.
static invalid_configuration invalid_argument_name(const std::string_view arg_name, const std::string_view reason) noexcept
static invalid_configuration argument_name_used(const detail::argument_name &arg_name) 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 unknown_argument(const std::string_view arg_name) noexcept
static parsing_failure invalid_nvalues(const detail::argument_name &arg_name, const std::weak_ordering ordering) 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