SeqAn3  3.0.2
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 
22 #include <seqan3/std/concepts>
23 #include <seqan3/std/charconv>
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 
162 private:
164  enum class option_parse_result
165  {
166  success,
167  error,
168  overflow_error
169  };
170 
175  std::string prepend_dash(std::string const & long_id)
176  {
177  return ("--" + long_id);
178  }
179 
184  std::string prepend_dash(char const short_id)
185  {
186  return ("-" + std::string(1, short_id));
187  }
188 
194  std::string combine_option_names(char const short_id, std::string const & long_id)
195  {
196  if (short_id == '\0')
197  return prepend_dash(long_id);
198  else if (long_id.empty())
199  return prepend_dash(short_id);
200  else // both are set (note: both cannot be empty, this is caught before)
201  return prepend_dash(short_id) + "/" + prepend_dash(long_id);
202  }
203 
225  template <typename id_type>
226  std::vector<std::string>::iterator find_option_id(std::vector<std::string>::iterator const begin_it, id_type const & id)
227  {
228  if (is_empty_id(id))
229  return end_of_options_it;
230 
231  return (std::find_if(begin_it, end_of_options_it,
232  [&] (std::string const & current_arg)
233  {
234  std::string full_id = prepend_dash(id);
235 
236  if constexpr (std::same_as<id_type, char>) // short id
237  {
238  // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
239  // "-ovalue", "-o=value", and "-o value".
240  return current_arg.substr(0, full_id.size()) == full_id;
241  }
242  else
243  {
244  // only "--opt Value" or "--opt=Value" are valid
245  return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
246  (current_arg.size() == full_id.size() || current_arg[full_id.size()] == '='); // space or `=`
247  }
248  }));
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_view const in)
321  {
322  auto map = seqan3::enumeration_names<option_t>;
323 
324  if (auto it = map.find(in); it == map.end())
325  return option_parse_result::error;
326  else
327  value = it->second;
328 
329  return option_parse_result::success;
330  }
331 
333  option_parse_result parse_option_value(std::string & value, std::string const & in)
334  {
335  value = in;
336  return option_parse_result::success;
337  }
339 
348  template <sequence_container container_option_t>
352  option_parse_result parse_option_value(container_option_t & value, std::string const & in)
353  {
354  typename container_option_t::value_type tmp{};
355 
356  auto res = parse_option_value(tmp, in);
357 
358  if (res == option_parse_result::success)
359  value.push_back(tmp);
360 
361  return res;
362  }
363 
376  template <arithmetic option_t>
380  option_parse_result parse_option_value(option_t & value, std::string const & in)
381  {
382  auto res = std::from_chars(&in[0], &in[in.size()], value);
383 
384  if (res.ec == std::errc::result_out_of_range)
385  return option_parse_result::overflow_error;
386  else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
387  return option_parse_result::error;
388 
389  return option_parse_result::success;
390  }
391 
402  option_parse_result parse_option_value(bool & value, std::string const & in)
403  {
404  if (in == "0")
405  value = false;
406  else if (in == "1")
407  value = true;
408  else if (in == "true")
409  value = true;
410  else if (in == "false")
411  value = false;
412  else
413  return option_parse_result::error;
414 
415  return option_parse_result::success;
416  }
417 
425  template <typename option_type>
426  void throw_on_input_error(option_parse_result const res,
427  std::string const & option_name,
428  std::string const & input_value)
429  {
430  std::string msg{"Value parse failed for " + option_name + ": "};
431 
432  if (res == option_parse_result::error)
433  {
434  throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type " +
435  get_type_name_as_string(input_value) + "."};
436  }
437 
438  if constexpr (arithmetic<option_type>)
439  {
440  if (res == option_parse_result::overflow_error)
441  {
442  throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range [" +
445  }
446  }
447 
448  assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
449  }
450 
468  template <typename option_type, typename id_type>
469  bool identify_and_retrieve_option_value(option_type & value,
471  id_type const & id)
472  {
473  if (option_it != end_of_options_it)
474  {
475  std::string input_value;
476  size_t id_size = (prepend_dash(id)).size();
477 
478  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
479  {
480  if ((*option_it)[id_size] == '=') // -key=value
481  {
482  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
483  throw too_few_arguments("Missing value for option " + prepend_dash(id));
484  input_value = (*option_it).substr(id_size + 1);
485  }
486  else // -kevValue
487  {
488  input_value = (*option_it).substr(id_size);
489  }
490 
491  *option_it = ""; // remove used identifier-value pair
492  }
493  else // -key value
494  {
495  *option_it = ""; // remove used identifier
496  ++option_it;
497  if (option_it == end_of_options_it) // should not happen
498  throw too_few_arguments("Missing value for option " + prepend_dash(id));
499  input_value = *option_it;
500  *option_it = ""; // remove value
501  }
502 
503  auto res = parse_option_value(value, input_value);
504  throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
505 
506  return true;
507  }
508  return false;
509  }
510 
528  template <typename option_type, typename id_type>
529  bool get_option_by_id(option_type & value, id_type const & id)
530  {
531  auto it = find_option_id(argv.begin(), id);
532 
533  if (it != end_of_options_it)
534  identify_and_retrieve_option_value(value, it, id);
535 
536  if (find_option_id(it, id) != end_of_options_it) // should not be found again
537  throw option_declared_multiple_times("Option " + prepend_dash(id) +
538  " is no list/container but declared multiple times.");
539 
540  return (it != end_of_options_it); // first search was successful or not
541  }
542 
554  template <sequence_container option_type, typename id_type>
556  requires (!std::is_same_v<option_type, std::string>)
558  bool get_option_by_id(option_type & value, id_type const & id)
559  {
560  auto it = find_option_id(argv.begin(), id);
561  bool seen_at_least_once{it != end_of_options_it};
562 
563  while (it != end_of_options_it)
564  {
565  identify_and_retrieve_option_value(value, it, id);
566  it = find_option_id(it, id);
567  }
568 
569  return seen_at_least_once;
570  }
571 
585  void check_for_unknown_ids()
586  {
587  for (auto it = argv.begin(); it != end_of_options_it; ++it)
588  {
589  std::string arg{*it};
590  if (!arg.empty() && arg[0] == '-') // may be an identifier
591  {
592  if (arg == "-")
593  {
594  continue; // positional option
595  }
596  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
597  {
598  throw unknown_option("Unknown flags " + expand_multiple_flags(arg) +
599  ". In case this is meant to be a non-option/argument/parameter, " +
600  "please specify the start of arguments with '--'. " +
601  "See -h/--help for program information.");
602  }
603  else // unknown short or long option
604  {
605  throw unknown_option("Unknown option " + arg +
606  ". In case this is meant to be a non-option/argument/parameter, " +
607  "please specify the start of non-options with '--'. " +
608  "See -h/--help for program information.");
609  }
610  }
611  }
612  }
613 
625  void check_for_left_over_args()
626  {
627  if (std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");}) != argv.end())
628  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
629  }
630 
651  template <typename option_type, typename validator_type>
652  void get_option(option_type & value,
653  char const short_id,
654  std::string const & long_id,
655  option_spec const spec,
656  validator_type && validator)
657  {
658  bool short_id_is_set{get_option_by_id(value, short_id)};
659  bool long_id_is_set{get_option_by_id(value, long_id)};
660 
661  // if value is no container we need to check for multiple declarations
662  if (short_id_is_set && long_id_is_set &&
663  !(sequence_container<option_type> && !std::is_same_v<option_type, std::string>))
664  throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id) +
665  " is no list/container but specified multiple times");
666 
667  if (short_id_is_set || long_id_is_set)
668  {
669  try
670  {
671  validator(value);
672  }
673  catch (std::exception & ex)
674  {
675  throw validation_error(std::string("Validation failed for option ") +
676  combine_option_names(short_id, long_id) + ": " + ex.what());
677  }
678  }
679  else // option is not set
680  {
681  // check if option is required
682  if (spec & option_spec::REQUIRED)
683  throw required_option_missing("Option " + combine_option_names(short_id, long_id) +
684  " is required but not set.");
685  }
686  }
687 
695  void get_flag(bool & value,
696  char const short_id,
697  std::string const & long_id)
698  {
699  value = flag_is_set(short_id) || flag_is_set(long_id);
700  }
701 
724  template <typename option_type, typename validator_type>
725  void get_positional_option(option_type & value,
726  validator_type && validator)
727  {
728  ++positional_option_count;
729  auto it = std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");});
730 
731  if (it == argv.end())
732  throw too_few_arguments("Not enough positional arguments provided (Need at least " +
733  std::to_string(positional_option_calls.size()) +
734  "). See -h/--help for more information.");
735 
736  if (sequence_container<option_type> && !std::is_same_v<option_type, std::string>) // vector/list will be filled with all remaining arguments
737  {
738  assert(positional_option_count == positional_option_calls.size()); // checked on set up.
739 
740  while (it != argv.end())
741  {
742  auto res = parse_option_value(value, *it);
743  std::string id = "positional option" + std::to_string(positional_option_count);
744  throw_on_input_error<option_type>(res, id, *it);
745 
746  *it = ""; // remove arg from argv
747  it = std::find_if(it, argv.end(), [](std::string const & s){return (s != "");});
748  ++positional_option_count;
749  }
750  }
751  else
752  {
753  auto res = parse_option_value(value, *it);
754  std::string id = "positional option" + std::to_string(positional_option_count);
755  throw_on_input_error<option_type>(res, id, *it);
756 
757  *it = ""; // remove arg from argv
758  }
759 
760  try
761  {
762  validator(value);
763  }
764  catch (std::exception & ex)
765  {
766  throw validation_error("Validation failed for positional option " +
767  std::to_string(positional_option_count) + ": " + ex.what());
768  }
769  }
770 
772  std::vector<std::function<void()>> option_calls;
774  std::vector<std::function<void()>> flag_calls;
776  std::vector<std::function<void()>> positional_option_calls;
778  unsigned positional_option_count{0};
780  int argc;
784  std::vector<std::string>::iterator end_of_options_it;
785 };
786 
787 } // namespace seqan3
sstream
seqan3::option_spec
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:233
std::string
sequence_container
A more refined container concept than seqan3::container.
std::exception
charconv
Provides std::from_chars and std::to_chars if not defined in the stl <charconv> header.
std::string_view
vector
std::find
T find(T... args)
std::string::size
T size(T... args)
std::istringstream
std::function
concepts
The Concepts library.
seqan3::views::move
auto const move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:68
seqan3::REQUIRED
@ REQUIRED
Definition: auxiliary.hpp:235
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.
predicate.hpp
Provides character predicates for tokenisation.
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)
std::remove_cvref_t
arithmetic
A type that satisfies std::is_arithmetic_v<t>.
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