9
9
10
10
#pragma once
11
11
12
- #include < set >
12
+ #include < unordered_set >
13
13
#include < variant>
14
14
15
15
#include < sharg/config.hpp>
@@ -426,9 +426,6 @@ class parser
426
426
// Determine the format and subcommand.
427
427
determine_format_and_subcommand ();
428
428
429
- // If a subcommand was provided, check that it is valid.
430
- verify_subcommand ();
431
-
432
429
// Apply all defered operations to the parser, e.g., `add_option`, `add_flag`, `add_positional_option`.
433
430
for (auto & operation : operations)
434
431
operation ();
@@ -512,7 +509,7 @@ class parser
512
509
}
513
510
}
514
511
515
- if (std::find (used_option_ids. begin (), used_option_ids. end (), std::string{id}) == used_option_ids. end ( ))
512
+ if (!used_ids. contains ( std::string{id}))
516
513
throw design_error{" You can only ask for option identifiers that you added with add_option() before." };
517
514
518
515
// we only need to search for an option before the `option_end_identifier` (`--`)
@@ -744,8 +741,8 @@ class parser
744
741
detail::format_copyright>
745
742
format{detail::format_short_help{}};
746
743
747
- // !\brief List of option/flag identifiers that are already used.
748
- std::set <std::string> used_option_ids {" h" , " hh" , " help" , " advanced-help" , " export-help" , " version" , " copyright" };
744
+ // !\brief List of option/flag identifiers (excluding -/--) that are already used.
745
+ std::unordered_set <std::string> used_ids {" h" , " hh" , " help" , " advanced-help" , " export-help" , " version" , " copyright" };
749
746
750
747
// !\brief The command line arguments that will be passed to the format.
751
748
std::vector<std::string> format_arguments{};
@@ -756,14 +753,18 @@ class parser
756
753
// !\brief The command that lead to calling this parser, e.g. [./build/bin/raptor, build]
757
754
std::vector<std::string> executable_name{};
758
755
756
+ // !\brief Set of option identifiers (including -/--) that have been added via `add_option`.
757
+ std::unordered_set<std::string> options{};
758
+
759
759
// !\brief Vector of functions that stores all calls.
760
760
std::vector<std::function<void ()>> operations;
761
761
762
- /* !\brief Initializes the sharg::parser class on construction .
762
+ /* !\brief Handles format and subcommand detection .
763
763
* \throws sharg::too_few_arguments if option --export-help was specified without a value
764
764
* \throws sharg::too_few_arguments if option --version-check was specified without a value
765
765
* \throws sharg::validation_error if the value passed to option --export-help was invalid.
766
766
* \throws sharg::validation_error if the value passed to option --version-check was invalid.
767
+ * \throws sharg::user_input_error if the subcommand is unknown.
767
768
* \details
768
769
*
769
770
* This function adds all command line parameters to the format_arguments member variable
@@ -788,23 +789,27 @@ class parser
788
789
void determine_format_and_subcommand ()
789
790
{
790
791
assert (!arguments.empty ());
792
+
791
793
auto it = arguments.begin ();
794
+ std::string_view arg{*it};
792
795
793
- executable_name.emplace_back (*it);
794
- ++it;
796
+ executable_name.emplace_back (arg);
795
797
796
- // Helper function for going to the next argument. This makes it more obvious that we are
798
+ // Helper function for reading the next argument. This makes it more obvious that we are
797
799
// incrementing `it` (version-check, and export-help).
798
- auto go_to_next_arg = [this , &it](std::string_view message ) -> auto
800
+ auto read_next_arg = [this , &it, &arg]( ) -> bool
799
801
{
800
802
assert (it != arguments.end ());
801
803
802
804
if (++it == arguments.end ())
803
- throw too_few_arguments{message.data ()};
805
+ return false ;
806
+
807
+ arg = *it;
808
+ return true ;
804
809
};
805
810
806
811
// Helper function for finding and processing subcommands.
807
- auto found_and_processed_subcommand = [this , &it](std::string_view arg) -> bool
812
+ auto found_subcommand = [this , &it, & arg]( ) -> bool
808
813
{
809
814
if (subcommands.empty ())
810
815
return false ;
@@ -824,25 +829,46 @@ class parser
824
829
}
825
830
else
826
831
{
827
- // Positional options are forbidden by design. Todo: Allow options. Forbidden in check_option_config.
832
+ // Positional options are forbidden by design.
828
833
// Flags and options, which both start with '-', are allowed for the top-level parser.
829
- // Otherwise, this is a wrongly spelled subcommand. The error will be thrown in parse() .
834
+ // Otherwise, this is an unknown subcommand.
830
835
if (!arg.starts_with (' -' ))
831
836
{
832
- format_arguments.emplace_back (arg);
833
- return true ;
837
+ std::string message = " You specified an unknown subcommand! Available subcommands are: [" ;
838
+ for (std::string const & command : subcommands)
839
+ message += command + " , " ;
840
+ message.replace (message.size () - 2 , 2 , " ]. Use -h/--help for more information." );
841
+
842
+ throw user_input_error{message};
834
843
}
835
844
}
836
845
837
846
return false ;
838
847
};
839
848
840
849
// Process the arguments.
841
- for (; it != arguments. end (); ++it )
850
+ for (; read_next_arg ();)
842
851
{
843
- std::string_view arg{*it};
852
+ // The argument is a known option.
853
+ if (options.contains (std::string{arg}))
854
+ {
855
+ // No futher checks are needed.
856
+ format_arguments.emplace_back (arg);
857
+
858
+ // Consume the next argument (the option value) if possible.
859
+ if (read_next_arg ())
860
+ {
861
+ format_arguments.emplace_back (arg);
862
+ continue ;
863
+ }
864
+ else // Too few arguments. This is handled by format_parse.
865
+ {
866
+ break ;
867
+ }
868
+ }
844
869
845
- if (found_and_processed_subcommand (arg))
870
+ // If we have a subcommand, all further arguments are passed to the subparser.
871
+ if (found_subcommand ())
846
872
break ;
847
873
848
874
if (arg == " -h" || arg == " --help" )
@@ -868,8 +894,8 @@ class parser
868
894
// --export-help man
869
895
if (arg.empty ())
870
896
{
871
- go_to_next_arg ( " Option --export-help must be followed by a value. " );
872
- arg = *it ;
897
+ if (! read_next_arg ())
898
+ throw too_few_arguments{ " Option --export-help must be followed by a value. " } ;
873
899
}
874
900
else // --export-help=man
875
901
{
@@ -891,8 +917,8 @@ class parser
891
917
}
892
918
else if (arg == " --version-check" )
893
919
{
894
- go_to_next_arg ( " Option --version-check must be followed by a value. " );
895
- arg = *it ;
920
+ if (! read_next_arg ())
921
+ throw too_few_arguments{ " Option --version-check must be followed by a value. " } ;
896
922
897
923
if (arg == " 1" || arg == " true" )
898
924
version_check_user_decision = true ;
@@ -903,6 +929,7 @@ class parser
903
929
}
904
930
else
905
931
{
932
+ // Flags, positional options, options using an alternative syntax (--optionValue, --option=value), etc.
906
933
format_arguments.emplace_back (arg);
907
934
}
908
935
}
@@ -927,7 +954,7 @@ class parser
927
954
{
928
955
if (detail::format_parse::is_empty_id (id))
929
956
return false ;
930
- return (!(used_option_ids .insert (std::string ({id}))).second );
957
+ return (!(used_ids .insert (std::string ({id}))).second );
931
958
}
932
959
933
960
/* !\brief Verifies that the short and the long identifiers are correctly formatted.
@@ -941,44 +968,47 @@ class parser
941
968
*/
942
969
void verify_identifiers (char const short_id, std::string const & long_id)
943
970
{
944
- constexpr std::string_view valid_chars{" @_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" };
945
- auto is_valid = [&valid_chars](char const c)
971
+ auto is_valid = [](char const c) -> bool
946
972
{
947
- return valid_chars.find (c) != std::string::npos;
973
+ return (c >= ' a' && c <= ' z' ) || (c >= ' A' && c <= ' Z' ) || (c >= ' 0' && c <= ' 9' ) // alphanumeric
974
+ || c == ' @' || c == ' _' || c == ' -' ; // additional characters
948
975
};
949
976
950
- if (id_exists (short_id))
951
- throw design_error (" Option Identifier '" + std::string (1 , short_id) + " ' was already used before." );
952
- if (id_exists (long_id))
953
- throw design_error (" Option Identifier '" + long_id + " ' was already used before." );
954
- if (long_id.length () == 1 )
955
- throw design_error (" Long IDs must be either empty, or longer than one character." );
956
- if ((short_id != ' \0 ' ) && !is_valid (short_id))
957
- throw design_error (" Option identifiers may only contain alphanumeric characters, '_', or '@'." );
958
- if (long_id.size () > 0 && (long_id[0 ] == ' -' ))
959
- throw design_error (" First character of long ID cannot be '-'." );
960
-
961
- std::for_each (long_id.begin (),
962
- long_id.end (),
963
- [&is_valid](char c)
964
- {
965
- if (!((c == ' -' ) || is_valid (c)))
966
- throw design_error (
967
- " Long identifiers may only contain alphanumeric characters, '_', '-', or '@'." );
968
- });
969
- if (detail::format_parse::is_empty_id (short_id) && detail::format_parse::is_empty_id (long_id))
970
- throw design_error (" Option Identifiers cannot both be empty." );
977
+ if (short_id == ' \0 ' && long_id.empty ())
978
+ throw design_error{" Short and long identifiers may not both be empty." };
979
+
980
+ if (short_id != ' \0 ' )
981
+ {
982
+ if (short_id == ' -' || !is_valid (short_id))
983
+ throw design_error{" Short identifiers may only contain alphanumeric characters, '_', or '@'." };
984
+ if (id_exists (short_id))
985
+ throw design_error{" Short identifier '" + std::string (1 , short_id) + " ' was already used before." };
986
+ }
987
+
988
+ if (!long_id.empty ())
989
+ {
990
+ if (long_id.size () == 1 )
991
+ throw design_error{" Long identifiers must be either empty or longer than one character." };
992
+ if (long_id[0 ] == ' -' )
993
+ throw design_error{" Long identifiers may not use '-' as first character." };
994
+ if (!std::ranges::all_of (long_id, is_valid))
995
+ throw design_error{" Long identifiers may only contain alphanumeric characters, '_', '-', or '@'." };
996
+ if (id_exists (long_id))
997
+ throw design_error{" Long identifier '" + long_id + " ' was already used before." };
998
+ }
971
999
}
972
1000
973
1001
// !brief Verify the configuration given to a sharg::parser::add_option call.
974
1002
template <typename validator_t >
975
1003
void verify_option_config (config<validator_t > const & config)
976
1004
{
977
- if (!subcommands.empty ())
978
- throw design_error{" You may only specify flags for the top-level parser." };
979
-
980
1005
verify_identifiers (config.short_id , config.long_id );
981
1006
1007
+ if (config.short_id != ' \0 ' )
1008
+ options.emplace (std::string{" -" } + config.short_id );
1009
+ if (!config.long_id .empty ())
1010
+ options.emplace (std::string{" --" } + config.long_id );
1011
+
982
1012
if (config.required && !config.default_message .empty ())
983
1013
throw design_error{" A required option cannot have a default message." };
984
1014
}
@@ -1005,7 +1035,7 @@ class parser
1005
1035
throw design_error{" Positional options are always required and therefore cannot be advanced nor hidden!" };
1006
1036
1007
1037
if (!subcommands.empty ())
1008
- throw design_error{" You may only specify flags for the top-level parser." };
1038
+ throw design_error{" You may only specify flags and options for the top-level parser." };
1009
1039
1010
1040
if (has_positional_list_option)
1011
1041
throw design_error{" You added a positional option with a list value before so you cannot add "
@@ -1075,27 +1105,6 @@ class parser
1075
1105
}
1076
1106
}
1077
1107
1078
- /* !\brief Verifies that the subcommand was correctly specified.
1079
- * \throws sharg::too_few_arguments if a subparser was configured at construction but a subcommand is missing.
1080
- */
1081
- inline void verify_subcommand ()
1082
- {
1083
- if (std::holds_alternative<detail::format_parse>(format) && !subcommands.empty () && sub_parser == nullptr )
1084
- {
1085
- assert (!subcommands.empty ());
1086
- std::string subcommands_str{" [" };
1087
- for (std::string const & command : subcommands)
1088
- subcommands_str += command + " , " ;
1089
- subcommands_str.replace (subcommands_str.size () - 2 , 2 , " ]" ); // replace last ", " by "]"
1090
-
1091
- throw too_few_arguments{" You misspelled the subcommand! Please specify which sub-program "
1092
- " you want to use: one of "
1093
- + subcommands_str
1094
- + " . Use -h/--help for more "
1095
- " information." };
1096
- }
1097
- }
1098
-
1099
1108
/* !\brief Parses the command line arguments according to the format.
1100
1109
* \throws sharg::option_declared_multiple_times if an option that is not a list was declared multiple times.
1101
1110
* \throws sharg::user_input_error if an incorrect argument is given as (positional) option value.
0 commit comments