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