SeqAn3 3.4.0-rc.4
The Modern C++ library for sequence analysis.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
argument_parser.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2025 Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2025 Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <future>
13#include <iostream>
14#include <regex>
15#include <set>
16#include <sstream>
17#include <string>
18#include <variant>
19#include <vector>
20
21// #include <seqan3/argument_parser/detail/format_ctd.hpp>
30
31SEQAN3_DEPRECATED_HEADER("This header and its functionality is deprecated and will be removed in a future version of SeqAn. Please use the sharg-parser (url: https://github.com/seqan/sharg-parser) instead.");
32
33namespace seqan3
34{
35
147{
148public:
152 argument_parser() = delete;
157
175 int const argc,
176 char const * const * const argv,
178 std::vector<std::string> subcommands = {}) :
179 version_check_dev_decision{version_updates},
180 subcommands{std::move(subcommands)}
181 {
182 if (!std::regex_match(app_name, app_name_regex))
183 {
184 throw design_error{("The application name must only contain alpha-numeric characters or '_' and '-' "
185 "(regex: \"^[a-zA-Z0-9_-]+$\").")};
186 }
187
188 for (auto & sub : this->subcommands)
189 {
190 if (!std::regex_match(sub, app_name_regex))
191 {
192 throw design_error{"The subcommand name must only contain alpha-numeric characters or '_' and '-' "
193 "(regex: \"^[a-zA-Z0-9_-]+$\")."};
194 }
195 }
196
197 info.app_name = std::move(app_name);
198
199 init(argc, argv);
200 }
201
204 {
205 // wait for another 3 seconds
206 if (version_check_future.valid())
207 version_check_future.wait_for(std::chrono::seconds(3));
208 }
210
234 template <typename option_type, validator validator_type = detail::default_validator<option_type>>
237 && std::invocable<validator_type, option_type>
239 char const short_id,
240 std::string const & long_id,
241 std::string const & desc,
243 validator_type option_validator = validator_type{}) // copy to bind rvalues
244 {
245 if (sub_parser != nullptr)
246 throw design_error{"You may only specify flags for the top-level parser."};
247
248 verify_identifiers(short_id, long_id);
249 // copy variables into the lambda because the calls are pushed to a stack
250 // and the references would go out of scope.
252 [=, &value](auto & f)
253 {
254 f.add_option(value, short_id, long_id, desc, spec, option_validator);
255 },
256 format);
257 }
258
270 void add_flag(bool & value,
271 char const short_id,
272 std::string const & long_id,
273 std::string const & desc,
275 {
276 if (value)
277 throw design_error("A flag's default value must be false.");
278
279 verify_identifiers(short_id, long_id);
280 // copy variables into the lambda because the calls are pushed to a stack
281 // and the references would go out of scope.
283 [=, &value](auto & f)
284 {
285 f.add_flag(value, short_id, long_id, desc, spec);
286 },
287 format);
288 }
289
310 template <typename option_type, validator validator_type = detail::default_validator<option_type>>
313 && std::invocable<validator_type, option_type>
315 std::string const & desc,
316 validator_type option_validator = validator_type{}) // copy to bind rvalues
317 {
318 if (sub_parser != nullptr)
319 throw design_error{"You may only specify flags for the top-level parser."};
320
321 if (has_positional_list_option)
322 throw design_error{"You added a positional option with a list value before so you cannot add "
323 "any other positional options."};
324
325 if constexpr (detail::is_container_option<option_type>)
326 has_positional_list_option = true; // keep track of a list option because there must be only one!
327
328 // copy variables into the lambda because the calls are pushed to a stack
329 // and the references would go out of scope.
331 [=, &value](auto & f)
332 {
333 f.add_positional_option(value, desc, option_validator);
334 },
335 format);
336 }
338
404 void parse()
405 {
406 if (parse_was_called)
407 throw design_error("The function parse() must only be called once!");
408
409 detail::version_checker app_version{info.app_name, info.version, info.url};
410
411 if (std::holds_alternative<detail::format_parse>(format) && !subcommands.empty() && sub_parser == nullptr)
412 {
413 throw too_few_arguments{detail::to_string("You either forgot or misspelled the subcommand! Please specify"
414 " which sub-program you want to use: one of ",
415 subcommands,
416 ". Use -h/--help for more information.")};
417 }
418
419 if (app_version.decide_if_check_is_performed(version_check_dev_decision, version_check_user_decision))
420 {
421 // must be done before calling parse on the format because this might std::exit
423 version_check_future = app_version_prom.get_future();
424 app_version(std::move(app_version_prom));
425 }
426
428 [this](auto & f)
429 {
430 f.parse(info);
431 },
432 format);
433 parse_was_called = true;
434 }
435
439 {
440 if (sub_parser == nullptr)
441 {
442 throw design_error("No subcommand was provided at the construction of the argument parser!");
443 }
444
445 return *sub_parser;
446 }
447
474 template <typename id_type>
475 requires std::same_as<id_type, char> || std::constructible_from<std::string, id_type>
476 bool is_option_set(id_type const & id) const
477 {
478 if (!parse_was_called)
479 throw design_error{"You can only ask which options have been set after calling the function `parse()`."};
480
481 // the detail::format_parse::find_option_id call in the end expects either a char or std::string
483 char_or_string_t short_or_long_id = {id}; // e.g. convert char * to string here if necessary
484
485 if constexpr (!std::same_as<id_type, char>) // long id was given
486 {
487 if (short_or_long_id.size() == 1)
488 {
489 throw design_error{"Long option identifiers must be longer than one character! If " + short_or_long_id
490 + "' was meant to be a short identifier, please pass it as a char ('') not a string"
491 " (\"\")!"};
492 }
493 }
494
495 if (std::find(used_option_ids.begin(), used_option_ids.end(), std::string{id}) == used_option_ids.end())
496 throw design_error{"You can only ask for option identifiers that you added with add_option() before."};
497
498 // we only need to search for an option before the `end_of_options_indentifier` (`--`)
499 auto end_of_options = std::find(cmd_arguments.begin(), cmd_arguments.end(), end_of_options_indentifier);
500 auto option_it = detail::format_parse::find_option_id(cmd_arguments.begin(), end_of_options, short_or_long_id);
501 return option_it != end_of_options;
502 }
503
506
514 {
516 [&](auto & f)
517 {
518 f.add_section(title, spec);
519 },
520 format);
521 }
522
530 {
532 [&](auto & f)
533 {
534 f.add_subsection(title, spec);
535 },
536 format);
537 }
538
549 {
551 [&](auto & f)
552 {
553 f.add_line(text, is_paragraph, spec);
554 },
555 format);
556 }
557
576 void
578 {
580 [&](auto & f)
581 {
582 f.add_list_item(key, desc, spec);
583 },
584 format);
585 }
587
637
638private:
640 bool parse_was_called{false};
641
643 bool has_positional_list_option{false};
644
646 update_notifications version_check_dev_decision{};
647
649 std::optional<bool> version_check_user_decision;
650
652 friend struct ::seqan3::detail::test_accessor;
653
655 std::future<bool> version_check_future;
656
658 std::regex app_name_regex{"^[a-zA-Z0-9_-]+$"};
659
661 static constexpr std::string_view const end_of_options_indentifier{"--"};
662
664 std::unique_ptr<argument_parser> sub_parser{nullptr};
665
667 std::vector<std::string> subcommands{};
668
676 std::variant<detail::format_parse,
677 detail::format_help,
678 detail::format_short_help,
679 detail::format_version,
680 detail::format_html,
681 detail::format_man,
682 detail::format_copyright/*,
683 detail::format_ctd*/> format{detail::format_help{{}, false}}; // Will be overwritten in any case.
684
686 std::set<std::string> used_option_ids{"h", "hh", "help", "advanced-help", "export-help", "version", "copyright"};
687
689 std::vector<std::string> cmd_arguments{};
690
723 void init(int argc, char const * const * const argv)
724 {
725 if (argc <= 1) // no arguments provided
726 {
727 format = detail::format_short_help{};
728 return;
729 }
730
731 bool special_format_was_set{false};
732
733 for (int i = 1, argv_len = argc; i < argv_len; ++i) // start at 1 to skip binary name
734 {
735 std::string arg{argv[i]};
736
737 if (std::ranges::find(subcommands, arg) != subcommands.end())
738 {
739 sub_parser = std::make_unique<argument_parser>(info.app_name + "-" + arg,
740 argc - i,
741 argv + i,
743 break;
744 }
745
746 if (arg == "-h" || arg == "--help")
747 {
748 format = detail::format_help{subcommands, false};
749 init_standard_options();
750 special_format_was_set = true;
751 }
752 else if (arg == "-hh" || arg == "--advanced-help")
753 {
754 format = detail::format_help{subcommands, true};
755 init_standard_options();
756 special_format_was_set = true;
757 }
758 else if (arg == "--version")
759 {
760 format = detail::format_version{};
761 special_format_was_set = true;
762 }
763 else if (arg.substr(0, 13) == "--export-help") // --export-help=man is also allowed
764 {
765 std::string export_format;
766
767 if (arg.size() > 13)
768 {
769 export_format = arg.substr(14);
770 }
771 else
772 {
773 if (argv_len <= i + 1)
774 throw too_few_arguments{"Option --export-help must be followed by a value."};
775 export_format = argv[i + 1];
776 }
777
778 if (export_format == "html")
779 format = detail::format_html{subcommands};
780 else if (export_format == "man")
781 format = detail::format_man{subcommands};
782 // TODO (smehringer) use when CTD support is available
783 // else if (export_format == "ctd")
784 // format = detail::format_ctd{};
785 else
786 throw validation_error{"Validation failed for option --export-help: "
787 "Value must be one of [html, man]"};
788 init_standard_options();
789 special_format_was_set = true;
790 }
791 else if (arg == "--copyright")
792 {
793 format = detail::format_copyright{};
794 special_format_was_set = true;
795 }
796 else if (arg == "--version-check")
797 {
798 if (++i >= argv_len)
799 throw too_few_arguments{"Option --version-check must be followed by a value."};
800
801 arg = argv[i];
802
803 if (arg == "1" || arg == "true")
804 version_check_user_decision = true;
805 else if (arg == "0" || arg == "false")
806 version_check_user_decision = false;
807 else
808 throw validation_error{"Value for option --version-check must be true (1) or false (0)."};
809
810 // in case --version-check is specified it shall not be passed to format_parse()
811 argc -= 2;
812 }
813 else
814 {
815 cmd_arguments.push_back(std::move(arg));
816 }
817 }
818
819 if (!special_format_was_set)
820 format = detail::format_parse(argc, cmd_arguments);
821 }
822
824 void init_standard_options()
825 {
826 add_subsection("Basic options:");
827 add_list_item("\fB-h\fP, \fB--help\fP", "Prints the help page.");
828 add_list_item("\fB-hh\fP, \fB--advanced-help\fP", "Prints the help page including advanced options.");
829 add_list_item("\fB--version\fP", "Prints the version information.");
830 add_list_item("\fB--copyright\fP", "Prints the copyright/license information.");
831 add_list_item("\fB--export-help\fP (std::string)",
832 "Export the help page information. Value must be one of [html, man].");
833 if (version_check_dev_decision == update_notifications::on)
834 add_list_item("\fB--version-check\fP (bool)",
835 "Whether to check for the newest app version. Default: true.");
836 }
837
843 template <typename id_type>
844 bool id_exists(id_type const & id)
845 {
846 if (detail::format_parse::is_empty_id(id))
847 return false;
848 return (!(used_option_ids.insert(std::string({id}))).second);
849 }
850
860 void verify_identifiers(char const short_id, std::string const & long_id)
861 {
862 constexpr auto allowed = is_alnum || is_char<'_'> || is_char<'@'>;
863
864 if (id_exists(short_id))
865 throw design_error("Option Identifier '" + std::string(1, short_id) + "' was already used before.");
866 if (id_exists(long_id))
867 throw design_error("Option Identifier '" + long_id + "' was already used before.");
868 if (long_id.length() == 1)
869 throw design_error("Long IDs must be either empty, or longer than one character.");
870 if (!allowed(short_id) && !is_char<'\0'>(short_id))
871 throw design_error("Option identifiers may only contain alphanumeric characters, '_', or '@'.");
872 if (long_id.size() > 0 && is_char<'-'>(long_id[0]))
873 throw design_error("First character of long ID cannot be '-'.");
874
875 std::for_each(long_id.begin(),
876 long_id.end(),
877 [&allowed](char c)
878 {
879 if (!(allowed(c) || is_char<'-'>(c)))
880 throw design_error(
881 "Long identifiers may only contain alphanumeric characters, '_', '-', or '@'.");
882 });
883 if (detail::format_parse::is_empty_id(short_id) && detail::format_parse::is_empty_id(long_id))
884 throw design_error("Option Identifiers cannot both be empty.");
885 }
886};
887
888} // namespace seqan3
T begin(T... args)
The SeqAn command line parser.
Definition argument_parser.hpp:147
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:314
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:270
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:476
argument_parser(argument_parser const &)=delete
Deleted. Holds std::future.
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:238
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:174
argument_parser & operator=(argument_parser &&)=default
Defaulted.
~argument_parser()
The destructor.
Definition argument_parser.hpp:203
argument_parser & operator=(argument_parser const &)=delete
Deleted. Holds std::future.
argument_parser_meta_data info
Aggregates all parser related meta data (see seqan3::argument_parser_meta_data struct).
Definition argument_parser.hpp:636
argument_parser(argument_parser &&)=default
Defaulted.
void parse()
Initiates the actual command line parsing.
Definition argument_parser.hpp:404
argument_parser()=delete
Deleted.
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:548
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:577
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:513
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:529
argument_parser & get_sub_parser()
Returns a reference to the sub-parser instance if subcommand parsing was enabled.
Definition argument_parser.hpp:438
A "pretty printer" for most SeqAn data structures and related types.
Definition debug_stream_type.hpp:79
Argument parser exception that is thrown whenever there is an design error directed at the developer ...
Definition exceptions.hpp:150
Argument parser exception thrown when too few arguments are provided.
Definition exceptions.hpp:78
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.
option_spec
Used to further specify argument_parser options/flags.
Definition auxiliary.hpp:249
@ standard
The default were no checking or special displaying is happening.
Definition auxiliary.hpp:250
constexpr auto is_alnum
Checks whether c is a alphanumeric character.
Definition predicate.hpp:194
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition predicate.hpp:60
T insert(T... args)
The main SeqAn3 namespace.
Definition aligned_sequence_concept.hpp:26
update_notifications
Indicates whether application allows automatic update notifications by the seqan3::argument_parser.
Definition auxiliary.hpp:268
@ off
Automatic update notifications should be disabled.
@ on
Automatic update notifications should be enabled.
SeqAn specific customisations in the standard namespace.
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:287
std::string version
The version information MAJOR.MINOR.PATH (e.g. 3.1.3)
Definition auxiliary.hpp:295
std::string app_name
The application name that will be displayed on the help page.
Definition auxiliary.hpp:293
std::string url
A link to your github/gitlab project with the newest release.
Definition auxiliary.hpp:307
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)
Hide me