Sharg 1.1.2-rc.1
The argument parser for bio-c++ tools.
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 <sharg/std/charconv>
13
14#include <sharg/concept.hpp>
16
17namespace sharg::detail
18{
19
47{
48public:
52 format_parse() = delete;
53 format_parse(format_parse const & pf) = default;
54 format_parse & operator=(format_parse const & pf) = default;
55 format_parse(format_parse &&) = default;
57 ~format_parse() = default;
58
62 format_parse(std::vector<std::string> cmd_arguments) : arguments{std::move(cmd_arguments)}
63 {}
65
69 template <typename option_type, typename validator_t>
70 void add_option(option_type & value, config<validator_t> const & config)
71 {
73 [this, &value, config]()
74 {
75 get_option(value, config);
76 });
77 }
78
82 template <typename validator_t>
83 void add_flag(bool & value, config<validator_t> const & config)
84 {
86 [this, &value, config]()
87 {
89 });
90 }
91
95 template <typename option_type, typename validator_t>
96 void add_positional_option(option_type & value, config<validator_t> const & config)
97 {
99 [this, &value, config]()
100 {
102 });
103 }
104
106 void parse(parser_meta_data const & /*meta*/)
107 {
109
110 // parse options first, because we need to rule out -keyValue pairs
111 // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
112 for (auto && f : option_calls)
113 f();
114
115 for (auto && f : flag_calls)
116 f();
117
119
121 *end_of_options_it = ""; // remove -- before parsing positional arguments
122
123 for (auto && f : positional_option_calls)
124 f();
125
127 }
128
129 // functions are not needed for command line parsing but are part of the format help interface.
131 void add_section(std::string const &, bool const)
132 {}
133 void add_subsection(std::string const &, bool const)
134 {}
135 void add_line(std::string const &, bool, bool const)
136 {}
137 void add_list_item(std::string const &, std::string const &, bool const)
138 {}
140
142 template <typename id_type>
143 static bool is_empty_id(id_type const & id)
144 {
146 return id.empty();
147 else // char
148 return id == '\0';
149 }
150
172 template <typename iterator_type, typename id_type>
173 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
174 {
175 if (is_empty_id(id))
176 return end_it;
177
178 return (std::find_if(begin_it,
179 end_it,
180 [&](std::string const & current_arg)
181 {
182 std::string full_id = prepend_dash(id);
183
184 if constexpr (std::same_as<id_type, char>) // short id
185 {
186 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
187 // "-ovalue", "-o=value", and "-o value".
188 return current_arg.substr(0, full_id.size()) == full_id;
189 }
190 else
191 {
192 // only "--opt Value" or "--opt=Value" are valid
193 return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
194 (current_arg.size() == full_id.size()
195 || current_arg[full_id.size()] == '='); // space or `=`
196 }
197 }));
198 }
199
200private:
203 {
204 success,
205 error,
207 };
208
213 static std::string prepend_dash(std::string const & long_id)
214 {
215 return {"--" + long_id};
216 }
217
222 static std::string prepend_dash(char const short_id)
223 {
224 return {'-', short_id};
225 }
226
232 std::string combine_option_names(char const short_id, std::string const & long_id)
233 {
234 if (short_id == '\0')
235 return prepend_dash(long_id);
236 else if (long_id.empty())
237 return prepend_dash(short_id);
238 else // both are set (note: both cannot be empty, this is caught before)
239 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
240 }
241
245 bool flag_is_set(std::string const & long_id)
246 {
248
249 if (it != end_of_options_it)
250 *it = ""; // remove seen flag
251
252 return (it != end_of_options_it);
253 }
254
258 bool flag_is_set(char const short_id)
259 {
260 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
261 for (std::string & arg : arguments)
262 {
263 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
264 {
265 auto pos = arg.find(short_id);
266
267 if (pos != std::string::npos)
268 {
269 arg.erase(pos, 1); // remove seen bool
270
271 if (arg == "-") // if flag is empty now
272 arg = "";
273
274 return true;
275 }
276 }
277 }
278 return false;
279 }
280
288 template <typename option_t>
289 requires istreamable<option_t>
291 {
292 std::istringstream stream{in};
293 stream >> value;
294
295 if (stream.fail() || !stream.eof())
297
299 }
300
308 template <named_enumeration option_t>
310 {
311 auto map = sharg::enumeration_names<option_t>;
312
313 if (auto it = map.find(in); it == map.end())
314 {
315 std::string keys = [&map]()
316 {
317 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
318
319 std::sort(key_value_pairs.begin(),
320 key_value_pairs.end(),
321 [](auto pair1, auto pair2)
322 {
323 if constexpr (std::totally_ordered<option_t>)
324 {
325 if (pair1.second != pair2.second)
326 return pair1.second < pair2.second;
327 }
328
329 return pair1.first < pair2.first;
330 }); // needed for deterministic output when using unordered maps
331
332 std::string result{'['};
333 for (auto const & [key, value] : key_value_pairs)
334 result += std::string{key.data()} + ", ";
335 result.replace(result.size() - 2, 2, "]"); // replace last ", " by "]"
336 return result;
337 }();
338
339 throw user_input_error{"You have chosen an invalid input value: " + in + ". Please use one of: " + keys};
340 }
341 else
342 {
343 value = it->second;
344 }
345
347 }
348
350 option_parse_result parse_option_value(std::string & value, std::string const & in)
351 {
352 value = in;
353 return option_parse_result::success;
354 }
356
368 // clang-format off
369 template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
370 requires requires (format_parse_t fp,
371 typename container_option_t::value_type & container_value,
372 std::string const & in)
373 {
374 {fp.parse_option_value(container_value, in)} -> std::same_as<option_parse_result>;
375 }
376 // clang-format on
377 option_parse_result parse_option_value(container_option_t & value, std::string const & in)
378 {
379 typename container_option_t::value_type tmp{};
380
381 auto res = parse_option_value(tmp, in);
382
383 if (res == option_parse_result::success)
384 value.push_back(tmp);
385
386 return res;
387 }
388
401 template <typename option_t>
404 {
405 auto res = std::from_chars(&in[0], &in[in.size()], value);
406
407 if (res.ec == std::errc::result_out_of_range)
408 return option_parse_result::overflow_error;
409 else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
410 return option_parse_result::error;
411
412 return option_parse_result::success;
413 }
414
426 {
427 if (in == "0")
428 value = false;
429 else if (in == "1")
430 value = true;
431 else if (in == "true")
432 value = true;
433 else if (in == "false")
434 value = false;
435 else
436 return option_parse_result::error;
437
438 return option_parse_result::success;
439 }
440
448 template <typename option_type>
450 std::string const & option_name,
451 std::string const & input_value)
452 {
453 std::string msg{"Value parse failed for " + option_name + ": "};
454
455 if (res == option_parse_result::error)
456 {
457 throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
458 + get_type_name_as_string(option_type{}) + "."};
459 }
460
462 {
463 if (res == option_parse_result::overflow_error)
464 {
465 throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
468 }
469 }
470
471 assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
472 }
473
491 template <typename option_type, typename id_type>
492 bool identify_and_retrieve_option_value(option_type & value,
494 id_type const & id)
495 {
496 if (option_it != end_of_options_it)
497 {
498 std::string input_value;
499 size_t id_size = (prepend_dash(id)).size();
500
501 if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
502 {
503 if ((*option_it)[id_size] == '=') // -key=value
504 {
505 if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
506 throw too_few_arguments("Missing value for option " + prepend_dash(id));
507 input_value = (*option_it).substr(id_size + 1);
508 }
509 else // -kevValue
510 {
511 input_value = (*option_it).substr(id_size);
512 }
513
514 *option_it = ""; // remove used identifier-value pair
515 }
516 else // -key value
517 {
518 *option_it = ""; // remove used identifier
519 ++option_it;
520 if (option_it == end_of_options_it) // should not happen
521 throw too_few_arguments("Missing value for option " + prepend_dash(id));
522 input_value = *option_it;
523 *option_it = ""; // remove value
524 }
525
526 auto res = parse_option_value(value, input_value);
527 throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
528
529 return true;
530 }
531 return false;
532 }
533
551 template <typename option_type, typename id_type>
552 bool get_option_by_id(option_type & value, id_type const & id)
553 {
554 auto it = find_option_id(arguments.begin(), end_of_options_it, id);
555
556 if (it != end_of_options_it)
557 identify_and_retrieve_option_value(value, it, id);
558
559 if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
560 throw option_declared_multiple_times("Option " + prepend_dash(id)
561 + " is no list/container but declared multiple times.");
562
563 return (it != end_of_options_it); // first search was successful or not
564 }
565
577 template <detail::is_container_option option_type, typename id_type>
578 bool get_option_by_id(option_type & value, id_type const & id)
579 {
580 auto it = find_option_id(arguments.begin(), end_of_options_it, id);
581 bool seen_at_least_once{it != end_of_options_it};
582
583 if (seen_at_least_once)
584 value.clear();
585
586 while (it != end_of_options_it)
587 {
588 identify_and_retrieve_option_value(value, it, id);
589 it = find_option_id(it, end_of_options_it, id);
590 }
591
592 return seen_at_least_once;
593 }
594
609 {
610 for (auto it = arguments.begin(); it != end_of_options_it; ++it)
611 {
612 std::string arg{*it};
613 if (!arg.empty() && arg[0] == '-') // may be an identifier
614 {
615 if (arg == "-")
616 {
617 continue; // positional option
618 }
619 else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
620 {
621 throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
622 + ". In case this is meant to be a non-option/argument/parameter, "
623 + "please specify the start of arguments with '--'. "
624 + "See -h/--help for program information.");
625 }
626 else // unknown short or long option
627 {
628 throw unknown_option("Unknown option " + arg
629 + ". In case this is meant to be a non-option/argument/parameter, "
630 + "please specify the start of non-options with '--'. "
631 + "See -h/--help for program information.");
632 }
633 }
634 }
635 }
636
649 {
650 if (std::find_if(arguments.begin(),
651 arguments.end(),
652 [](std::string const & s)
653 {
654 return (s != "");
655 })
656 != arguments.end())
657 throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
658 }
659
677 template <typename option_type, typename validator_t>
678 void get_option(option_type & value, config<validator_t> const & config)
679 {
680 bool short_id_is_set{get_option_by_id(value, config.short_id)};
681 bool long_id_is_set{get_option_by_id(value, config.long_id)};
682
683 // if value is no container we need to check for multiple declarations
684 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
685 throw option_declared_multiple_times("Option " + combine_option_names(config.short_id, config.long_id)
686 + " is no list/container but specified multiple times");
687
688 if (short_id_is_set || long_id_is_set)
689 {
690 try
691 {
692 config.validator(value);
693 }
694 catch (std::exception & ex)
695 {
696 throw validation_error(std::string("Validation failed for option ")
697 + combine_option_names(config.short_id, config.long_id) + ": " + ex.what());
698 }
699 }
700 else // option is not set
701 {
702 // check if option is required
703 if (config.required)
704 throw required_option_missing("Option " + combine_option_names(config.short_id, config.long_id)
705 + " is required but not set.");
706 }
707 }
708
716 void get_flag(bool & value, char const short_id, std::string const & long_id)
717 {
718 // `|| value` is needed to keep the value if it was set before.
719 // It must be last because `flag_is_set` removes the flag from the arguments.
720 value = flag_is_set(short_id) || flag_is_set(long_id) || value;
721 }
722
745 template <typename option_type, typename validator_type>
746 void get_positional_option(option_type & value, validator_type && validator)
747 {
748 ++positional_option_count;
749 auto it = std::find_if(arguments.begin(),
750 arguments.end(),
751 [](std::string const & s)
752 {
753 return (s != "");
754 });
755
756 if (it == arguments.end())
757 throw too_few_arguments("Not enough positional arguments provided (Need at least "
758 + std::to_string(positional_option_calls.size())
759 + "). See -h/--help for more information.");
760
761 if constexpr (detail::is_container_option<
762 option_type>) // vector/list will be filled with all remaining arguments
763 {
764 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
765
766 value.clear();
767
768 while (it != arguments.end())
769 {
770 auto res = parse_option_value(value, *it);
771 std::string id = "positional option" + std::to_string(positional_option_count);
772 throw_on_input_error<option_type>(res, id, *it);
773
774 *it = ""; // remove arg from arguments
775 it = std::find_if(it,
776 arguments.end(),
777 [](std::string const & s)
778 {
779 return (s != "");
780 });
781 ++positional_option_count;
782 }
783 }
784 else
785 {
786 auto res = parse_option_value(value, *it);
787 std::string id = "positional option" + std::to_string(positional_option_count);
788 throw_on_input_error<option_type>(res, id, *it);
789
790 *it = ""; // remove arg from arguments
791 }
792
793 try
794 {
795 validator(value);
796 }
797 catch (std::exception & ex)
798 {
799 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
800 + ": " + ex.what());
801 }
802 }
803
811 unsigned positional_option_count{0};
816};
817
818} // namespace sharg::detail
T begin(T... args)
The <charconv> header from C++17's standard library.
The format that contains all helper functions needed in all formats.
Definition format_base.hpp:31
The format that organizes the actual parsing of command line arguments.
Definition format_parse.hpp:47
std::vector< std::function< void()> > positional_option_calls
Stores get_positional_option calls to be evaluated when calling format_parse::parse().
Definition format_parse.hpp:809
format_parse()=delete
Deleted.
void throw_on_input_error(option_parse_result const res, std::string const &option_name, std::string const &input_value)
Tries to parse an input string into boolean value.
Definition format_parse.hpp:449
format_parse(std::vector< std::string > cmd_arguments)
The constructor of the parse format.
Definition format_parse.hpp:62
void check_for_unknown_ids()
Checks format_parse::arguments for unknown options/flags.
Definition format_parse.hpp:608
void parse(parser_meta_data const &)
Initiates the actual command line parsing.
Definition format_parse.hpp:106
static std::string prepend_dash(char const short_id)
Appends a double dash to a short identifier and returns it.
Definition format_parse.hpp:222
std::vector< std::string >::iterator end_of_options_it
Artificial end of arguments if -- was seen.
Definition format_parse.hpp:815
bool get_option_by_id(option_type &value, id_type const &id)
Handles value retrieval (non container type) options.
Definition format_parse.hpp:552
void add_positional_option(option_type &value, config< validator_t > const &config)
Adds a get_positional_option call to be evaluated later on.
Definition format_parse.hpp:96
bool flag_is_set(char const short_id)
Returns true and removes the short identifier if it is in format_parse::arguments.
Definition format_parse.hpp:258
void add_option(option_type &value, config< validator_t > const &config)
Adds an sharg::detail::get_option call to be evaluated later on.
Definition format_parse.hpp:70
std::vector< std::function< void()> > flag_calls
Stores get_flag calls to be evaluated when calling format_parse::parse().
Definition format_parse.hpp:807
void add_flag(bool &value, config< validator_t > const &config)
Adds a get_flag call to be evaluated later on.
Definition format_parse.hpp:83
void get_flag(bool &value, char const short_id, std::string const &long_id)
Handles command line flags, whether they are set or not.
Definition format_parse.hpp:716
std::vector< std::string > arguments
Vector of command line arguments.
Definition format_parse.hpp:813
format_parse(format_parse &&)=default
Defaulted.
format_parse(format_parse const &pf)=default
Defaulted.
option_parse_result
Describes the result of parsing the user input string given the respective option value type.
Definition format_parse.hpp:203
@ overflow_error
Parsing was successful but the arithmetic value would cause an overflow.
@ success
Parsing of user input was successful.
@ error
There was some error while trying to parse the user input.
option_parse_result parse_option_value(option_t &value, std::string const &in)
Sets an option value depending on the keys found in sharg::enumeration_names<option_t>.
Definition format_parse.hpp:309
static bool is_empty_id(id_type const &id)
Checks whether id is empty.
Definition format_parse.hpp:143
option_parse_result parse_option_value(bool &value, std::string const &in)
Tries to parse an input string into a boolean value.
Definition format_parse.hpp:425
~format_parse()=default
Defaulted.
bool flag_is_set(std::string const &long_id)
Returns true and removes the long identifier if it is in format_parse::arguments.
Definition format_parse.hpp:245
void get_option(option_type &value, config< validator_t > const &config)
Handles command line option retrieval.
Definition format_parse.hpp:678
option_parse_result parse_option_value(option_t &value, std::string const &in)
Tries to parse an input string into a value using the stream operator>>.
Definition format_parse.hpp:290
void get_positional_option(option_type &value, validator_type &&validator)
Handles command line positional option retrieval.
Definition format_parse.hpp:746
bool identify_and_retrieve_option_value(option_type &value, std::vector< std::string >::iterator &option_it, id_type const &id)
Handles value retrieval for options based on different key-value pairs.
Definition format_parse.hpp:492
static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const &id)
Finds the position of a short/long identifier in format_parse::arguments.
Definition format_parse.hpp:173
format_parse & operator=(format_parse &&)=default
Defaulted.
std::string combine_option_names(char const short_id, std::string const &long_id)
Returns "-[short_id]/--[long_id]" if both are non-empty or just one of them if the other is empty.
Definition format_parse.hpp:232
option_parse_result parse_option_value(container_option_t &value, std::string const &in)
Parses the given option value and appends it to the target container.
Definition format_parse.hpp:377
void check_for_left_over_args()
Checks format_parse::arguments for unknown options/flags.
Definition format_parse.hpp:648
format_parse & operator=(format_parse const &pf)=default
Defaulted.
static std::string prepend_dash(std::string const &long_id)
Appends a double dash to a long identifier and returns it.
Definition format_parse.hpp:213
std::vector< std::function< void()> > option_calls
Stores get_option calls to be evaluated when calling format_parse::parse().
Definition format_parse.hpp:805
option_parse_result parse_option_value(option_t &value, std::string const &in)
Tries to parse an input string into an arithmetic value.
Definition format_parse.hpp:403
Parser exception thrown when a non-list option is declared multiple times.
Definition exceptions.hpp:140
Parser exception thrown when a required option is missing.
Definition exceptions.hpp:120
Parser exception thrown when too few arguments are provided.
Definition exceptions.hpp:100
Parser exception thrown when too many arguments are provided.
Definition exceptions.hpp:80
Parser exception thrown when encountering unknown option.
Definition exceptions.hpp:60
Parser exception thrown when an incorrect argument is given as (positional) option value.
Definition exceptions.hpp:160
Parser exception thrown when an argument could not be casted to the according type.
Definition exceptions.hpp:180
Provides helper concepts.
Whether the option type is considered to be a container.
Definition detail/concept.hpp:38
Concept for types that can be parsed from a std::istream via the stream operator.
Definition concept.hpp:37
The concept for option validators passed to add_option/positional_option.
Definition validators.hpp:49
T data(T... args)
T empty(T... args)
T end(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)
T is_same_v
T push_back(T... args)
T size(T... args)
T sort(T... args)
Option struct that is passed to the sharg::parser::add_option() function.
Definition config.hpp:43
std::string long_id
The long identifier for the option (e.g. "age", making the option callable via --age).
Definition config.hpp:62
bool required
Whether the option is required.
Definition config.hpp:129
validator_t validator
A sharg::validator that verifies the value after parsing (callable).
Definition config.hpp:135
char short_id
The short identifier for the option (e.g. 'a', making the option callable via -a).
Definition config.hpp:53
Stores all parser related meta information of the sharg::parser.
Definition auxiliary.hpp:45
T substr(T... args)
T to_string(T... args)
T what(T... args)
Hide me