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>
17
18namespace sharg::detail
19{
20
47class format_parse : public format_base
48{
49public:
53 format_parse() = delete;
54 format_parse(format_parse const & pf) = default;
55 format_parse & operator=(format_parse const & pf) = default;
56 format_parse(format_parse &&) = default;
57 format_parse & operator=(format_parse &&) = default;
58 ~format_parse() = default;
59
63 format_parse(std::vector<std::string> cmd_arguments) : arguments{std::move(cmd_arguments)}
64 {}
66
70 template <typename option_type, typename validator_t>
71 void add_option(option_type & value, config<validator_t> const & config)
72 {
73 option_calls.push_back(
74 [this, &value, config]()
75 {
76 get_option(value, config);
77 });
78 }
79
83 template <typename validator_t>
84 void add_flag(bool & value, config<validator_t> const & config)
85 {
86 flag_calls.push_back(
87 [this, &value, config]()
88 {
89 get_flag(value, config.short_id, config.long_id);
90 });
91 }
92
96 template <typename option_type, typename validator_t>
97 void add_positional_option(option_type & value, config<validator_t> const & config)
98 {
99 positional_option_calls.push_back(
100 [this, &value, config]()
101 {
102 get_positional_option(value, config.validator);
103 });
104 }
105
107 void parse(parser_meta_data const & /*meta*/)
108 {
109 end_of_options_it = std::find(arguments.begin(), arguments.end(), "--");
110
111 // parse options first, because we need to rule out -keyValue pairs
112 // (e.g. -AnoSpaceAfterIdentifierA) before parsing flags
113 for (auto && f : option_calls)
114 f();
115
116 for (auto && f : flag_calls)
117 f();
118
119 check_for_unknown_ids();
120
121 if (end_of_options_it != arguments.end())
122 *end_of_options_it = ""; // remove -- before parsing positional arguments
123
124 for (auto && f : positional_option_calls)
125 f();
126
127 check_for_left_over_args();
128 }
129
130 // functions are not needed for command line parsing but are part of the format help interface.
132 void add_section(std::string const &, bool const)
133 {}
134 void add_subsection(std::string const &, bool const)
135 {}
136 void add_line(std::string const &, bool, bool const)
137 {}
138 void add_list_item(std::string const &, std::string const &, bool const)
139 {}
141
163 template <typename iterator_type>
164 static iterator_type find_option_id(iterator_type begin_it, iterator_type end_it, detail::id_pair const & id)
165 {
166 bool const short_id_empty{id.empty_short_id()};
167 bool const long_id_empty{id.empty_long_id()};
168
169 if (short_id_empty && long_id_empty)
170 return end_it;
171
172 std::string const short_id = prepend_dash(id.short_id);
173 std::string const long_id_equals = prepend_dash(id.long_id) + "=";
174 std::string_view const long_id_space = [&long_id_equals]()
175 {
176 std::string_view tmp{long_id_equals};
177 tmp.remove_suffix(1u);
178 return tmp;
179 }();
180
181 auto cmp = [&](std::string_view const current_arg)
182 {
183 // check if current_arg starts with "-o", i.e. it correctly identifies all short notations:
184 // "-ovalue", "-o=value", and "-o value".
185 if (!short_id_empty && current_arg.starts_with(short_id))
186 return true;
187
188 // only "--opt Value" or "--opt=Value" are valid
189 if (!long_id_empty && (current_arg == long_id_space || current_arg.starts_with(long_id_equals)))
190 return true;
191
192 return false;
193 };
194
195 return std::find_if(begin_it, end_it, cmp);
196 }
197
198private:
200 enum class option_parse_result : uint8_t
201 {
202 success,
203 error,
204 overflow_error
205 };
206
211 static std::string prepend_dash(std::string const & long_id)
212 {
213 return {"--" + long_id};
214 }
215
220 static std::string prepend_dash(char const short_id)
221 {
222 return {'-', short_id};
223 }
224
230 std::string combine_option_names(char const short_id, std::string const & long_id)
231 {
232 if (short_id == '\0')
233 return prepend_dash(long_id);
234 else if (long_id.empty())
235 return prepend_dash(short_id);
236 else // both are set (note: both cannot be empty, this is caught before)
237 return prepend_dash(short_id) + "/" + prepend_dash(long_id);
238 }
239
243 bool flag_is_set(std::string const & long_id)
244 {
245 auto it = std::find(arguments.begin(), end_of_options_it, prepend_dash(long_id));
246
247 if (it != end_of_options_it)
248 *it = ""; // remove seen flag
249
250 return (it != end_of_options_it);
251 }
252
256 bool flag_is_set(char const short_id)
257 {
258 // short flags need special attention, since they could be grouped (-rGv <=> -r -G -v)
259 for (std::string & arg : arguments)
260 {
261 if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') // is option && not dash && no long option
262 {
263 auto pos = arg.find(short_id);
264
265 if (pos != std::string::npos)
266 {
267 arg.erase(pos, 1); // remove seen bool
268
269 if (arg == "-") // if flag is empty now
270 arg = "";
271
272 return true;
273 }
274 }
275 }
276 return false;
277 }
278
286 template <typename option_t>
287 requires istreamable<option_t>
288 option_parse_result parse_option_value(option_t & value, std::string const & in)
289 {
290 std::istringstream stream{in};
291 stream >> value;
292
293 if (stream.fail() || !stream.eof())
294 return option_parse_result::error;
295
296 return option_parse_result::success;
297 }
298
306 template <named_enumeration option_t>
307 option_parse_result parse_option_value(option_t & value, std::string const & in)
308 {
309 auto map = sharg::enumeration_names<option_t>;
310
311 if (auto it = map.find(in); it == map.end())
312 {
313 std::string keys = [&map]()
314 {
315 std::vector<std::pair<std::string_view, option_t>> key_value_pairs(map.begin(), map.end());
316
317 std::sort(key_value_pairs.begin(),
318 key_value_pairs.end(),
319 [](auto pair1, auto pair2)
320 {
321 if constexpr (std::totally_ordered<option_t>)
322 {
323 if (pair1.second != pair2.second)
324 return pair1.second < pair2.second;
325 }
326
327 return pair1.first < pair2.first;
328 }); // needed for deterministic output when using unordered maps
329
330 std::string result{'['};
331 for (auto const & [key, value] : key_value_pairs)
332 result += std::string{key} + ", ";
333 result.replace(result.size() - 2, 2, "]"); // replace last ", " by "]"
334 return result;
335 }();
336
337 throw user_input_error{"You have chosen an invalid input value: " + in + ". Please use one of: " + keys};
338 }
339 else
340 {
341 value = it->second;
342 }
343
344 return option_parse_result::success;
345 }
346
348 option_parse_result parse_option_value(std::string & value, std::string const & in)
349 {
350 value = in;
351 return option_parse_result::success;
352 }
354
366 // clang-format off
367 template <detail::is_container_option container_option_t, typename format_parse_t = format_parse>
368 requires requires (format_parse_t fp,
369 typename container_option_t::value_type & container_value,
370 std::string const & in)
371 {
372 {fp.parse_option_value(container_value, in)} -> std::same_as<option_parse_result>;
373 }
374 // clang-format on
375 option_parse_result parse_option_value(container_option_t & value, std::string const & in)
376 {
377 typename container_option_t::value_type tmp{};
378
379 auto res = parse_option_value(tmp, in);
380
381 if (res == option_parse_result::success)
382 value.push_back(tmp);
383
384 return res;
385 }
386
399 template <typename option_t>
400 requires std::is_arithmetic_v<option_t> && istreamable<option_t>
401 option_parse_result parse_option_value(option_t & value, std::string const & in)
402 {
403 auto res = std::from_chars(in.data(), in.data() + in.size(), value);
404
405 if (res.ec == std::errc::result_out_of_range)
406 return option_parse_result::overflow_error;
407 else if (res.ec == std::errc::invalid_argument || res.ptr != in.data() + in.size())
408 return option_parse_result::error;
409
410 return option_parse_result::success;
411 }
412
423 option_parse_result parse_option_value(bool & value, std::string const & in)
424 {
425 if (in == "0" || in == "false")
426 value = false;
427 else if (in == "1" || in == "true")
428 value = true;
429 else
430 return option_parse_result::error;
431
432 return option_parse_result::success;
433 }
434
442 template <typename option_type>
443 void throw_on_input_error(option_parse_result const res,
444 std::string const & option_name,
445 std::string const & input_value)
446 {
447 std::string msg{"Value parse failed for " + option_name + ": "};
448
449 if (res == option_parse_result::error)
450 {
451 throw user_input_error{msg + "Argument " + input_value + " could not be parsed as type "
452 + get_type_name_as_string(option_type{}) + "."};
453 }
454
456 {
457 if (res == option_parse_result::overflow_error)
458 {
459 throw user_input_error{msg + "Numeric argument " + input_value + " is not in the valid range ["
462 }
463 }
464
465 assert(res == option_parse_result::success); // if nothing was thrown, the result must have been a success
466 }
467
485 template <typename option_type, typename id_type>
486 bool identify_and_retrieve_option_value(option_type & value,
488 id_type const & id)
489 {
490 if (option_it != end_of_options_it)
491 {
492 std::string input_value;
493 size_t id_size = (prepend_dash(id)).size();
494
495 if ((*option_it).size() > id_size) // identifier includes value (-keyValue or -key=value)
496 {
497 if ((*option_it)[id_size] == '=') // -key=value
498 {
499 if ((*option_it).size() == id_size + 1) // malformed because no value follows '-i='
500 throw too_few_arguments("Missing value for option " + prepend_dash(id));
501 input_value = (*option_it).substr(id_size + 1);
502 }
503 else // -kevValue
504 {
505 input_value = (*option_it).substr(id_size);
506 }
507
508 *option_it = ""; // remove used identifier-value pair
509 }
510 else // -key value
511 {
512 *option_it = ""; // remove used identifier
513 ++option_it;
514 if (option_it == end_of_options_it) // should not happen
515 throw too_few_arguments("Missing value for option " + prepend_dash(id));
516 input_value = *option_it;
517 *option_it = ""; // remove value
518 }
519
520 auto res = parse_option_value(value, input_value);
521 throw_on_input_error<option_type>(res, prepend_dash(id), input_value);
522
523 return true;
524 }
525 return false;
526 }
527
545 template <typename option_type, typename id_type>
546 bool get_option_by_id(option_type & value, id_type const & id)
547 {
548 auto it = find_option_id(arguments.begin(), end_of_options_it, id);
549
550 if (it != end_of_options_it)
551 identify_and_retrieve_option_value(value, it, id);
552
553 if (find_option_id(it, end_of_options_it, id) != end_of_options_it) // should not be found again
554 throw option_declared_multiple_times("Option " + prepend_dash(id)
555 + " is no list/container but declared multiple times.");
556
557 return (it != end_of_options_it); // first search was successful or not
558 }
559
571 template <detail::is_container_option option_type, typename id_type>
572 bool get_option_by_id(option_type & value, id_type const & id)
573 {
574 auto it = find_option_id(arguments.begin(), end_of_options_it, id);
575 bool seen_at_least_once{it != end_of_options_it};
576
577 if (seen_at_least_once)
578 value.clear();
579
580 while (it != end_of_options_it)
581 {
582 identify_and_retrieve_option_value(value, it, id);
583 it = find_option_id(it, end_of_options_it, id);
584 }
585
586 return seen_at_least_once;
587 }
588
602 void check_for_unknown_ids()
603 {
604 for (auto it = arguments.begin(); it != end_of_options_it; ++it)
605 {
606 std::string & arg{*it};
607 if (!arg.empty() && arg[0] == '-') // may be an identifier
608 {
609 if (arg == "-")
610 {
611 continue; // positional option
612 }
613 else if (arg[1] != '-' && arg.size() > 2) // one dash, but more than one character (-> multiple flags)
614 {
615 throw unknown_option("Unknown flags " + expand_multiple_flags(arg)
616 + ". In case this is meant to be a non-option/argument/parameter, "
617 + "please specify the start of arguments with '--'. "
618 + "See -h/--help for program information.");
619 }
620 else // unknown short or long option
621 {
622 throw unknown_option("Unknown option " + arg
623 + ". In case this is meant to be a non-option/argument/parameter, "
624 + "please specify the start of non-options with '--'. "
625 + "See -h/--help for program information.");
626 }
627 }
628 }
629 }
630
642 void check_for_left_over_args()
643 {
644 if (std::find_if(arguments.begin(),
645 arguments.end(),
646 [](std::string const & s)
647 {
648 return (s != "");
649 })
650 != arguments.end())
651 throw too_many_arguments("Too many arguments provided. Please see -h/--help for more information.");
652 }
653
671 template <typename option_type, typename validator_t>
672 void get_option(option_type & value, config<validator_t> const & config)
673 {
674 bool short_id_is_set{get_option_by_id(value, config.short_id)};
675 bool long_id_is_set{get_option_by_id(value, config.long_id)};
676
677 // if value is no container we need to check for multiple declarations
678 if (short_id_is_set && long_id_is_set && !detail::is_container_option<option_type>)
679 throw option_declared_multiple_times("Option " + combine_option_names(config.short_id, config.long_id)
680 + " is no list/container but specified multiple times");
681
682 if (short_id_is_set || long_id_is_set)
683 {
684 try
685 {
686 config.validator(value);
687 }
688 catch (std::exception & ex)
689 {
690 throw validation_error(std::string("Validation failed for option ")
691 + combine_option_names(config.short_id, config.long_id) + ": " + ex.what());
692 }
693 }
694 else // option is not set
695 {
696 // check if option is required
697 if (config.required)
698 throw required_option_missing("Option " + combine_option_names(config.short_id, config.long_id)
699 + " is required but not set.");
700 }
701 }
702
710 void get_flag(bool & value, char const short_id, std::string const & long_id)
711 {
712 // `|| value` is needed to keep the value if it was set before.
713 // It must be last because `flag_is_set` removes the flag from the arguments.
714 value = flag_is_set(short_id) || flag_is_set(long_id) || value;
715 }
716
739 template <typename option_type, typename validator_type>
740 void get_positional_option(option_type & value, validator_type && validator)
741 {
742 ++positional_option_count;
743 auto it = std::find_if(arguments.begin(),
744 arguments.end(),
745 [](std::string const & s)
746 {
747 return (s != "");
748 });
749
750 if (it == arguments.end())
751 throw too_few_arguments("Not enough positional arguments provided (Need at least "
752 + std::to_string(positional_option_calls.size())
753 + "). See -h/--help for more information.");
754
755 if constexpr (detail::is_container_option<
756 option_type>) // vector/list will be filled with all remaining arguments
757 {
758 assert(positional_option_count == positional_option_calls.size()); // checked on set up.
759
760 value.clear();
761
762 while (it != arguments.end())
763 {
764 auto res = parse_option_value(value, *it);
765 std::string id = "positional option" + std::to_string(positional_option_count);
766 throw_on_input_error<option_type>(res, id, *it);
767
768 *it = ""; // remove arg from arguments
769 it = std::find_if(it,
770 arguments.end(),
771 [](std::string const & s)
772 {
773 return (s != "");
774 });
775 ++positional_option_count;
776 }
777 }
778 else
779 {
780 auto res = parse_option_value(value, *it);
781 std::string id = "positional option" + std::to_string(positional_option_count);
782 throw_on_input_error<option_type>(res, id, *it);
783
784 *it = ""; // remove arg from arguments
785 }
786
787 try
788 {
789 validator(value);
790 }
791 catch (std::exception & ex)
792 {
793 throw validation_error("Validation failed for positional option " + std::to_string(positional_option_count)
794 + ": " + ex.what());
795 }
796 }
797
799 std::vector<std::function<void()>> option_calls;
801 std::vector<std::function<void()>> flag_calls;
803 std::vector<std::function<void()>> positional_option_calls;
805 unsigned positional_option_count{0};
807 std::vector<std::string> arguments;
809 std::vector<std::string>::iterator end_of_options_it;
810};
811
812} // 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)
Provides sharg::detail::id_pair.
T is_same_v
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)
Hide me