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// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2006-2023, Knut Reinert & Freie Universität Berlin
3// Copyright (c) 2016-2023, 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
69 format_parse(int const, std::vector<std::string> argv_) : argv{std::move(argv_)}
70 {}
72
76 template <typename option_type, typename validator_type>
77 void add_option(option_type & value,
78 char const short_id,
79 std::string const & long_id,
80 std::string const & SEQAN3_DOXYGEN_ONLY(desc),
81 option_spec const spec,
82 validator_type && option_validator)
83 {
84 option_calls.push_back(
85 [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(
101 [this, &value, short_id, long_id]()
102 {
103 get_flag(value, short_id, long_id);
104 });
105 }
106
110 template <typename option_type, typename validator_type>
111 void add_positional_option(option_type & value,
112 std::string const & SEQAN3_DOXYGEN_ONLY(desc),
113 validator_type && option_validator)
114 {
115 positional_option_calls.push_back(
116 [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 {}
150 void add_subsection(std::string const &, option_spec const)
151 {}
152 void add_line(std::string const &, bool, option_spec const)
153 {}
154 void add_list_item(std::string const &, std::string const &, option_spec const)
155 {}
157
159 template <typename id_type>
160 static bool is_empty_id(id_type const & id)
161 {
162 if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
163 return id.empty();
164 else // char
165 return is_char<'\0'>(id);
166 }
167
189 template <typename iterator_type, typename id_type>
190 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
191 {
192 if (is_empty_id(id))
193 return end_it;
194
195 return (std::find_if(begin_it,
196 end_it,
197 [&](std::string const & current_arg)
198 {
199 std::string full_id = prepend_dash(id);
200
201 if constexpr (std::same_as<id_type, char>) // short id
202 {
203 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
204 // "-ovalue", "-o=value", and "-o value".
205 return current_arg.substr(0, full_id.size()) == full_id;
206 }
207 else
208 {
209 // only "--opt Value" or "--opt=Value" are valid
210 return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
211 (current_arg.size() == full_id.size()
212 || current_arg[full_id.size()] == '='); // space or `=`
213 }
214 }));
215 }
216
217private:
219 enum class option_parse_result
220 {
221 success,
222 error,
223 overflow_error
224 };
225
230 static std::string prepend_dash(std::string const & long_id)
231 {
232 return {"--" + long_id};
233 }
234
239 static std::string prepend_dash(char const short_id)
240 {
241 return {'-', short_id};
242 }
243
249 std::string combine_option_names(char const short_id, std::string const & long_id)
250 {
251 if (short_id == '\0')
252 return prepend_dash(long_id);
253 else if (long_id.empty())
254 return prepend_dash(short_id);
255 else // both are set (note: both cannot be empty, this is caught before)
256 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
257 }
258
262 bool flag_is_set(std::string const & long_id)
263 {
264 auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
265
266 if (it != end_of_options_it)
267 *it = ""; // remove seen flag
268
269 return (it != end_of_options_it);
270 }
271
275 bool flag_is_set(char const short_id)
276 {
277 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
278 for (std::string & arg : argv)
279 {
280 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
281 {
282 auto pos = arg.find(short_id);
283
284 if (pos != std::string::npos)
285 {
286 arg.erase(pos, 1); // remove seen bool
287
288 if (arg == "-") // if flag is empty now
289 arg = "";
290
291 return true;
292 }
293 }
294 }
295 return false;
296 }
297
305 template <typename option_t>
307 option_parse_result parse_option_value(option_t & value, std::string const & in)
308 {
309 std::istringstream stream{in};
310 stream >> value;
311
312 if (stream.fail() || !stream.eof())
313 return option_parse_result::error;
314
315 return option_parse_result::success;
316 }
317
325 template <named_enumeration option_t>
326 option_parse_result parse_option_value(option_t & value, std::string const & in)
327 {
328 auto map = seqan3::enumeration_names<option_t>;
329
330 if (auto it = map.find(in); it == map.end())
331 {
332 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
333 std::ranges::sort(key_value_pairs,
334 [](auto pair1, auto pair2)
335 {
336 if constexpr (std::totally_ordered<option_t>)
337 {
338 if (pair1.second != pair2.second)
339 return pair1.second < pair2.second;
340 }
341 return pair1.first < pair2.first;
342 });
343
344 throw user_input_error{detail::to_string("You have chosen an invalid input value: ",
345 in,
346 ". Please use one of: ",
347 key_value_pairs | std::views::keys)};
348 }
349 else
350 {
351 value = it->second;
352 }
353
354 return option_parse_result::success;
355 }
356
358 option_parse_result parse_option_value(std::string & value, std::string const & in)
359 {
360 value = in;
361 return option_parse_result::success;
362 }
364
376 template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
377 requires requires (format_parse_t fp,
378 typename container_option_t::value_type & container_value,
379 std::string const & in) {
380 {
381 fp.parse_option_value(container_value, in)
382 } -> std::same_as<option_parse_result>;
383 }
384 option_parse_result parse_option_value(container_option_t & value, std::string const & in)
385 {
386 typename container_option_t::value_type tmp{};
387
388 auto res = parse_option_value(tmp, in);
389
390 if (res == option_parse_result::success)
391 value.push_back(tmp);
392
393 return res;
394 }
395
408 template <arithmetic option_t>
410 option_parse_result parse_option_value(option_t & value, std::string const & in)
411 {
412 auto res = std::from_chars(&in[0], &in[in.size()], value);
413
414 if (res.ec == std::errc::result_out_of_range)
415 return option_parse_result::overflow_error;
416 else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
417 return option_parse_result::error;
418
419 return option_parse_result::success;
420 }
421
432 option_parse_result parse_option_value(bool & value, std::string const & in)
433 {
434 if (in == "0")
435 value = false;
436 else if (in == "1")
437 value = true;
438 else if (in == "true")
439 value = true;
440 else if (in == "false")
441 value = false;
442 else
443 return option_parse_result::error;
444
445 return option_parse_result::success;
446 }
447
455 template <typename option_type>
456 void throw_on_input_error(option_parse_result const res,
457 std::string const & option_name,
458 std::string const & input_value)
459 {
460 std::string msg{"Value parse failed for " + option_name + ": "};
461
462 if (res == option_parse_result::error)
463 {
464 throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
465 + get_type_name_as_string(option_type{}) + "."};
466 }
467
468 if constexpr (arithmetic<option_type>)
469 {
470 if (res == option_parse_result::overflow_error)
471 {
472 throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
475 }
476 }
477
478 assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
479 }
480
498 template <typename option_type, typename id_type>
499 bool identify_and_retrieve_option_value(option_type & value,
501 id_type const & id)
502 {
503 if (option_it != end_of_options_it)
504 {
505 std::string input_value;
506 size_t id_size = (prepend_dash(id)).size();
507
508 if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
509 {
510 if ((*option_it)[id_size] == '=') // -key=value
511 {
512 if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
513 throw too_few_arguments("Missing value for option " + prepend_dash(id));
514 input_value = (*option_it).substr(id_size + 1);
515 }
516 else // -kevValue
517 {
518 input_value = (*option_it).substr(id_size);
519 }
520
521 *option_it = ""; // remove used identifier-value pair
522 }
523 else // -key value
524 {
525 *option_it = ""; // remove used identifier
526 ++option_it;
527 if (option_it == end_of_options_it) // should not happen
528 throw too_few_arguments("Missing value for option " + prepend_dash(id));
529 input_value = *option_it;
530 *option_it = ""; // remove value
531 }
532
533 auto res = parse_option_value(value, input_value);
534 throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
535
536 return true;
537 }
538 return false;
539 }
540
558 template <typename option_type, typename id_type>
559 bool get_option_by_id(option_type & value, id_type const & id)
560 {
561 auto it = find_option_id(argv.begin(), end_of_options_it, id);
562
563 if (it != end_of_options_it)
564 identify_and_retrieve_option_value(value, it, id);
565
566 if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
567 throw option_declared_multiple_times("Option " + prepend_dash(id)
568 + " is no list/container but declared multiple times.");
569
570 return (it != end_of_options_it); // first search was successful or not
571 }
572
584 template <detail::is_container_option option_type, typename id_type>
585 bool get_option_by_id(option_type & value, id_type const & id)
586 {
587 auto it = find_option_id(argv.begin(), end_of_options_it, id);
588 bool seen_at_least_once{it != end_of_options_it};
589
590 if (seen_at_least_once)
591 value.clear();
592
593 while (it != end_of_options_it)
594 {
595 identify_and_retrieve_option_value(value, it, id);
596 it = find_option_id(it, end_of_options_it, id);
597 }
598
599 return seen_at_least_once;
600 }
601
615 void check_for_unknown_ids()
616 {
617 for (auto it = argv.begin(); it != end_of_options_it; ++it)
618 {
619 std::string arg{*it};
620 if (!arg.empty() && arg[0] == '-') // may be an identifier
621 {
622 if (arg == "-")
623 {
624 continue; // positional option
625 }
626 else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
627 {
628 throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
629 + ". In case this is meant to be a non-option/argument/parameter, "
630 + "please specify the start of arguments with '--'. "
631 + "See -h/--help for program information.");
632 }
633 else // unknown short or long option
634 {
635 throw unknown_option("Unknown option " + arg
636 + ". In case this is meant to be a non-option/argument/parameter, "
637 + "please specify the start of non-options with '--'. "
638 + "See -h/--help for program information.");
639 }
640 }
641 }
642 }
643
655 void check_for_left_over_args()
656 {
657 if (std::find_if(argv.begin(),
658 argv.end(),
659 [](std::string const & s)
660 {
661 return (s != "");
662 })
663 != argv.end())
664 throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
665 }
666
687 template <typename option_type, typename validator_type>
688 void get_option(option_type & value,
689 char const short_id,
690 std::string const & long_id,
691 option_spec const spec,
692 validator_type && validator)
693 {
694 bool short_id_is_set{get_option_by_id(value, short_id)};
695 bool long_id_is_set{get_option_by_id(value, long_id)};
696
697 // if value is no container we need to check for multiple declarations
698 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
699 throw option_declared_multiple_times("Option " + combine_option_names(short_id, long_id)
700 + " is no list/container but specified multiple times");
701
702 if (short_id_is_set || long_id_is_set)
703 {
704 try
705 {
706 validator(value);
707 }
708 catch (std::exception & ex)
709 {
710 throw validation_error(std::string("Validation failed for option ")
711 + combine_option_names(short_id, long_id) + ": " + ex.what());
712 }
713 }
714 else // option is not set
715 {
716 // check if option is required
717 if (spec & option_spec::required)
718 throw required_option_missing("Option " + combine_option_names(short_id, long_id)
719 + " is required but not set.");
720 }
721 }
722
730 void get_flag(bool & value, char const short_id, std::string const & long_id)
731 {
732 value = flag_is_set(short_id) || flag_is_set(long_id);
733 }
734
757 template <typename option_type, typename validator_type>
758 void get_positional_option(option_type & value, validator_type && validator)
759 {
760 ++positional_option_count;
761 auto it = std::find_if(argv.begin(),
762 argv.end(),
763 [](std::string const & s)
764 {
765 return (s != "");
766 });
767
768 if (it == argv.end())
769 throw too_few_arguments("Not enough positional arguments provided (Need at least "
770 + std::to_string(positional_option_calls.size())
771 + "). See -h/--help for more information.");
772
773 if constexpr (detail::is_container_option<
774 option_type>) // vector/list will be filled with all remaining arguments
775 {
776 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
777
778 value.clear();
779
780 while (it != argv.end())
781 {
782 auto res = parse_option_value(value, *it);
783 std::string id = "positional option" + std::to_string(positional_option_count);
784 throw_on_input_error<option_type>(res, id, *it);
785
786 *it = ""; // remove arg from argv
787 it = std::find_if(it,
788 argv.end(),
789 [](std::string const & s)
790 {
791 return (s != "");
792 });
793 ++positional_option_count;
794 }
795 }
796 else
797 {
798 auto res = parse_option_value(value, *it);
799 std::string id = "positional option" + std::to_string(positional_option_count);
800 throw_on_input_error<option_type>(res, id, *it);
801
802 *it = ""; // remove arg from argv
803 }
804
805 try
806 {
807 validator(value);
808 }
809 catch (std::exception & ex)
810 {
811 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
812 + ": " + ex.what());
813 }
814 }
815
817 std::vector<std::function<void()>> option_calls;
819 std::vector<std::function<void()>> flag_calls;
821 std::vector<std::function<void()>> positional_option_calls;
823 unsigned positional_option_count{0};
827 std::vector<std::string>::iterator end_of_options_it;
828};
829
830} // 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.
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 type_pack/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.
SeqAn specific customisations in the standard namespace.
Provides character predicates for tokenisation.
T size(T... args)
T substr(T... args)
T to_string(T... args)
T what(T... args)