Sharg 1.1.1
The argument parser for bio-c++ tools.
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages Concepts
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/sharg-parser/blob/main/LICENSE.md
6// --------------------------------------------------------------------------------------------------------
7
13#pragma once
14
15#include <sharg/std/charconv>
16
17#include <sharg/concept.hpp>
19
20namespace sharg::detail
21{
22
49class format_parse : public format_base
50{
51public:
55 format_parse() = delete;
56 format_parse(format_parse const & pf) = default;
57 format_parse & operator=(format_parse const & pf) = default;
58 format_parse(format_parse &&) = default;
59 format_parse & operator=(format_parse &&) = default;
60 ~format_parse() = default;
61
66 format_parse(int const argc_, std::vector<std::string> argv_) : argv{std::move(argv_)}
67 {
68 (void)argc_;
69 }
71
75 template <typename option_type, typename validator_t>
76 void add_option(option_type & value, config<validator_t> const & config)
77 {
78 option_calls.push_back(
79 [this, &value, config]()
80 {
81 get_option(value, config);
82 });
83 }
84
88 template <typename validator_t>
89 void add_flag(bool & value, config<validator_t> const & config)
90 {
91 flag_calls.push_back(
92 [this, &value, config]()
93 {
94 get_flag(value, config.short_id, config.long_id);
95 });
96 }
97
101 template <typename option_type, typename validator_t>
102 void add_positional_option(option_type & value, config<validator_t> const & config)
103 {
104 positional_option_calls.push_back(
105 [this, &value, config]()
106 {
107 get_positional_option(value, config.validator);
108 });
109 }
110
112 void parse(parser_meta_data const & /*meta*/)
113 {
114 end_of_options_it = std::find(argv.begin(), argv.end(), "--");
115
116 // parse options first, because we need to rule out -keyValue pairs
117 // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
118 for (auto && f : option_calls)
119 f();
120
121 for (auto && f : flag_calls)
122 f();
123
124 check_for_unknown_ids();
125
126 if (end_of_options_it != argv.end())
127 *end_of_options_it = ""; // remove -- before parsing positional arguments
128
129 for (auto && f : positional_option_calls)
130 f();
131
132 check_for_left_over_args();
133 }
134
135 // functions are not needed for command line parsing but are part of the format help interface.
137 void add_section(std::string const &, bool const)
138 {}
139 void add_subsection(std::string const &, bool const)
140 {}
141 void add_line(std::string const &, bool, bool const)
142 {}
143 void add_list_item(std::string const &, std::string const &, bool const)
144 {}
146
148 template <typename id_type>
149 static bool is_empty_id(id_type const & id)
150 {
151 if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
152 return id.empty();
153 else // char
154 return id == '\0';
155 }
156
178 template <typename iterator_type, typename id_type>
179 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
180 {
181 if (is_empty_id(id))
182 return end_it;
183
184 return (std::find_if(begin_it,
185 end_it,
186 [&](std::string const & current_arg)
187 {
188 std::string full_id = prepend_dash(id);
189
190 if constexpr (std::same_as<id_type, char>) // short id
191 {
192 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
193 // "-ovalue", "-o=value", and "-o value".
194 return current_arg.substr(0, full_id.size()) == full_id;
195 }
196 else
197 {
198 // only "--opt Value" or "--opt=Value" are valid
199 return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
200 (current_arg.size() == full_id.size()
201 || current_arg[full_id.size()] == '='); // space or `=`
202 }
203 }));
204 }
205
206private:
208 enum class option_parse_result
209 {
210 success,
211 error,
212 overflow_error
213 };
214
219 static std::string prepend_dash(std::string const & long_id)
220 {
221 return {"--" + long_id};
222 }
223
228 static std::string prepend_dash(char const short_id)
229 {
230 return {'-', short_id};
231 }
232
238 std::string combine_option_names(char const short_id, std::string const & long_id)
239 {
240 if (short_id == '\0')
241 return prepend_dash(long_id);
242 else if (long_id.empty())
243 return prepend_dash(short_id);
244 else // both are set (note: both cannot be empty, this is caught before)
245 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
246 }
247
251 bool flag_is_set(std::string const & long_id)
252 {
253 auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
254
255 if (it != end_of_options_it)
256 *it = ""; // remove seen flag
257
258 return (it != end_of_options_it);
259 }
260
264 bool flag_is_set(char const short_id)
265 {
266 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
267 for (std::string & arg : argv)
268 {
269 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
270 {
271 auto pos = arg.find(short_id);
272
273 if (pos != std::string::npos)
274 {
275 arg.erase(pos, 1); // remove seen bool
276
277 if (arg == "-") // if flag is empty now
278 arg = "";
279
280 return true;
281 }
282 }
283 }
284 return false;
285 }
286
294 template <typename option_t>
295 requires istreamable<option_t>
296 option_parse_result parse_option_value(option_t & value, std::string const & in)
297 {
298 std::istringstream stream{in};
299 stream >> value;
300
301 if (stream.fail() || !stream.eof())
302 return option_parse_result::error;
303
304 return option_parse_result::success;
305 }
306
314 template <named_enumeration option_t>
315 option_parse_result parse_option_value(option_t & value, std::string const & in)
316 {
317 auto map = sharg::enumeration_names<option_t>;
318
319 if (auto it = map.find(in); it == map.end())
320 {
321 std::string keys = [&map]()
322 {
323 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
324
325 std::sort(key_value_pairs.begin(),
326 key_value_pairs.end(),
327 [](auto pair1, auto pair2)
328 {
329 if constexpr (std::totally_ordered<option_t>)
330 {
331 if (pair1.second != pair2.second)
332 return pair1.second < pair2.second;
333 }
334
335 return pair1.first < pair2.first;
336 }); // needed for deterministic output when using unordered maps
337
338 std::string result{'['};
339 for (auto const & [key, value] : key_value_pairs)
340 result += std::string{key.data()} + ", ";
341 result.replace(result.size() - 2, 2, "]"); // replace last ", " by "]"
342 return result;
343 }();
344
345 throw user_input_error{"You have chosen an invalid input value: " + in + ". Please use one of: " + keys};
346 }
347 else
348 {
349 value = it->second;
350 }
351
352 return option_parse_result::success;
353 }
354
356 option_parse_result parse_option_value(std::string & value, std::string const & in)
357 {
358 value = in;
359 return option_parse_result::success;
360 }
362
374 // clang-format off
375 template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
376 requires requires (format_parse_t fp,
377 typename container_option_t::value_type & container_value,
378 std::string const & in)
379 {
380 {fp.parse_option_value(container_value, in)} -> std::same_as<option_parse_result>;
381 }
382 // clang-format on
383 option_parse_result parse_option_value(container_option_t & value, std::string const & in)
384 {
385 typename container_option_t::value_type tmp{};
386
387 auto res = parse_option_value(tmp, in);
388
389 if (res == option_parse_result::success)
390 value.push_back(tmp);
391
392 return res;
393 }
394
407 template <typename option_t>
408 requires std::is_arithmetic_v<option_t> && istreamable<option_t>
409 option_parse_result parse_option_value(option_t & value, std::string const & in)
410 {
411 auto res = std::from_chars(&in[0], &in[in.size()], value);
412
413 if (res.ec == std::errc::result_out_of_range)
414 return option_parse_result::overflow_error;
415 else if (res.ec == std::errc::invalid_argument || res.ptr != &in[in.size()])
416 return option_parse_result::error;
417
418 return option_parse_result::success;
419 }
420
431 option_parse_result parse_option_value(bool & value, std::string const & in)
432 {
433 if (in == "0")
434 value = false;
435 else if (in == "1")
436 value = true;
437 else if (in == "true")
438 value = true;
439 else if (in == "false")
440 value = false;
441 else
442 return option_parse_result::error;
443
444 return option_parse_result::success;
445 }
446
454 template <typename option_type>
455 void throw_on_input_error(option_parse_result const res,
456 std::string const & option_name,
457 std::string const & input_value)
458 {
459 std::string msg{"Value parse failed for " + option_name + ": "};
460
461 if (res == option_parse_result::error)
462 {
463 throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
464 + get_type_name_as_string(option_type{}) + "."};
465 }
466
467 if constexpr (std::is_arithmetic_v<option_type>)
468 {
469 if (res == option_parse_result::overflow_error)
470 {
471 throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
474 }
475 }
476
477 assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
478 }
479
497 template <typename option_type, typename id_type>
498 bool identify_and_retrieve_option_value(option_type & value,
500 id_type const & id)
501 {
502 if (option_it != end_of_options_it)
503 {
504 std::string input_value;
505 size_t id_size = (prepend_dash(id)).size();
506
507 if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
508 {
509 if ((*option_it)[id_size] == '=') // -key=value
510 {
511 if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
512 throw too_few_arguments("Missing value for option " + prepend_dash(id));
513 input_value = (*option_it).substr(id_size + 1);
514 }
515 else // -kevValue
516 {
517 input_value = (*option_it).substr(id_size);
518 }
519
520 *option_it = ""; // remove used identifier-value pair
521 }
522 else // -key value
523 {
524 *option_it = ""; // remove used identifier
525 ++option_it;
526 if (option_it == end_of_options_it) // should not happen
527 throw too_few_arguments("Missing value for option " + prepend_dash(id));
528 input_value = *option_it;
529 *option_it = ""; // remove value
530 }
531
532 auto res = parse_option_value(value, input_value);
533 throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
534
535 return true;
536 }
537 return false;
538 }
539
557 template <typename option_type, typename id_type>
558 bool get_option_by_id(option_type & value, id_type const & id)
559 {
560 auto it = find_option_id(argv.begin(), end_of_options_it, id);
561
562 if (it != end_of_options_it)
563 identify_and_retrieve_option_value(value, it, id);
564
565 if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
566 throw option_declared_multiple_times("Option " + prepend_dash(id)
567 + " is no list/container but declared multiple times.");
568
569 return (it != end_of_options_it); // first search was successful or not
570 }
571
583 template <detail::is_container_option option_type, typename id_type>
584 bool get_option_by_id(option_type & value, id_type const & id)
585 {
586 auto it = find_option_id(argv.begin(), end_of_options_it, id);
587 bool seen_at_least_once{it != end_of_options_it};
588
589 if (seen_at_least_once)
590 value.clear();
591
592 while (it != end_of_options_it)
593 {
594 identify_and_retrieve_option_value(value, it, id);
595 it = find_option_id(it, end_of_options_it, id);
596 }
597
598 return seen_at_least_once;
599 }
600
614 void check_for_unknown_ids()
615 {
616 for (auto it = argv.begin(); it != end_of_options_it; ++it)
617 {
618 std::string arg{*it};
619 if (!arg.empty() && arg[0] == '-') // may be an identifier
620 {
621 if (arg == "-")
622 {
623 continue; // positional option
624 }
625 else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
626 {
627 throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
628 + ". In case this is meant to be a non-option/argument/parameter, "
629 + "please specify the start of arguments with '--'. "
630 + "See -h/--help for program information.");
631 }
632 else // unknown short or long option
633 {
634 throw unknown_option("Unknown option " + arg
635 + ". In case this is meant to be a non-option/argument/parameter, "
636 + "please specify the start of non-options with '--'. "
637 + "See -h/--help for program information.");
638 }
639 }
640 }
641 }
642
654 void check_for_left_over_args()
655 {
656 if (std::find_if(argv.begin(),
657 argv.end(),
658 [](std::string const & s)
659 {
660 return (s != "");
661 })
662 != argv.end())
663 throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
664 }
665
683 template <typename option_type, typename validator_t>
684 void get_option(option_type & value, config<validator_t> const & config)
685 {
686 bool short_id_is_set{get_option_by_id(value, config.short_id)};
687 bool long_id_is_set{get_option_by_id(value, config.long_id)};
688
689 // if value is no container we need to check for multiple declarations
690 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
691 throw option_declared_multiple_times("Option " + combine_option_names(config.short_id, config.long_id)
692 + " is no list/container but specified multiple times");
693
694 if (short_id_is_set || long_id_is_set)
695 {
696 try
697 {
698 config.validator(value);
699 }
700 catch (std::exception & ex)
701 {
702 throw validation_error(std::string("Validation failed for option ")
703 + combine_option_names(config.short_id, config.long_id) + ": " + ex.what());
704 }
705 }
706 else // option is not set
707 {
708 // check if option is required
709 if (config.required)
710 throw required_option_missing("Option " + combine_option_names(config.short_id, config.long_id)
711 + " is required but not set.");
712 }
713 }
714
722 void get_flag(bool & value, char const short_id, std::string const & long_id)
723 {
724 value = flag_is_set(short_id) || flag_is_set(long_id);
725 }
726
749 template <typename option_type, typename validator_type>
750 void get_positional_option(option_type & value, validator_type && validator)
751 {
752 ++positional_option_count;
753 auto it = std::find_if(argv.begin(),
754 argv.end(),
755 [](std::string const & s)
756 {
757 return (s != "");
758 });
759
760 if (it == argv.end())
761 throw too_few_arguments("Not enough positional arguments provided (Need at least "
762 + std::to_string(positional_option_calls.size())
763 + "). See -h/--help for more information.");
764
765 if constexpr (detail::is_container_option<
766 option_type>) // vector/list will be filled with all remaining arguments
767 {
768 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
769
770 value.clear();
771
772 while (it != argv.end())
773 {
774 auto res = parse_option_value(value, *it);
775 std::string id = "positional option" + std::to_string(positional_option_count);
776 throw_on_input_error<option_type>(res, id, *it);
777
778 *it = ""; // remove arg from argv
779 it = std::find_if(it,
780 argv.end(),
781 [](std::string const & s)
782 {
783 return (s != "");
784 });
785 ++positional_option_count;
786 }
787 }
788 else
789 {
790 auto res = parse_option_value(value, *it);
791 std::string id = "positional option" + std::to_string(positional_option_count);
792 throw_on_input_error<option_type>(res, id, *it);
793
794 *it = ""; // remove arg from argv
795 }
796
797 try
798 {
799 validator(value);
800 }
801 catch (std::exception & ex)
802 {
803 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
804 + ": " + ex.what());
805 }
806 }
807
809 std::vector<std::function<void()>> option_calls;
811 std::vector<std::function<void()>> flag_calls;
813 std::vector<std::function<void()>> positional_option_calls;
815 unsigned positional_option_count{0};
819 std::vector<std::string>::iterator end_of_options_it;
820};
821
822} // namespace sharg::detail
The <charconv> header from C++17's standard library.
Provides helper concepts.
T data(T... args)
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)
T move(T... args)
T parse(T... args)
T size(T... args)
T sort(T... args)
T substr(T... args)
T to_string(T... args)
T what(T... args)