SeqAn3
format_parse.hpp
Go to the documentation of this file.
1 // ============================================================================
2 // SeqAn - The Library for Sequence Analysis
3 // ============================================================================
4 //
5 // Copyright (c) 2006-2018, Knut Reinert & Freie Universitaet Berlin
6 // Copyright (c) 2016-2018, Knut Reinert & MPI Molekulare Genetik
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are met:
11 //
12 // * Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // * Redistributions in binary form must reproduce the above copyright
15 // notice, this list of conditions and the following disclaimer in the
16 // documentation and/or other materials provided with the distribution.
17 // * Neither the name of Knut Reinert or the FU Berlin nor the names of
18 // its contributors may be used to endorse or promote products derived
19 // from this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
25 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31 // DAMAGE.
32 //
33 // ============================================================================
34 
40 #pragma once
41 
42 #include <sstream>
43 #include <string>
44 #include <vector>
45 
47 #include <seqan3/std/concepts>
48 
49 namespace seqan3::detail
50 {
51 
76 class format_parse : public format_base
77 {
78 public:
83  format_parse() = delete;
84  format_parse(format_parse const & pf) = default;
85  format_parse & operator=(format_parse const & pf) = default;
86  format_parse(format_parse &&) = default;
87  format_parse & operator=(format_parse &&) = default;
88 
93  format_parse(int const argc_, char const * const * const argv_) :
94  argc(argc_ - 1)
95  {
96  init(argc_, argv_);
97  }
98 
99  ~format_parse() = default;
101 
115  template <typename option_type, typename validator_type>
116  void add_option(option_type & value,
117  char const short_id,
118  std::string const & long_id,
119  std::string const & /*desc*/,
120  option_spec const & spec,
121  validator_type && validator)
122  {
123  option_calls.push_back([=, &value]()
124  {
125  get_option(value, short_id, long_id, spec, validator);
126  });
127  }
128 
137  void add_flag(bool & value,
138  char const short_id,
139  std::string const & long_id,
140  std::string const & /*desc*/,
141  option_spec const & /*spec*/)
142  {
143  flag_calls.push_back([=, &value]()
144  {
145  get_flag(value, short_id, long_id);
146  });
147  }
148 
159  template <typename option_type, typename validator_type>
160  void add_positional_option(option_type & value,
161  std::string const & /*desc*/,
162  validator_type && validator)
163  {
164  positional_option_calls.push_back([=, &value]()
165  {
166  get_positional_option(value, validator);
167  });
168  }
169 
171  void parse(argument_parser_meta_data const & /*meta*/)
172  {
173  end_of_options_it = std::find(argv.begin(), argv.end(), "--");
174 
175  // parse options first, because we need to rule out -keyValue pairs
176  // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
177  for (auto && f : option_calls)
178  f();
179 
180  for (auto && f : flag_calls)
181  f();
182 
183  check_for_unknown_ids();
184 
185  if (end_of_options_it != argv.end())
186  *end_of_options_it = ""; // remove -- before parsing positional arguments
187 
188  for (auto && f : positional_option_calls)
189  f();
190 
191  check_for_left_over_args();
192  }
193 
194  // functions are not needed for command line parsing but are part of the format interface.
196  void add_section(std::string const &) {}
197  void add_subsection(std::string const &) {}
198  void add_line(std::string const &, bool) {}
199  void add_list_item(std::string const &, std::string const &) {}
201 
202 private:
212  void init(int argc_, char const * const * const argv_)
213  {
214  argv.resize(argc_ - 1); // -1 because of the binary name
215 
216  for(int i = 1; i < argc_; ++i) // start at 1 to skip binary name
217  argv.push_back(argv_[i]);
218  }
219 
224  std::string prepend_dash(std::string const & long_id)
225  {
226  return ("--" + long_id);
227  }
228 
233  std::string prepend_dash(char const short_id)
234  {
235  return ("-" + std::string(1, short_id));
236  }
237 
250  template <typename id_type>
251  std::vector<std::string>::iterator find_option_id(std::vector<std::string>::iterator const begin_it, id_type const & id)
252  {
253  return(std::find_if(begin_it, end_of_options_it,
254  [&] (const std::string & v)
255  {
256  size_t id_size{(prepend_dash(id)).size()};
257  if (v.size() < id_size)
258  return false; // cannot be the correct identifier
259 
260  return v.substr(0, id_size) == prepend_dash(id); // check if prefix of v is the same
261  }));
262  }
263 
267  bool flag_is_set(std::string const & long_id)
268  {
269  auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
270 
271  if (it != end_of_options_it)
272  *it = ""; // remove seen flag
273 
274  return(it != end_of_options_it);
275  }
276 
280  bool flag_is_set(char const short_id)
281  {
282  // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
283  for (std::string & arg : argv)
284  {
285  if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
286  {
287  auto pos = arg.find(short_id);
288 
289  if (pos != std::string::npos)
290  {
291  arg.erase(pos, 1); // remove seen bool
292 
293  if (arg == "-") // if flag is empty now
294  arg = "";
295 
296  return true;
297  }
298  }
299  }
300  return false;
301  }
302 
312  template <typename option_t>
314  requires istream_concept<std::istringstream, option_t>
316  void retrieve_value(option_t & value, std::string const & in)
317  {
318  std::istringstream stream{in};
319  stream >> value;
320 
321  if (stream.fail() || !stream.eof())
322  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
323  get_type_name_as_string(value) + ".");
324  }
325 
327  void retrieve_value(std::string & value, std::string const & in)
328  {
329  value = in;
330  }
332 
341  template <sequence_container_concept container_option_t>
343  requires istream_concept<std::istringstream, typename container_option_t::value_type>
345  void retrieve_value(container_option_t & value, std::string const & in)
346  {
347  typename container_option_t::value_type tmp;
348 
349  retrieve_value(tmp, in); // throws on failure
350  value.push_back(tmp);
351  }
352 
372  template <std::Integral option_t>
374  requires istream_concept<std::istringstream, option_t>
376  void retrieve_value(option_t & value, std::string const & in)
377  {
378  int64_t tmp;
379  std::istringstream stream{in};
380  stream >> tmp;
381 
382  if (stream.fail() || !stream.eof()) // !stream.eof() catches 13a as wrong
383  throw type_conversion_failed("Argument " + in + " could not be casted to type " +
384  get_type_name_as_string(value) + ".");
385 
386  if (tmp > static_cast<int64_t>(std::numeric_limits<option_t>::max()) ||
387  tmp < static_cast<int64_t>(std::numeric_limits<option_t>::min()))
388  throw overflow_error_on_conversion("Argument " + in + " is not in integer range [" +
389  std::to_string(std::numeric_limits<option_t>::min()) + "," +
390  std::to_string(std::numeric_limits<option_t>::max()) + "].");
391 
392  value = static_cast<option_t>(tmp);
393  }
394 
411  template <typename option_type, typename id_type>
412  bool identify_and_retrieve_option_value(option_type & value,
413  std::vector<std::string>::iterator & option_it,
414  id_type const & id)
415  {
416  if (option_it != end_of_options_it)
417  {
418  std::string input_value;
419  size_t id_size = (prepend_dash(id)).size();
420 
421  if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
422  {
423  if ((*option_it)[id_size] == '=') // -key=value
424  {
425  if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
426  throw parser_invalid_argument("Value cast failed for option " +
427  prepend_dash(id) +
428  ": No value was provided.");
429  input_value = (*option_it).substr(id_size + 1);
430  }
431  else // -kevValue
432  {
433  input_value = (*option_it).substr(id_size);
434  }
435 
436  *option_it = ""; // remove used identifier-value pair
437  }
438  else // -key value
439  {
440  *option_it = ""; // remove used identifier
441  ++option_it;
442  if (option_it == end_of_options_it) // should not happen
443  throw parser_invalid_argument("Value cast failed for option " +
444  prepend_dash(id) +
445  ": No value was provided.");
446  input_value = *option_it;
447  *option_it = ""; // remove value
448  }
449 
450  try
451  {
452  retrieve_value(value, input_value);
453  }
454  catch (parser_invalid_argument const & ex)
455  {
456  throw parser_invalid_argument("Value cast failed for option " + prepend_dash(id) + ": " + ex.what());
457  }
458 
459  return true;
460  }
461  return false;
462  }
463 
481  template <typename option_type, typename id_type>
482  bool get_option_by_id(option_type & value, id_type const & id)
483  {
484  auto it = find_option_id(argv.begin(), id);
485 
486  if (it != end_of_options_it)
487  identify_and_retrieve_option_value(value, it, id);
488 
489  if (find_option_id(it, id) != end_of_options_it) // should not be found again
490  throw option_declared_multiple_times("Option " + prepend_dash(id) +
491  "is no list/container but declared multiple times.");
492 
493  return (it != end_of_options_it); // first search was successful or not
494  }
495 
507  template <sequence_container_concept option_type, typename id_type>
509  requires !std::is_same_v<option_type, std::string>
511  bool get_option_by_id(option_type & value, id_type const & id)
512  {
513  auto it = find_option_id(argv.begin(), id);
514  bool seen_at_least_once{it != end_of_options_it};
515 
516  while (it != end_of_options_it)
517  {
518  identify_and_retrieve_option_value(value, it, id);
519  it = find_option_id(it, id);
520  }
521 
522  return seen_at_least_once;
523  }
524 
538  void check_for_unknown_ids()
539  {
540  for (auto it = argv.begin(); it != end_of_options_it; ++it)
541  {
542  std::string arg{*it};
543  if (!arg.empty() && arg[0] == '-') // may be an identifier
544  {
545  if (arg == "-")
546  {
547  continue; // positional option
548  }
549  else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
550  {
551  throw unknown_option("Unknown flags " + expand_multiple_flags(arg) +
552  ". In case this is meant to be a non-option/argument/parameter, " +
553  "please specify the start of arguments with '--'. " +
554  "See -h/--help for program information.");
555  }
556  else // unknown short or long option
557  {
558  throw unknown_option("Unknown option " + arg +
559  ". In case this is meant to be a non-option/argument/parameter, " +
560  "please specify the start of non-options with '--'. " +
561  "See -h/--help for program information.");
562  }
563  }
564  }
565  }
566 
578  void check_for_left_over_args()
579  {
580  if (std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");}) != argv.end())
581  throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
582  }
583 
604  template <typename option_type, typename validator_type>
605  void get_option(option_type & value,
606  char const short_id,
607  std::string const & long_id,
608  option_spec const & spec,
609  validator_type && validator)
610  {
611  bool short_id_is_set{get_option_by_id(value, short_id)};
612  bool long_id_is_set{get_option_by_id(value, long_id)};
613 
614  // if value is no container we need to check for multiple declarations
615  if (short_id_is_set && long_id_is_set &&
616  !(sequence_container_concept<option_type> && !std::is_same_v<option_type, std::string>))
617  throw option_declared_multiple_times("Option " + prepend_dash(short_id) + "/" + prepend_dash(long_id) +
618  " is no list/container but specified multiple times");
619 
620  if (short_id_is_set || long_id_is_set)
621  {
622  try
623  {
624  validator(value);
625  }
626  catch (std::exception & ex)
627  {
628  throw validation_failed(std::string("Validation failed for option ") + prepend_dash(short_id) + "/" +
629  prepend_dash(long_id) + ": " + ex.what());
630  }
631  }
632  else // option is not set
633  {
634  // check if option is required
635  if (spec & option_spec::REQUIRED)
636  throw required_option_missing("Option " + prepend_dash(short_id) + "/" + prepend_dash(long_id) +
637  " is required but not set.");
638  }
639  }
640 
648  void get_flag(bool & value,
649  char const short_id,
650  std::string const & long_id)
651  {
652  value = flag_is_set(short_id) || flag_is_set(long_id);
653  }
654 
680  template <typename option_type, typename validator_type>
681  void get_positional_option(option_type & value,
682  validator_type && validator)
683  {
684  ++positional_option_count;
685  auto it = std::find_if(argv.begin(), argv.end(), [](std::string const & s){return (s != "");});
686 
687  if (it == argv.end())
688  throw too_few_arguments("Not enough positional arguments provided (Need at least " +
689  std::to_string(positional_option_calls.size()) + "). See -h/--help for more information.");
690 
691  if (sequence_container_concept<option_type> && !std::is_same_v<option_type, std::string>) // vector/list will be filled with all remaining arguments
692  {
693  if (positional_option_count != (positional_option_calls.size()))
694  throw parser_design_error("Lists are only allowed as the last positional option!");
695 
696  while (it != argv.end())
697  {
698  try
699  {
700  retrieve_value(value, *it);
701  }
702  catch (parser_invalid_argument const & ex)
703  {
704  throw parser_invalid_argument("Value cast failed for positional option " +
705  std::to_string(positional_option_count) + ": " + ex.what());
706  }
707 
708  *it = ""; // remove arg from argv
709  it = std::find_if(it, argv.end(), [](std::string const & s){return (s != "");});
710  ++positional_option_count;
711  }
712  }
713  else
714  {
715  try
716  {
717  retrieve_value(value, *it);
718  }
719  catch (parser_invalid_argument const & ex)
720  {
721  throw parser_invalid_argument("Value cast failed for positional option " +
722  std::to_string(positional_option_count) + ": " + ex.what());
723  }
724 
725  *it = ""; // remove arg from argv
726  }
727 
728  try
729  {
730  validator(value);
731  }
732  catch (std::exception & ex)
733  {
734  throw validation_failed("Validation failed for positional option " +
735  std::to_string(positional_option_count) + ": " + ex.what());
736  }
737  }
738 
740  std::vector<std::function<void()>> option_calls;
742  std::vector<std::function<void()>> flag_calls;
744  std::vector<std::function<void()>> positional_option_calls;
746  unsigned positional_option_count{0};
748  std::vector<std::string> argv;
750  int argc;
752  std::vector<std::string>::iterator end_of_options_it;
753 };
754 
755 } // namespace seqan3
Contains the format_base struct containing all helper functions that are needed in all formats...
::ranges::size size
Alias for ranges::size. Obtains the size of a range whose size can be calculated in constant time...
Definition: ranges:195
The Concepts library.
Definition: aligned_sequence_concept.hpp:288
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:60
Definition: auxiliary.hpp:63