CLI11
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 == 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
98CLI11_MODULE_INLINE const 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 // Store a single copy of the set in a shared_ptr so the lambdas below can share it
226 auto shared_set = std::make_shared<T>(std::move(set));
227
228 // This is the type name for help, it will take the current version of the set contents
229 desc_function_ = [shared_set]() { return detail::generate_set(detail::smart_deref(*shared_set)); };
230
231 // This is the function that validates
232 // It stores a copy of the set pointer-like, so shared_ptr will stay alive
233 func_ = [shared_set, filter_fn](std::string &input) {
234 using CLI::detail::lexical_cast;
235 local_item_t b;
236 if(!lexical_cast(input, b)) {
237 throw ValidationError(input); // name is added later
238 }
239 if(filter_fn) {
240 b = filter_fn(b);
241 }
242 auto res = detail::search(*shared_set, b, filter_fn);
243 if(res.first) {
244 // Make sure the version in the input string is identical to the one in the set
245 if(filter_fn) {
246 input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
247 }
248
249 // Return empty error string (success)
250 return std::string{};
251 }
252
253 // If you reach this point, the result was not found
254 return input + " not in " + detail::generate_set(detail::smart_deref(*shared_set));
255 };
256 }
257
259 template <typename T, typename... Args>
260 IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
261 : IsMember(
262 std::forward<T>(set),
263 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
264 other...) {}
265};
266
268template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
269
271class Transformer : public Validator {
272 public:
273 using filter_fn_t = std::function<std::string(std::string)>;
274
276 template <typename... Args>
277 Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
278 : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
279
281 template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
282
285 template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
286
288 "mapping must produce value pairs");
289 // Get the type of the contained item - requires a container have ::value_type
290 // if the type does not have first_type and second_type, these are both value_type
291 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
292 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
293 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
294 // (const char * to std::string)
295
296 // Make a local copy of the filter function, using a std::function if not one already
297 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
298
299 // Store a single copy of the mapping in a shared_ptr so the lambdas below can share it
300 auto shared_mapping = std::make_shared<T>(std::move(mapping));
301
302 // This is the type name for help, it will take the current version of the set contents
303 desc_function_ = [shared_mapping]() { return detail::generate_map(detail::smart_deref(*shared_mapping)); };
304
305 func_ = [shared_mapping, filter_fn](std::string &input) {
306 using CLI::detail::lexical_cast;
307 local_item_t b;
308 if(!lexical_cast(input, b)) {
309 return std::string();
310 // there is no possible way we can match anything in the mapping if we can't convert so just return
311 }
312 if(filter_fn) {
313 b = filter_fn(b);
314 }
315 auto res = detail::search(*shared_mapping, b, filter_fn);
316 if(res.first) {
317 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
318 }
319 return std::string{};
320 };
321 }
322
324 template <typename T, typename... Args>
325 Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
326 : Transformer(
327 std::forward<T>(mapping),
328 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
329 other...) {}
330};
331
334 public:
335 using filter_fn_t = std::function<std::string(std::string)>;
336
338 template <typename... Args>
339 CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
340 : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
341
343 template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
344
347 template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
348
350 "mapping must produce value pairs");
351 // Get the type of the contained item - requires a container have ::value_type
352 // if the type does not have first_type and second_type, these are both value_type
353 using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
354 using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
355 using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
356 // (const char * to std::string)
357 using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
358
359 // Make a local copy of the filter function, using a std::function if not one already
360 std::function<local_item_t(local_item_t)> filter_fn = filter_function;
361
362 // Store a single copy of the mapping in a shared_ptr so the lambdas below can share it
363 auto shared_mapping = std::make_shared<T>(std::move(mapping));
364
365 auto tfunc = [shared_mapping]() {
366 std::string out("value in ");
367 out += detail::generate_map(detail::smart_deref(*shared_mapping)) + " OR {";
368 out += detail::join(
369 detail::smart_deref(*shared_mapping),
370 [](const iteration_type_t &v) {
371 return detail::value_string(detail::pair_adaptor<element_t>::second(v));
372 },
373 ",");
374 out.push_back('}');
375 return out;
376 };
377
378 desc_function_ = tfunc;
379
380 func_ = [shared_mapping, tfunc, filter_fn](std::string &input) {
381 using CLI::detail::lexical_cast;
382 local_item_t b;
383 bool converted = lexical_cast(input, b);
384 if(converted) {
385 if(filter_fn) {
386 b = filter_fn(b);
387 }
388 auto res = detail::search(*shared_mapping, b, filter_fn);
389 if(res.first) {
390 input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
391 return std::string{};
392 }
393 }
394 for(const auto &v : detail::smart_deref(*shared_mapping)) {
395 auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
396 if(output_string == input) {
397 return std::string();
398 }
399 }
400
401 return "Check " + input + " " + tfunc() + " FAILED";
402 };
403 }
404
406 template <typename T, typename... Args>
407 CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
409 std::forward<T>(mapping),
410 [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
411 other...) {}
412};
413
415inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
416
418inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
419
421inline std::string ignore_space(std::string item) {
422 item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
423 item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
424 return item;
425}
426
439 public:
444 enum Options : std::uint8_t {
445 CASE_SENSITIVE = 0,
446 CASE_INSENSITIVE = 1,
447 UNIT_OPTIONAL = 0,
448 UNIT_REQUIRED = 2,
449 DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
450 };
451
452 template <typename Number>
453 explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
454 Options opts = DEFAULT,
455 const std::string &unit_name = "UNIT") {
456 description(generate_description<Number>(unit_name, opts));
457 validate_mapping(mapping, opts);
458
459 // transform function
460 func_ = [mapping, opts](std::string &input) -> std::string {
461 Number num{};
462
463 detail::rtrim(input);
464 if(input.empty()) {
465 throw ValidationError("Input is empty");
466 }
467
468 // Find split position between number and prefix
469 auto unit_begin = input.end();
470 const std::locale loc{};
471 while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), loc)) {
472 --unit_begin;
473 }
474
475 std::string unit{unit_begin, input.end()};
476 input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
477 detail::trim(input);
478
479 if(opts & UNIT_REQUIRED && unit.empty()) {
480 throw ValidationError("Missing mandatory unit");
481 }
482 if(opts & CASE_INSENSITIVE) {
483 unit = detail::to_lower(unit);
484 }
485 if(unit.empty()) {
486 using CLI::detail::lexical_cast;
487 if(!lexical_cast(input, num)) {
488 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
489 detail::type_name<Number>());
490 }
491 // No need to modify input if no unit passed
492 return {};
493 }
494
495 // find corresponding factor
496 auto it = mapping.find(unit);
497 if(it == mapping.end()) {
498 throw ValidationError(unit +
499 " unit not recognized. "
500 "Allowed values: " +
501 detail::generate_map(mapping, true));
502 }
503
504 if(!input.empty()) {
505 using CLI::detail::lexical_cast;
506 bool converted = lexical_cast(input, num);
507 if(!converted) {
508 throw ValidationError(std::string("Value ") + input + " could not be converted to " +
509 detail::type_name<Number>());
510 }
511 // perform safe multiplication
512 bool ok = detail::checked_multiply(num, it->second);
513 if(!ok) {
514 throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
515 " factor would cause number overflow. Use smaller value.");
516 }
517 } else {
518 num = static_cast<Number>(it->second);
519 }
520
521 input = detail::to_string(num);
522
523 return {};
524 };
525 }
526
527 private:
530 template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
531 for(auto &kv : mapping) {
532 if(kv.first.empty()) {
533 throw ValidationError("Unit must not be empty.");
534 }
535 if(!detail::isalpha(kv.first)) {
536 throw ValidationError("Unit must contain only letters.");
537 }
538 }
539
540 // make all units lowercase if CASE_INSENSITIVE
541 if(opts & CASE_INSENSITIVE) {
542 std::map<std::string, Number> lower_mapping;
543 for(auto &kv : mapping) {
544 auto s = detail::to_lower(kv.first);
545 if(lower_mapping.count(s)) {
546 throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
547 s);
548 }
549 lower_mapping[std::move(s)] = kv.second;
550 }
551 mapping = std::move(lower_mapping);
552 }
553 }
554
556 template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
557 std::stringstream out;
558 out << detail::type_name<Number>() << ' ';
559 if(opts & UNIT_REQUIRED) {
560 out << name;
561 } else {
562 out << '[' << name << ']';
563 }
564 return out.str();
565 }
566};
567
569 return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b));
570}
571
584 public:
585 using result_t = std::uint64_t;
586
594 explicit AsSizeValue(bool kb_is_1000);
595
596 private:
598 static std::map<std::string, result_t> init_mapping(bool kb_is_1000);
599
601 static const std::map<std::string, result_t> &get_mapping(bool kb_is_1000);
602};
603
604#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
605// new extra validators
606#if CLI11_HAS_FILESYSTEM
607namespace detail {
608enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 };
609class PermissionValidator : public Validator {
610 public:
611 explicit PermissionValidator(Permission permission);
612};
613} // namespace detail
614
615class FileSizeValidator : public Validator {
616 public:
617 explicit FileSizeValidator(std::uint64_t min_size, std::uint64_t max_size = 0);
618};
619
621const detail::PermissionValidator ReadPermissions(detail::Permission::read);
622
624const detail::PermissionValidator WritePermissions(detail::Permission::write);
625
627const detail::PermissionValidator ExecPermissions(detail::Permission::exec);
628
630const FileSizeValidator NonEmptyFile(1, 0);
631#endif
632
633#endif
634// [CLI11:extra_validators_hpp:end]
635} // namespace CLI
636
637#ifndef CLI11_COMPILE
638#include "impl/ExtraValidators_inl.hpp" // IWYU pragma: export
639#endif
640
641#endif
Definition ExtraValidators.hpp:438
Options
Definition ExtraValidators.hpp:444
Definition ExtraValidators.hpp:583
AsSizeValue(bool kb_is_1000)
Definition ExtraValidators_inl.hpp:60
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:333
CheckedTransformer(T mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:343
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:407
CheckedTransformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:339
CheckedTransformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:347
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:260
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:271
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:325
Transformer(std::initializer_list< std::pair< std::string, std::string > > values, Args &&...args)
This allows in-place construction.
Definition ExtraValidators.hpp:277
Transformer(T &&mapping)
direct map of std::string to std::string
Definition ExtraValidators.hpp:281
Transformer(T mapping, F filter_function)
Definition ExtraValidators.hpp:285
Validate the input as a particular type.
Definition ExtraValidators.hpp:46
Thrown when validation of results fails.
Definition Error.hpp:222
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