SeqAn3
The Modern C++ library for sequence analysis.
format_parse.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 <sstream>
16 #include <string>
17 #include <vector>
18 
20 #include <seqan3/std/concepts>
21 #include <seqan3/std/charconv>
22 
23 namespace seqan3::detail
24 {
25 
50 class format_parse : public format_base
51 {
52 public:
56  format_parse() = delete;
57  format_parse(format_parse const & pf) = default;
58  format_parse & operator=(format_parse const & pf) = default;
59  format_parse(format_parse &&) = default;
60  format_parse & operator=(format_parse &&) = default;
61  ~format_parse() = default;
62 
67  format_parse(int const argc_, std::vector<std::string> && argv_) :
68  argc{argc_ - 1}, argv{std::move(argv_)}
69  {}
71 
85  template <typename option_type, typename validator_type>
86  void add_option(option_type & value,
87  char const short_id,
88  std::string const & long_id,
89  std::string const & /*desc*/,
90  option_spec const & spec,
91  validator_type && validator)
92  {
93  option_calls.push_back([this, &value, short_id, long_id, spec, validator]()
94  {
95  get_option(value, short_id, long_id, spec, validator);
96  });
97  }
98 
107  void add_flag(bool & value,
108  char const short_id,
109  std::string const & long_id,
110  std::string const & /*desc*/,
111  option_spec const & /*spec*/)
112  {
113  flag_calls.push_back([this, &value, short_id, long_id]()
114  {
115  get_flag(value, short_id, long_id);
116  });
117  }
118 
129  template <typename option_type, typename validator_type>
130  void add_positional_option(option_type & value,
131  std::string const & /*desc*/,
132  validator_type && validator)
133  {
134  positional_option_calls.push_back([this, &value, validator]()
135  {
136  get_positional_option(value, validator);
137  });
138  }
139 
141  void parse(argument_parser_meta_data const & /*meta*/)
142  {
143  end_of_options_it = std::find(argv.begin(), argv.end(), "--");
144 
145  // parse options first, because we need to rule out -keyValue pairs
146  // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
147  for (auto && f : option_calls)
148  f();
149 
150  for (auto && f : flag_calls)
151  f();
152 
153  check_for_unknown_ids();
154 
155  if (end_of_options_it != argv.end())
156  *end_of_options_it = ""; // remove -- before parsing positional arguments
157 
158  for (auto && f : positional_option_calls)
159  f();
160 
161  check_for_left_over_args();
162  }
163 
164  // functions are not needed for command line parsing but are part of the format interface.
166  void add_section(std::string const &) {}
167  void add_subsection(std::string const &) {}
168  void add_line(std::string const &, bool) {}
169  void add_list_item(std::string const &, std::string const &) {}
171 
173  template <typename id_type>
174  static bool is_empty_id(id_type const & id)
175  {
176  if constexpr (std::Same<remove_cvref_t<id_type>, std::string>)
177  return id.empty();
178  else // char
179  return is_char<'\0'>(id);
180  }
181 
182 private:
183 
188  std::string prepend_dash(std::string const & long_id)
189  {
190  return ("--" + long_id);
191  }
192 
197  std::string prepend_dash(char const short_id)
198  {
199  return ("-" + std::string(1, short_id));
200  }
201 
207  std::string combine_option_names(char const short_id, std::string const & long_id)
208  {
209  if (short_id == '\0')
210  return prepend_dash(long_id);
211  else if (long_id.empty())
212  return prepend_dash(short_id);
213  else // both are set (note: both cannot be empty, this is caught before)
214  return prepend_dash(short_id) + "/" + prepend_dash(long_id);
215  }
216 
229  template <typename id_type>
230  std::vector<std::string>::iterator find_option_id(std::vector<std::string>::iterator const begin_it, id_type const & id)
231  {
232  if (is_empty_id(id))
233  return end_of_options_it;
234 
235  return (std::find_if(begin_it, end_of_options_it,
236  [&] (const std::string & v)
237  {
238  size_t id_size{(prepend_dash(id)).size()};
239  if (v.size() < id_size)
240  return false; // cannot be the correct identifier
241 
242  return v.substr(0, id_size) == prepend_dash(id); // check if prefix of v is the same
243  }));
244  }
245 
249  bool flag_is_set(std::string const & long_id)
250  {
251  auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
252 
253  if (it != end_of_options_it)
254  *it = ""; // remove seen flag
255 
256  return(it != end_of_options_it);
257  }
258 
262  bool flag_is_set(char const short_id)
263  {
264  // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
265  for (std::string & arg : argv)
266  {
267  if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
268  {
269  auto pos = arg.find(short_id);
270 
271  if (pos != std::string::npos)
272  {
273  arg.erase(pos, 1); // remove seen bool
274 
275  if (arg == "-") // if flag is empty now
276  arg = "";
277 
278  return true;
279  }
280  }
281  }
282  return false;
283  }
284 
294  template <typename option_t>
296  requires IStream<std::istringstream, option_t>
298  void retrieve_value(option_t & value, std::string const & in)
299  {
300  std::istringstream stream{in};
301  stream >> value;
302 
303  if (stream.fail() || !stream.eof())
304  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
305  get_type_name_as_string(value) + ".");
306  }
307 
309  void retrieve_value(std::string & value, std::string const & in)
310  {
311  value = in;
312  }
314 
323  template <SequenceContainer container_option_t>
325  requires IStream<std::istringstream, typename container_option_t::value_type>
327  void retrieve_value(container_option_t & value, std::string const & in)
328  {
329  typename container_option_t::value_type tmp;
330 
331  retrieve_value(tmp, in); // throws on failure
332  value.push_back(tmp);
333  }
334 
347  template <Arithmetic option_t>
349  requires IStream<std::istringstream, option_t>
351  void retrieve_value(option_t & value, std::string const & in)
352  {
353  auto res = std::from_chars(&in[0], &in[in.size()], value);
354 
355  if (res.ec == std::errc::result_out_of_range)
356  throw overflow_error_on_conversion("Argument " + in + " is not in integer range [" +
359  else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
360  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
361  get_type_name_as_string(value) + ".");
362  }
363 
374  void retrieve_value(bool & value, std::string const & in)
375  {
376  if (in == "0")
377  value = false;
378  else if (in == "1")
379  value = true;
380  else if (in == "true")
381  value = true;
382  else if (in == "false")
383  value = false;
384  else
385  throw type_conversion_failed("Argument '" + in + "' could not be casted to boolean.");
386  }
387 
404  template <typename option_type, typename id_type>
405  bool identify_and_retrieve_option_value(option_type & value,
407  id_type const & id)
408  {
409  if (option_it != end_of_options_it)
410  {
411  std::string input_value;
412  size_t id_size = (prepend_dash(id)).size();
413 
414  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
415  {
416  if ((*option_it)[id_size] == '=') // -key=value
417  {
418  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
419  throw parser_invalid_argument("Value cast failed for option " +
420  prepend_dash(id) +
421  ": No value was provided.");
422  input_value = (*option_it).substr(id_size + 1);
423  }
424  else // -kevValue
425  {
426  input_value = (*option_it).substr(id_size);
427  }
428 
429  *option_it = ""; // remove used identifier-value pair
430  }
431  else // -key value
432  {
433  *option_it = ""; // remove used identifier
434  ++option_it;
435  if (option_it == end_of_options_it) // should not happen
436  throw parser_invalid_argument("Value cast failed for option " +
437  prepend_dash(id) +
438  ": No value was provided.");
439  input_value = *option_it;
440  *option_it = ""; // remove value
441  }
442 
443  try
444  {
445  retrieve_value(value, input_value);
446  }
447  catch (parser_invalid_argument const & ex)
448  {
449  throw parser_invalid_argument("Value cast failed for option " + prepend_dash(id) + ": " + ex.what());
450  }
451 
452  return true;
453  }
454  return false;
455  }
456 
474  template <typename option_type, typename id_type>
475  bool get_option_by_id(option_type & value, id_type const & id)
476  {
477  auto it = find_option_id(argv.begin(), id);
478 
479  if (it != end_of_options_it)
480  identify_and_retrieve_option_value(value, it, id);
481 
482  if (find_option_id(it, id) != end_of_options_it) // should not be found again
483  throw option_declared_multiple_times("Option " + prepend_dash(id) +
484  " is no list/container but declared multiple times.");
485 
486  return (it != end_of_options_it); // first search was successful or not
487  }
488 
500  template <SequenceContainer option_type, typename id_type>
502  requires !std::is_same_v<option_type, std::string>
504  bool get_option_by_id(option_type & value, id_type const & id)
505  {
506  auto it = find_option_id(argv.begin(), id);
507  bool seen_at_least_once{it != end_of_options_it};
508 
509  while (it != end_of_options_it)
510  {
511  identify_and_retrieve_option_value(value, it, id);
512  it = find_option_id(it, id);
513  }
514 
515  return seen_at_least_once;
516  }
517 
531  void check_for_unknown_ids()
532  {
533  for (auto it = argv.begin(); it != end_of_options_it; ++it)
534  {
535  std::string arg{*it};
536  if (!arg.empty() && arg[0] == '-') // may be an identifier
537  {
538  if (arg == "-")
539  {
540  continue; // positional option
541  }
542  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
543  {
544  throw unknown_option("Unknown flags " + expand_multiple_flags(arg) +
545  ". In case this is meant to be a non-option/argument/parameter, " +
546  "please specify the start of arguments with '--'. " +
547  "See -h/--help for program information.");
548  }
549  else // unknown short or long option
550  {
551  throw unknown_option("Unknown option " + arg +
552  ". In case this is meant to be a non-option/argument/parameter, " +
553  "please specify the start of non-options with '--'. " +
554  "See -h/--help for program information.");
555  }
556  }
557  }
558  }
559 
571  void check_for_left_over_args()
572  {
573  if (std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");}) != argv.end())
574  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
575  }
576 
597  template <typename option_type, typename validator_type>
598  void get_option(option_type & value,
599  char const short_id,
600  std::string const & long_id,
601  option_spec const & spec,
602  validator_type && validator)
603  {
604  bool short_id_is_set{get_option_by_id(value, short_id)};
605  bool long_id_is_set{get_option_by_id(value, long_id)};
606 
607  // if value is no container we need to check for multiple declarations
608  if (short_id_is_set && long_id_is_set &&
609  !(SequenceContainer<option_type> && !std::is_same_v<option_type, std::string>))
610  throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id) +
611  " is no list/container but specified multiple times");
612 
613  if (short_id_is_set || long_id_is_set)
614  {
615  try
616  {
617  validator(value);
618  }
619  catch (std::exception & ex)
620  {
621  throw validation_failed(std::string("Validation failed for option ") +
622  combine_option_names(short_id, long_id) + ": " + ex.what());
623  }
624  }
625  else // option is not set
626  {
627  // check if option is required
628  if (spec & option_spec::REQUIRED)
629  throw required_option_missing("Option " + combine_option_names(short_id, long_id) +
630  " is required but not set.");
631  }
632  }
633 
641  void get_flag(bool & value,
642  char const short_id,
643  std::string const & long_id)
644  {
645  value = flag_is_set(short_id) || flag_is_set(long_id);
646  }
647 
673  template <typename option_type, typename validator_type>
674  void get_positional_option(option_type & value,
675  validator_type && validator)
676  {
677  ++positional_option_count;
678  auto it = std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");});
679 
680  if (it == argv.end())
681  throw too_few_arguments("Not enough positional arguments provided (Need at least " +
682  std::to_string(positional_option_calls.size()) + "). See -h/--help for more information.");
683 
684  if (SequenceContainer<option_type> && !std::is_same_v<option_type, std::string>) // vector/list will be filled with all remaining arguments
685  {
686  if (positional_option_count != (positional_option_calls.size()))
687  throw parser_design_error("Lists are only allowed as the last positional option!");
688 
689  while (it != argv.end())
690  {
691  try
692  {
693  retrieve_value(value, *it);
694  }
695  catch (parser_invalid_argument const & ex)
696  {
697  throw parser_invalid_argument("Value cast failed for positional option " +
698  std::to_string(positional_option_count) + ": " + ex.what());
699  }
700 
701  *it = ""; // remove arg from argv
702  it = std::find_if(it, argv.end(), [](std::string const & s){return (s != "");});
703  ++positional_option_count;
704  }
705  }
706  else
707  {
708  try
709  {
710  retrieve_value(value, *it);
711  }
712  catch (parser_invalid_argument const & ex)
713  {
714  throw parser_invalid_argument("Value cast failed for positional option " +
715  std::to_string(positional_option_count) + ": " + ex.what());
716  }
717 
718  *it = ""; // remove arg from argv
719  }
720 
721  try
722  {
723  validator(value);
724  }
725  catch (std::exception & ex)
726  {
727  throw validation_failed("Validation failed for positional option " +
728  std::to_string(positional_option_count) + ": " + ex.what());
729  }
730  }
731 
733  std::vector<std::function<void()>> option_calls;
737  std::vector<std::function<void()>> positional_option_calls;
739  unsigned positional_option_count{0};
741  int argc;
745  std::vector<std::string>::iterator end_of_options_it;
746 };
747 
748 } // namespace seqan3
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:83
T empty(T... args)
Provides the format_base struct containing all helper functions that are needed in all formats...
T to_string(T... args)
SeqAn specific customisations in the standard namespace.
::ranges::size size
Alias for ranges::size. Obtains the size of a range whose size can be calculated in constant time...
Definition: ranges:189
Provides std::from_chars and std::to_chars if not defined in the stl <charconv> header.
T what(T... args)
std::from_chars_result from_chars(char const *first, char const *last, value_type &value, int base) noexcept
Parse a char sequence into an integral.
Definition: charconv:174
The Concepts library.
The concept std::Same<T, U> is satisfied if and only if T and U denote the same type.
Definition: aligned_sequence_concept.hpp:35
T find(T... args)
T size(T... args)
::ranges::empty empty
Alias for ranges::empty. Checks whether a range is empty.
Definition: ranges:194
T substr(T... args)
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:34
Definition: auxiliary.hpp:37