SeqAn3 3.2.0
The Modern C++ library for sequence analysis.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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
24namespace seqan3::detail
25{
26
53class format_parse : public format_base
54{
55public:
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
218private:
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.
T empty(T... args)
T find(T... args)
Provides the format_base struct containing all helper functions that are needed in all formats.
T from_chars(T... args)
option_spec
Used to further specify argument_parser options/flags.
Definition: auxiliary.hpp:248
@ required
Definition: auxiliary.hpp:250
constexpr size_t size
The size of a type pack.
Definition: 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.
T move(T... args)
SeqAn specific customisations in the standard namespace.
T parse(T... args)
Provides character predicates for tokenisation.
T size(T... args)
T sort(T... args)
T substr(T... args)
T to_string(T... args)
T what(T... args)