SeqAn3  3.2.0
The Modern C++ library for sequence analysis.
format_parse.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2022, 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 <concepts>
16 #include <seqan3/std/charconv>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20 
23 
24 namespace seqan3::detail
25 {
26 
53 class format_parse : public format_base
54 {
55 public:
59  format_parse() = delete;
60  format_parse(format_parse const & pf) = default;
61  format_parse & operator=(format_parse const & pf) = default;
62  format_parse(format_parse &&) = default;
63  format_parse & operator=(format_parse &&) = default;
64  ~format_parse() = default;
65 
70  format_parse(int const argc_, std::vector<std::string> argv_) : 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(
86  [this, &value, short_id, long_id, spec, option_validator]()
87  {
88  get_option(value, short_id, long_id, spec, option_validator);
89  });
90  }
91 
95  void add_flag(bool & value,
96  char const short_id,
97  std::string const & long_id,
98  std::string const & SEQAN3_DOXYGEN_ONLY(desc),
99  option_spec const & SEQAN3_DOXYGEN_ONLY(spec))
100  {
101  flag_calls.push_back(
102  [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(
117  [this, &value, option_validator]()
118  {
119  get_positional_option(value, option_validator);
120  });
121  }
122 
124  void parse(argument_parser_meta_data const & /*meta*/)
125  {
126  end_of_options_it = std::find(argv.begin(), argv.end(), "--");
127 
128  // parse options first, because we need to rule out -keyValue pairs
129  // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
130  for (auto && f : option_calls)
131  f();
132 
133  for (auto && f : flag_calls)
134  f();
135 
136  check_for_unknown_ids();
137 
138  if (end_of_options_it != argv.end())
139  *end_of_options_it = ""; // remove -- before parsing positional arguments
140 
141  for (auto && f : positional_option_calls)
142  f();
143 
144  check_for_left_over_args();
145  }
146 
147  // functions are not needed for command line parsing but are part of the format interface.
149  void add_section(std::string const &, option_spec const)
150  {}
151  void add_subsection(std::string const &, option_spec const)
152  {}
153  void add_line(std::string const &, bool, option_spec const)
154  {}
155  void add_list_item(std::string const &, std::string const &, option_spec const)
156  {}
158 
160  template <typename id_type>
161  static bool is_empty_id(id_type const & id)
162  {
163  if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
164  return id.empty();
165  else // char
166  return is_char<'\0'>(id);
167  }
168 
190  template <typename iterator_type, typename id_type>
191  static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
192  {
193  if (is_empty_id(id))
194  return end_it;
195 
196  return (std::find_if(begin_it,
197  end_it,
198  [&](std::string const & current_arg)
199  {
200  std::string full_id = prepend_dash(id);
201 
202  if constexpr (std::same_as<id_type, char>) // short id
203  {
204  // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
205  // "-ovalue", "-o=value", and "-o value".
206  return current_arg.substr(0, full_id.size()) == full_id;
207  }
208  else
209  {
210  // only "--opt Value" or "--opt=Value" are valid
211  return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
212  (current_arg.size() == full_id.size()
213  || current_arg[full_id.size()] == '='); // space or `=`
214  }
215  }));
216  }
217 
218 private:
220  enum class option_parse_result
221  {
222  success,
223  error,
224  overflow_error
225  };
226 
231  static std::string prepend_dash(std::string const & long_id)
232  {
233  return {"--" + long_id};
234  }
235 
240  static std::string prepend_dash(char const short_id)
241  {
242  return {'-', short_id};
243  }
244 
250  std::string combine_option_names(char const short_id, std::string const & long_id)
251  {
252  if (short_id == '\0')
253  return prepend_dash(long_id);
254  else if (long_id.empty())
255  return prepend_dash(short_id);
256  else // both are set (note: both cannot be empty, this is caught before)
257  return prepend_dash(short_id) + "/" + prepend_dash(long_id);
258  }
259 
263  bool flag_is_set(std::string const & long_id)
264  {
265  auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
266 
267  if (it != end_of_options_it)
268  *it = ""; // remove seen flag
269 
270  return (it != end_of_options_it);
271  }
272 
276  bool flag_is_set(char const short_id)
277  {
278  // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
279  for (std::string & arg : argv)
280  {
281  if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
282  {
283  auto pos = arg.find(short_id);
284 
285  if (pos != std::string::npos)
286  {
287  arg.erase(pos, 1); // remove seen bool
288 
289  if (arg == "-") // if flag is empty now
290  arg = "";
291 
292  return true;
293  }
294  }
295  }
296  return false;
297  }
298 
306  template <typename option_t>
308  option_parse_result parse_option_value(option_t & value, std::string const & in)
309  {
310  std::istringstream stream{in};
311  stream >> value;
312 
313  if (stream.fail() || !stream.eof())
314  return option_parse_result::error;
315 
316  return option_parse_result::success;
317  }
318 
326  template <named_enumeration option_t>
327  option_parse_result parse_option_value(option_t & value, std::string const & in)
328  {
329  auto map = seqan3::enumeration_names<option_t>;
330 
331  if (auto it = map.find(in); it == map.end())
332  {
333  std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
334  std::ranges::sort(key_value_pairs,
335  [](auto pair1, auto pair2)
336  {
337  if constexpr (std::totally_ordered<option_t>)
338  {
339  if (pair1.second != pair2.second)
340  return pair1.second < pair2.second;
341  }
342  return pair1.first < pair2.first;
343  });
344 
345  throw user_input_error{detail::to_string("You have chosen an invalid input value: ",
346  in,
347  ". Please use one of: ",
348  key_value_pairs | std::views::keys)};
349  }
350  else
351  {
352  value = it->second;
353  }
354 
355  return option_parse_result::success;
356  }
357 
359  option_parse_result parse_option_value(std::string & value, std::string const & in)
360  {
361  value = in;
362  return option_parse_result::success;
363  }
365 
377  template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
378  requires requires (format_parse_t fp,
379  typename container_option_t::value_type & container_value,
380  std::string const & in) {
381  {
382  fp.parse_option_value(container_value, in)
383  } -> std::same_as<option_parse_result>;
384  }
385  option_parse_result parse_option_value(container_option_t & value, std::string const & in)
386  {
387  typename container_option_t::value_type tmp{};
388 
389  auto res = parse_option_value(tmp, in);
390 
391  if (res == option_parse_result::success)
392  value.push_back(tmp);
393 
394  return res;
395  }
396 
409  template <arithmetic option_t>
411  option_parse_result parse_option_value(option_t & value, std::string const & in)
412  {
413  auto res = std::from_chars(&in[0], &in[in.size()], value);
414 
415  if (res.ec == std::errc::result_out_of_range)
416  return option_parse_result::overflow_error;
417  else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
418  return option_parse_result::error;
419 
420  return option_parse_result::success;
421  }
422 
433  option_parse_result parse_option_value(bool & value, std::string const & in)
434  {
435  if (in == "0")
436  value = false;
437  else if (in == "1")
438  value = true;
439  else if (in == "true")
440  value = true;
441  else if (in == "false")
442  value = false;
443  else
444  return option_parse_result::error;
445 
446  return option_parse_result::success;
447  }
448 
456  template <typename option_type>
457  void throw_on_input_error(option_parse_result const res,
458  std::string const & option_name,
459  std::string const & input_value)
460  {
461  std::string msg{"Value parse failed for " + option_name + ": "};
462 
463  if (res == option_parse_result::error)
464  {
465  throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
466  + get_type_name_as_string(option_type{}) + "."};
467  }
468 
469  if constexpr (arithmetic<option_type>)
470  {
471  if (res == option_parse_result::overflow_error)
472  {
473  throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
476  }
477  }
478 
479  assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
480  }
481 
499  template <typename option_type, typename id_type>
500  bool identify_and_retrieve_option_value(option_type & value,
502  id_type const & id)
503  {
504  if (option_it != end_of_options_it)
505  {
506  std::string input_value;
507  size_t id_size = (prepend_dash(id)).size();
508 
509  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
510  {
511  if ((*option_it)[id_size] == '=') // -key=value
512  {
513  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
514  throw too_few_arguments("Missing value for option " + prepend_dash(id));
515  input_value = (*option_it).substr(id_size + 1);
516  }
517  else // -kevValue
518  {
519  input_value = (*option_it).substr(id_size);
520  }
521 
522  *option_it = ""; // remove used identifier-value pair
523  }
524  else // -key value
525  {
526  *option_it = ""; // remove used identifier
527  ++option_it;
528  if (option_it == end_of_options_it) // should not happen
529  throw too_few_arguments("Missing value for option " + prepend_dash(id));
530  input_value = *option_it;
531  *option_it = ""; // remove value
532  }
533 
534  auto res = parse_option_value(value, input_value);
535  throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
536 
537  return true;
538  }
539  return false;
540  }
541 
559  template <typename option_type, typename id_type>
560  bool get_option_by_id(option_type & value, id_type const & id)
561  {
562  auto it = find_option_id(argv.begin(), end_of_options_it, id);
563 
564  if (it != end_of_options_it)
565  identify_and_retrieve_option_value(value, it, id);
566 
567  if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
568  throw option_declared_multiple_times("Option " + prepend_dash(id)
569  + " is no list/container but declared multiple times.");
570 
571  return (it != end_of_options_it); // first search was successful or not
572  }
573 
585  template <detail::is_container_option option_type, typename id_type>
586  bool get_option_by_id(option_type & value, id_type const & id)
587  {
588  auto it = find_option_id(argv.begin(), end_of_options_it, id);
589  bool seen_at_least_once{it != end_of_options_it};
590 
591  if (seen_at_least_once)
592  value.clear();
593 
594  while (it != end_of_options_it)
595  {
596  identify_and_retrieve_option_value(value, it, id);
597  it = find_option_id(it, end_of_options_it, id);
598  }
599 
600  return seen_at_least_once;
601  }
602 
616  void check_for_unknown_ids()
617  {
618  for (auto it = argv.begin(); it != end_of_options_it; ++it)
619  {
620  std::string arg{*it};
621  if (!arg.empty() && arg[0] == '-') // may be an identifier
622  {
623  if (arg == "-")
624  {
625  continue; // positional option
626  }
627  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
628  {
629  throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
630  + ". In case this is meant to be a non-option/argument/parameter, "
631  + "please specify the start of arguments with '--'. "
632  + "See -h/--help for program information.");
633  }
634  else // unknown short or long option
635  {
636  throw unknown_option("Unknown option " + arg
637  + ". In case this is meant to be a non-option/argument/parameter, "
638  + "please specify the start of non-options with '--'. "
639  + "See -h/--help for program information.");
640  }
641  }
642  }
643  }
644 
656  void check_for_left_over_args()
657  {
658  if (std::find_if(argv.begin(),
659  argv.end(),
660  [](std::string const & s)
661  {
662  return (s != "");
663  })
664  != argv.end())
665  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
666  }
667 
688  template <typename option_type, typename validator_type>
689  void get_option(option_type & value,
690  char const short_id,
691  std::string const & long_id,
692  option_spec const spec,
693  validator_type && validator)
694  {
695  bool short_id_is_set{get_option_by_id(value, short_id)};
696  bool long_id_is_set{get_option_by_id(value, long_id)};
697 
698  // if value is no container we need to check for multiple declarations
699  if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
700  throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id)
701  + " is no list/container but specified multiple times");
702 
703  if (short_id_is_set || long_id_is_set)
704  {
705  try
706  {
707  validator(value);
708  }
709  catch (std::exception & ex)
710  {
711  throw validation_error(std::string("Validation failed for option ")
712  + combine_option_names(short_id, long_id) + ": " + ex.what());
713  }
714  }
715  else // option is not set
716  {
717  // check if option is required
718  if (spec & option_spec::required)
719  throw required_option_missing("Option " + combine_option_names(short_id, long_id)
720  + " is required but not set.");
721  }
722  }
723 
731  void get_flag(bool & value, char const short_id, std::string const & long_id)
732  {
733  value = flag_is_set(short_id) || flag_is_set(long_id);
734  }
735 
758  template <typename option_type, typename validator_type>
759  void get_positional_option(option_type & value, validator_type && validator)
760  {
761  ++positional_option_count;
762  auto it = std::find_if(argv.begin(),
763  argv.end(),
764  [](std::string const & s)
765  {
766  return (s != "");
767  });
768 
769  if (it == argv.end())
770  throw too_few_arguments("Not enough positional arguments provided (Need at least "
771  + std::to_string(positional_option_calls.size())
772  + "). See -h/--help for more information.");
773 
774  if constexpr (detail::is_container_option<
775  option_type>) // vector/list will be filled with all remaining arguments
776  {
777  assert(positional_option_count == positional_option_calls.size()); // checked on set up.
778 
779  value.clear();
780 
781  while (it != argv.end())
782  {
783  auto res = parse_option_value(value, *it);
784  std::string id = "positional option" + std::to_string(positional_option_count);
785  throw_on_input_error<option_type>(res, id, *it);
786 
787  *it = ""; // remove arg from argv
788  it = std::find_if(it,
789  argv.end(),
790  [](std::string const & s)
791  {
792  return (s != "");
793  });
794  ++positional_option_count;
795  }
796  }
797  else
798  {
799  auto res = parse_option_value(value, *it);
800  std::string id = "positional option" + std::to_string(positional_option_count);
801  throw_on_input_error<option_type>(res, id, *it);
802 
803  *it = ""; // remove arg from argv
804  }
805 
806  try
807  {
808  validator(value);
809  }
810  catch (std::exception & ex)
811  {
812  throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
813  + ": " + ex.what());
814  }
815  }
816 
818  std::vector<std::function<void()>> option_calls;
820  std::vector<std::function<void()>> flag_calls;
822  std::vector<std::function<void()>> positional_option_calls;
824  unsigned positional_option_count{0};
826  int argc;
830  std::vector<std::string>::iterator end_of_options_it;
831 };
832 
833 } // namespace seqan3::detail
The <charconv> header from C++17's standard library.
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.
requires requires
The rank_type of the semi-alphabet; defined as the return type of seqan3::to_rank....
Definition: alphabet/concept.hpp:164
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:248
@ required
Definition: auxiliary.hpp:250
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition: predicate.hpp:63
constexpr size_t size
The size of a type pack.
Definition: type_pack/traits.hpp:146
A type that satisfies std::is_arithmetic_v<t>.
Concept for input streams.
The concept for option validators passed to add_option/positional_option.
SeqAn specific customisations in the standard namespace.
Provides character predicates for tokenisation.
T size(T... args)
T substr(T... args)
T to_string(T... args)
T what(T... args)