SeqAn3  3.1.0
The Modern C++ library for sequence analysis.
format_parse.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2021, 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 <seqan3/std/charconv>
16 #include <seqan3/std/concepts>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20 
24 
25 namespace seqan3::detail
26 {
27 
54 class format_parse : public format_base
55 {
56 public:
60  format_parse() = delete;
61  format_parse(format_parse const & pf) = default;
62  format_parse & operator=(format_parse const & pf) = default;
63  format_parse(format_parse &&) = default;
64  format_parse & operator=(format_parse &&) = default;
65  ~format_parse() = default;
66 
71  format_parse(int const argc_, std::vector<std::string> argv_) :
72  argc{argc_ - 1}, argv{std::move(argv_)}
73  {}
75 
79  template <typename option_type, typename validator_type>
80  void add_option(option_type & value,
81  char const short_id,
82  std::string const & long_id,
83  std::string const & SEQAN3_DOXYGEN_ONLY(desc),
84  option_spec const spec,
85  validator_type && option_validator)
86  {
87  option_calls.push_back([this, &value, short_id, long_id, spec, option_validator]()
88  {
89  get_option(value, short_id, long_id, spec, option_validator);
90  });
91  }
92 
96  void add_flag(bool & value,
97  char const short_id,
98  std::string const & long_id,
99  std::string const & SEQAN3_DOXYGEN_ONLY(desc),
100  option_spec const & SEQAN3_DOXYGEN_ONLY(spec))
101  {
102  flag_calls.push_back([this, &value, short_id, long_id]()
103  {
104  get_flag(value, short_id, long_id);
105  });
106  }
107 
111  template <typename option_type, typename validator_type>
112  void add_positional_option(option_type & value,
113  std::string const & SEQAN3_DOXYGEN_ONLY(desc),
114  validator_type && option_validator)
115  {
116  positional_option_calls.push_back([this, &value, option_validator]()
117  {
118  get_positional_option(value, option_validator);
119  });
120  }
121 
123  void parse(argument_parser_meta_data const & /*meta*/)
124  {
125  end_of_options_it = std::find(argv.begin(), argv.end(), "--");
126 
127  // parse options first, because we need to rule out -keyValue pairs
128  // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
129  for (auto && f : option_calls)
130  f();
131 
132  for (auto && f : flag_calls)
133  f();
134 
135  check_for_unknown_ids();
136 
137  if (end_of_options_it != argv.end())
138  *end_of_options_it = ""; // remove -- before parsing positional arguments
139 
140  for (auto && f : positional_option_calls)
141  f();
142 
143  check_for_left_over_args();
144  }
145 
146  // functions are not needed for command line parsing but are part of the format interface.
148  void add_section(std::string const &, option_spec const) {}
149  void add_subsection(std::string const &, option_spec const) {}
150  void add_line(std::string const &, bool, option_spec const) {}
151  void add_list_item(std::string const &, std::string const &, option_spec const) {}
153 
155  template <typename id_type>
156  static bool is_empty_id(id_type const & id)
157  {
158  if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
159  return id.empty();
160  else // char
161  return is_char<'\0'>(id);
162  }
163 
185  template <typename iterator_type, typename id_type>
186  static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
187  {
188  if (is_empty_id(id))
189  return end_it;
190 
191  return (std::find_if(begin_it, end_it,
192  [&] (std::string const & current_arg)
193  {
194  std::string full_id = prepend_dash(id);
195 
196  if constexpr (std::same_as<id_type, char>) // short id
197  {
198  // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
199  // "-ovalue", "-o=value", and "-o value".
200  return current_arg.substr(0, full_id.size()) == full_id;
201  }
202  else
203  {
204  // only "--opt Value" or "--opt=Value" are valid
205  return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
206  (current_arg.size() == full_id.size() || current_arg[full_id.size()] == '='); // space or `=`
207  }
208  }));
209  }
210 
211 private:
213  enum class option_parse_result
214  {
215  success,
216  error,
217  overflow_error
218  };
219 
224  static std::string prepend_dash(std::string const & long_id)
225  {
226  return {"--" + long_id};
227  }
228 
233  static std::string prepend_dash(char const short_id)
234  {
235  return {"-" + std::string{short_id}};
236  }
237 
243  std::string combine_option_names(char const short_id, std::string const & long_id)
244  {
245  if (short_id == '\0')
246  return prepend_dash(long_id);
247  else if (long_id.empty())
248  return prepend_dash(short_id);
249  else // both are set (note: both cannot be empty, this is caught before)
250  return prepend_dash(short_id) + "/" + prepend_dash(long_id);
251  }
252 
256  bool flag_is_set(std::string const & long_id)
257  {
258  auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
259 
260  if (it != end_of_options_it)
261  *it = ""; // remove seen flag
262 
263  return(it != end_of_options_it);
264  }
265 
269  bool flag_is_set(char const short_id)
270  {
271  // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
272  for (std::string & arg : argv)
273  {
274  if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
275  {
276  auto pos = arg.find(short_id);
277 
278  if (pos != std::string::npos)
279  {
280  arg.erase(pos, 1); // remove seen bool
281 
282  if (arg == "-") // if flag is empty now
283  arg = "";
284 
285  return true;
286  }
287  }
288  }
289  return false;
290  }
291 
299  template <typename option_t>
303  option_parse_result parse_option_value(option_t & value, std::string const & in)
304  {
305  std::istringstream stream{in};
306  stream >> value;
307 
308  if (stream.fail() || !stream.eof())
309  return option_parse_result::error;
310 
311  return option_parse_result::success;
312  }
313 
321  template <named_enumeration option_t>
322  option_parse_result parse_option_value(option_t & value, std::string const & in)
323  {
324  auto map = seqan3::enumeration_names<option_t>;
325 
326  if (auto it = map.find(in); it == map.end())
327  {
328  std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
329  std::ranges::sort(key_value_pairs, [] (auto pair1, auto pair2)
330  {
331  if constexpr (std::totally_ordered<option_t>)
332  {
333  if (pair1.second != pair2.second)
334  return pair1.second < pair2.second;
335  }
336  return pair1.first < pair2.first;
337  });
338 
339  throw user_input_error{detail::to_string("You have chosen an invalid input value: ", in,
340  ". Please use one of: ", key_value_pairs | std::views::keys)};
341  }
342  else
343  {
344  value = it->second;
345  }
346 
347  return option_parse_result::success;
348  }
349 
351  option_parse_result parse_option_value(std::string & value, std::string const & in)
352  {
353  value = in;
354  return option_parse_result::success;
355  }
357 
366  template <sequence_container container_option_t>
368  requires requires (format_parse fp, typename container_option_t::value_type & container_value, std::string const & in)
369  {
370  SEQAN3_RETURN_TYPE_CONSTRAINT(fp.parse_option_value(container_value, in), std::same_as, option_parse_result);
371  }
373  option_parse_result parse_option_value(container_option_t & value, std::string const & in)
374  {
375  typename container_option_t::value_type tmp{};
376 
377  auto res = parse_option_value(tmp, in);
378 
379  if (res == option_parse_result::success)
380  value.push_back(tmp);
381 
382  return res;
383  }
384 
397  template <arithmetic option_t>
401  option_parse_result parse_option_value(option_t & value, std::string const & in)
402  {
403  auto res = std::from_chars(&in[0], &in[in.size()], value);
404 
405  if (res.ec == std::errc::result_out_of_range)
406  return option_parse_result::overflow_error;
407  else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
408  return option_parse_result::error;
409 
410  return option_parse_result::success;
411  }
412 
423  option_parse_result parse_option_value(bool & value, std::string const & in)
424  {
425  if (in == "0")
426  value = false;
427  else if (in == "1")
428  value = true;
429  else if (in == "true")
430  value = true;
431  else if (in == "false")
432  value = false;
433  else
434  return option_parse_result::error;
435 
436  return option_parse_result::success;
437  }
438 
446  template <typename option_type>
447  void throw_on_input_error(option_parse_result const res,
448  std::string const & option_name,
449  std::string const & input_value)
450  {
451  std::string msg{"Value parse failed for " + option_name + ": "};
452 
453  if (res == option_parse_result::error)
454  {
455  throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type " +
456  get_type_name_as_string(option_type{}) + "."};
457  }
458 
459  if constexpr (arithmetic<option_type>)
460  {
461  if (res == option_parse_result::overflow_error)
462  {
463  throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range [" +
466  }
467  }
468 
469  assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
470  }
471 
489  template <typename option_type, typename id_type>
490  bool identify_and_retrieve_option_value(option_type & value,
492  id_type const & id)
493  {
494  if (option_it != end_of_options_it)
495  {
496  std::string input_value;
497  size_t id_size = (prepend_dash(id)).size();
498 
499  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
500  {
501  if ((*option_it)[id_size] == '=') // -key=value
502  {
503  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
504  throw too_few_arguments("Missing value for option " + prepend_dash(id));
505  input_value = (*option_it).substr(id_size + 1);
506  }
507  else // -kevValue
508  {
509  input_value = (*option_it).substr(id_size);
510  }
511 
512  *option_it = ""; // remove used identifier-value pair
513  }
514  else // -key value
515  {
516  *option_it = ""; // remove used identifier
517  ++option_it;
518  if (option_it == end_of_options_it) // should not happen
519  throw too_few_arguments("Missing value for option " + prepend_dash(id));
520  input_value = *option_it;
521  *option_it = ""; // remove value
522  }
523 
524  auto res = parse_option_value(value, input_value);
525  throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
526 
527  return true;
528  }
529  return false;
530  }
531 
549  template <typename option_type, typename id_type>
550  bool get_option_by_id(option_type & value, id_type const & id)
551  {
552  auto it = find_option_id(argv.begin(), end_of_options_it, id);
553 
554  if (it != end_of_options_it)
555  identify_and_retrieve_option_value(value, it, id);
556 
557  if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
558  throw option_declared_multiple_times("Option " + prepend_dash(id) +
559  " is no list/container but declared multiple times.");
560 
561  return (it != end_of_options_it); // first search was successful or not
562  }
563 
575  template <sequence_container option_type, typename id_type>
577  requires (!std::is_same_v<option_type, std::string>)
579  bool get_option_by_id(option_type & value, id_type const & id)
580  {
581  auto it = find_option_id(argv.begin(), end_of_options_it, id);
582  bool seen_at_least_once{it != end_of_options_it};
583 
584  if (seen_at_least_once)
585  value.clear();
586 
587  while (it != end_of_options_it)
588  {
589  identify_and_retrieve_option_value(value, it, id);
590  it = find_option_id(it, end_of_options_it, id);
591  }
592 
593  return seen_at_least_once;
594  }
595 
609  void check_for_unknown_ids()
610  {
611  for (auto it = argv.begin(); it != end_of_options_it; ++it)
612  {
613  std::string arg{*it};
614  if (!arg.empty() && arg[0] == '-') // may be an identifier
615  {
616  if (arg == "-")
617  {
618  continue; // positional option
619  }
620  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
621  {
622  throw unknown_option("Unknown flags " + expand_multiple_flags(arg) +
623  ". In case this is meant to be a non-option/argument/parameter, " +
624  "please specify the start of arguments with '--'. " +
625  "See -h/--help for program information.");
626  }
627  else // unknown short or long option
628  {
629  throw unknown_option("Unknown option " + arg +
630  ". In case this is meant to be a non-option/argument/parameter, " +
631  "please specify the start of non-options with '--'. " +
632  "See -h/--help for program information.");
633  }
634  }
635  }
636  }
637 
649  void check_for_left_over_args()
650  {
651  if (std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");}) != argv.end())
652  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
653  }
654 
675  template <typename option_type, typename validator_type>
676  void get_option(option_type & value,
677  char const short_id,
678  std::string const & long_id,
679  option_spec const spec,
680  validator_type && validator)
681  {
682  bool short_id_is_set{get_option_by_id(value, short_id)};
683  bool long_id_is_set{get_option_by_id(value, long_id)};
684 
685  // if value is no container we need to check for multiple declarations
686  if (short_id_is_set && long_id_is_set &&
687  !(sequence_container<option_type> && !std::is_same_v<option_type, std::string>))
688  throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id) +
689  " is no list/container but specified multiple times");
690 
691  if (short_id_is_set || long_id_is_set)
692  {
693  try
694  {
695  validator(value);
696  }
697  catch (std::exception & ex)
698  {
699  throw validation_error(std::string("Validation failed for option ") +
700  combine_option_names(short_id, long_id) + ": " + ex.what());
701  }
702  }
703  else // option is not set
704  {
705  // check if option is required
706  if (spec & option_spec::required)
707  throw required_option_missing("Option " + combine_option_names(short_id, long_id) +
708  " is required but not set.");
709  }
710  }
711 
719  void get_flag(bool & value,
720  char const short_id,
721  std::string const & long_id)
722  {
723  value = flag_is_set(short_id) || flag_is_set(long_id);
724  }
725 
748  template <typename option_type, typename validator_type>
749  void get_positional_option(option_type & value,
750  validator_type && validator)
751  {
752  ++positional_option_count;
753  auto it = std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");});
754 
755  if (it == argv.end())
756  throw too_few_arguments("Not enough positional arguments provided (Need at least " +
757  std::to_string(positional_option_calls.size()) +
758  "). See -h/--help for more information.");
759 
760  if constexpr (sequence_container<option_type> && !std::is_same_v<option_type, std::string>) // vector/list will be filled with all remaining arguments
761  {
762  assert(positional_option_count == positional_option_calls.size()); // checked on set up.
763 
764  value.clear();
765 
766  while (it != argv.end())
767  {
768  auto res = parse_option_value(value, *it);
769  std::string id = "positional option" + std::to_string(positional_option_count);
770  throw_on_input_error<option_type>(res, id, *it);
771 
772  *it = ""; // remove arg from argv
773  it = std::find_if(it, argv.end(), [](std::string const & s){return (s != "");});
774  ++positional_option_count;
775  }
776  }
777  else
778  {
779  auto res = parse_option_value(value, *it);
780  std::string id = "positional option" + std::to_string(positional_option_count);
781  throw_on_input_error<option_type>(res, id, *it);
782 
783  *it = ""; // remove arg from argv
784  }
785 
786  try
787  {
788  validator(value);
789  }
790  catch (std::exception & ex)
791  {
792  throw validation_error("Validation failed for positional option " +
793  std::to_string(positional_option_count) + ": " + ex.what());
794  }
795  }
796 
798  std::vector<std::function<void()>> option_calls;
800  std::vector<std::function<void()>> flag_calls;
802  std::vector<std::function<void()>> positional_option_calls;
804  unsigned positional_option_count{0};
806  int argc;
810  std::vector<std::string>::iterator end_of_options_it;
811 };
812 
813 } // namespace seqan3
The <concepts> header from C++20's standard library.
T empty(T... args)
T find(T... args)
Provides the format_base struct containing all helper functions that are needed in all formats.
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:249
@ required
Definition: auxiliary.hpp:251
typename remove_cvref< t >::type remove_cvref_t
Return the input type with const, volatile and references removed (transformation_trait shortcut).
Definition: type_traits:73
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:65
constexpr size_t size
The size of a type pack.
Definition: traits.hpp:151
A type that satisfies std::is_arithmetic_v<t>.
Concept for input streams.
A more refined container concept than seqan3::container.
The concept for option validators passed to add_option/positional_option.
SeqAn specific customisations in the standard namespace.
#define SEQAN3_RETURN_TYPE_CONSTRAINT(expression, concept_name,...)
Same as writing {expression} -> concept_name<type1[, ...]> in a concept definition.
Definition: platform.hpp:57
Provides character predicates for tokenisation.
T size(T... args)
The <charconv> header from C++17's standard library.
T substr(T... args)
T to_string(T... args)
Provides traits to inspect some information of a type, for example its name.
T what(T... args)