CLI11
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
ExtraValidators.hpp
1// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner
2// under NSF AWARD 1414736 and by the respective contributors.
3// All rights reserved.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6
7#pragma once
8#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
9 (!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
10// IWYU pragma: private, include "CLI/CLI.hpp"
11
12#include "Error.hpp"
13#include "Macros.hpp"
14#include "StringTools.hpp"
15#include "Validators.hpp"
16
17// [CLI11:public_includes:set]
18#include <cmath>
19#include <cstdint>
20#include <functional>
21#include <iostream>
22#include <limits>
23#include <map>
24#include <memory>
25#include <string>
26#include <utility>
27#include <vector>
28// [CLI11:public_includes:end]
29
30namespace CLI {
31// [CLI11:extra_validators_hpp:verbatim]
32// The implementation of the extra validators is using the Validator class;
33// the user is only expected to use the const (static) versions (since there's no setup).
34// Therefore, this is in detail.
35namespace detail {
36
38class IPV4Validator : public Validator {
39 public:
41};
42
43} // namespace detail
44
46template <typename DesiredType> class TypeValidator : public Validator {
47 public:
48 explicit TypeValidator(const std::string &validator_name)
49 : Validator(validator_name, [](std::string &input_string) {
50 using CLI::detail::lexical_cast;
51 auto val = DesiredType();
52 if(!lexical_cast(input_string, val)) {
53 return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
54 }
55 return std::string{};
56 }) {}
57 TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
58};
59
61const TypeValidator<double> Number("NUMBER");
62
64class Bound : public Validator {
65 public:
70 template <typename T> Bound(T min_val, T max_val) {
71 std::stringstream out;
72 out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
73 description(out.str());
74
75 func_ = [min_val, max_val](std::string &input) {
76 using CLI::detail::lexical_cast;
77 T val;
78 bool converted = lexical_cast(input, val);
79 if(!converted) {
80 return std::string("Value ") + input + " could not be converted";
81 }
82 if(val < min_val)
83 input = detail::to_string(min_val);
84 else if(val > max_val)
85 input = detail::to_string(max_val);
86
87 return std::string{};
88 };
89 }
90
92 template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
93};
94
95// Static is not needed here, because global const implies static.
96
98const detail::IPV4Validator ValidIPV4;
99
100namespace detail {
101template <typename T,
102 enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
103auto smart_deref(T value) -> decltype(*value) {
104 return *value;
105}
106
107template <
108 typename T,
109 enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
110typename std::remove_reference<T>::type &smart_deref(T &value) {
111 // NOLINTNEXTLINE
112 return value;
113}
115template <typename T> std::string generate_set(const T &set) {
116 using element_t = typename detail::element_type<T>::type;
117 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
118 std::string out(1, '{');
119 out.append(detail::join(
120 detail::smart_deref(set),
121 [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
122 ","));
123 out.push_back('}');
124 return out;
125}
126
128template <typename T> std::string generate_map(const T &map, bool key_only = false) {
129 using element_t = typename detail::element_type<T>::type;
130 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
131 std::string out(1, '{');
132 out.append(detail::join(
133 detail::smart_deref(map),
134 [key_only](const iteration_type_t &v) {
135 std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
136
137 if(!key_only) {
138 res.append("->");
139 res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
140 }
141 return res;
142 },
143 ","));
144 out.push_back('}');
145 return out;
146}
147
148template <typename C, typename V> struct has_find {
149 template <typename CC, typename VV>
150 static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
151 template <typename, typename> static auto test(...) -> decltype(std::false_type());
152
153 static const auto value = decltype(test<C, V>(0))::value;
154 using type = std::integral_constant<bool, value>;
155};
156
158template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
159auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
160 using element_t = typename detail::element_type<T>::type;
161 auto &setref = detail::smart_deref(set);
162 auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
164 });
165 return {(it != std::end(setref)), it};
166}
167
169template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
170auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
171 auto &setref = detail::smart_deref(set);
172 auto it = setref.find(val);
173 return {(it != std::end(setref)), it};
174}
175
177template <typename T, typename V>
178auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
179 -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
180 using element_t = typename detail::element_type<T>::type;
181 // do the potentially faster first search
182 auto res = search(set, val);
183 if((res.first) || (!(filter_function))) {
184 return res;
185 }
186 // if we haven't found it do the longer linear search with all the element translations
187 auto &setref = detail::smart_deref(set);
188 auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
190 a = filter_function(a);
191 return (a == val);
192 });
193 return {(it != std::end(setref)), it};
194}
195
196} // namespace detail
198class IsMember : public Validator {
199 public:
200 using filter_fn_t = std::function<std::string(std::string)>;
201
203 template <typename T, typename... Args>
204 IsMember(std::initializer_list<T> values, Args &&...args)
205 : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
206
208 template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
209
212 template <typename T, typename F> explicit IsMember(T set, F filter_function) {
213
214 // Get the type of the contained item - requires a container have ::value_type
215 // if the type does not have first_type and second_type, these are both value_type
216 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
217 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
218
219 using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
220 // (const char * to std::string)
221
222 // Make a local copy of the filter function, using a std::function if not one already
223 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
224
225 // This is the type name for help, it will take the current version of the set contents
226 desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
227
228 // This is the function that validates
229 // It stores a copy of the set pointer-like, so shared_ptr will stay alive
230 func_ = [set, filter_fn](std::string &input) {
231 using CLI::detail::lexical_cast;
232 local_item_t b;
233 if(!lexical_cast(input, b)) {
234 throw ValidationError(input); // name is added later
235 }
236 if(filter_fn) {
237 b = filter_fn(b);
238 }
239 auto res = detail::search(set, b, filter_fn);
240 if(res.first) {
241 // Make sure the version in the input string is identical to the one in the set
242 if(filter_fn) {
243 input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
244 }
245
246 // Return empty error string (success)
247 return std::string{};
248 }
249
250 // If you reach this point, the result was not found
251 return input + " not in " + detail::generate_set(detail::smart_deref(set));
252 };
253 }
254
256 template <typename T, typename... Args>
257 IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
258 : IsMember(
259 std::forward<T>(set),
260 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
261 other...) {}
262};
263
265template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
266
268class Transformer : public Validator {
269 public:
270 using filter_fn_t = std::function<std::string(std::string)>;
271
273 template <typename... Args>
274 Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
275 : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
276
278 template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
279
282 template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
283
285 "mapping must produce value pairs");
286 // Get the type of the contained item - requires a container have ::value_type
287 // if the type does not have first_type and second_type, these are both value_type
288 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
289 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
290 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
291 // (const char * to std::string)
292
293 // Make a local copy of the filter function, using a std::function if not one already
294 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
295
296 // This is the type name for help, it will take the current version of the set contents
297 desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
298
299 func_ = [mapping, filter_fn](std::string &input) {
300 using CLI::detail::lexical_cast;
301 local_item_t b;
302 if(!lexical_cast(input, b)) {
303 return std::string();
304 // there is no possible way we can match anything in the mapping if we can't convert so just return
305 }
306 if(filter_fn) {
307 b = filter_fn(b);
308 }
309 auto res = detail::search(mapping, b, filter_fn);
310 if(res.first) {
311 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
312 }
313 return std::string{};
314 };
315 }
316
318 template <typename T, typename... Args>
319 Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
320 : Transformer(
321 std::forward<T>(mapping),
322 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
323 other...) {}
324};
325
328 public:
329 using filter_fn_t = std::function<std::string(std::string)>;
330
332 template <typename... Args>
333 CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
334 : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
335
337 template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
338
341 template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
342
344 "mapping must produce value pairs");
345 // Get the type of the contained item - requires a container have ::value_type
346 // if the type does not have first_type and second_type, these are both value_type
347 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
348 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
349 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
350 // (const char * to std::string)
351 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
352
353 // Make a local copy of the filter function, using a std::function if not one already
354 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
355
356 auto tfunc = [mapping]() {
357 std::string out("value in ");
358 out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
359 out += detail::join(
360 detail::smart_deref(mapping),
361 [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
362 ",");
363 out.push_back('}');
364 return out;
365 };
366
367 desc_function_ = tfunc;
368
369 func_ = [mapping, tfunc, filter_fn](std::string &input) {
370 using CLI::detail::lexical_cast;
371 local_item_t b;
372 bool converted = lexical_cast(input, b);
373 if(converted) {
374 if(filter_fn) {
375 b = filter_fn(b);
376 }
377 auto res = detail::search(mapping, b, filter_fn);
378 if(res.first) {
379 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
380 return std::string{};
381 }
382 }
383 for(const auto &v : detail::smart_deref(mapping)) {
384 auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
385 if(output_string == input) {
386 return std::string();
387 }
388 }
389
390 return "Check " + input + " " + tfunc() + " FAILED";
391 };
392 }
393
395 template <typename T, typename... Args>
396 CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
398 std::forward<T>(mapping),
399 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
400 other...) {}
401};
402
404inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
405
407inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
408
410inline std::string ignore_space(std::string item) {
411 item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
412 item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
413 return item;
414}
415
428 public:
433 enum Options : std::uint8_t {
434 CASE_SENSITIVE = 0,
435 CASE_INSENSITIVE = 1,
436 UNIT_OPTIONAL = 0,
437 UNIT_REQUIRED = 2,
438 DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
439 };
440
441 template <typename Number>
442 explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
443 Options opts = DEFAULT,
444 const std::string &unit_name = "UNIT") {
445 description(generate_description<Number>(unit_name, opts));
446 validate_mapping(mapping, opts);
447
448 // transform function
449 func_ = [mapping, opts](std::string &input) -> std::string {
450 Number num{};
451
452 detail::rtrim(input);
453 if(input.empty()) {
454 throw ValidationError("Input is empty");
455 }
456
457 // Find split position between number and prefix
458 auto unit_begin = input.end();
459 while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
460 --unit_begin;
461 }
462
463 std::string unit{unit_begin, input.end()};
464 input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
465 detail::trim(input);
466
467 if(opts & UNIT_REQUIRED && unit.empty()) {
468 throw ValidationError("Missing mandatory unit");
469 }
470 if(opts & CASE_INSENSITIVE) {
471 unit = detail::to_lower(unit);
472 }
473 if(unit.empty()) {
474 using CLI::detail::lexical_cast;
475 if(!lexical_cast(input, num)) {
476 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
477 detail::type_name<Number>());
478 }
479 // No need to modify input if no unit passed
480 return {};
481 }
482
483 // find corresponding factor
484 auto it = mapping.find(unit);
485 if(it == mapping.end()) {
486 throw ValidationError(unit +
487 " unit not recognized. "
488 "Allowed values: " +
489 detail::generate_map(mapping, true));
490 }
491
492 if(!input.empty()) {
493 using CLI::detail::lexical_cast;
494 bool converted = lexical_cast(input, num);
495 if(!converted) {
496 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
497 detail::type_name<Number>());
498 }
499 // perform safe multiplication
500 bool ok = detail::checked_multiply(num, it->second);
501 if(!ok) {
502 throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
503 " factor would cause number overflow. Use smaller value.");
504 }
505 } else {
506 num = static_cast<Number>(it->second);
507 }
508
509 input = detail::to_string(num);
510
511 return {};
512 };
513 }
514
515 private:
518 template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
519 for(auto &kv : mapping) {
520 if(kv.first.empty()) {
521 throw ValidationError("Unit must not be empty.");
522 }
523 if(!detail::isalpha(kv.first)) {
524 throw ValidationError("Unit must contain only letters.");
525 }
526 }
527
528 // make all units lowercase if CASE_INSENSITIVE
529 if(opts & CASE_INSENSITIVE) {
530 std::map<std::string, Number> lower_mapping;
531 for(auto &kv : mapping) {
532 auto s = detail::to_lower(kv.first);
533 if(lower_mapping.count(s)) {
534 throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
535 s);
536 }
537 lower_mapping[detail::to_lower(kv.first)] = kv.second;
538 }
539 mapping = std::move(lower_mapping);
540 }
541 }
542
544 template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
545 std::stringstream out;
546 out << detail::type_name<Number>() << ' ';
547 if(opts & UNIT_REQUIRED) {
548 out << name;
549 } else {
550 out << '[' << name << ']';
551 }
552 return out.str();
553 }
554};
555
557 return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
558}
559
572 public:
573 using result_t = std::uint64_t;
574
582 explicit AsSizeValue(bool kb_is_1000);
583
584 private:
586 static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
587
589 static std::map<std::string, result_t> get_mapping(bool kb_is_1000);
590};
591
592#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
593// new extra validators
594
595#endif
596// [CLI11:extra_validators_hpp:end]
597} // namespace CLI
598
599#ifndef CLI11_COMPILE
600#include "impl/ExtraValidators_inl.hpp" // IWYU pragma: export
601#endif
602
603#endif
Definition ExtraValidators.hpp:427
Options
Definition ExtraValidators.hpp:433
Definition ExtraValidators.hpp:571
AsSizeValue(bool kb_is_1000)
Definition ExtraValidators_inl.hpp:59
Produce a bounded range (factory). Min and max are inclusive.
Definition ExtraValidators.hpp:64
Bound(T min_val, T max_val)
Definition ExtraValidators.hpp:70
Bound(T max_val)
Range of one value is 0 to value.
Definition ExtraValidators.hpp:92
translate named items to other or a value set
Definition ExtraValidators.hpp:327
CheckedTransformer(T mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:337
CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
You can pass in as many filter functions as you like, they nest.
Definition ExtraValidators.hpp:396
CheckedTransformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:333
CheckedTransformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:341
Verify items are in a set.
Definition ExtraValidators.hpp:198
IsMember(T &&set)
This checks to see if an item is in a set (empty function)
Definition ExtraValidators.hpp:208
IsMember(T set, F filter_function)
Definition ExtraValidators.hpp:212
IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
You can pass in as many filter functions as you like, they nest (string only currently)
Definition ExtraValidators.hpp:257
IsMember(std::initializer_list< T > values, Args &&...args)
This allows in-place construction using an initializer list.
Definition ExtraValidators.hpp:204
Translate named items to other or a value set.
Definition ExtraValidators.hpp:268
Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
You can pass in as many filter functions as you like, they nest.
Definition ExtraValidators.hpp:319
Transformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:274
Transformer(T &&mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:278
Transformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:282
Validate the input as a particular type.
Definition ExtraValidators.hpp:46
Thrown when validation of results fails.
Definition Error.hpp:221
Some validators that are provided.
Definition Validators.hpp:54
Validator & description(std::string validator_desc)
Specify the type string.
Definition Validators.hpp:99
Validator & name(std::string validator_name)
Specify the type string.
Definition Validators.hpp:114
std::function< std::string()> desc_function_
This is the description function, if empty the description_ will be used.
Definition Validators.hpp:57
std::function< std::string(std::string &)> func_
Definition Validators.hpp:61
Validate the given string is a legal ipv4 address.
Definition ExtraValidators.hpp:38
Definition ExtraValidators.hpp:148
Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost ...
Definition TypeTools.hpp:130
static auto second(Q &&pair_value) -> decltype(std::forward< Q >(pair_value))
Get the second value (really just the underlying value)
Definition TypeTools.hpp:140
static auto first(Q &&pair_value) -> decltype(std::forward< Q >(pair_value))
Get the first value (really just the underlying value)
Definition TypeTools.hpp:136