SeqAn3  3.0.0
The Modern C++ library for sequence analysis.
argument_parser.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2019, 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 <set>
18 #include <sstream>
19 #include <string>
20 #include <variant>
21 #include <vector>
22 
23 // #include <seqan3/argument_parser/detail/format_ctd.hpp>
32 
33 namespace seqan3
34 {
35 
151 {
152 public:
156  argument_parser() = delete;
157  argument_parser(argument_parser const &) = default;
158  argument_parser & operator=(argument_parser const &) = default;
159  argument_parser(argument_parser &&) = default;
160  argument_parser & operator=(argument_parser &&) = default;
161 
172  argument_parser(std::string const app_name,
173  int const argc,
174  char const * const * const argv,
175  bool version_check = true) :
176  version_check_dev_decision{version_check}
177  {
178  info.app_name = std::move(app_name);
179  init(argc, argv);
180  }
181 
184  {
185  // wait for another 3 seconds
186  if (version_check_future.valid())
187  version_check_future.wait_for(std::chrono::seconds(3));
188  }
190 
212  template <typename option_type, Validator validator_type = detail::default_validator<option_type>>
218  void add_option(option_type & value,
219  char const short_id,
220  std::string const & long_id,
221  std::string const & desc,
222  option_spec const & spec = option_spec::DEFAULT,
223  validator_type validator = validator_type{}) // copy to bind rvalues
224  {
225  verify_identifiers(short_id, long_id);
226  // copy variables into the lambda because the calls are pushed to a stack
227  // and the references would go out of scope.
228  std::visit([=, &value] (auto & f) { f.add_option(value, short_id, long_id, desc, spec, validator); }, format);
229  }
230 
239  void add_flag(bool & value,
240  char const short_id,
241  std::string const & long_id,
242  std::string const & desc,
243  option_spec const & spec = option_spec::DEFAULT)
244  {
245  verify_identifiers(short_id, long_id);
246  // copy variables into the lambda because the calls are pushed to a stack
247  // and the references would go out of scope.
248  std::visit([=, &value] (auto & f) { f.add_flag(value, short_id, long_id, desc, spec); }, format);
249  }
250 
271  template <typename option_type, Validator validator_type = detail::default_validator<option_type>>
277  void add_positional_option(option_type & value,
278  std::string const & desc,
279  validator_type validator = validator_type{}) // copy to bind rvalues
280  {
281  // copy variables into the lambda because the calls are pushed to a stack
282  // and the references would go out of scope.
283  std::visit([=, &value] (auto & f) { f.add_positional_option(value, desc, validator); }, format);
284  }
286 
357  void parse()
358  {
359  if (parse_was_called)
360  throw parser_design_error("The function parse() must only be called once!");
361 
362  detail::version_checker app_version{info.app_name, info.version, info.url};
363 
364  if (app_version.decide_if_check_is_performed(version_check_dev_decision, version_check_user_decision))
365  {
366  // must be done before calling parse on the format because this might std::exit
367  std::promise<bool> app_version_prom;
368  version_check_future = app_version_prom.get_future();
369  app_version(std::move(app_version_prom));
370  }
371 
372  std::visit([this] (auto & f) { f.parse(info); }, format);
373  parse_was_called = true;
374  }
375 
378 
383  void add_section(std::string const & title)
384  {
385  std::visit([&] (auto & f) { f.add_section(title); }, format);
386  }
387 
392  void add_subsection(std::string const & title)
393  {
394  std::visit([&] (auto & f) { f.add_subsection(title); }, format);
395  }
396 
405  void add_line(std::string const & text, bool line_is_paragraph = false)
406  {
407  std::visit([&] (auto & f) { f.add_line(text, line_is_paragraph); }, format);
408  }
409 
426  void add_list_item(std::string const & key, std::string const & desc)
427  {
428  std::visit([&] (auto & f) { f.add_list_item(key, desc); }, format);
429  }
431 
481 
482 private:
484  bool parse_was_called{false};
485 
487  bool version_check_dev_decision{};
488 
490  std::optional<bool> version_check_user_decision;
491 
493  std::future<bool> version_check_future;
494 
523  void init(int argc, char const * const * const argv)
524  {
525  // cash command line input, in case --version-check is specified but shall not be passed to format_parse()
526  std::vector<std::string> argv_new{};
527 
528  if (argc <= 1) // no arguments provided
529  {
530  format = detail::format_short_help{};
531  return;
532  }
533 
534  for(int i = 1, argv_len = argc; i < argv_len; ++i) // start at 1 to skip binary name
535  {
536  std::string arg{argv[i]};
537 
538  if (arg == "-h" || arg == "--help")
539  {
540  format = detail::format_help{false};
541  init_standard_options();
542  return;
543  }
544  else if (arg == "-hh" || arg == "--advanced-help")
545  {
546  format = detail::format_help{true};
547  init_standard_options();
548  return;
549  }
550  else if (arg == "--version")
551  {
552  format = detail::format_version{};
553  return;
554  }
555  else if (arg.substr(0, 13) == "--export-help") // --export-help=man is also allowed
556  {
557  std::string export_format;
558 
559  if (arg.size() > 13)
560  {
561  export_format = arg.substr(14);
562  }
563  else
564  {
565  if (argv_len <= i + 1)
566  throw parser_invalid_argument{"Option --export-help must be followed by a value."};
567  export_format = {argv[i+1]};
568  }
569 
570  if (export_format == "html")
571  format = detail::format_html{};
572  else if (export_format == "man")
573  format = detail::format_man{};
574  // TODO (smehringer) use when CTD support is available
575  // else if (export_format == "ctd")
576  // format = detail::format_ctd{};
577  else
578  throw validation_failed{"Validation failed for option --export-help: "
579  "Value must be one of [html, man]"};
580  init_standard_options();
581  return;
582  }
583  else if (arg == "--copyright")
584  {
585  format = detail::format_copyright{};
586  return;
587  }
588  else if (arg == "--version-check")
589  {
590  if (++i >= argv_len)
591  throw parser_invalid_argument{"Option --version-check must be followed by a value."};
592 
593  arg = argv[i];
594 
595  if (arg == "1")
596  version_check_user_decision = true;
597  else if (arg == "0")
598  version_check_user_decision = false;
599  else
600  throw parser_invalid_argument{"Value for option --version-check must be 1 or 0."};
601 
602  argc -= 2;
603  }
604  else
605  {
606  argv_new.push_back(std::move(arg));
607  }
608  }
609 
610  format = detail::format_parse(argc, std::move(argv_new));
611  }
612 
614  void init_standard_options()
615  {
616  add_subsection("Basic options:");
617  add_list_item("\\fB-h\\fP, \\fB--help\\fP", "Prints the help page.");
618  add_list_item("\\fB-hh\\fP, \\fB--advanced-help\\fP",
619  "Prints the help page including advanced options.");
620  add_list_item("\\fB--version\\fP", "Prints the version information.");
621  add_list_item("\\fB--copyright\\fP", "Prints the copyright/license information.");
622  add_list_item("\\fB--export-help\\fP (std::string)",
623  "Export the help page information. Value must be one of [html, man].");
624  if (version_check_dev_decision)
625  add_list_item("\\fB--version-check\\fP (bool)", "Whether to to check for the newest app version. Default: 1.");
626  add_subsection(""); // add a new line (todo smehringer) add a add_newline() function
627  }
628 
634  template <typename id_type>
635  bool id_exists(id_type const & id)
636  {
637  if (detail::format_parse::is_empty_id(id))
638  return false;
639  return (!(used_option_ids.insert(std::string({id}))).second);
640  }
641 
651  void verify_identifiers(char const short_id, std::string const & long_id)
652  {
653  auto constexpr allowed = is_alnum || is_char<'_'> || is_char<'@'>;
654 
655  if (id_exists(short_id))
656  throw parser_design_error("Option Identifier '" + std::string(1, short_id) + "' was already used before.");
657  if (id_exists(long_id))
658  throw parser_design_error("Option Identifier '" + long_id + "' was already used before.");
659  if (long_id.length() == 1)
660  throw parser_design_error("Long IDs must be either empty, or longer than one character.");
661  if (!allowed(short_id) && !is_char<'\0'>(short_id))
662  throw parser_design_error("Option identifiers may only contain alphanumeric characters, '_', or '@'.");
663  if (long_id.size() > 0 && is_char<'-'>(long_id[0]))
664  throw parser_design_error("First character of long ID cannot be '-'.");
665 
666  std::for_each(long_id.begin(), long_id.end(), [&allowed] (char c)
667  {
668  if (!(allowed(c) || is_char<'-'>(c)))
669  throw parser_design_error("Long identifiers may only contain alphanumeric characters, '_', '-', or '@'.");
670  });
671  if (detail::format_parse::is_empty_id(short_id) && detail::format_parse::is_empty_id(long_id))
672  throw parser_design_error("Option Identifiers cannot both be empty.");
673  }
674 
679  std::variant<detail::format_parse,
680  detail::format_help,
681  detail::format_short_help,
682  detail::format_version,
683  detail::format_html,
684  detail::format_man,
685  detail::format_copyright/*,
686  detail::format_ctd*/> format{detail::format_help(0)};
687 
689  std::set<std::string> used_option_ids{"h", "hh", "help", "advanced-help", "export-help", "version", "copyright"};
690 };
691 
692 } // namespace seqan3
void add_line(std::string const &text, bool line_is_paragraph=false)
Adds an help page text line to the seqan3::argument_parser.
Definition: argument_parser.hpp:405
std::string url
A link to your github/gitlab project with the newest release.
Definition: auxiliary.hpp:81
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:83
T visit(T... args)
void add_subsection(std::string const &title)
Adds an help page subsection to the seqan3::argument_parser.
Definition: argument_parser.hpp:392
std::string version
The version information MAJOR.MINOR.PATH (e.g. 3.1.3)
Definition: auxiliary.hpp:69
void add_section(std::string const &title)
Adds an help page section to the seqan3::argument_parser.
Definition: argument_parser.hpp:383
T end(T... args)
The SeqAn command line parser.
Definition: argument_parser.hpp:150
The main SeqAn3 namespace.
Provides the format_man struct and its helper functions.
Checks if program is run interactively and retrieves dimensions of terminal (Transferred from seqan2)...
Argument parser exception that is thrown whenever there is an design error directed at the developer ...
Definition: exceptions.hpp:136
Stores all parser related meta information of the seqan3::argument_parser.
Definition: auxiliary.hpp:64
Concept for input streams.
T get_future(T... args)
void add_positional_option(option_type &value, std::string const &desc, validator_type validator=validator_type{})
Adds a positional option to the seqan3::argument_parser.
Definition: argument_parser.hpp:277
argument_parser & operator=(argument_parser const &)=default
Defaulted.
T valid(T... args)
argument_parser()=delete
Deleted.
std::string app_name
The application name that will be displayed on the help page.
Definition: auxiliary.hpp:67
void add_list_item(std::string const &key, std::string const &desc)
Adds an help page list item (key-value) to the seqan3::argument_parser.
Definition: argument_parser.hpp:426
argument_parser_meta_data info
Aggregates all parser related meta data (see seqan3::argument_parser_meta_data struct).
Definition: argument_parser.hpp:480
~argument_parser()
The destructor.
Definition: argument_parser.hpp:183
argument_parser(std::string const app_name, int const argc, char const *const *const argv, bool version_check=true)
Initializes an argument_parser object from the command line arguments.
Definition: argument_parser.hpp:172
T insert(T... args)
T length(T... args)
Provides character predicates for tokenisation.
Stream concepts.
T begin(T... args)
Provides the version check functionality.
Specifies whether the given callable is invocable with the given arguments.
T wait_for(T... args)
T substr(T... args)
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:34
Provides the format_html struct and its helper functions.
Provides the format_parse class.
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::DEFAULT, validator_type validator=validator_type{})
Adds an option to the seqan3::argument_parser.
Definition: argument_parser.hpp:218
T for_each(T... args)
void add_flag(bool &value, char const short_id, std::string const &long_id, std::string const &desc, option_spec const &spec=option_spec::DEFAULT)
Adds a flag to the seqan3::argument_parser.
Definition: argument_parser.hpp:239
void parse()
Initiates the actual command line parsing.
Definition: argument_parser.hpp:357
auto constexpr is_alnum
Checks whether c is a alphanumeric character.
Definition: predicate.hpp:220
Provides the format_help struct that print the help page to the command line and the two child format...
The default were no checking or special displaying is happening.
Definition: auxiliary.hpp:36