SeqAn3 3.4.0-rc.1
The Modern C++ library for sequence analysis.
Loading...
Searching...
No Matches
format_parse.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2024 Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2024 Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <concepts>
13#include <seqan3/std/charconv>
14#include <sstream>
15#include <string>
16#include <vector>
17
20
21namespace seqan3::detail
22{
23
50class format_parse : public format_base
51{
52public:
56 format_parse() = delete;
57 format_parse(format_parse const & pf) = default;
58 format_parse & operator=(format_parse const & pf) = default;
59 format_parse(format_parse &&) = default;
60 format_parse & operator=(format_parse &&) = default;
61 ~format_parse() = default;
62
66 format_parse(int const, std::vector<std::string> argv_) : argv{std::move(argv_)}
67 {}
69
73 template <typename option_type, typename validator_type>
74 void add_option(option_type & value,
75 char const short_id,
76 std::string const & long_id,
77 std::string const & SEQAN3_DOXYGEN_ONLY(desc),
78 option_spec const spec,
79 validator_type && option_validator)
80 {
81 option_calls.push_back(
82 [this, &value, short_id, long_id, spec, option_validator]()
83 {
84 get_option(value, short_id, long_id, spec, option_validator);
85 });
86 }
87
91 void add_flag(bool & value,
92 char const short_id,
93 std::string const & long_id,
94 std::string const & SEQAN3_DOXYGEN_ONLY(desc),
95 option_spec const & SEQAN3_DOXYGEN_ONLY(spec))
96 {
97 flag_calls.push_back(
98 [this, &value, short_id, long_id]()
99 {
100 get_flag(value, short_id, long_id);
101 });
102 }
103
107 template <typename option_type, typename validator_type>
108 void add_positional_option(option_type & value,
109 std::string const & SEQAN3_DOXYGEN_ONLY(desc),
110 validator_type && option_validator)
111 {
112 positional_option_calls.push_back(
113 [this, &value, option_validator]()
114 {
115 get_positional_option(value, option_validator);
116 });
117 }
118
120 void parse(argument_parser_meta_data const & /*meta*/)
121 {
122 end_of_options_it = std::find(argv.begin(), argv.end(), "--");
123
124 // parse options first, because we need to rule out -keyValue pairs
125 // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
126 for (auto && f : option_calls)
127 f();
128
129 for (auto && f : flag_calls)
130 f();
131
132 check_for_unknown_ids();
133
134 if (end_of_options_it != argv.end())
135 *end_of_options_it = ""; // remove -- before parsing positional arguments
136
137 for (auto && f : positional_option_calls)
138 f();
139
140 check_for_left_over_args();
141 }
142
143 // functions are not needed for command line parsing but are part of the format interface.
145 void add_section(std::string const &, option_spec const)
146 {}
147 void add_subsection(std::string const &, option_spec const)
148 {}
149 void add_line(std::string const &, bool, option_spec const)
150 {}
151 void add_list_item(std::string const &, std::string const &, option_spec const)
152 {}
154
156 template <typename id_type>
157 static bool is_empty_id(id_type const & id)
158 {
159 if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
160 return id.empty();
161 else // char
162 return is_char<'\0'>(id);
163 }
164
186 template <typename iterator_type, typename id_type>
187 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
188 {
189 if (is_empty_id(id))
190 return end_it;
191
192 return (std::find_if(begin_it,
193 end_it,
194 [&](std::string const & current_arg)
195 {
196 std::string full_id = prepend_dash(id);
197
198 if constexpr (std::same_as<id_type, char>) // short id
199 {
200 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
201 // "-ovalue", "-o=value", and "-o value".
202 return current_arg.substr(0, full_id.size()) == full_id;
203 }
204 else
205 {
206 // only "--opt Value" or "--opt=Value" are valid
207 return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
208 (current_arg.size() == full_id.size()
209 || current_arg[full_id.size()] == '='); // space or `=`
210 }
211 }));
212 }
213
214private:
216 enum class option_parse_result
217 {
218 success,
219 error,
220 overflow_error
221 };
222
227 static std::string prepend_dash(std::string const & long_id)
228 {
229 return {"--" + long_id};
230 }
231
236 static std::string prepend_dash(char const short_id)
237 {
238 return {'-', short_id};
239 }
240
246 std::string combine_option_names(char const short_id, std::string const & long_id)
247 {
248 if (short_id == '\0')
249 return prepend_dash(long_id);
250 else if (long_id.empty())
251 return prepend_dash(short_id);
252 else // both are set (note: both cannot be empty, this is caught before)
253 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
254 }
255
259 bool flag_is_set(std::string const & long_id)
260 {
261 auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
262
263 if (it != end_of_options_it)
264 *it = ""; // remove seen flag
265
266 return (it != end_of_options_it);
267 }
268
272 bool flag_is_set(char const short_id)
273 {
274 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
275 for (std::string & arg : argv)
276 {
277 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
278 {
279 auto pos = arg.find(short_id);
280
281 if (pos != std::string::npos)
282 {
283 arg.erase(pos, 1); // remove seen bool
284
285 if (arg == "-") // if flag is empty now
286 arg = "";
287
288 return true;
289 }
290 }
291 }
292 return false;
293 }
294
302 template <typename option_t>
304 option_parse_result parse_option_value(option_t & value, std::string const & in)
305 {
306 std::istringstream stream{in};
307 stream >> value;
308
309 if (stream.fail() || !stream.eof())
310 return option_parse_result::error;
311
312 return option_parse_result::success;
313 }
314
322 template <named_enumeration option_t>
323 option_parse_result parse_option_value(option_t & value, std::string const & in)
324 {
325 auto map = seqan3::enumeration_names<option_t>;
326
327 if (auto it = map.find(in); it == map.end())
328 {
329 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
330 std::ranges::sort(key_value_pairs,
331 [](auto pair1, auto pair2)
332 {
333 if constexpr (std::totally_ordered<option_t>)
334 {
335 if (pair1.second != pair2.second)
336 return pair1.second < pair2.second;
337 }
338 return pair1.first < pair2.first;
339 });
340
341 throw user_input_error{detail::to_string("You have chosen an invalid input value: ",
342 in,
343 ". Please use one of: ",
344 key_value_pairs | std::views::keys)};
345 }
346 else
347 {
348 value = it->second;
349 }
350
351 return option_parse_result::success;
352 }
353
355 option_parse_result parse_option_value(std::string & value, std::string const & in)
356 {
357 value = in;
358 return option_parse_result::success;
359 }
361
373 template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
374 requires requires (format_parse_t fp,
375 typename container_option_t::value_type & container_value,
376 std::string const & in) {
377 {
378 fp.parse_option_value(container_value, in)
379 } -> std::same_as<option_parse_result>;
380 }
381 option_parse_result parse_option_value(container_option_t & value, std::string const & in)
382 {
383 typename container_option_t::value_type tmp{};
384
385 auto res = parse_option_value(tmp, in);
386
387 if (res == option_parse_result::success)
388 value.push_back(tmp);
389
390 return res;
391 }
392
405 template <arithmetic option_t>
407 option_parse_result parse_option_value(option_t & value, std::string const & in)
408 {
409 auto res = std::from_chars(&in[0], &in[in.size()], value);
410
411 if (res.ec == std::errc::result_out_of_range)
412 return option_parse_result::overflow_error;
413 else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
414 return option_parse_result::error;
415
416 return option_parse_result::success;
417 }
418
429 option_parse_result parse_option_value(bool & value, std::string const & in)
430 {
431 if (in == "0")
432 value = false;
433 else if (in == "1")
434 value = true;
435 else if (in == "true")
436 value = true;
437 else if (in == "false")
438 value = false;
439 else
440 return option_parse_result::error;
441
442 return option_parse_result::success;
443 }
444
452 template <typename option_type>
453 void throw_on_input_error(option_parse_result const res,
454 std::string const & option_name,
455 std::string const & input_value)
456 {
457 std::string msg{"Value parse failed for " + option_name + ": "};
458
459 if (res == option_parse_result::error)
460 {
461 throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
462 + get_type_name_as_string(option_type{}) + "."};
463 }
464
465 if constexpr (arithmetic<option_type>)
466 {
467 if (res == option_parse_result::overflow_error)
468 {
469 throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
472 }
473 }
474
475 assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
476 }
477
495 template <typename option_type, typename id_type>
496 bool identify_and_retrieve_option_value(option_type & value,
498 id_type const & id)
499 {
500 if (option_it != end_of_options_it)
501 {
502 std::string input_value;
503 size_t id_size = (prepend_dash(id)).size();
504
505 if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
506 {
507 if ((*option_it)[id_size] == '=') // -key=value
508 {
509 if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
510 throw too_few_arguments("Missing value for option " + prepend_dash(id));
511 input_value = (*option_it).substr(id_size + 1);
512 }
513 else // -kevValue
514 {
515 input_value = (*option_it).substr(id_size);
516 }
517
518 *option_it = ""; // remove used identifier-value pair
519 }
520 else // -key value
521 {
522 *option_it = ""; // remove used identifier
523 ++option_it;
524 if (option_it == end_of_options_it) // should not happen
525 throw too_few_arguments("Missing value for option " + prepend_dash(id));
526 input_value = *option_it;
527 *option_it = ""; // remove value
528 }
529
530 auto res = parse_option_value(value, input_value);
531 throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
532
533 return true;
534 }
535 return false;
536 }
537
555 template <typename option_type, typename id_type>
556 bool get_option_by_id(option_type & value, id_type const & id)
557 {
558 auto it = find_option_id(argv.begin(), end_of_options_it, id);
559
560 if (it != end_of_options_it)
561 identify_and_retrieve_option_value(value, it, id);
562
563 if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
564 throw option_declared_multiple_times("Option " + prepend_dash(id)
565 + " is no list/container but declared multiple times.");
566
567 return (it != end_of_options_it); // first search was successful or not
568 }
569
581 template <detail::is_container_option option_type, typename id_type>
582 bool get_option_by_id(option_type & value, id_type const & id)
583 {
584 auto it = find_option_id(argv.begin(), end_of_options_it, id);
585 bool seen_at_least_once{it != end_of_options_it};
586
587 if (seen_at_least_once)
588 value.clear();
589
590 while (it != end_of_options_it)
591 {
592 identify_and_retrieve_option_value(value, it, id);
593 it = find_option_id(it, end_of_options_it, id);
594 }
595
596 return seen_at_least_once;
597 }
598
612 void check_for_unknown_ids()
613 {
614 for (auto it = argv.begin(); it != end_of_options_it; ++it)
615 {
616 std::string arg{*it};
617 if (!arg.empty() && arg[0] == '-') // may be an identifier
618 {
619 if (arg == "-")
620 {
621 continue; // positional option
622 }
623 else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
624 {
625 throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
626 + ". In case this is meant to be a non-option/argument/parameter, "
627 + "please specify the start of arguments with '--'. "
628 + "See -h/--help for program information.");
629 }
630 else // unknown short or long option
631 {
632 throw unknown_option("Unknown option " + arg
633 + ". In case this is meant to be a non-option/argument/parameter, "
634 + "please specify the start of non-options with '--'. "
635 + "See -h/--help for program information.");
636 }
637 }
638 }
639 }
640
652 void check_for_left_over_args()
653 {
654 if (std::find_if(argv.begin(),
655 argv.end(),
656 [](std::string const & s)
657 {
658 return (s != "");
659 })
660 != argv.end())
661 throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
662 }
663
684 template <typename option_type, typename validator_type>
685 void get_option(option_type & value,
686 char const short_id,
687 std::string const & long_id,
688 option_spec const spec,
689 validator_type && validator)
690 {
691 bool short_id_is_set{get_option_by_id(value, short_id)};
692 bool long_id_is_set{get_option_by_id(value, long_id)};
693
694 // if value is no container we need to check for multiple declarations
695 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
696 throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id)
697 + " is no list/container but specified multiple times");
698
699 if (short_id_is_set || long_id_is_set)
700 {
701 try
702 {
703 validator(value);
704 }
705 catch (std::exception & ex)
706 {
707 throw validation_error(std::string("Validation failed for option ")
708 + combine_option_names(short_id, long_id) + ": " + ex.what());
709 }
710 }
711 else // option is not set
712 {
713 // check if option is required
714 if (spec & option_spec::required)
715 throw required_option_missing("Option " + combine_option_names(short_id, long_id)
716 + " is required but not set.");
717 }
718 }
719
727 void get_flag(bool & value, char const short_id, std::string const & long_id)
728 {
729 value = flag_is_set(short_id) || flag_is_set(long_id);
730 }
731
754 template <typename option_type, typename validator_type>
755 void get_positional_option(option_type & value, validator_type && validator)
756 {
757 ++positional_option_count;
758 auto it = std::find_if(argv.begin(),
759 argv.end(),
760 [](std::string const & s)
761 {
762 return (s != "");
763 });
764
765 if (it == argv.end())
766 throw too_few_arguments("Not enough positional arguments provided (Need at least "
767 + std::to_string(positional_option_calls.size())
768 + "). See -h/--help for more information.");
769
770 if constexpr (detail::is_container_option<
771 option_type>) // vector/list will be filled with all remaining arguments
772 {
773 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
774
775 value.clear();
776
777 while (it != argv.end())
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 it = std::find_if(it,
785 argv.end(),
786 [](std::string const & s)
787 {
788 return (s != "");
789 });
790 ++positional_option_count;
791 }
792 }
793 else
794 {
795 auto res = parse_option_value(value, *it);
796 std::string id = "positional option" + std::to_string(positional_option_count);
797 throw_on_input_error<option_type>(res, id, *it);
798
799 *it = ""; // remove arg from argv
800 }
801
802 try
803 {
804 validator(value);
805 }
806 catch (std::exception & ex)
807 {
808 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
809 + ": " + ex.what());
810 }
811 }
812
814 std::vector<std::function<void()>> option_calls;
816 std::vector<std::function<void()>> flag_calls;
818 std::vector<std::function<void()>> positional_option_calls;
820 unsigned positional_option_count{0};
824 std::vector<std::string>::iterator end_of_options_it;
825};
826
827} // 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:245
@ required
Definition auxiliary.hpp:247
constexpr size_t size
The size of a type pack.
Definition type_pack/traits.hpp:143
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)
Hide me