CLI11  1.9.1
Validators.hpp
Go to the documentation of this file.
1 // Copyright (c) 2017-2020, 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 
9 #include "Macros.hpp"
10 #include "StringTools.hpp"
11 #include "TypeTools.hpp"
12 
13 #include <cmath>
14 #include <cstdint>
15 #include <functional>
16 #include <iostream>
17 #include <limits>
18 #include <map>
19 #include <memory>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 // [CLI11:verbatim]
25 
26 // C standard library
27 // Only needed for existence checking
28 #if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
29 #if __has_include(<filesystem>)
30 // Filesystem cannot be used if targeting macOS < 10.15
31 #if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
32 #define CLI11_HAS_FILESYSTEM 0
33 #else
34 #include <filesystem>
35 #if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
36 #if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
37 #define CLI11_HAS_FILESYSTEM 1
38 #elif defined(__GLIBCXX__)
39 // if we are using gcc and Version <9 default to no filesystem
40 #define CLI11_HAS_FILESYSTEM 0
41 #else
42 #define CLI11_HAS_FILESYSTEM 1
43 #endif
44 #else
45 #define CLI11_HAS_FILESYSTEM 0
46 #endif
47 #endif
48 #endif
49 #endif
50 
51 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
52 #include <filesystem> // NOLINT(build/include)
53 #else
54 #include <sys/stat.h>
55 #include <sys/types.h>
56 #endif
57 
58 // [CLI11:verbatim]
59 
60 namespace CLI {
61 
62 class Option;
63 
65 
72 
74 class Validator {
75  protected:
77  std::function<std::string()> desc_function_{[]() { return std::string{}; }};
78 
81  std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
83  std::string name_{};
87  bool active_{true};
89  bool non_modifying_{false};
90 
91  public:
92  Validator() = default;
94  explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {}
96  Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "")
97  : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
98  name_(std::move(validator_name)) {}
100  Validator &operation(std::function<std::string(std::string &)> op) {
101  func_ = std::move(op);
102  return *this;
103  }
106  std::string operator()(std::string &str) const {
107  std::string retstring;
108  if(active_) {
109  if(non_modifying_) {
110  std::string value = str;
111  retstring = func_(value);
112  } else {
113  retstring = func_(str);
114  }
115  }
116  return retstring;
117  }
118 
121  std::string operator()(const std::string &str) const {
122  std::string value = str;
123  return (active_) ? func_(value) : std::string{};
124  }
125 
127  Validator &description(std::string validator_desc) {
128  desc_function_ = [validator_desc]() { return validator_desc; };
129  return *this;
130  }
132  Validator description(std::string validator_desc) const {
133  Validator newval(*this);
134  newval.desc_function_ = [validator_desc]() { return validator_desc; };
135  return newval;
136  }
138  std::string get_description() const {
139  if(active_) {
140  return desc_function_();
141  }
142  return std::string{};
143  }
145  Validator &name(std::string validator_name) {
146  name_ = std::move(validator_name);
147  return *this;
148  }
150  Validator name(std::string validator_name) const {
151  Validator newval(*this);
152  newval.name_ = std::move(validator_name);
153  return newval;
154  }
156  const std::string &get_name() const { return name_; }
158  Validator &active(bool active_val = true) {
159  active_ = active_val;
160  return *this;
161  }
163  Validator active(bool active_val = true) const {
164  Validator newval(*this);
165  newval.active_ = active_val;
166  return newval;
167  }
168 
170  Validator &non_modifying(bool no_modify = true) {
171  non_modifying_ = no_modify;
172  return *this;
173  }
175  Validator &application_index(int app_index) {
176  application_index_ = app_index;
177  return *this;
178  }
180  Validator application_index(int app_index) const {
181  Validator newval(*this);
182  newval.application_index_ = app_index;
183  return newval;
184  }
188  bool get_active() const { return active_; }
189 
191  bool get_modifying() const { return !non_modifying_; }
192 
195  Validator operator&(const Validator &other) const {
196  Validator newval;
197 
198  newval._merge_description(*this, other, " AND ");
199 
200  // Give references (will make a copy in lambda function)
201  const std::function<std::string(std::string & filename)> &f1 = func_;
202  const std::function<std::string(std::string & filename)> &f2 = other.func_;
203 
204  newval.func_ = [f1, f2](std::string &input) {
205  std::string s1 = f1(input);
206  std::string s2 = f2(input);
207  if(!s1.empty() && !s2.empty())
208  return std::string("(") + s1 + ") AND (" + s2 + ")";
209  else
210  return s1 + s2;
211  };
212 
213  newval.active_ = (active_ & other.active_);
215  return newval;
216  }
217 
220  Validator operator|(const Validator &other) const {
221  Validator newval;
222 
223  newval._merge_description(*this, other, " OR ");
224 
225  // Give references (will make a copy in lambda function)
226  const std::function<std::string(std::string &)> &f1 = func_;
227  const std::function<std::string(std::string &)> &f2 = other.func_;
228 
229  newval.func_ = [f1, f2](std::string &input) {
230  std::string s1 = f1(input);
231  std::string s2 = f2(input);
232  if(s1.empty() || s2.empty())
233  return std::string();
234 
235  return std::string("(") + s1 + ") OR (" + s2 + ")";
236  };
237  newval.active_ = (active_ & other.active_);
239  return newval;
240  }
241 
244  Validator newval;
245  const std::function<std::string()> &dfunc1 = desc_function_;
246  newval.desc_function_ = [dfunc1]() {
247  auto str = dfunc1();
248  return (!str.empty()) ? std::string("NOT ") + str : std::string{};
249  };
250  // Give references (will make a copy in lambda function)
251  const std::function<std::string(std::string & res)> &f1 = func_;
252 
253  newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
254  std::string s1 = f1(test);
255  if(s1.empty()) {
256  return std::string("check ") + dfunc1() + " succeeded improperly";
257  }
258  return std::string{};
259  };
260  newval.active_ = active_;
262  return newval;
263  }
264 
265  private:
266  void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
267 
268  const std::function<std::string()> &dfunc1 = val1.desc_function_;
269  const std::function<std::string()> &dfunc2 = val2.desc_function_;
270 
271  desc_function_ = [=]() {
272  std::string f1 = dfunc1();
273  std::string f2 = dfunc2();
274  if((f1.empty()) || (f2.empty())) {
275  return f1 + f2;
276  }
277  return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
278  };
279  }
280 }; // namespace CLI
281 
283 class CustomValidator : public Validator {
284  public:
285 };
286 // The implementation of the built in validators is using the Validator class;
287 // the user is only expected to use the const (static) versions (since there's no setup).
288 // Therefore, this is in detail.
289 namespace detail {
290 
293 
294 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
295 inline path_type check_path(const char *file) noexcept {
297  std::error_code ec;
298  auto stat = std::filesystem::status(file, ec);
299  if(ec) {
300  return path_type::nonexistent;
301  }
302  switch(stat.type()) {
303  case std::filesystem::file_type::none:
304  case std::filesystem::file_type::not_found:
305  return path_type::nonexistent;
306  case std::filesystem::file_type::directory:
307  return path_type::directory;
308  case std::filesystem::file_type::symlink:
309  case std::filesystem::file_type::block:
310  case std::filesystem::file_type::character:
311  case std::filesystem::file_type::fifo:
312  case std::filesystem::file_type::socket:
313  case std::filesystem::file_type::regular:
314  case std::filesystem::file_type::unknown:
315  default:
316  return path_type::file;
317  }
318 }
319 #else
320 inline path_type check_path(const char *file) noexcept {
322 #if defined(_MSC_VER)
323  struct __stat64 buffer;
324  if(_stat64(file, &buffer) == 0) {
325  return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
326  }
327 #else
328  struct stat buffer;
329  if(stat(file, &buffer) == 0) {
330  return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
331  }
332 #endif
333  return path_type::nonexistent;
334 }
335 #endif
336 class ExistingFileValidator : public Validator {
338  public:
340  func_ = [](std::string &filename) {
341  auto path_result = check_path(filename.c_str());
342  if(path_result == path_type::nonexistent) {
343  return "File does not exist: " + filename;
344  }
345  if(path_result == path_type::directory) {
346  return "File is actually a directory: " + filename;
347  }
348  return std::string();
349  };
350  }
351 };
352 
355  public:
357  func_ = [](std::string &filename) {
358  auto path_result = check_path(filename.c_str());
359  if(path_result == path_type::nonexistent) {
360  return "Directory does not exist: " + filename;
361  }
362  if(path_result == path_type::file) {
363  return "Directory is actually a file: " + filename;
364  }
365  return std::string();
366  };
367  }
368 };
369 
372  public:
373  ExistingPathValidator() : Validator("PATH(existing)") {
374  func_ = [](std::string &filename) {
375  auto path_result = check_path(filename.c_str());
376  if(path_result == path_type::nonexistent) {
377  return "Path does not exist: " + filename;
378  }
379  return std::string();
380  };
381  }
382 };
383 
386  public:
387  NonexistentPathValidator() : Validator("PATH(non-existing)") {
388  func_ = [](std::string &filename) {
389  auto path_result = check_path(filename.c_str());
390  if(path_result != path_type::nonexistent) {
391  return "Path already exists: " + filename;
392  }
393  return std::string();
394  };
395  }
396 };
397 
399 class IPV4Validator : public Validator {
400  public:
401  IPV4Validator() : Validator("IPV4") {
402  func_ = [](std::string &ip_addr) {
403  auto result = CLI::detail::split(ip_addr, '.');
404  if(result.size() != 4) {
405  return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
406  }
407  int num;
408  for(const auto &var : result) {
409  bool retval = detail::lexical_cast(var, num);
410  if(!retval) {
411  return std::string("Failed parsing number (") + var + ')';
412  }
413  if(num < 0 || num > 255) {
414  return std::string("Each IP number must be between 0 and 255 ") + var;
415  }
416  }
417  return std::string();
418  };
419  }
420 };
421 
423 class PositiveNumber : public Validator {
424  public:
425  PositiveNumber() : Validator("POSITIVE") {
426  func_ = [](std::string &number_str) {
427  double number;
428  if(!detail::lexical_cast(number_str, number)) {
429  return std::string("Failed parsing number: (") + number_str + ')';
430  }
431  if(number <= 0) {
432  return std::string("Number less or equal to 0: (") + number_str + ')';
433  }
434  return std::string();
435  };
436  }
437 };
439 class NonNegativeNumber : public Validator {
440  public:
441  NonNegativeNumber() : Validator("NONNEGATIVE") {
442  func_ = [](std::string &number_str) {
443  double number;
444  if(!detail::lexical_cast(number_str, number)) {
445  return std::string("Failed parsing number: (") + number_str + ')';
446  }
447  if(number < 0) {
448  return std::string("Number less than 0: (") + number_str + ')';
449  }
450  return std::string();
451  };
452  }
453 };
454 
456 class Number : public Validator {
457  public:
458  Number() : Validator("NUMBER") {
459  func_ = [](std::string &number_str) {
460  double number;
461  if(!detail::lexical_cast(number_str, number)) {
462  return std::string("Failed parsing as a number (") + number_str + ')';
463  }
464  return std::string();
465  };
466  }
467 };
468 
469 } // namespace detail
470 
471 // Static is not needed here, because global const implies static.
472 
475 
478 
481 
484 
487 
490 
493 
496 
498 class Range : public Validator {
499  public:
504  template <typename T> Range(T min, T max) {
505  std::stringstream out;
506  out << detail::type_name<T>() << " in [" << min << " - " << max << "]";
507  description(out.str());
508 
509  func_ = [min, max](std::string &input) {
510  T val;
511  bool converted = detail::lexical_cast(input, val);
512  if((!converted) || (val < min || val > max))
513  return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " +
514  std::to_string(max);
515 
516  return std::string();
517  };
518  }
519 
521  template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}
522 };
523 
525 class Bound : public Validator {
526  public:
531  template <typename T> Bound(T min, T max) {
532  std::stringstream out;
533  out << detail::type_name<T>() << " bounded to [" << min << " - " << max << "]";
534  description(out.str());
535 
536  func_ = [min, max](std::string &input) {
537  T val;
538  bool converted = detail::lexical_cast(input, val);
539  if(!converted) {
540  return std::string("Value ") + input + " could not be converted";
541  }
542  if(val < min)
543  input = detail::to_string(min);
544  else if(val > max)
545  input = detail::to_string(max);
546 
547  return std::string{};
548  };
549  }
550 
552  template <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {}
553 };
554 
555 namespace detail {
556 template <typename T,
557  enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
558 auto smart_deref(T value) -> decltype(*value) {
559  return *value;
560 }
561 
562 template <
563  typename T,
565 typename std::remove_reference<T>::type &smart_deref(T &value) {
566  return value;
567 }
569 template <typename T> std::string generate_set(const T &set) {
570  using element_t = typename detail::element_type<T>::type;
571  using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
572  std::string out(1, '{');
573  out.append(detail::join(
574  detail::smart_deref(set),
575  [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
576  ","));
577  out.push_back('}');
578  return out;
579 }
580 
582 template <typename T> std::string generate_map(const T &map, bool key_only = false) {
583  using element_t = typename detail::element_type<T>::type;
584  using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
585  std::string out(1, '{');
586  out.append(detail::join(
587  detail::smart_deref(map),
588  [key_only](const iteration_type_t &v) {
590 
591  if(!key_only) {
592  res.append("->");
594  }
595  return res;
596  },
597  ","));
598  out.push_back('}');
599  return out;
600 }
601 
602 template <typename C, typename V> struct has_find {
603  template <typename CC, typename VV>
604  static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
605  template <typename, typename> static auto test(...) -> decltype(std::false_type());
606 
607  static const auto value = decltype(test<C, V>(0))::value;
608  using type = std::integral_constant<bool, value>;
609 };
610 
612 template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
613 auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
614  using element_t = typename detail::element_type<T>::type;
615  auto &setref = detail::smart_deref(set);
616  auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
617  return (detail::pair_adaptor<element_t>::first(v) == val);
618  });
619  return {(it != std::end(setref)), it};
620 }
621 
623 template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
624 auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
625  auto &setref = detail::smart_deref(set);
626  auto it = setref.find(val);
627  return {(it != std::end(setref)), it};
628 }
629 
631 template <typename T, typename V>
632 auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
633  -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
634  using element_t = typename detail::element_type<T>::type;
635  // do the potentially faster first search
636  auto res = search(set, val);
637  if((res.first) || (!(filter_function))) {
638  return res;
639  }
640  // if we haven't found it do the longer linear search with all the element translations
641  auto &setref = detail::smart_deref(set);
642  auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
644  a = filter_function(a);
645  return (a == val);
646  });
647  return {(it != std::end(setref)), it};
648 }
649 
650 // the following suggestion was made by Nikita Ofitserov(@himikof)
651 // done in templates to prevent compiler warnings on negation of unsigned numbers
652 
654 template <typename T>
655 inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
656  if((a > 0) == (b > 0)) {
657  return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
658  } else {
659  return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
660  }
661 }
663 template <typename T>
664 inline typename std::enable_if<!std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
665  return ((std::numeric_limits<T>::max)() / a < b);
666 }
667 
669 template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) {
670  if(a == 0 || b == 0 || a == 1 || b == 1) {
671  a *= b;
672  return true;
673  }
674  if(a == (std::numeric_limits<T>::min)() || b == (std::numeric_limits<T>::min)()) {
675  return false;
676  }
677  if(overflowCheck(a, b)) {
678  return false;
679  }
680  a *= b;
681  return true;
682 }
683 
685 template <typename T>
686 typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_multiply(T &a, T b) {
687  T c = a * b;
688  if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) {
689  return false;
690  }
691  a = c;
692  return true;
693 }
694 
695 } // namespace detail
697 class IsMember : public Validator {
698  public:
699  using filter_fn_t = std::function<std::string(std::string)>;
700 
702  template <typename T, typename... Args>
703  IsMember(std::initializer_list<T> values, Args &&... args)
704  : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
705 
707  template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
708 
711  template <typename T, typename F> explicit IsMember(T set, F filter_function) {
712 
713  // Get the type of the contained item - requires a container have ::value_type
714  // if the type does not have first_type and second_type, these are both value_type
715  using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
716  using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
717 
718  using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones
719  // (const char * to std::string)
720 
721  // Make a local copy of the filter function, using a std::function if not one already
722  std::function<local_item_t(local_item_t)> filter_fn = filter_function;
723 
724  // This is the type name for help, it will take the current version of the set contents
725  desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
726 
727  // This is the function that validates
728  // It stores a copy of the set pointer-like, so shared_ptr will stay alive
729  func_ = [set, filter_fn](std::string &input) {
730  local_item_t b;
731  if(!detail::lexical_cast(input, b)) {
732  throw ValidationError(input); // name is added later
733  }
734  if(filter_fn) {
735  b = filter_fn(b);
736  }
737  auto res = detail::search(set, b, filter_fn);
738  if(res.first) {
739  // Make sure the version in the input string is identical to the one in the set
740  if(filter_fn) {
742  }
743 
744  // Return empty error string (success)
745  return std::string{};
746  }
747 
748  // If you reach this point, the result was not found
749  std::string out(" not in ");
751  return out;
752  };
753  }
754 
756  template <typename T, typename... Args>
757  IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
758  : IsMember(
759  std::forward<T>(set),
760  [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
761  other...) {}
762 };
763 
765 template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
766 
768 class Transformer : public Validator {
769  public:
770  using filter_fn_t = std::function<std::string(std::string)>;
771 
773  template <typename... Args>
774  Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
775  : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
776 
778  template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
779 
782  template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
783 
784  static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
785  "mapping must produce value pairs");
786  // Get the type of the contained item - requires a container have ::value_type
787  // if the type does not have first_type and second_type, these are both value_type
788  using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
789  using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
790  using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
791  // (const char * to std::string)
792 
793  // Make a local copy of the filter function, using a std::function if not one already
794  std::function<local_item_t(local_item_t)> filter_fn = filter_function;
795 
796  // This is the type name for help, it will take the current version of the set contents
797  desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
798 
799  func_ = [mapping, filter_fn](std::string &input) {
800  local_item_t b;
801  if(!detail::lexical_cast(input, b)) {
802  return std::string();
803  // there is no possible way we can match anything in the mapping if we can't convert so just return
804  }
805  if(filter_fn) {
806  b = filter_fn(b);
807  }
808  auto res = detail::search(mapping, b, filter_fn);
809  if(res.first) {
811  }
812  return std::string{};
813  };
814  }
815 
817  template <typename T, typename... Args>
818  Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
819  : Transformer(
820  std::forward<T>(mapping),
821  [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
822  other...) {}
823 };
824 
827  public:
828  using filter_fn_t = std::function<std::string(std::string)>;
829 
831  template <typename... Args>
832  CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args)
833  : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
834 
836  template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
837 
840  template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
841 
842  static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
843  "mapping must produce value pairs");
844  // Get the type of the contained item - requires a container have ::value_type
845  // if the type does not have first_type and second_type, these are both value_type
846  using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed
847  using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map
848  using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones
849  // (const char * to std::string)
850  using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair
851 
852  // Make a local copy of the filter function, using a std::function if not one already
853  std::function<local_item_t(local_item_t)> filter_fn = filter_function;
854 
855  auto tfunc = [mapping]() {
856  std::string out("value in ");
857  out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
858  out += detail::join(
859  detail::smart_deref(mapping),
860  [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
861  ",");
862  out.push_back('}');
863  return out;
864  };
865 
866  desc_function_ = tfunc;
867 
868  func_ = [mapping, tfunc, filter_fn](std::string &input) {
869  local_item_t b;
870  bool converted = detail::lexical_cast(input, b);
871  if(converted) {
872  if(filter_fn) {
873  b = filter_fn(b);
874  }
875  auto res = detail::search(mapping, b, filter_fn);
876  if(res.first) {
878  return std::string{};
879  }
880  }
881  for(const auto &v : detail::smart_deref(mapping)) {
883  if(output_string == input) {
884  return std::string();
885  }
886  }
887 
888  return "Check " + input + " " + tfunc() + " FAILED";
889  };
890  }
891 
893  template <typename T, typename... Args>
894  CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other)
896  std::forward<T>(mapping),
897  [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
898  other...) {}
899 };
900 
902 inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
903 
905 inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
906 
908 inline std::string ignore_space(std::string item) {
909  item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
910  item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
911  return item;
912 }
913 
925 class AsNumberWithUnit : public Validator {
926  public:
931  enum Options {
937  };
938 
939  template <typename Number>
940  explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
941  Options opts = DEFAULT,
942  const std::string &unit_name = "UNIT") {
943  description(generate_description<Number>(unit_name, opts));
944  validate_mapping(mapping, opts);
945 
946  // transform function
947  func_ = [mapping, opts](std::string &input) -> std::string {
948  Number num;
949 
950  detail::rtrim(input);
951  if(input.empty()) {
952  throw ValidationError("Input is empty");
953  }
954 
955  // Find split position between number and prefix
956  auto unit_begin = input.end();
957  while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
958  --unit_begin;
959  }
960 
961  std::string unit{unit_begin, input.end()};
962  input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
963  detail::trim(input);
964 
965  if(opts & UNIT_REQUIRED && unit.empty()) {
966  throw ValidationError("Missing mandatory unit");
967  }
968  if(opts & CASE_INSENSITIVE) {
969  unit = detail::to_lower(unit);
970  }
971 
972  bool converted = detail::lexical_cast(input, num);
973  if(!converted) {
974  throw ValidationError(std::string("Value ") + input + " could not be converted to " +
975  detail::type_name<Number>());
976  }
977 
978  if(unit.empty()) {
979  // No need to modify input if no unit passed
980  return {};
981  }
982 
983  // find corresponding factor
984  auto it = mapping.find(unit);
985  if(it == mapping.end()) {
986  throw ValidationError(unit +
987  " unit not recognized. "
988  "Allowed values: " +
989  detail::generate_map(mapping, true));
990  }
991 
992  // perform safe multiplication
993  bool ok = detail::checked_multiply(num, it->second);
994  if(!ok) {
995  throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
996  " factor would cause number overflow. Use smaller value.");
997  }
998  input = detail::to_string(num);
999 
1000  return {};
1001  };
1002  }
1003 
1004  private:
1007  template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
1008  for(auto &kv : mapping) {
1009  if(kv.first.empty()) {
1010  throw ValidationError("Unit must not be empty.");
1011  }
1012  if(!detail::isalpha(kv.first)) {
1013  throw ValidationError("Unit must contain only letters.");
1014  }
1015  }
1016 
1017  // make all units lowercase if CASE_INSENSITIVE
1018  if(opts & CASE_INSENSITIVE) {
1019  std::map<std::string, Number> lower_mapping;
1020  for(auto &kv : mapping) {
1021  auto s = detail::to_lower(kv.first);
1022  if(lower_mapping.count(s)) {
1023  throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
1024  s);
1025  }
1026  lower_mapping[detail::to_lower(kv.first)] = kv.second;
1027  }
1028  mapping = std::move(lower_mapping);
1029  }
1030  }
1031 
1033  template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
1034  std::stringstream out;
1035  out << detail::type_name<Number>() << ' ';
1036  if(opts & UNIT_REQUIRED) {
1037  out << name;
1038  } else {
1039  out << '[' << name << ']';
1040  }
1041  return out.str();
1042  }
1043 };
1044 
1057  public:
1058  using result_t = std::uint64_t;
1059 
1067  explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
1068  if(kb_is_1000) {
1069  description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
1070  } else {
1071  description("SIZE [b, kb(=1024b), ...]");
1072  }
1073  }
1074 
1075  private:
1077  static std::map<std::string, result_t> init_mapping(bool kb_is_1000) {
1078  std::map<std::string, result_t> m;
1079  result_t k_factor = kb_is_1000 ? 1000 : 1024;
1080  result_t ki_factor = 1024;
1081  result_t k = 1;
1082  result_t ki = 1;
1083  m["b"] = 1;
1084  for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
1085  k *= k_factor;
1086  ki *= ki_factor;
1087  m[p] = k;
1088  m[p + "b"] = k;
1089  m[p + "i"] = ki;
1090  m[p + "ib"] = ki;
1091  }
1092  return m;
1093  }
1094 
1096  static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
1097  if(kb_is_1000) {
1098  static auto m = init_mapping(true);
1099  return m;
1100  } else {
1101  static auto m = init_mapping(false);
1102  return m;
1103  }
1104  }
1105 };
1106 
1107 namespace detail {
1112 inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
1113  // try to determine the programName
1114  std::pair<std::string, std::string> vals;
1115  trim(commandline);
1116  auto esp = commandline.find_first_of(' ', 1);
1117  while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
1118  esp = commandline.find_first_of(' ', esp + 1);
1119  if(esp == std::string::npos) {
1120  // if we have reached the end and haven't found a valid file just assume the first argument is the
1121  // program name
1122  esp = commandline.find_first_of(' ', 1);
1123  break;
1124  }
1125  }
1126  vals.first = commandline.substr(0, esp);
1127  rtrim(vals.first);
1128  // strip the program name
1129  vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{};
1130  ltrim(vals.second);
1131  return vals;
1132 }
1133 
1134 } // namespace detail
1136 
1137 } // namespace CLI
CLI::detail::ExistingDirectoryValidator::ExistingDirectoryValidator
ExistingDirectoryValidator()
Definition: Validators.hpp:356
CLI::Range::Range
Range(T max)
Range of one value is 0 to value.
Definition: Validators.hpp:521
CLI::Validator::get_active
bool get_active() const
Get a boolean if the validator is active.
Definition: Validators.hpp:188
CLI::PositiveNumber
const detail::PositiveNumber PositiveNumber
Check for a positive number.
Definition: Validators.hpp:489
CLI::AsNumberWithUnit::UNIT_REQUIRED
@ UNIT_REQUIRED
Definition: Validators.hpp:935
CLI::ignore_space
std::string ignore_space(std::string item)
Helper function to allow checks to ignore spaces to be passed to IsMember or Transform.
Definition: Validators.hpp:908
CLI::Range::Range
Range(T min, T max)
Definition: Validators.hpp:504
CLI::Validator::description
Validator description(std::string validator_desc) const
Specify the type string.
Definition: Validators.hpp:132
CLI::Validator::get_description
std::string get_description() const
Generate type description information for the Validator.
Definition: Validators.hpp:138
CLI::Number
const detail::Number Number
Check for a number.
Definition: Validators.hpp:495
CLI::CheckedTransformer::CheckedTransformer
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: Validators.hpp:894
CLI::Validator::operator|
Validator operator|(const Validator &other) const
Definition: Validators.hpp:220
CLI::Bound
Produce a bounded range (factory). Min and max are inclusive.
Definition: Validators.hpp:525
CLI::detail::has_find::test
static auto test(int) -> decltype(std::declval< CC >().find(std::declval< VV >()), std::true_type())
CLI::Validator::active
Validator & active(bool active_val=true)
Specify whether the Validator is active or not.
Definition: Validators.hpp:158
CLI::IsMemberType::type
T type
Definition: TypeTools.hpp:78
CLI::detail::enabler
enabler
Simple empty scoped class.
Definition: TypeTools.hpp:26
CLI::Transformer::Transformer
Transformer(T mapping, F filter_function)
Definition: Validators.hpp:782
CLI::detail::ExistingPathValidator::ExistingPathValidator
ExistingPathValidator()
Definition: Validators.hpp:373
CLI::detail::overflowCheck
std::enable_if< std::is_signed< T >::value, T >::type overflowCheck(const T &a, const T &b)
Do a check for overflow on signed numbers.
Definition: Validators.hpp:655
CLI::AsSizeValue::AsSizeValue
AsSizeValue(bool kb_is_1000)
Definition: Validators.hpp:1067
CLI::Validator
Some validators that are provided.
Definition: Validators.hpp:74
CLI::Validator::operator&
Validator operator&(const Validator &other) const
Definition: Validators.hpp:195
CLI::Validator::get_application_index
int get_application_index() const
Get the current value of the application index.
Definition: Validators.hpp:186
CLI::Bound::Bound
Bound(T max)
Range of one value is 0 to value.
Definition: Validators.hpp:552
CLI::detail::has_find
Definition: Validators.hpp:602
CLI::detail::split
std::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition: StringTools.hpp:42
CLI::AsNumberWithUnit::Options
Options
Definition: Validators.hpp:931
CLI::Transformer::Transformer
Transformer(std::initializer_list< std::pair< std::string, std::string >> values, Args &&... args)
This allows in-place construction.
Definition: Validators.hpp:774
TypeTools.hpp
CLI::detail::PositiveNumber
Validate the argument is a number and greater than 0.
Definition: Validators.hpp:423
CLI::Validator::func_
std::function< std::string(std::string &)> func_
Definition: Validators.hpp:81
CLI::Validator::name
Validator name(std::string validator_name) const
Specify the type string.
Definition: Validators.hpp:150
CLI::enable_if_t
typename std::enable_if< B, T >::type enable_if_t
Definition: TypeTools.hpp:37
CLI::detail::has_find::value
static const auto value
Definition: Validators.hpp:607
CLI::AsNumberWithUnit::AsNumberWithUnit
AsNumberWithUnit(std::map< std::string, Number > mapping, Options opts=DEFAULT, const std::string &unit_name="UNIT")
Definition: Validators.hpp:940
CLI::Validator::get_modifying
bool get_modifying() const
Get a boolean if the validator is allowed to modify the input returns true if it can modify the input...
Definition: Validators.hpp:191
CLI::AsSizeValue
Definition: Validators.hpp:1056
CLI::Transformer::filter_fn_t
std::function< std::string(std::string)> filter_fn_t
Definition: Validators.hpp:770
CLI::CustomValidator
Class wrapping some of the accessors of Validator.
Definition: Validators.hpp:283
CLI::Validator::active_
bool active_
Enable for Validator to allow it to be disabled if need be.
Definition: Validators.hpp:87
CLI::detail::rtrim
std::string & rtrim(std::string &str)
Trim whitespace from right of string.
Definition: StringTools.hpp:115
CLI::detail::generate_set
std::string generate_set(const T &set)
Generate a string representation of a set.
Definition: Validators.hpp:569
CLI::Range
Produce a range (factory). Min and max are inclusive.
Definition: Validators.hpp:498
CLI::CheckedTransformer
translate named items to other or a value set
Definition: Validators.hpp:826
CLI::detail::join
std::string join(const T &v, std::string delim=",")
Simple function to join a string.
Definition: StringTools.hpp:59
CLI::detail::pair_adaptor::first_type
typename std::remove_const< value_type >::type first_type
Definition: TypeTools.hpp:104
CLI::Validator::get_name
const std::string & get_name() const
Get the name of the Validator.
Definition: Validators.hpp:156
CLI::detail::check_path
path_type check_path(const char *file) noexcept
get the type of the path from a file name
Definition: Validators.hpp:321
CLI::detail::path_type::nonexistent
@ nonexistent
CLI::detail::smart_deref
auto smart_deref(T value) -> decltype(*value)
Definition: Validators.hpp:558
CLI::detail::to_string
auto to_string(T &&value) -> decltype(std::forward< T >(value))
Convert an object to a string (directly forward if this can become a string)
Definition: TypeTools.hpp:226
CLI::detail::PositiveNumber::PositiveNumber
PositiveNumber()
Definition: Validators.hpp:425
CLI::detail::IPV4Validator
Validate the given string is a legal ipv4 address.
Definition: Validators.hpp:399
CLI::detail::ExistingDirectoryValidator
Check for an existing directory (returns error message if check fails)
Definition: Validators.hpp:354
CLI::detail::lexical_cast
bool lexical_cast(const std::string &input, T &output)
Signed integers.
Definition: TypeTools.hpp:609
CLI::IsMember::IsMember
IsMember(T &&set)
This checks to see if an item is in a set (empty function)
Definition: Validators.hpp:707
CLI::Transformer::Transformer
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: Validators.hpp:818
CLI
Definition: App.hpp:32
CLI::detail::pair_adaptor::first
static auto first(Q &&pair_value) -> decltype(std::forward< Q >(pair_value))
Get the first value (really just the underlying value)
Definition: TypeTools.hpp:108
CLI::Validator::application_index
Validator application_index(int app_index) const
Specify the application index of a validator.
Definition: Validators.hpp:180
CLI::detail::search
auto search(const T &set, const V &val) -> std::pair< bool, decltype(std::begin(detail::smart_deref(set)))>
A search function.
Definition: Validators.hpp:613
CLI::detail::Number
Validate the argument is a number.
Definition: Validators.hpp:456
CLI::detail::split_program_name
std::pair< std::string, std::string > split_program_name(std::string commandline)
Definition: Validators.hpp:1112
CLI::detail::dummy
constexpr enabler dummy
An instance to use in EnableIf.
Definition: TypeTools.hpp:29
CLI::detail::ltrim
std::string & ltrim(std::string &str)
Trim whitespace from left of string.
Definition: StringTools.hpp:101
CLI::Validator::name_
std::string name_
The name for search purposes of the Validator.
Definition: Validators.hpp:83
CLI::ignore_case
std::string ignore_case(std::string item)
Helper function to allow ignore_case to be passed to IsMember or Transform.
Definition: Validators.hpp:902
CLI::detail::ExistingFileValidator
Check for an existing file (returns error message if check fails)
Definition: Validators.hpp:337
CLI::CheckedTransformer::CheckedTransformer
CheckedTransformer(T mapping)
direct map of std::string to std::string
Definition: Validators.hpp:836
CLI::ExistingDirectory
const detail::ExistingDirectoryValidator ExistingDirectory
Check for an existing directory (returns error message if check fails)
Definition: Validators.hpp:477
CLI::detail::path_type::directory
@ directory
CLI::ignore_underscore
std::string ignore_underscore(std::string item)
Helper function to allow ignore_underscore to be passed to IsMember or Transform.
Definition: Validators.hpp:905
CLI::Transformer
Translate named items to other or a value set.
Definition: Validators.hpp:768
CLI::detail::path_type::file
@ file
CLI::detail::has_find::type
std::integral_constant< bool, value > type
Definition: Validators.hpp:608
CLI::Validator::application_index
Validator & application_index(int app_index)
Specify the application index of a validator.
Definition: Validators.hpp:175
CLI::detail::to_lower
std::string to_lower(std::string str)
Return a lower case version of a string.
Definition: StringTools.hpp:199
CLI::Validator::operator()
std::string operator()(std::string &str) const
Definition: Validators.hpp:106
CLI::CheckedTransformer::CheckedTransformer
CheckedTransformer(T mapping, F filter_function)
Definition: Validators.hpp:840
CLI::ValidIPV4
const detail::IPV4Validator ValidIPV4
Check for an IP4 address.
Definition: Validators.hpp:486
CLI::IsMember::IsMember
IsMember(std::initializer_list< T > values, Args &&... args)
This allows in-place construction using an initializer list.
Definition: Validators.hpp:703
CLI::AsNumberWithUnit::UNIT_OPTIONAL
@ UNIT_OPTIONAL
Definition: Validators.hpp:934
CLI::detail::NonexistentPathValidator
Check for an non-existing path.
Definition: Validators.hpp:385
CLI::AsNumberWithUnit::CASE_INSENSITIVE
@ CASE_INSENSITIVE
Definition: Validators.hpp:933
CLI::detail::remove_underscore
std::string remove_underscore(std::string str)
remove underscores from a string
Definition: StringTools.hpp:207
CLI::IsMember
Verify items are in a set.
Definition: Validators.hpp:697
CLI::AsNumberWithUnit::CASE_SENSITIVE
@ CASE_SENSITIVE
Definition: Validators.hpp:932
CLI::ValidationError
Thrown when validation of results fails.
Definition: Error.hpp:202
StringTools.hpp
CLI::detail::trim
std::string & trim(std::string &str)
Trim whitespace from string.
Definition: StringTools.hpp:130
CLI::Transformer::Transformer
Transformer(T &&mapping)
direct map of std::string to std::string
Definition: Validators.hpp:778
CLI::detail::NonNegativeNumber::NonNegativeNumber
NonNegativeNumber()
Definition: Validators.hpp:441
Macros.hpp
CLI::ExistingPath
const detail::ExistingPathValidator ExistingPath
Check for an existing path.
Definition: Validators.hpp:480
CLI::detail::ExistingFileValidator::ExistingFileValidator
ExistingFileValidator()
Definition: Validators.hpp:339
CLI::AsNumberWithUnit
Definition: Validators.hpp:925
CLI::detail::pair_adaptor::value_type
typename T::value_type value_type
Definition: TypeTools.hpp:103
CLI::AsSizeValue::result_t
std::uint64_t result_t
Definition: Validators.hpp:1058
CLI::detail::element_type::type
T type
Definition: TypeTools.hpp:91
CLI::IsMember::IsMember
IsMember(T set, F filter_function)
Definition: Validators.hpp:711
CLI::TransformPairs
std::vector< std::pair< std::string, T > > TransformPairs
definition of the default transformation object
Definition: Validators.hpp:765
CLI::detail::Number::Number
Number()
Definition: Validators.hpp:458
CLI::Validator::operation
Validator & operation(std::function< std::string(std::string &)> op)
Set the Validator operation function.
Definition: Validators.hpp:100
CLI::Bound::Bound
Bound(T min, T max)
Definition: Validators.hpp:531
CLI::detail::pair_adaptor
Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost ...
Definition: TypeTools.hpp:102
CLI::ExistingFile
const detail::ExistingFileValidator ExistingFile
Check for existing file (returns error message if check fails)
Definition: Validators.hpp:474
CLI::Validator::name
Validator & name(std::string validator_name)
Specify the type string.
Definition: Validators.hpp:145
CLI::detail::NonNegativeNumber
Validate the argument is a number and greater than or equal to 0.
Definition: Validators.hpp:439
CLI::Validator::operator!
Validator operator!() const
Create a validator that fails when a given validator succeeds.
Definition: Validators.hpp:243
CLI::Validator::non_modifying
Validator & non_modifying(bool no_modify=true)
Specify whether the Validator can be modifying or not.
Definition: Validators.hpp:170
CLI::Validator::non_modifying_
bool non_modifying_
specify that a validator should not modify the input
Definition: Validators.hpp:89
CLI::detail::value_string
std::string value_string(const T &value)
get a string as a convertible value for arithmetic types
Definition: TypeTools.hpp:294
CLI::detail::checked_multiply
std::enable_if< std::is_integral< T >::value, bool >::type checked_multiply(T &a, T b)
Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
Definition: Validators.hpp:669
CLI::NonexistentPath
const detail::NonexistentPathValidator NonexistentPath
Check for an non-existing path.
Definition: Validators.hpp:483
CLI::detail::ExistingPathValidator
Check for an existing path.
Definition: Validators.hpp:371
CLI::AsNumberWithUnit::DEFAULT
@ DEFAULT
Definition: Validators.hpp:936
CLI::CheckedTransformer::filter_fn_t
std::function< std::string(std::string)> filter_fn_t
Definition: Validators.hpp:828
CLI::CheckedTransformer::CheckedTransformer
CheckedTransformer(std::initializer_list< std::pair< std::string, std::string >> values, Args &&... args)
This allows in-place construction.
Definition: Validators.hpp:832
CLI::Validator::Validator
Validator(std::function< std::string(std::string &)> op, std::string validator_desc, std::string validator_name="")
Construct Validator from basic information.
Definition: Validators.hpp:96
CLI::Validator::operator()
std::string operator()(const std::string &str) const
Definition: Validators.hpp:121
CLI::IsMember::filter_fn_t
std::function< std::string(std::string)> filter_fn_t
Definition: Validators.hpp:699
CLI::Validator::active
Validator active(bool active_val=true) const
Specify whether the Validator is active or not.
Definition: Validators.hpp:163
CLI::detail::isalpha
bool isalpha(const std::string &str)
Verify that str consists of letters only.
Definition: StringTools.hpp:194
CLI::ExitCodes::ValidationError
@ ValidationError
CLI::detail::IPV4Validator::IPV4Validator
IPV4Validator()
Definition: Validators.hpp:401
CLI::NonNegativeNumber
const detail::NonNegativeNumber NonNegativeNumber
Check for a non-negative number.
Definition: Validators.hpp:492
CLI::detail::NonexistentPathValidator::NonexistentPathValidator
NonexistentPathValidator()
Definition: Validators.hpp:387
CLI::Validator::description
Validator & description(std::string validator_desc)
Specify the type string.
Definition: Validators.hpp:127
CLI::Validator::application_index_
int application_index_
A Validator will only apply to an indexed value (-1 is all elements)
Definition: Validators.hpp:85
CLI::detail::path_type
path_type
CLI enumeration of different file types.
Definition: Validators.hpp:292
CLI::Validator::Validator
Validator()=default
CLI::IsMember::IsMember
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: Validators.hpp:757
CLI::Validator::Validator
Validator(std::string validator_desc)
Construct a Validator with just the description string.
Definition: Validators.hpp:94
CLI::Validator::desc_function_
std::function< std::string()> desc_function_
This is the description function, if empty the description_ will be used.
Definition: Validators.hpp:77
CLI::detail::generate_map
std::string generate_map(const T &map, bool key_only=false)
Generate a string representation of a map.
Definition: Validators.hpp:582