Sharg 1.1.2-rc.1
The argument parser for bio-c++ tools.
Loading...
Searching...
No Matches
parser.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2024, Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2024, Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <unordered_set>
13#include <variant>
14
15#include <sharg/config.hpp>
22
23namespace sharg
24{
25
154{
155public:
159 parser() = delete;
160 parser(parser const &) = delete;
161 parser & operator=(parser const &) = delete;
162 parser(parser &&) = default;
163 parser & operator=(parser &&) = default;
164
183 parser(std::string const & app_name,
187 version_check_dev_decision{version_updates},
190 {
191 info.app_name = app_name;
192 }
193
195 parser(std::string const & app_name,
196 int const argc,
197 char const * const * const argv,
200 parser{app_name, std::vector<std::string>{argv, argv + argc}, version_updates, std::move(subcommands)}
201 {}
202
205 {
206 // wait for another 3 seconds
207 if (version_check_future.valid())
208 version_check_future.wait_for(std::chrono::seconds(3));
209 }
211
240 template <typename option_type, typename validator_type>
243 void add_option(option_type & value, config<validator_type> const & config)
244 {
245 check_parse_not_called("add_option");
246 verify_option_config(config);
247
248 auto operation = [this, &value, config]()
249 {
250 auto visit_fn = [&value, &config](auto & f)
251 {
252 f.add_option(value, config);
253 };
254
255 std::visit(std::move(visit_fn), format);
256 };
257
258 operations.push_back(std::move(operation));
259 }
260
274 template <typename validator_type>
276 void add_flag(bool & value, config<validator_type> const & config)
277 {
278 check_parse_not_called("add_flag");
279 verify_flag_config(config);
280
281 if (value)
282 throw design_error("A flag's default value must be false.");
283
284 auto operation = [this, &value, config]()
285 {
286 auto visit_fn = [&value, &config](auto & f)
287 {
288 f.add_flag(value, config);
289 };
290
291 std::visit(std::move(visit_fn), format);
292 };
293
294 operations.push_back(std::move(operation));
295 }
296
324 template <typename option_type, typename validator_type>
327 void add_positional_option(option_type & value, config<validator_type> const & config)
328 {
329 check_parse_not_called("add_positional_option");
330 verify_positional_option_config(config);
331
333 has_positional_list_option = true; // keep track of a list option because there must be only one!
334
335 auto operation = [this, &value, config]()
336 {
337 auto visit_fn = [&value, &config](auto & f)
338 {
339 f.add_positional_option(value, config);
340 };
341
342 std::visit(std::move(visit_fn), format);
343 };
344
345 operations.push_back(std::move(operation));
346 }
348
416 void parse()
417 {
418 if (parse_was_called)
419 throw design_error("The function parse() must only be called once!");
420
421 parse_was_called = true;
422
423 // User input sanitization must happen before version check!
424 verify_app_and_subcommand_names();
425
426 // Determine the format and subcommand.
427 determine_format_and_subcommand();
428
429 // Apply all defered operations to the parser, e.g., `add_option`, `add_flag`, `add_positional_option`.
430 for (auto & operation : operations)
431 operation();
432
433 // The version check, which might exit the program, must be called before calling parse on the format.
434 run_version_check();
435
436 // Parse the command line arguments.
437 parse_format();
438
439 // Exit after parsing any special format.
441 std::exit(EXIT_SUCCESS);
442 }
443
451 {
452 if (sub_parser == nullptr)
453 {
454 throw design_error("No subcommand was provided at the construction of the argument parser!");
455 }
456
457 return *sub_parser;
458 }
459
489 // clang-format off
490 template <typename id_type>
492 bool is_option_set(id_type const & id) const
493 // clang-format on
494 {
495 if (!parse_was_called)
496 throw design_error{"You can only ask which options have been set after calling the function `parse()`."};
497
498 // the detail::format_parse::find_option_id call in the end expects either a char or std::string
499 using char_or_string_t = std::conditional_t<std::same_as<id_type, char>, char, std::string>;
500 char_or_string_t short_or_long_id = {id}; // e.g. convert char * to string here if necessary
501
502 if constexpr (!std::same_as<id_type, char>) // long id was given
503 {
504 if (short_or_long_id.size() == 1)
505 {
506 throw design_error{"Long option identifiers must be longer than one character! If " + short_or_long_id
507 + "' was meant to be a short identifier, please pass it as a char ('') not a string"
508 " (\"\")!"};
509 }
510 }
511
512 if (!used_ids.contains(std::string{id}))
513 throw design_error{"You can only ask for option identifiers that you added with add_option() before."};
514
515 // we only need to search for an option before the `option_end_identifier` (`--`)
516 auto option_end = std::find(format_arguments.begin(), format_arguments.end(), option_end_identifier);
517 auto option_it = detail::format_parse::find_option_id(format_arguments.begin(), option_end, short_or_long_id);
518 return option_it != option_end;
519 }
520
523
534 void add_section(std::string const & title, bool const advanced_only = false)
535 {
536 check_parse_not_called("add_section");
537
538 auto operation = [this, title, advanced_only]()
539 {
540 auto visit_fn = [&title, advanced_only](auto & f)
541 {
542 f.add_section(title, advanced_only);
543 };
544
545 std::visit(std::move(visit_fn), format);
546 };
547
548 operations.push_back(std::move(operation));
549 }
550
561 void add_subsection(std::string const & title, bool const advanced_only = false)
562 {
563 check_parse_not_called("add_subsection");
564
565 auto operation = [this, title, advanced_only]()
566 {
567 auto visit_fn = [&title, advanced_only](auto & f)
568 {
569 f.add_subsection(title, advanced_only);
570 };
571
572 std::visit(std::move(visit_fn), format);
573 };
574
575 operations.push_back(std::move(operation));
576 }
577
589 void add_line(std::string const & text, bool is_paragraph = false, bool const advanced_only = false)
590 {
591 check_parse_not_called("add_line");
592
593 auto operation = [this, text, is_paragraph, advanced_only]()
594 {
595 auto visit_fn = [&text, is_paragraph, advanced_only](auto & f)
596 {
597 f.add_line(text, is_paragraph, advanced_only);
598 };
599
600 std::visit(std::move(visit_fn), format);
601 };
602
603 operations.push_back(std::move(operation));
604 }
605
626 void add_list_item(std::string const & key, std::string const & desc, bool const advanced_only = false)
627 {
628 check_parse_not_called("add_list_item");
629
630 auto operation = [this, key, desc, advanced_only]()
631 {
632 auto visit_fn = [&key, &desc, advanced_only](auto & f)
633 {
634 f.add_list_item(key, desc, advanced_only);
635 };
636
637 std::visit(std::move(visit_fn), format);
638 };
639
640 operations.push_back(std::move(operation));
641 }
643
695
696private:
698 bool parse_was_called{false};
699
701 bool has_positional_list_option{false};
702
704 update_notifications version_check_dev_decision{};
705
708
710 friend struct ::sharg::detail::test_accessor;
711
714
716 std::regex app_name_regex{"^[a-zA-Z0-9_-]+$"};
717
719 static constexpr std::string_view const option_end_identifier{"--"};
720
722 std::unique_ptr<parser> sub_parser{nullptr};
723
726
743
745 std::unordered_set<std::string> used_ids{"h", "hh", "help", "advanced-help", "export-help", "version", "copyright"};
746
748 std::vector<std::string> format_arguments{};
749
752
754 std::vector<std::string> executable_name{};
755
758
761
790 {
791 assert(!arguments.empty());
792
793 auto it = arguments.begin();
794 std::string_view arg{*it};
795
796 executable_name.emplace_back(arg);
797
798 // Helper function for reading the next argument. This makes it more obvious that we are
799 // incrementing `it` (version-check, and export-help).
800 auto read_next_arg = [this, &it, &arg]() -> bool
801 {
802 assert(it != arguments.end());
803
804 if (++it == arguments.end())
805 return false;
806
807 arg = *it;
808 return true;
809 };
810
811 // Helper function for finding and processing subcommands.
812 auto found_subcommand = [this, &it, &arg]() -> bool
813 {
814 if (subcommands.empty())
815 return false;
816
817 if (std::ranges::find(subcommands, arg) != subcommands.end())
818 {
819 sub_parser = std::make_unique<parser>(info.app_name + "-" + arg.data(),
820 std::vector<std::string>{it, arguments.end()},
822
823 // Add the original calls to the front, e.g. ["raptor"],
824 // s.t. ["raptor", "build"] will be the list after constructing the subparser
825 sub_parser->executable_name.insert(sub_parser->executable_name.begin(),
826 executable_name.begin(),
827 executable_name.end());
828 return true;
829 }
830 else
831 {
832 // Positional options are forbidden by design.
833 // Flags and options, which both start with '-', are allowed for the top-level parser.
834 // Otherwise, this is an unknown subcommand.
835 if (!arg.starts_with('-'))
836 {
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};
843 }
844 }
845
846 return false;
847 };
848
849 // Process the arguments.
850 for (; read_next_arg();)
851 {
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 }
869
870 // If we have a subcommand, all further arguments are passed to the subparser.
871 if (found_subcommand())
872 break;
873
874 if (arg == "-h" || arg == "--help")
875 {
876 format = detail::format_help{subcommands, version_check_dev_decision, false};
877 }
878 else if (arg == "-hh" || arg == "--advanced-help")
879 {
880 format = detail::format_help{subcommands, version_check_dev_decision, true};
881 }
882 else if (arg == "--version")
883 {
884 format = detail::format_version{};
885 }
886 else if (arg == "--copyright")
887 {
888 format = detail::format_copyright{};
889 }
890 else if (arg == "--export-help" || arg.starts_with("--export-help="))
891 {
892 arg.remove_prefix(std::string_view{"--export-help"}.size());
893
894 // --export-help man
895 if (arg.empty())
896 {
897 if (!read_next_arg())
898 throw too_few_arguments{"Option --export-help must be followed by a value."};
899 }
900 else // --export-help=man
901 {
902 arg.remove_prefix(1u);
903 }
904
905 if (arg == "html")
906 format = detail::format_html{subcommands, version_check_dev_decision};
907 else if (arg == "man")
908 format = detail::format_man{subcommands, version_check_dev_decision};
909 else if (arg == "ctd")
911 else if (arg == "cwl")
913 else
914 throw validation_error{"Validation failed for option --export-help: "
915 "Value must be one of "
916 + detail::supported_exports + "."};
917 }
918 else if (arg == "--version-check")
919 {
920 if (!read_next_arg())
921 throw too_few_arguments{"Option --version-check must be followed by a value."};
922
923 if (arg == "1" || arg == "true")
924 version_check_user_decision = true;
925 else if (arg == "0" || arg == "false")
926 version_check_user_decision = false;
927 else
928 throw validation_error{"Value for option --version-check must be true (1) or false (0)."};
929 }
930 else
931 {
932 // Flags, positional options, options using an alternative syntax (--optionValue, --option=value), etc.
933 format_arguments.emplace_back(arg);
934 }
935 }
936
937 // A special format was set. We do not need to parse the format_arguments.
939 return;
940
941 // All special options have been handled. If there are arguments left or we have a subparser,
942 // we call format_parse. Oterhwise, we print the short help (default variant).
943 if (!format_arguments.empty() || sub_parser)
944 format = detail::format_parse(format_arguments);
945 }
946
952 template <typename id_type>
953 bool id_exists(id_type const & id)
954 {
956 return false;
957 return (!(used_ids.insert(std::string({id}))).second);
958 }
959
969 void verify_identifiers(char const short_id, std::string const & long_id)
970 {
971 auto is_valid = [](char const c) -> bool
972 {
973 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') // alphanumeric
974 || c == '@' || c == '_' || c == '-'; // additional characters
975 };
976
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 }
999 }
1000
1002 template <typename validator_t>
1004 {
1005 verify_identifiers(config.short_id, config.long_id);
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
1013 throw design_error{"A required option cannot have a default message."};
1014 }
1015
1017 template <typename validator_t>
1019 {
1020 verify_identifiers(config.short_id, config.long_id);
1021
1023 throw design_error{"A flag may not have a default message because the default is always `false`."};
1024 }
1025
1027 template <typename validator_t>
1029 {
1030 if (config.short_id != '\0' || config.long_id != "")
1031 throw design_error{"Positional options are identified by their position on the command line. "
1032 "Short or long ids are not permitted!"};
1033
1035 throw design_error{"Positional options are always required and therefore cannot be advanced nor hidden!"};
1036
1037 if (!subcommands.empty())
1038 throw design_error{"You may only specify flags and options for the top-level parser."};
1039
1040 if (has_positional_list_option)
1041 throw design_error{"You added a positional option with a list value before so you cannot add "
1042 "any other positional options."};
1043
1045 throw design_error{"A positional option may not have a default message because it is always required."};
1046 }
1047
1057 inline void check_parse_not_called(std::string_view const function_name) const
1058 {
1059 if (parse_was_called)
1060 throw design_error{detail::to_string(function_name.data(), " may only be used before calling parse().")};
1061 }
1062
1071 {
1072 // Before creating the detail::version_checker, we have to make sure that
1073 // malicious code cannot be injected through the app name.
1074 if (!std::regex_match(info.app_name, app_name_regex))
1075 {
1076 throw design_error{("The application name must only contain alpha-numeric characters or '_' and '-' "
1077 "(regex: \"^[a-zA-Z0-9_-]+$\").")};
1078 }
1079
1080 for (auto & sub : this->subcommands)
1081 {
1082 if (!std::regex_match(sub, app_name_regex))
1083 {
1084 throw design_error{"The subcommand name must only contain alpha-numeric characters or '_' and '-' "
1085 "(regex: \"^[a-zA-Z0-9_-]+$\")."};
1086 }
1087 }
1088 }
1089
1095 inline void run_version_check()
1096 {
1097 detail::version_checker app_version{info.app_name, info.version, info.url};
1098
1099 if (app_version.decide_if_check_is_performed(version_check_dev_decision, version_check_user_decision))
1100 {
1101 // must be done before calling parse on the format because this might std::exit
1102 std::promise<bool> app_version_prom;
1103 version_check_future = app_version_prom.get_future();
1104 app_version(std::move(app_version_prom));
1105 }
1106 }
1107
1118 inline void parse_format()
1119 {
1120 auto format_parse_fn = [this]<typename format_t>(format_t & f)
1121 {
1123 f.parse(info, executable_name);
1124 else
1125 f.parse(info);
1126 };
1127
1128 std::visit(std::move(format_parse_fn), format);
1129 }
1130};
1131
1132} // namespace sharg
T all_of(T... args)
Parser exception that is thrown whenever there is an design error directed at the developer of the ap...
Definition exceptions.hpp:207
The format that prints the help page to std::cout.
Definition format_help.hpp:34
The format that prints the help page as html to std::cout.
Definition format_html.hpp:30
The format that prints the help page information formatted for a man page to std::cout.
Definition format_man.hpp:31
The format that organizes the actual parsing of command line arguments.
Definition format_parse.hpp:47
static bool is_empty_id(id_type const &id)
Checks whether id is empty.
Definition format_parse.hpp:143
static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const &id)
Finds the position of a short/long identifier in format_parse::arguments.
Definition format_parse.hpp:173
The format that prints a short help message to std::cout.
Definition format_help.hpp:407
A generalized format to create different tool description files.
Definition format_tdl.hpp:106
@ CTD
Support for CTD format.
@ CWL
Support for CWL format.
The format that prints the version to std::cout.
Definition format_help.hpp:437
A functor whose operator() performs the server http request and version checks.
Definition version_check.hpp:55
The Sharg command line parser.
Definition parser.hpp:154
void verify_app_and_subcommand_names() const
Verifies that the app and subcommand names are correctly formatted.
Definition parser.hpp:1070
void verify_identifiers(char const short_id, std::string const &long_id)
Verifies that the short and the long identifiers are correctly formatted.
Definition parser.hpp:969
void add_option(option_type &value, config< validator_type > const &config)
Adds an option to the sharg::parser.
Definition parser.hpp:243
std::future< bool > version_check_future
The future object that keeps track of the detached version check call thread.
Definition parser.hpp:713
void add_flag(bool &value, config< validator_type > const &config)
Adds a flag to the sharg::parser.
Definition parser.hpp:276
parser()=delete
Deleted.
void parse_format()
Parses the command line arguments according to the format.
Definition parser.hpp:1118
void add_subsection(std::string const &title, bool const advanced_only=false)
Adds an help page subsection to the sharg::parser.
Definition parser.hpp:561
bool is_option_set(id_type const &id) const
Checks whether the option identifier (id) was set on the command line by the user.
Definition parser.hpp:492
std::optional< bool > version_check_user_decision
Whether the user specified to perform the version check (true) or not (false), default unset.
Definition parser.hpp:707
void add_positional_option(option_type &value, config< validator_type > const &config)
Adds a positional option to the sharg::parser.
Definition parser.hpp:327
parser_meta_data info
Aggregates all parser related meta data (see sharg::parser_meta_data struct).
Definition parser.hpp:694
parser(std::string const &app_name, std::vector< std::string > const &arguments, update_notifications version_updates=update_notifications::on, std::vector< std::string > subcommands={})
Initializes an sharg::parser object from the command line arguments.
Definition parser.hpp:183
parser(std::string const &app_name, int const argc, char const *const *const argv, update_notifications version_updates=update_notifications::on, std::vector< std::string > subcommands={})
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition parser.hpp:195
parser(parser &&)=default
Defaulted.
void parse()
Initiates the actual command line parsing.
Definition parser.hpp:416
parser & operator=(parser const &)=delete
Deleted.
parser(parser const &)=delete
Deleted.
void add_list_item(std::string const &key, std::string const &desc, bool const advanced_only=false)
Adds an help page list item (key-value) to the sharg::parser.
Definition parser.hpp:626
std::vector< std::function< void()> > operations
Vector of functions that stores all calls.
Definition parser.hpp:760
parser & operator=(parser &&)=default
Defaulted.
void check_parse_not_called(std::string_view const function_name) const
Throws a sharg::design_error if parse() was already called.
Definition parser.hpp:1057
bool id_exists(id_type const &id)
Checks whether the long identifier has already been used before.
Definition parser.hpp:953
void add_section(std::string const &title, bool const advanced_only=false)
Adds an help page section to the sharg::parser.
Definition parser.hpp:534
~parser()
The destructor.
Definition parser.hpp:204
std::vector< std::string > arguments
The original command line arguments.
Definition parser.hpp:751
void determine_format_and_subcommand()
Handles format and subcommand detection.
Definition parser.hpp:789
std::vector< std::string > subcommands
Stores the sub-parser names in case subcommand parsing is enabled.
Definition parser.hpp:725
parser & get_sub_parser()
Returns a reference to the sub-parser instance if subcommand parsing was enabled.
Definition parser.hpp:450
update_notifications version_check_dev_decision
Set on construction and indicates whether the developer deactivates the version check calls completel...
Definition parser.hpp:704
void verify_flag_config(config< validator_t > const &config)
brief Verify the configuration given to a sharg::parser::add_flag call.
Definition parser.hpp:1018
void add_line(std::string const &text, bool is_paragraph=false, bool const advanced_only=false)
Adds an help page text line to the sharg::parser.
Definition parser.hpp:589
void run_version_check()
Runs the version check if the user has not disabled it.
Definition parser.hpp:1095
void verify_option_config(config< validator_t > const &config)
brief Verify the configuration given to a sharg::parser::add_option call.
Definition parser.hpp:1003
void verify_positional_option_config(config< validator_t > const &config) const
brief Verify the configuration given to a sharg::parser::add_positional_option call.
Definition parser.hpp:1028
Parser exception thrown when too few arguments are provided.
Definition exceptions.hpp:100
Parser exception thrown when an incorrect argument is given as (positional) option value.
Definition exceptions.hpp:160
Parser exception thrown when an argument could not be casted to the according type.
Definition exceptions.hpp:180
Whether the option type is considered to be a container.
Definition detail/concept.hpp:38
Checks whether the the type can be used in an add_(positional_)option call on the parser.
Definition concept.hpp:91
Provides sharg::config class.
T data(T... args)
T empty(T... args)
T exit(T... args)
T find(T... args)
Provides the format_help struct that print the help page to the command line and the two child format...
Provides the format_html struct and its helper functions.
Provides the format_man struct and its helper functions.
Provides the format_parse class.
Provides the format_tdl struct and its helper functions.
T get_future(T... args)
std::string to_string(value_types &&... values)
Streams all parameters via std::ostringstream and returns a concatenated string.
Definition to_string.hpp:40
update_notifications
Indicates whether application allows automatic update notifications by the sharg::parser.
Definition auxiliary.hpp:26
@ off
Automatic update notifications should be disabled.
@ on
Automatic update notifications should be enabled.
T is_same_v
T regex_match(T... args)
T replace(T... args)
T size(T... args)
Option struct that is passed to the sharg::parser::add_option() function.
Definition config.hpp:43
std::string long_id
The long identifier for the option (e.g. "age", making the option callable via --age).
Definition config.hpp:62
bool hidden
Whether the option should be hidden.
Definition config.hpp:117
bool advanced
Whether the option should only be displayed on the advanced help page.
Definition config.hpp:105
bool required
Whether the option is required.
Definition config.hpp:129
char short_id
The short identifier for the option (e.g. 'a', making the option callable via -a).
Definition config.hpp:53
std::string default_message
The default message to be shown on any (exported) help page.
Definition config.hpp:87
Stores all parser related meta information of the sharg::parser.
Definition auxiliary.hpp:45
std::string app_name
The application name that will be displayed on the help page.
Definition auxiliary.hpp:51
std::string version
The version information MAJOR.MINOR.PATH (e.g. 3.1.3)
Definition auxiliary.hpp:54
std::string url
A link to your github/gitlab project with the newest release.
Definition auxiliary.hpp:71
Provides the version check functionality.
T visit(T... args)
Hide me