SeqAn3  3.0.1
The Modern C++ library for sequence analysis.
format_parse.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2020, 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 <sstream>
16 #include <string>
17 #include <vector>
18 
21 #include <seqan3/std/concepts>
22 #include <seqan3/std/charconv>
23 
24 namespace seqan3::detail
25 {
26 
51 class format_parse : public format_base
52 {
53 public:
57  format_parse() = delete;
58  format_parse(format_parse const & pf) = default;
59  format_parse & operator=(format_parse const & pf) = default;
60  format_parse(format_parse &&) = default;
61  format_parse & operator=(format_parse &&) = default;
62  ~format_parse() = default;
63 
68  format_parse(int const argc_, std::vector<std::string> && argv_) :
69  argc{argc_ - 1}, argv{std::move(argv_)}
70  {}
72 
86  template <typename option_type, typename validator_type>
87  void add_option(option_type & value,
88  char const short_id,
89  std::string const & long_id,
90  std::string const & /*desc*/,
91  option_spec const & spec,
92  validator_type && validator)
93  {
94  option_calls.push_back([this, &value, short_id, long_id, spec, validator]()
95  {
96  get_option(value, short_id, long_id, spec, validator);
97  });
98  }
99 
108  void add_flag(bool & value,
109  char const short_id,
110  std::string const & long_id,
111  std::string const & /*desc*/,
112  option_spec const & /*spec*/)
113  {
114  flag_calls.push_back([this, &value, short_id, long_id]()
115  {
116  get_flag(value, short_id, long_id);
117  });
118  }
119 
130  template <typename option_type, typename validator_type>
131  void add_positional_option(option_type & value,
132  std::string const & /*desc*/,
133  validator_type && validator)
134  {
135  positional_option_calls.push_back([this, &value, validator]()
136  {
137  get_positional_option(value, validator);
138  });
139  }
140 
142  void parse(argument_parser_meta_data const & /*meta*/)
143  {
144  end_of_options_it = std::find(argv.begin(), argv.end(), "--");
145 
146  // parse options first, because we need to rule out -keyValue pairs
147  // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
148  for (auto && f : option_calls)
149  f();
150 
151  for (auto && f : flag_calls)
152  f();
153 
154  check_for_unknown_ids();
155 
156  if (end_of_options_it != argv.end())
157  *end_of_options_it = ""; // remove -- before parsing positional arguments
158 
159  for (auto && f : positional_option_calls)
160  f();
161 
162  check_for_left_over_args();
163  }
164 
165  // functions are not needed for command line parsing but are part of the format interface.
167  void add_section(std::string const &) {}
168  void add_subsection(std::string const &) {}
169  void add_line(std::string const &, bool) {}
170  void add_list_item(std::string const &, std::string const &) {}
172 
174  template <typename id_type>
175  static bool is_empty_id(id_type const & id)
176  {
177  if constexpr (std::same_as<remove_cvref_t<id_type>, std::string>)
178  return id.empty();
179  else // char
180  return is_char<'\0'>(id);
181  }
182 
183 private:
184 
189  std::string prepend_dash(std::string const & long_id)
190  {
191  return ("--" + long_id);
192  }
193 
198  std::string prepend_dash(char const short_id)
199  {
200  return ("-" + std::string(1, short_id));
201  }
202 
208  std::string combine_option_names(char const short_id, std::string const & long_id)
209  {
210  if (short_id == '\0')
211  return prepend_dash(long_id);
212  else if (long_id.empty())
213  return prepend_dash(short_id);
214  else // both are set (note: both cannot be empty, this is caught before)
215  return prepend_dash(short_id) + "/" + prepend_dash(long_id);
216  }
217 
230  template <typename id_type>
231  std::vector<std::string>::iterator find_option_id(std::vector<std::string>::iterator const begin_it, id_type const & id)
232  {
233  if (is_empty_id(id))
234  return end_of_options_it;
235 
236  return (std::find_if(begin_it, end_of_options_it,
237  [&] (const std::string & v)
238  {
239  size_t id_size{(prepend_dash(id)).size()};
240  if (v.size() < id_size)
241  return false; // cannot be the correct identifier
242 
243  return v.substr(0, id_size) == prepend_dash(id); // check if prefix of v is the same
244  }));
245  }
246 
250  bool flag_is_set(std::string const & long_id)
251  {
252  auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
253 
254  if (it != end_of_options_it)
255  *it = ""; // remove seen flag
256 
257  return(it != end_of_options_it);
258  }
259 
263  bool flag_is_set(char const short_id)
264  {
265  // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
266  for (std::string & arg : argv)
267  {
268  if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
269  {
270  auto pos = arg.find(short_id);
271 
272  if (pos != std::string::npos)
273  {
274  arg.erase(pos, 1); // remove seen bool
275 
276  if (arg == "-") // if flag is empty now
277  arg = "";
278 
279  return true;
280  }
281  }
282  }
283  return false;
284  }
285 
293  template <typename option_t>
297  void retrieve_value(option_t & value, std::string const & in)
298  {
299  std::istringstream stream{in};
300  stream >> value;
301 
302  if (stream.fail() || !stream.eof())
303  {
304  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
305  get_type_name_as_string(value) + ".");
306  }
307  }
308 
316  template <named_enumeration option_t>
317  void retrieve_value(option_t & value, std::string_view const in)
318  {
319  auto map = seqan3::enumeration_names<option_t>;
320 
321  if (auto it = map.find(in); it == map.end())
322  {
323  throw type_conversion_failed("Argument " + std::string{in} + " could not be cast to enum type " +
324  type_name_as_string<option_t> + ".");
325  }
326  else
327  {
328  value = it->second;
329  }
330  }
331 
333  void retrieve_value(std::string & value, std::string const & in)
334  {
335  value = in;
336  }
338 
347  template <sequence_container container_option_t>
351  void retrieve_value(container_option_t & value, std::string const & in)
352  {
353  typename container_option_t::value_type tmp;
354 
355  retrieve_value(tmp, in); // throws on failure
356  value.push_back(tmp);
357  }
358 
371  template <arithmetic option_t>
375  void retrieve_value(option_t & value, std::string const & in)
376  {
377  auto res = std::from_chars(&in[0], &in[in.size()], value);
378 
379  if (res.ec == std::errc::result_out_of_range)
380  throw overflow_error_on_conversion("Argument " + in + " is not in integer range [" +
383  else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
384  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
385  get_type_name_as_string(value) + ".");
386  }
387 
398  void retrieve_value(bool & value, std::string const & in)
399  {
400  if (in == "0")
401  value = false;
402  else if (in == "1")
403  value = true;
404  else if (in == "true")
405  value = true;
406  else if (in == "false")
407  value = false;
408  else
409  throw type_conversion_failed("Argument '" + in + "' could not be casted to boolean.");
410  }
411 
428  template <typename option_type, typename id_type>
429  bool identify_and_retrieve_option_value(option_type & value,
431  id_type const & id)
432  {
433  if (option_it != end_of_options_it)
434  {
435  std::string input_value;
436  size_t id_size = (prepend_dash(id)).size();
437 
438  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
439  {
440  if ((*option_it)[id_size] == '=') // -key=value
441  {
442  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
443  throw parser_invalid_argument("Value cast failed for option " +
444  prepend_dash(id) +
445  ": No value was provided.");
446  input_value = (*option_it).substr(id_size + 1);
447  }
448  else // -kevValue
449  {
450  input_value = (*option_it).substr(id_size);
451  }
452 
453  *option_it = ""; // remove used identifier-value pair
454  }
455  else // -key value
456  {
457  *option_it = ""; // remove used identifier
458  ++option_it;
459  if (option_it == end_of_options_it) // should not happen
460  throw parser_invalid_argument("Value cast failed for option " +
461  prepend_dash(id) +
462  ": No value was provided.");
463  input_value = *option_it;
464  *option_it = ""; // remove value
465  }
466 
467  try
468  {
469  retrieve_value(value, input_value);
470  }
471  catch (parser_invalid_argument const & ex)
472  {
473  throw parser_invalid_argument("Value cast failed for option " + prepend_dash(id) + ": " + ex.what());
474  }
475 
476  return true;
477  }
478  return false;
479  }
480 
498  template <typename option_type, typename id_type>
499  bool get_option_by_id(option_type & value, id_type const & id)
500  {
501  auto it = find_option_id(argv.begin(), id);
502 
503  if (it != end_of_options_it)
504  identify_and_retrieve_option_value(value, it, id);
505 
506  if (find_option_id(it, id) != end_of_options_it) // should not be found again
507  throw option_declared_multiple_times("Option " + prepend_dash(id) +
508  " is no list/container but declared multiple times.");
509 
510  return (it != end_of_options_it); // first search was successful or not
511  }
512 
524  template <sequence_container option_type, typename id_type>
526  requires !std::is_same_v<option_type, std::string>
528  bool get_option_by_id(option_type & value, id_type const & id)
529  {
530  auto it = find_option_id(argv.begin(), id);
531  bool seen_at_least_once{it != end_of_options_it};
532 
533  while (it != end_of_options_it)
534  {
535  identify_and_retrieve_option_value(value, it, id);
536  it = find_option_id(it, id);
537  }
538 
539  return seen_at_least_once;
540  }
541 
555  void check_for_unknown_ids()
556  {
557  for (auto it = argv.begin(); it != end_of_options_it; ++it)
558  {
559  std::string arg{*it};
560  if (!arg.empty() && arg[0] == '-') // may be an identifier
561  {
562  if (arg == "-")
563  {
564  continue; // positional option
565  }
566  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
567  {
568  throw unknown_option("Unknown flags " + expand_multiple_flags(arg) +
569  ". In case this is meant to be a non-option/argument/parameter, " +
570  "please specify the start of arguments with '--'. " +
571  "See -h/--help for program information.");
572  }
573  else // unknown short or long option
574  {
575  throw unknown_option("Unknown option " + arg +
576  ". In case this is meant to be a non-option/argument/parameter, " +
577  "please specify the start of non-options with '--'. " +
578  "See -h/--help for program information.");
579  }
580  }
581  }
582  }
583 
595  void check_for_left_over_args()
596  {
597  if (std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");}) != argv.end())
598  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
599  }
600 
621  template <typename option_type, typename validator_type>
622  void get_option(option_type & value,
623  char const short_id,
624  std::string const & long_id,
625  option_spec const & spec,
626  validator_type && validator)
627  {
628  bool short_id_is_set{get_option_by_id(value, short_id)};
629  bool long_id_is_set{get_option_by_id(value, long_id)};
630 
631  // if value is no container we need to check for multiple declarations
632  if (short_id_is_set && long_id_is_set &&
633  !(sequence_container<option_type> && !std::is_same_v<option_type, std::string>))
634  throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id) +
635  " is no list/container but specified multiple times");
636 
637  if (short_id_is_set || long_id_is_set)
638  {
639  try
640  {
641  validator(value);
642  }
643  catch (std::exception & ex)
644  {
645  throw validation_failed(std::string("Validation failed for option ") +
646  combine_option_names(short_id, long_id) + ": " + ex.what());
647  }
648  }
649  else // option is not set
650  {
651  // check if option is required
652  if (spec & option_spec::REQUIRED)
653  throw required_option_missing("Option " + combine_option_names(short_id, long_id) +
654  " is required but not set.");
655  }
656  }
657 
665  void get_flag(bool & value,
666  char const short_id,
667  std::string const & long_id)
668  {
669  value = flag_is_set(short_id) || flag_is_set(long_id);
670  }
671 
697  template <typename option_type, typename validator_type>
698  void get_positional_option(option_type & value,
699  validator_type && validator)
700  {
701  ++positional_option_count;
702  auto it = std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");});
703 
704  if (it == argv.end())
705  throw too_few_arguments("Not enough positional arguments provided (Need at least " +
706  std::to_string(positional_option_calls.size()) + "). See -h/--help for more information.");
707 
708  if (sequence_container<option_type> && !std::is_same_v<option_type, std::string>) // vector/list will be filled with all remaining arguments
709  {
710  assert(positional_option_count == positional_option_calls.size()); // checked on set up.
711 
712  while (it != argv.end())
713  {
714  try
715  {
716  retrieve_value(value, *it);
717  }
718  catch (parser_invalid_argument const & ex)
719  {
720  throw parser_invalid_argument("Value cast failed for positional option " +
721  std::to_string(positional_option_count) + ": " + ex.what());
722  }
723 
724  *it = ""; // remove arg from argv
725  it = std::find_if(it, argv.end(), [](std::string const & s){return (s != "");});
726  ++positional_option_count;
727  }
728  }
729  else
730  {
731  try
732  {
733  retrieve_value(value, *it);
734  }
735  catch (parser_invalid_argument const & ex)
736  {
737  throw parser_invalid_argument("Value cast failed for positional option " +
738  std::to_string(positional_option_count) + ": " + ex.what());
739  }
740 
741  *it = ""; // remove arg from argv
742  }
743 
744  try
745  {
746  validator(value);
747  }
748  catch (std::exception & ex)
749  {
750  throw validation_failed("Validation failed for positional option " +
751  std::to_string(positional_option_count) + ": " + ex.what());
752  }
753  }
754 
756  std::vector<std::function<void()>> option_calls;
758  std::vector<std::function<void()>> flag_calls;
760  std::vector<std::function<void()>> positional_option_calls;
762  unsigned positional_option_count{0};
764  int argc;
768  std::vector<std::string>::iterator end_of_options_it;
769 };
770 
771 } // namespace seqan3
sstream
seqan3::option_spec
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:231
std::string
sequence_container
A more refined container concept than seqan3::container.
std::exception
std::string_view
charconv
Provides std::from_chars and std::to_chars if not defined in the stl <charconv> header.
vector
std::find
T find(T... args)
std::string::size
T size(T... args)
seqan3::field::id
The identifier, usually a string.
std::istringstream
seqan3::views::move
const auto move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:68
std::function
concepts
The Concepts library.
same_as
The concept std::same_as<T, U> is satisfied if and only if T and U denote the same type.
seqan3::REQUIRED
Definition: auxiliary.hpp:234
std::to_string
T to_string(T... args)
std::from_chars
T from_chars(T... args)
seqan3::pack_traits::size
constexpr size_t size
The size of a type pack.
Definition: traits.hpp:116
std::string::substr
T substr(T... args)
format_base.hpp
Provides the format_base struct containing all helper functions that are needed in all formats.
std
SeqAn specific customisations in the standard namespace.
type_inspection.hpp
Provides traits to inspect some information of a type, for example its name.
validator
The concept for option validators passed to add_option/positional_option.
std::string::empty
T empty(T... args)
input_stream_over
Concept for input streams.
seqan3::is_char
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:83
std::numeric_limits
std::exception::what
T what(T... args)
string