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