Sharg 1.1.0
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_) : argc{argc_ - 1}, argv{std::move(argv_)}
67 {}
69
73 template <typename option_type, typename config_type>
74 void add_option(option_type & value, config_type const & config)
75 {
76 option_calls.push_back(
77 [this, &value, config]()
78 {
79 get_option(value, config);
80 });
81 }
82
86 template <typename config_type>
87 void add_flag(bool & value, config_type const & config)
88 {
89 flag_calls.push_back(
90 [this, &value, config]()
91 {
92 get_flag(value, config.short_id, config.long_id);
93 });
94 }
95
99 template <typename option_type, typename config_type>
100 void add_positional_option(option_type & value, config_type const & config)
101 {
102 positional_option_calls.push_back(
103 [this, &value, config]()
104 {
105 get_positional_option(value, config.validator);
106 });
107 }
108
110 void parse(parser_meta_data const & /*meta*/)
111 {
112 end_of_options_it = std::find(argv.begin(), argv.end(), "--");
113
114 // parse options first, because we need to rule out -keyValue pairs
115 // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
116 for (auto && f : option_calls)
117 f();
118
119 for (auto && f : flag_calls)
120 f();
121
122 check_for_unknown_ids();
123
124 if (end_of_options_it != argv.end())
125 *end_of_options_it = ""; // remove -- before parsing positional arguments
126
127 for (auto && f : positional_option_calls)
128 f();
129
130 check_for_left_over_args();
131 }
132
133 // functions are not needed for command line parsing but are part of the format help interface.
135 void add_section(std::string const &, bool const)
136 {}
137 void add_subsection(std::string const &, bool const)
138 {}
139 void add_line(std::string const &, bool, bool const)
140 {}
141 void add_list_item(std::string const &, std::string const &, bool const)
142 {}
144
146 template <typename id_type>
147 static bool is_empty_id(id_type const & id)
148 {
149 if constexpr (std::same_as<std::remove_cvref_t<id_type>, std::string>)
150 return id.empty();
151 else // char
152 return id == '\0';
153 }
154
176 template <typename iterator_type, typename id_type>
177 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, id_type const & id)
178 {
179 if (is_empty_id(id))
180 return end_it;
181
182 return (std::find_if(begin_it,
183 end_it,
184 [&](std::string const & current_arg)
185 {
186 std::string full_id = prepend_dash(id);
187
188 if constexpr (std::same_as<id_type, char>) // short id
189 {
190 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
191 // "-ovalue", "-o=value", and "-o value".
192 return current_arg.substr(0, full_id.size()) == full_id;
193 }
194 else
195 {
196 // only "--opt Value" or "--opt=Value" are valid
197 return current_arg.substr(0, full_id.size()) == full_id && // prefix is the same
198 (current_arg.size() == full_id.size()
199 || current_arg[full_id.size()] == '='); // space or `=`
200 }
201 }));
202 }
203
204private:
206 enum class option_parse_result
207 {
208 success,
209 error,
210 overflow_error
211 };
212
217 static std::string prepend_dash(std::string const & long_id)
218 {
219 return {"--" + long_id};
220 }
221
226 static std::string prepend_dash(char const short_id)
227 {
228 return {'-', short_id};
229 }
230
236 std::string combine_option_names(char const short_id, std::string const & long_id)
237 {
238 if (short_id == '\0')
239 return prepend_dash(long_id);
240 else if (long_id.empty())
241 return prepend_dash(short_id);
242 else // both are set (note: both cannot be empty, this is caught before)
243 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
244 }
245
249 bool flag_is_set(std::string const & long_id)
250 {
251 auto it = std::find(argv.begin(), end_of_options_it, prepend_dash(long_id));
252
253 if (it != end_of_options_it)
254 *it = ""; // remove seen flag
255
256 return (it != end_of_options_it);
257 }
258
262 bool flag_is_set(char const short_id)
263 {
264 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
265 for (std::string & arg : argv)
266 {
267 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
268 {
269 auto pos = arg.find(short_id);
270
271 if (pos != std::string::npos)
272 {
273 arg.erase(pos, 1); // remove seen bool
274
275 if (arg == "-") // if flag is empty now
276 arg = "";
277
278 return true;
279 }
280 }
281 }
282 return false;
283 }
284
292 template <typename option_t>
293 requires istreamable<option_t>
294 option_parse_result parse_option_value(option_t & value, std::string const & in)
295 {
296 std::istringstream stream{in};
297 stream >> value;
298
299 if (stream.fail() || !stream.eof())
300 return option_parse_result::error;
301
302 return option_parse_result::success;
303 }
304
312 template <named_enumeration option_t>
313 option_parse_result parse_option_value(option_t & value, std::string const & in)
314 {
315 auto map = sharg::enumeration_names<option_t>;
316
317 if (auto it = map.find(in); it == map.end())
318 {
319 std::string keys = [&map]()
320 {
321 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
322
323 std::sort(key_value_pairs.begin(),
324 key_value_pairs.end(),
325 [](auto pair1, auto pair2)
326 {
327 if constexpr (std::totally_ordered<option_t>)
328 {
329 if (pair1.second != pair2.second)
330 return pair1.second < pair2.second;
331 }
332
333 return pair1.first < pair2.first;
334 }); // needed for deterministic output when using unordered maps
335
336 std::string result{'['};
337 for (auto const & [key, value] : key_value_pairs)
338 result += std::string{key.data()} + ", ";
339 result.replace(result.size() - 2, 2, "]"); // replace last ", " by "]"
340 return result;
341 }();
342
343 throw user_input_error{"You have chosen an invalid input value: " + in + ". Please use one of: " + keys};
344 }
345 else
346 {
347 value = it->second;
348 }
349
350 return option_parse_result::success;
351 }
352
354 option_parse_result parse_option_value(std::string & value, std::string const & in)
355 {
356 value = in;
357 return option_parse_result::success;
358 }
360
372 // clang-format off
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)} -> std::same_as<option_parse_result>;
379 }
380 // clang-format on
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 <typename option_t>
406 requires std::is_arithmetic_v<option_t> && istreamable<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 (std::is_arithmetic_v<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
681 template <typename option_type, typename config_type>
682 void get_option(option_type & value, config_type const & config)
683 {
684 bool short_id_is_set{get_option_by_id(value, config.short_id)};
685 bool long_id_is_set{get_option_by_id(value, config.long_id)};
686
687 // if value is no container we need to check for multiple declarations
688 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
689 throw option_declared_multiple_times("Option " + combine_option_names(config.short_id, config.long_id)
690 + " is no list/container but specified multiple times");
691
692 if (short_id_is_set || long_id_is_set)
693 {
694 try
695 {
696 config.validator(value);
697 }
698 catch (std::exception & ex)
699 {
700 throw validation_error(std::string("Validation failed for option ")
701 + combine_option_names(config.short_id, config.long_id) + ": " + ex.what());
702 }
703 }
704 else // option is not set
705 {
706 // check if option is required
707 if (config.required)
708 throw required_option_missing("Option " + combine_option_names(config.short_id, config.long_id)
709 + " is required but not set.");
710 }
711 }
712
720 void get_flag(bool & value, char const short_id, std::string const & long_id)
721 {
722 value = flag_is_set(short_id) || flag_is_set(long_id);
723 }
724
747 template <typename option_type, typename validator_type>
748 void get_positional_option(option_type & value, validator_type && validator)
749 {
750 ++positional_option_count;
751 auto it = std::find_if(argv.begin(),
752 argv.end(),
753 [](std::string const & s)
754 {
755 return (s != "");
756 });
757
758 if (it == argv.end())
759 throw too_few_arguments("Not enough positional arguments provided (Need at least "
760 + std::to_string(positional_option_calls.size())
761 + "). See -h/--help for more information.");
762
763 if constexpr (detail::is_container_option<
764 option_type>) // vector/list will be filled with all remaining arguments
765 {
766 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
767
768 value.clear();
769
770 while (it != argv.end())
771 {
772 auto res = parse_option_value(value, *it);
773 std::string id = "positional option" + std::to_string(positional_option_count);
774 throw_on_input_error<option_type>(res, id, *it);
775
776 *it = ""; // remove arg from argv
777 it = std::find_if(it,
778 argv.end(),
779 [](std::string const & s)
780 {
781 return (s != "");
782 });
783 ++positional_option_count;
784 }
785 }
786 else
787 {
788 auto res = parse_option_value(value, *it);
789 std::string id = "positional option" + std::to_string(positional_option_count);
790 throw_on_input_error<option_type>(res, id, *it);
791
792 *it = ""; // remove arg from argv
793 }
794
795 try
796 {
797 validator(value);
798 }
799 catch (std::exception & ex)
800 {
801 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
802 + ": " + ex.what());
803 }
804 }
805
807 std::vector<std::function<void()>> option_calls;
809 std::vector<std::function<void()>> flag_calls;
811 std::vector<std::function<void()>> positional_option_calls;
813 unsigned positional_option_count{0};
815 int argc;
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)