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