SeqAn3 3.3.0-rc.1
The Modern C++ library for sequence analysis.
 
Loading...
Searching...
No Matches
argument_parser.hpp
Go to the documentation of this file.
1// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2022, Knut Reinert & MPI für molekulare Genetik
4// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6// -----------------------------------------------------------------------------------------------------
7
13#pragma once
14
15#include <future>
16#include <iostream>
17#include <regex>
18#include <set>
19#include <sstream>
20#include <string>
21#include <variant>
22#include <vector>
23
24// #include <seqan3/argument_parser/detail/format_ctd.hpp>
33
34namespace seqan3
35{
36
148{
149public:
153 argument_parser() = delete;
154 argument_parser(argument_parser const &) = default;
158
176 int const argc,
177 char const * const * const argv,
180 version_check_dev_decision{version_updates},
181 subcommands{std::move(subcommands)}
182 {
183 if (!std::regex_match(app_name, app_name_regex))
184 {
185 throw design_error{("The application name must only contain alpha-numeric characters or '_' and '-' "
186 "(regex: \"^[a-zA-Z0-9_-]+$\").")};
187 }
188
189 for (auto & sub : this->subcommands)
190 {
192 {
193 throw design_error{"The subcommand name must only contain alpha-numeric characters or '_' and '-' "
194 "(regex: \"^[a-zA-Z0-9_-]+$\")."};
195 }
196 }
197
198 info.app_name = std::move(app_name);
199
200 init(argc, argv);
201 }
202
205 {
206 // wait for another 3 seconds
209 }
211
235 template <typename option_type, validator validator_type = detail::default_validator<option_type>>
238 && std::invocable<validator_type, option_type>
239 void add_option(option_type & value,
240 char const short_id,
241 std::string const & long_id,
242 std::string const & desc,
244 validator_type option_validator = validator_type{}) // copy to bind rvalues
245 {
246 if (sub_parser != nullptr)
247 throw design_error{"You may only specify flags for the top-level parser."};
248
249 verify_identifiers(short_id, long_id);
250 // copy variables into the lambda because the calls are pushed to a stack
251 // and the references would go out of scope.
253 [=, &value](auto & f)
254 {
255 f.add_option(value, short_id, long_id, desc, spec, option_validator);
256 },
257 format);
258 }
259
271 void add_flag(bool & value,
272 char const short_id,
273 std::string const & long_id,
274 std::string const & desc,
276 {
277 if (value)
278 throw design_error("A flag's default value must be false.");
279
280 verify_identifiers(short_id, long_id);
281 // copy variables into the lambda because the calls are pushed to a stack
282 // and the references would go out of scope.
284 [=, &value](auto & f)
285 {
286 f.add_flag(value, short_id, long_id, desc, spec);
287 },
288 format);
289 }
290
311 template <typename option_type, validator validator_type = detail::default_validator<option_type>>
314 && std::invocable<validator_type, option_type>
315 void add_positional_option(option_type & value,
316 std::string const & desc,
317 validator_type option_validator = validator_type{}) // copy to bind rvalues
318 {
319 if (sub_parser != nullptr)
320 throw design_error{"You may only specify flags for the top-level parser."};
321
323 throw design_error{"You added a positional option with a list value before so you cannot add "
324 "any other positional options."};
325
326 if constexpr (detail::is_container_option<option_type>)
327 has_positional_list_option = true; // keep track of a list option because there must be only one!
328
329 // copy variables into the lambda because the calls are pushed to a stack
330 // and the references would go out of scope.
332 [=, &value](auto & f)
333 {
334 f.add_positional_option(value, desc, option_validator);
335 },
336 format);
337 }
339
405 void parse()
406 {
408 throw design_error("The function parse() must only be called once!");
409
411
412 if (std::holds_alternative<detail::format_parse>(format) && !subcommands.empty() && sub_parser == nullptr)
413 {
414 throw too_few_arguments{detail::to_string("You either forgot or misspelled the subcommand! Please specify"
415 " which sub-program you want to use: one of ",
417 ". Use -h/--help for more information.")};
418 }
419
420 if (app_version.decide_if_check_is_performed(version_check_dev_decision, version_check_user_decision))
421 {
422 // must be done before calling parse on the format because this might std::exit
423 std::promise<bool> app_version_prom;
424 version_check_future = app_version_prom.get_future();
425 app_version(std::move(app_version_prom));
426 }
427
429 [this](auto & f)
430 {
431 f.parse(info);
432 },
433 format);
434 parse_was_called = true;
435 }
436
440 {
441 if (sub_parser == nullptr)
442 {
443 throw design_error("No subcommand was provided at the construction of the argument parser!");
444 }
445
446 return *sub_parser;
447 }
448
475 template <typename id_type>
476 requires std::same_as<id_type, char> || std::constructible_from<std::string, id_type> bool
477 is_option_set(id_type const & id) const
478 {
479 if (!parse_was_called)
480 throw design_error{"You can only ask which options have been set after calling the function `parse()`."};
481
482 // the detail::format_parse::find_option_id call in the end expects either a char or std::string
483 using char_or_string_t = std::conditional_t<std::same_as<id_type, char>, char, std::string>;
484 char_or_string_t short_or_long_id = {id}; // e.g. convert char * to string here if necessary
485
486 if constexpr (!std::same_as<id_type, char>) // long id was given
487 {
488 if (short_or_long_id.size() == 1)
489 {
490 throw design_error{"Long option identifiers must be longer than one character! If " + short_or_long_id
491 + "' was meant to be a short identifier, please pass it as a char ('') not a string"
492 " (\"\")!"};
493 }
494 }
495
497 throw design_error{"You can only ask for option identifiers that you added with add_option() before."};
498
499 // we only need to search for an option before the `end_of_options_indentifier` (`--`)
501 auto option_it = detail::format_parse::find_option_id(cmd_arguments.begin(), end_of_options, short_or_long_id);
502 return option_it != end_of_options;
503 }
504
507
515 {
517 [&](auto & f)
518 {
519 f.add_section(title, spec);
520 },
521 format);
522 }
523
531 {
533 [&](auto & f)
534 {
535 f.add_subsection(title, spec);
536 },
537 format);
538 }
539
549 void add_line(std::string const & text, bool is_paragraph = false, option_spec const spec = option_spec::standard)
550 {
552 [&](auto & f)
553 {
554 f.add_line(text, is_paragraph, spec);
555 },
556 format);
557 }
558
577 void
579 {
581 [&](auto & f)
582 {
583 f.add_list_item(key, desc, spec);
584 },
585 format);
586 }
588
638
639private:
641 bool parse_was_called{false};
642
645
648
651
653 friend struct ::seqan3::detail::test_accessor;
654
657
659 std::regex app_name_regex{"^[a-zA-Z0-9_-]+$"};
660
662 static constexpr std::string_view const end_of_options_indentifier{"--"};
663
666
669
684 detail::format_ctd*/> format{detail::format_help{{}, false}}; // Will be overwritten in any case.
685
687 std::set<std::string> used_option_ids{"h", "hh", "help", "advanced-help", "export-help", "version", "copyright"};
688
691
724 void init(int argc, char const * const * const argv)
725 {
726 if (argc <= 1) // no arguments provided
727 {
729 return;
730 }
731
732 bool special_format_was_set{false};
733
734 for (int i = 1, argv_len = argc; i < argv_len; ++i) // start at 1 to skip binary name
735 {
736 std::string arg{argv[i]};
737
739 {
740 sub_parser = std::make_unique<argument_parser>(info.app_name + "-" + arg,
741 argc - i,
742 argv + i,
744 break;
745 }
746
747 if (arg == "-h" || arg == "--help")
748 {
751 special_format_was_set = true;
752 }
753 else if (arg == "-hh" || arg == "--advanced-help")
754 {
757 special_format_was_set = true;
758 }
759 else if (arg == "--version")
760 {
762 special_format_was_set = true;
763 }
764 else if (arg.substr(0, 13) == "--export-help") // --export-help=man is also allowed
765 {
766 std::string export_format;
767
768 if (arg.size() > 13)
769 {
770 export_format = arg.substr(14);
771 }
772 else
773 {
774 if (argv_len <= i + 1)
775 throw too_few_arguments{"Option --export-help must be followed by a value."};
776 export_format = {argv[i + 1]};
777 }
778
779 if (export_format == "html")
781 else if (export_format == "man")
783 // TODO (smehringer) use when CTD support is available
784 // else if (export_format == "ctd")
785 // format = detail::format_ctd{};
786 else
787 throw validation_error{"Validation failed for option --export-help: "
788 "Value must be one of [html, man]"};
790 special_format_was_set = true;
791 }
792 else if (arg == "--copyright")
793 {
795 special_format_was_set = true;
796 }
797 else if (arg == "--version-check")
798 {
799 if (++i >= argv_len)
800 throw too_few_arguments{"Option --version-check must be followed by a value."};
801
802 arg = argv[i];
803
804 if (arg == "1" || arg == "true")
806 else if (arg == "0" || arg == "false")
808 else
809 throw validation_error{"Value for option --version-check must be true (1) or false (0)."};
810
811 // in case --version-check is specified it shall not be passed to format_parse()
812 argc -= 2;
813 }
814 else
815 {
816 cmd_arguments.push_back(std::move(arg));
817 }
818 }
819
820 if (!special_format_was_set)
822 }
823
826 {
827 add_subsection("Basic options:");
828 add_list_item("\\fB-h\\fP, \\fB--help\\fP", "Prints the help page.");
829 add_list_item("\\fB-hh\\fP, \\fB--advanced-help\\fP", "Prints the help page including advanced options.");
830 add_list_item("\\fB--version\\fP", "Prints the version information.");
831 add_list_item("\\fB--copyright\\fP", "Prints the copyright/license information.");
832 add_list_item("\\fB--export-help\\fP (std::string)",
833 "Export the help page information. Value must be one of [html, man].");
835 add_list_item("\\fB--version-check\\fP (bool)",
836 "Whether to check for the newest app version. Default: true.");
837 }
838
844 template <typename id_type>
845 bool id_exists(id_type const & id)
846 {
848 return false;
849 return (!(used_option_ids.insert(std::string({id}))).second);
850 }
851
861 void verify_identifiers(char const short_id, std::string const & long_id)
862 {
863 constexpr auto allowed = is_alnum || is_char<'_'> || is_char<'@'>;
864
865 if (id_exists(short_id))
866 throw design_error("Option Identifier '" + std::string(1, short_id) + "' was already used before.");
867 if (id_exists(long_id))
868 throw design_error("Option Identifier '" + long_id + "' was already used before.");
869 if (long_id.length() == 1)
870 throw design_error("Long IDs must be either empty, or longer than one character.");
871 if (!allowed(short_id) && !is_char<'\0'>(short_id))
872 throw design_error("Option identifiers may only contain alphanumeric characters, '_', or '@'.");
873 if (long_id.size() > 0 && is_char<'-'>(long_id[0]))
874 throw design_error("First character of long ID cannot be '-'.");
875
876 std::for_each(long_id.begin(),
877 long_id.end(),
878 [&allowed](char c)
879 {
880 if (!(allowed(c) || is_char<'-'>(c)))
881 throw design_error(
882 "Long identifiers may only contain alphanumeric characters, '_', '-', or '@'.");
883 });
885 throw design_error("Option Identifiers cannot both be empty.");
886 }
887};
888
889} // namespace seqan3
T begin(T... args)
The SeqAn command line parser.
Definition: argument_parser.hpp:148
update_notifications version_check_dev_decision
Set on construction and indicates whether the developer deactivates the version check calls completel...
Definition: argument_parser.hpp:647
void add_positional_option(option_type &value, std::string const &desc, validator_type option_validator=validator_type{})
Adds a positional option to the seqan3::argument_parser.
Definition: argument_parser.hpp:315
void add_flag(bool &value, char const short_id, std::string const &long_id, std::string const &desc, option_spec const spec=option_spec::standard)
Adds a flag to the seqan3::argument_parser.
Definition: argument_parser.hpp:271
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: argument_parser.hpp:477
std::regex app_name_regex
Validates the application name to ensure an escaped server call.
Definition: argument_parser.hpp:659
void add_option(option_type &value, char const short_id, std::string const &long_id, std::string const &desc, option_spec const spec=option_spec::standard, validator_type option_validator=validator_type{})
Adds an option to the seqan3::argument_parser.
Definition: argument_parser.hpp:239
argument_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={})
Initializes an seqan3::argument_parser object from the command line arguments.
Definition: argument_parser.hpp:175
argument_parser & operator=(argument_parser &&)=default
Defaulted.
~argument_parser()
The destructor.
Definition: argument_parser.hpp:204
argument_parser_meta_data info
Aggregates all parser related meta data (see seqan3::argument_parser_meta_data struct).
Definition: argument_parser.hpp:637
std::unique_ptr< argument_parser > sub_parser
Stores the sub-parser in case subcommand parsing is enabled.
Definition: argument_parser.hpp:665
void verify_identifiers(char const short_id, std::string const &long_id)
Verifies that the short and the long identifiers are correctly formatted.
Definition: argument_parser.hpp:861
std::set< std::string > used_option_ids
List of option/flag identifiers that are already used.
Definition: argument_parser.hpp:687
argument_parser(argument_parser &&)=default
Defaulted.
bool has_positional_list_option
Keeps track of whether the user has added a positional list option to check if this was the very last...
Definition: argument_parser.hpp:644
bool id_exists(id_type const &id)
Checks whether the long identifier has already been used before.
Definition: argument_parser.hpp:845
void parse()
Initiates the actual command line parsing.
Definition: argument_parser.hpp:405
static constexpr std::string_view const end_of_options_indentifier
Signals the argument parser that no options follow this string but only positional arguments.
Definition: argument_parser.hpp:662
argument_parser()=delete
Deleted.
std::future< bool > version_check_future
The future object that keeps track of the detached version check call thread.
Definition: argument_parser.hpp:656
std::vector< std::string > subcommands
Stores the sub-parser names in case subcommand parsing is enabled.
Definition: argument_parser.hpp:668
bool parse_was_called
Keeps track of whether the parse function has been called already.
Definition: argument_parser.hpp:641
void init_standard_options()
Adds standard options to the help page.
Definition: argument_parser.hpp:825
void init(int argc, char const *const *const argv)
Initializes the seqan3::argument_parser class on construction.
Definition: argument_parser.hpp:724
void add_line(std::string const &text, bool is_paragraph=false, option_spec const spec=option_spec::standard)
Adds an help page text line to the seqan3::argument_parser.
Definition: argument_parser.hpp:549
std::vector< std::string > cmd_arguments
The command line arguments.
Definition: argument_parser.hpp:690
void add_list_item(std::string const &key, std::string const &desc, option_spec const spec=option_spec::standard)
Adds an help page list item (key-value) to the seqan3::argument_parser.
Definition: argument_parser.hpp:578
void add_section(std::string const &title, option_spec const spec=option_spec::standard)
Adds an help page section to the seqan3::argument_parser.
Definition: argument_parser.hpp:514
std::optional< bool > version_check_user_decision
Whether the user specified to perform the version check (true) or not (false), default unset.
Definition: argument_parser.hpp:650
std::variant< detail::format_parse, detail::format_help, detail::format_short_help, detail::format_version, detail::format_html, detail::format_man, detail::format_copyright > format
The format of the argument parser that decides the behavior when calling the seqan3::argument_parser:...
Definition: argument_parser.hpp:684
void add_subsection(std::string const &title, option_spec const spec=option_spec::standard)
Adds an help page subsection to the seqan3::argument_parser.
Definition: argument_parser.hpp:530
argument_parser & operator=(argument_parser const &)=default
Defaulted.
argument_parser & get_sub_parser()
Returns a reference to the sub-parser instance if subcommand parsing was enabled.
Definition: argument_parser.hpp:439
argument_parser(argument_parser const &)=default
Defaulted.
Argument parser exception that is thrown whenever there is an design error directed at the developer ...
Definition: exceptions.hpp:151
The format that prints the help page to std::cout.
Definition: format_help.hpp:40
The format that prints the help page as html to std::cout.
Definition: format_html.hpp:37
The format that prints the help page information formatted for a man page to std::cout.
Definition: format_man.hpp:38
The format that organizes the actual parsing of command line arguments.
Definition: format_parse.hpp:54
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::argv.
Definition: format_parse.hpp:191
static bool is_empty_id(id_type const &id)
Checks whether id is empty.
Definition: format_parse.hpp:161
The format that prints a short help message to std::cout.
Definition: format_help.hpp:414
The format that prints the version to std::cout.
Definition: format_help.hpp:446
A functor whose operator() performs the server http request and version checks.
Definition: version_check.hpp:60
Argument parser exception thrown when too few arguments are provided.
Definition: exceptions.hpp:79
Argument parser exception thrown when an argument could not be casted to the according type.
Definition: exceptions.hpp:131
T empty(T... args)
T end(T... args)
T find(T... args)
T for_each(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.
T get_future(T... args)
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:248
@ standard
The default were no checking or special displaying is happening.
Definition: auxiliary.hpp:249
constexpr auto is_alnum
Checks whether c is a alphanumeric character.
Definition: predicate.hpp:197
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:63
T insert(T... args)
Checks whether the the type can be used in an add_(positional_)option call on the argument parser.
std::string to_string(value_type &&... values)
Streams all parameters via the seqan3::debug_stream and returns a concatenated string.
Definition: to_string.hpp:29
The main SeqAn3 namespace.
Definition: aligned_sequence_concept.hpp:29
update_notifications
Indicates whether application allows automatic update notifications by the seqan3::argument_parser.
Definition: auxiliary.hpp:267
@ off
Automatic update notifications should be disabled.
@ on
Automatic update notifications should be enabled.
T push_back(T... args)
T regex_match(T... args)
T length(T... args)
Stores all parser related meta information of the seqan3::argument_parser.
Definition: auxiliary.hpp:286
std::string version
The version information MAJOR.MINOR.PATH (e.g. 3.1.3)
Definition: auxiliary.hpp:294
std::string app_name
The application name that will be displayed on the help page.
Definition: auxiliary.hpp:292
std::string url
A link to your github/gitlab project with the newest release.
Definition: auxiliary.hpp:306
T substr(T... args)
Checks if program is run interactively and retrieves dimensions of terminal (Transferred from seqan2)...
Forward declares seqan3::detail::test_accessor.
Auxiliary for pretty printing of exception messages.
T valid(T... args)
Provides the version check functionality.
T visit(T... args)
T wait_for(T... args)