CLI11 2.7.2
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
ExtraValidators.hpp
1// Copyright (c) 2017-2026, 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 != 0) || \
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 <algorithm>
19#include <cstddef>
20#include <cstdint>
21#include <functional>
22#include <initializer_list>
23#include <iterator>
24#include <locale>
25#include <map>
26#include <memory>
27#include <sstream>
28#include <string>
29#include <type_traits>
30#include <utility>
31#include <vector>
32// [CLI11:public_includes:end]
33
34namespace CLI {
35// [CLI11:extra_validators_hpp:verbatim]
36// The implementation of the extra validators is using the Validator class;
37// the user is only expected to use the const (static) versions (since there's no setup).
38// Therefore, this is in detail.
39namespace detail {
40
42class IPV4Validator : public Validator {
43 public:
44 IPV4Validator();
45};
46
47} // namespace detail
48
50template <typename DesiredType> class TypeValidator : public Validator {
51 public:
52 explicit TypeValidator(const std::string &validator_name)
53 : Validator(validator_name, [](std::string &input_string) {
54 using CLI::detail::lexical_cast;
55 auto val = DesiredType();
56 if(!lexical_cast(input_string, val)) {
57 return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
58 }
59 return std::string{};
60 }) {}
61 TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
62};
63
65CLI11_MODULE_INLINE const TypeValidator<double> Number("NUMBER");
66
68class Bound : public Validator {
69 public:
74 template <typename T> Bound(T min_val, T max_val) {
75 std::stringstream out;
76 out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
77 description(out.str());
78
79 func_ = [min_val, max_val](std::string &input) {
80 using CLI::detail::lexical_cast;
81 T val;
82 bool converted = lexical_cast(input, val);
83 if(!converted) {
84 return std::string("Value ") + input + " could not be converted";
85 }
86 if(val < min_val)
87 input = detail::to_string(min_val);
88 else if(val > max_val)
89 input = detail::to_string(max_val);
90
91 return std::string{};
92 };
93 }
94
96 template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
97};
98
99// Static is not needed here, because global const implies static.
100
102CLI11_MODULE_INLINE const detail::IPV4Validator ValidIPV4;
103
104namespace detail {
105template <typename T,
106 enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
107auto smart_deref(T value) -> decltype(*value) {
108 return *value;
109}
110
111template <
112 typename T,
113 enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
114typename std::remove_reference<T>::type &smart_deref(T &value) {
115 // NOLINTNEXTLINE
116 return value;
117}
119template <typename T> std::string generate_set(const T &set) {
120 using element_t = typename detail::element_type<T>::type;
121 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
122 std::string out(1, '{');
123 out.append(detail::join(
124 detail::smart_deref(set),
125 [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
126 ","));
127 out.push_back('}');
128 return out;
129}
130
132template <typename T> std::string generate_map(const T &map, bool key_only = false) {
133 using element_t = typename detail::element_type<T>::type;
134 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
135 std::string out(1, '{');
136 out.append(detail::join(
137 detail::smart_deref(map),
138 [key_only](const iteration_type_t &v) {
139 std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
140
141 if(!key_only) {
142 res.append("->");
143 res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
144 }
145 return res;
146 },
147 ","));
148 out.push_back('}');
149 return out;
150}
151
152template <typename C, typename V> struct has_find {
153 template <typename CC, typename VV>
154 static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
155 template <typename, typename> static auto test(...) -> decltype(std::false_type());
156
157 static const auto value = decltype(test<C, V>(0))::value;
158 using type = std::integral_constant<bool, value>;
159};
160
162template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
163auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
164 using element_t = typename detail::element_type<T>::type;
165 auto &setref = detail::smart_deref(set);
166 auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
168 });
169 return {(it != std::end(setref)), it};
170}
171
173template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
174auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
175 auto &setref = detail::smart_deref(set);
176 auto it = setref.find(val);
177 return {(it != std::end(setref)), it};
178}
179
181template <typename T, typename V>
182auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
183 -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
184 using element_t = typename detail::element_type<T>::type;
185 // do the potentially faster first search
186 auto res = search(set, val);
187 if((res.first) || (!(filter_function))) {
188 return res;
189 }
190 // if we haven't found it do the longer linear search with all the element translations
191 auto &setref = detail::smart_deref(set);
192 auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
194 a = filter_function(a);
195 return (a == val);
196 });
197 return {(it != std::end(setref)), it};
198}
199
200} // namespace detail
202class IsMember : public Validator {
203 public:
204 using filter_fn_t = std::function<std::string(std::string)>;
205
207 template <typename T, typename... Args>
208 IsMember(std::initializer_list<T> values, Args &&...args)
209 : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
210
212 template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
213
216 template <typename T, typename F> explicit IsMember(T set, F filter_function) {
217
218 // Get the type of the contained item - requires a container have ::value_type
219 // if the type does not have first_type and second_type, these are both value_type
220 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
221 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
222
223 using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
224 // (const char * to std::string)
225
226 // Make a local copy of the filter function, using a std::function if not one already
227 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
228
229 // Store a single copy of the set in a shared_ptr so the lambdas below can share it
230 auto shared_set = std::make_shared<T>(std::move(set));
231
232 // This is the type name for help, it will take the current version of the set contents
233 desc_function_ = [shared_set]() { return detail::generate_set(detail::smart_deref(*shared_set)); };
234
235 // This is the function that validates
236 // It stores a copy of the set pointer-like, so shared_ptr will stay alive
237 func_ = [shared_set, filter_fn](std::string &input) {
238 using CLI::detail::lexical_cast;
239 local_item_t b;
240 if(!lexical_cast(input, b)) {
241 throw ValidationError(input); // name is added later
242 }
243 if(filter_fn) {
244 b = filter_fn(b);
245 }
246 auto res = detail::search(*shared_set, b, filter_fn);
247 if(res.first) {
248 // Make sure the version in the input string is identical to the one in the set
249 if(filter_fn) {
250 input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
251 }
252
253 // Return empty error string (success)
254 return std::string{};
255 }
256
257 // If you reach this point, the result was not found
258 return input + " not in " + detail::generate_set(detail::smart_deref(*shared_set));
259 };
260 }
261
263 template <typename T, typename... Args>
264 IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
265 : IsMember(
266 std::forward<T>(set),
267 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
268 other...) {}
269};
270
272template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
273
275class Transformer : public Validator {
276 public:
277 using filter_fn_t = std::function<std::string(std::string)>;
278
280 template <typename... Args>
281 Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
282 : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
283
285 template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
286
289 template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
290
292 "mapping must produce value pairs");
293 // Get the type of the contained item - requires a container have ::value_type
294 // if the type does not have first_type and second_type, these are both value_type
295 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
296 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
297 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
298 // (const char * to std::string)
299
300 // Make a local copy of the filter function, using a std::function if not one already
301 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
302
303 // Store a single copy of the mapping in a shared_ptr so the lambdas below can share it
304 auto shared_mapping = std::make_shared<T>(std::move(mapping));
305
306 // This is the type name for help, it will take the current version of the set contents
307 desc_function_ = [shared_mapping]() { return detail::generate_map(detail::smart_deref(*shared_mapping)); };
308
309 func_ = [shared_mapping, filter_fn](std::string &input) {
310 using CLI::detail::lexical_cast;
311 local_item_t b;
312 if(!lexical_cast(input, b)) {
313 return std::string();
314 // there is no possible way we can match anything in the mapping if we can't convert so just return
315 }
316 if(filter_fn) {
317 b = filter_fn(b);
318 }
319 auto res = detail::search(*shared_mapping, b, filter_fn);
320 if(res.first) {
321 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
322 }
323 return std::string{};
324 };
325 }
326
328 template <typename T, typename... Args>
329 Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
330 : Transformer(
331 std::forward<T>(mapping),
332 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
333 other...) {}
334};
335
337class CheckedTransformer : public Validator {
338 public:
339 using filter_fn_t = std::function<std::string(std::string)>;
340
342 template <typename... Args>
343 CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
344 : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
345
347 template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
348
351 template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
352
354 "mapping must produce value pairs");
355 // Get the type of the contained item - requires a container have ::value_type
356 // if the type does not have first_type and second_type, these are both value_type
357 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
358 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
359 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
360 // (const char * to std::string)
361 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
362
363 // Make a local copy of the filter function, using a std::function if not one already
364 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
365
366 // Store a single copy of the mapping in a shared_ptr so the lambdas below can share it
367 auto shared_mapping = std::make_shared<T>(std::move(mapping));
368
369 auto tfunc = [shared_mapping]() {
370 std::string out("value in ");
371 out += detail::generate_map(detail::smart_deref(*shared_mapping)) + " OR {";
372 out += detail::join(
373 detail::smart_deref(*shared_mapping),
374 [](const iteration_type_t &v) {
375 return detail::value_string(detail::pair_adaptor<element_t>::second(v));
376 },
377 ",");
378 out.push_back('}');
379 return out;
380 };
381
382 desc_function_ = tfunc;
383
384 func_ = [shared_mapping, tfunc, filter_fn](std::string &input) {
385 using CLI::detail::lexical_cast;
386 local_item_t b;
387 bool converted = lexical_cast(input, b);
388 if(converted) {
389 if(filter_fn) {
390 b = filter_fn(b);
391 }
392 auto res = detail::search(*shared_mapping, b, filter_fn);
393 if(res.first) {
394 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
395 return std::string{};
396 }
397 }
398 for(const auto &v : detail::smart_deref(*shared_mapping)) {
399 auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
400 if(output_string == input) {
401 return std::string();
402 }
403 }
404
405 return "Check " + input + " " + tfunc() + " FAILED";
406 };
407 }
408
410 template <typename T, typename... Args>
411 CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
413 std::forward<T>(mapping),
414 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
415 other...) {}
416};
417
419inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
420
422inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
423
425inline std::string ignore_space(std::string item) {
426 item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
427 item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
428 return item;
429}
430
442class AsNumberWithUnit : public Validator {
443 public:
448 enum Options : std::uint8_t {
449 CASE_SENSITIVE = 0,
450 CASE_INSENSITIVE = 1,
451 UNIT_OPTIONAL = 0,
452 UNIT_REQUIRED = 2,
453 DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
454 };
455
456 template <typename Number>
457 explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
458 Options opts = DEFAULT,
459 const std::string &unit_name = "UNIT") {
460 description(generate_description<Number>(unit_name, opts));
461 validate_mapping(mapping, opts);
462
463 // transform function
464 func_ = [mapping, opts](std::string &input) -> std::string {
465 Number num{};
466
467 detail::rtrim(input);
468 if(input.empty()) {
469 throw ValidationError("Input is empty");
470 }
471
472 // Find split position between number and prefix
473 auto unit_begin = input.end();
474 const std::locale loc{};
475 while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), loc)) {
476 --unit_begin;
477 }
478
479 std::string unit{unit_begin, input.end()};
480 input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
481 detail::trim(input);
482
483 if(opts & UNIT_REQUIRED && unit.empty()) {
484 throw ValidationError("Missing mandatory unit");
485 }
486 if(opts & CASE_INSENSITIVE) {
487 unit = detail::to_lower(unit);
488 }
489 if(unit.empty()) {
490 using CLI::detail::lexical_cast;
491 if(!lexical_cast(input, num)) {
492 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
493 detail::type_name<Number>());
494 }
495 // No need to modify input if no unit passed
496 return {};
497 }
498
499 // find corresponding factor
500 auto it = mapping.find(unit);
501 if(it == mapping.end()) {
502 throw ValidationError(unit +
503 " unit not recognized. "
504 "Allowed values: " +
505 detail::generate_map(mapping, true));
506 }
507
508 if(!input.empty()) {
509 using CLI::detail::lexical_cast;
510 bool converted = lexical_cast(input, num);
511 if(!converted) {
512 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
513 detail::type_name<Number>());
514 }
515 // perform safe multiplication
516 bool ok = detail::checked_multiply(num, it->second);
517 if(!ok) {
518 throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
519 " factor would cause number overflow. Use smaller value.");
520 }
521 } else {
522 num = static_cast<Number>(it->second);
523 }
524
525 input = detail::to_string(num);
526
527 return {};
528 };
529 }
530
531 private:
534 template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
535 for(auto &kv : mapping) {
536 if(kv.first.empty()) {
537 throw ValidationError("Unit must not be empty.");
538 }
539 if(!detail::isalpha(kv.first)) {
540 throw ValidationError("Unit must contain only letters.");
541 }
542 }
543
544 // make all units lowercase if CASE_INSENSITIVE
545 if(opts & CASE_INSENSITIVE) {
546 std::map<std::string, Number> lower_mapping;
547 for(auto &kv : mapping) {
548 auto s = detail::to_lower(kv.first);
549 if(lower_mapping.count(s)) {
550 throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
551 s);
552 }
553 lower_mapping[std::move(s)] = kv.second;
554 }
555 mapping = std::move(lower_mapping);
556 }
557 }
558
560 template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
561 std::stringstream out;
562 out << detail::type_name<Number>() << ' ';
563 if(opts & UNIT_REQUIRED) {
564 out << name;
565 } else {
566 out << '[' << name << ']';
567 }
568 return out.str();
569 }
570};
571
573 return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
574}
575
587class AsSizeValue : public AsNumberWithUnit {
588 public:
589 using result_t = std::uint64_t;
590
598 explicit AsSizeValue(bool kb_is_1000);
599
600 private:
602 static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
603
605 static const std::map<std::string, result_t> &get_mapping(bool kb_is_1000);
606};
607
608#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
609// new extra validators
610#if CLI11_HAS_FILESYSTEM
611namespace detail {
612enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 };
613class PermissionValidator : public Validator {
614 public:
615 explicit PermissionValidator(Permission permission);
616};
617} // namespace detail
618
619class FileSizeValidator : public Validator {
620 public:
621 explicit FileSizeValidator(std::uint64_t min_size, std::uint64_t max_size = 0);
622};
623
625CLI11_MODULE_INLINE const detail::PermissionValidator ReadPermissions(detail::Permission::read);
626
628CLI11_MODULE_INLINE const detail::PermissionValidator WritePermissions(detail::Permission::write);
629
631CLI11_MODULE_INLINE const detail::PermissionValidator ExecPermissions(detail::Permission::exec);
632
634CLI11_MODULE_INLINE const FileSizeValidator NonEmptyFile(1, 0);
635#endif
636
637#endif
638// [CLI11:extra_validators_hpp:end]
639} // namespace CLI
640
641#ifndef CLI11_COMPILE
642#include "impl/ExtraValidators_inl.hpp" // IWYU pragma: export
643#endif
644
645#endif
Definition ExtraValidators.hpp:442
Options
Definition ExtraValidators.hpp:448
AsSizeValue(bool kb_is_1000)
Definition ExtraValidators_inl.hpp:60
Bound(T min_val, T max_val)
Definition ExtraValidators.hpp:74
Bound(T max_val)
Range of one value is 0 to value.
Definition ExtraValidators.hpp:96
CheckedTransformer(T mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:347
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:411
CheckedTransformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:343
CheckedTransformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:351
IsMember(T &&set)
This checks to see if an item is in a set (empty function).
Definition ExtraValidators.hpp:212
IsMember(T set, F filter_function)
Definition ExtraValidators.hpp:216
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:264
IsMember(std::initializer_list< T > values, Args &&...args)
This allows in-place construction using an initializer list.
Definition ExtraValidators.hpp:208
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:329
Transformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:281
Transformer(T &&mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:285
Transformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:289
Validate the input as a particular type.
Definition ExtraValidators.hpp:50
Thrown when validation of results fails.
Definition Error.hpp:222
Some validators that are provided.
Definition Validators.hpp:55
Validator & description(std::string validator_desc)
Specify the type string.
Definition Validators.hpp:96
Validator & name(std::string validator_name)
Specify the type string.
Definition Validators.hpp:106
std::function< std::string()> desc_function_
This is the description function, if empty the description_ will be used.
Definition Validators.hpp:58
std::function< std::string(std::string &)> func_
Definition Validators.hpp:62
Definition ExtraValidators.hpp:152
Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost ...
Definition TypeTools.hpp:133
static auto second(Q &&pair_value) -> decltype(std::forward< Q >(pair_value))
Get the second value (really just the underlying value).
Definition TypeTools.hpp:143
static auto first(Q &&pair_value) -> decltype(std::forward< Q >(pair_value))
Get the first value (really just the underlying value).
Definition TypeTools.hpp:139