From cfafafd7ffe2354d77b53fb60639d3d448d5b329 Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Sat, 28 Feb 2026 16:16:01 -0600 Subject: [PATCH] Update vendored/CLI11 to v2.6.2 --- vendored/CLI11/CLI11.hpp | 2733 ++++++++++++++++++++++++-------------- 1 file changed, 1711 insertions(+), 1022 deletions(-) diff --git a/vendored/CLI11/CLI11.hpp b/vendored/CLI11/CLI11.hpp index 9fa9cc02..4524c543 100644 --- a/vendored/CLI11/CLI11.hpp +++ b/vendored/CLI11/CLI11.hpp @@ -1,11 +1,11 @@ -// CLI11: Version 2.5.0 +// CLI11: Version 2.6.2 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v2.5.0 +// from: v2.6.2 // -// CLI11 2.5.0 Copyright (c) 2017-2025 University of Cincinnati, developed by Henry +// CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without @@ -65,9 +65,9 @@ #define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 5 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "2.5.0" +#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.6.2" @@ -184,6 +184,32 @@ #endif #endif +/** rtti enabled */ +#ifndef CLI11_HAS_RTTI +#if defined(__GXX_RTTI) && __GXX_RTTI == 1 +// gcc +#define CLI11_HAS_RTTI 1 +#elif defined(_CPPRTTI) && _CPPRTTI == 1 +// msvc +#define CLI11_HAS_RTTI 1 +#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1 +// intel +#define CLI11_HAS_RTTI 0 +#elif defined(__has_feature) +// clang and other newer compilers +#if __has_feature(cxx_rtti) +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#elif defined(__RTTI) || defined(__INTEL_RTTI__) +// more intel and some other compilers +#define CLI11_HAS_RTTI 1 +#else +#define CLI11_HAS_RTTI 0 +#endif +#endif + /** disable deprecations */ #if defined(__GNUC__) // GCC or clang #define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") @@ -212,6 +238,13 @@ #define CLI11_INLINE inline #endif +/** Module inline to support module operations**/ +#if defined CLI11_CPP17 +#define CLI11_MODULE_INLINE inline +#else +#define CLI11_MODULE_INLINE static +#endif + #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 @@ -469,7 +502,8 @@ namespace enums { template ::value>::type> std::ostream &operator<<(std::ostream &in, const T &item) { // make sure this is out of the detail namespace otherwise it won't be found when needed - return in << static_cast::type>(item); + // https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number + return in << +static_cast::type>(item); } } // namespace enums @@ -480,7 +514,7 @@ using enums::operator<<; namespace detail { /// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not /// produce overflow for some expected uses -constexpr int expected_max_vector_size{1 << 29}; +CLI11_MODULE_INLINE constexpr int expected_max_vector_size{1 << 29}; // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim CLI11_INLINE std::vector split(const std::string &s, char delim); @@ -600,14 +634,12 @@ CLI11_INLINE bool valid_name_string(const std::string &str); /// Verify an app name inline bool valid_alias_name_string(const std::string &str) { - static const std::string badChars(std::string("\n") + '\0'); - return (str.find_first_of(badChars) == std::string::npos); + return ((str.find_first_of('\n') == std::string::npos) && (str.find_first_of('\0') == std::string::npos)); } /// check if a string is a container segment separator (empty or "%%") inline bool is_separator(const std::string &str) { - static const std::string sep("%%"); - return (str.empty() || str == sep); + return (str.empty() || (str.size() == 2 && str[0] == '%' && str[1] == '%')); } /// Verify that str consists of letters only @@ -629,6 +661,10 @@ inline std::string remove_underscore(std::string str) { return str; } +/// @brief get valid group separators _' + local separator if different +/// @return a string containing the group separators +CLI11_INLINE std::string get_group_separators(); + /// Find and replace a substring with another substring CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); @@ -686,7 +722,7 @@ CLI11_INLINE std::string add_escaped_characters(const std::string &str); CLI11_INLINE std::string remove_escaped_characters(const std::string &str); /// generate a string with all non printable characters escaped to hex codes -CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force = false); CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); @@ -694,7 +730,10 @@ CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); /// process a quoted string, remove the quotes and if appropriate handle escaped characters -CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); +CLI11_INLINE bool process_quoted_string(std::string &str, + char string_char = '\"', + char literal_char = '\'', + bool disable_secondary_array_processing = false); /// This function formats the given text as a paragraph with fixed width and applies correct line wrapping /// with a custom line prefix. The paragraph will get streamed to the given ostream. @@ -774,7 +813,7 @@ CLI11_INLINE std::string &remove_outer(std::string &str, char key) { CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { std::string::size_type n = 0; while(n != std::string::npos && n < input.size()) { - n = input.find('\n', n); + n = input.find_first_of("\r\n", n); if(n != std::string::npos) { input = input.substr(0, n + 1) + leader + input.substr(n + 1); n += leader.size(); @@ -811,6 +850,15 @@ CLI11_INLINE bool valid_name_string(const std::string &str) { return true; } +CLI11_INLINE std::string get_group_separators() { + std::string separators{"_'"}; +#if CLI11_HAS_RTTI != 0 + char group_separator = std::use_facet>(std::locale()).thousands_sep(); + separators.push_back(group_separator); +#endif + return separators; +} + CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { std::size_t start_pos = 0; @@ -864,10 +912,10 @@ find_member(std::string name, const std::vector names, bool ignore_ return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } -static const std::string escapedChars("\b\t\n\f\r\"\\"); -static const std::string escapedCharsCode("btnfr\"\\"); -static const std::string bracketChars{"\"'`[(<{"}; -static const std::string matchBracketChars("\"'`])>}"); +CLI11_MODULE_INLINE const std::string &escapedChars("\b\t\n\f\r\"\\"); +CLI11_MODULE_INLINE const std::string &escapedCharsCode("btnfr\"\\"); +CLI11_MODULE_INLINE const std::string &bracketChars("\"'`[(<{"); +CLI11_MODULE_INLINE const std::string &matchBracketChars("\"'`])>}"); CLI11_INLINE bool has_escapable_character(const std::string &str) { return (str.find_first_of(escapedChars) != std::string::npos); @@ -1015,7 +1063,11 @@ CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t star return close_string_quote(str, start, closure_char); case 1: case 2: +#if defined(_MSC_VER) && _MSC_VER < 1920 + case(std::size_t)-1: +#else case std::string::npos: +#endif return close_literal_quote(str, start, closure_char); default: break; @@ -1106,7 +1158,7 @@ CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { return offset + 1; } -CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force) { // s is our escaped output string std::string escaped_string{}; // loop through all characters @@ -1133,7 +1185,7 @@ CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escap escaped_string.push_back(c); } } - if(escaped_string != string_to_escape) { + if(escaped_string != string_to_escape || force) { auto sqLoc = escaped_string.find('\''); while(sqLoc != std::string::npos) { escaped_string[sqLoc] = '\\'; @@ -1219,13 +1271,15 @@ CLI11_INLINE void handle_secondary_array(std::string &str) { } } -CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { +CLI11_INLINE bool +process_quoted_string(std::string &str, char string_char, char literal_char, bool disable_secondary_array_processing) { if(str.size() <= 1) { return false; } if(detail::is_binary_escaped_string(str)) { str = detail::extract_binary_string(str); - handle_secondary_array(str); + if(!disable_secondary_array_processing) + handle_secondary_array(str); return true; } if(str.front() == string_char && str.back() == string_char) { @@ -1233,23 +1287,25 @@ CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char if(str.find_first_of('\\') != std::string::npos) { str = detail::remove_escaped_characters(str); } - handle_secondary_array(str); + if(!disable_secondary_array_processing) + handle_secondary_array(str); return true; } if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { detail::remove_outer(str, str.front()); - handle_secondary_array(str); + if(!disable_secondary_array_processing) + handle_secondary_array(str); return true; } return false; } std::string get_environment_value(const std::string &env_name) { - char *buffer = nullptr; std::string ename_string; #ifdef _MSC_VER // Windows version + char *buffer = nullptr; std::size_t sz = 0; if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { ename_string = std::string(buffer); @@ -1257,6 +1313,9 @@ std::string get_environment_value(const std::string &env_name) { } #else // This also works on Windows, but gives a warning + + // MISRA static analysis need. MISRACPP2023-25_5_2-a-1 + const char *buffer = nullptr; buffer = std::getenv(env_name.c_str()); if(buffer != nullptr) { ename_string = std::string(buffer); @@ -1281,13 +1340,17 @@ CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, std::size_t charsWritten = 0; while(iss >> word) { - if(word.length() + charsWritten > paragraphWidth) { + if(charsWritten > 0 && (word.length() + 1 + charsWritten > paragraphWidth)) { out << '\n' << linePrefix; charsWritten = 0; } - - out << word << " "; - charsWritten += word.length() + 1; + if(charsWritten == 0) { + out << word; + charsWritten += word.length(); + } else { + out << ' ' << word; + charsWritten += word.length() + 1; + } } if(!lss.eof()) @@ -1318,7 +1381,7 @@ CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, /// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, /// int values from e.get_error_code(). -enum class ExitCodes { +enum class ExitCodes : int { Success = 0, IncorrectConstruction = 100, BadNameString, @@ -1552,7 +1615,7 @@ class ArgumentMismatch : public ParseError { std::to_string(received)); } static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { - return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + std::to_string(received)); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { @@ -1587,13 +1650,13 @@ class ExtrasError : public ParseError { explicit ExtrasError(std::vector args) : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::join(args, " "), ExitCodes::ExtrasError) {} ExtrasError(const std::string &name, std::vector args) : ExtrasError(name, (args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::join(args, " "), ExitCodes::ExtrasError) {} }; @@ -1644,10 +1707,10 @@ class OptionNotFound : public Error { namespace detail { // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class -enum class enabler {}; +enum class enabler : std::uint8_t {}; /// An instance to use in EnableIf -constexpr enabler dummy = {}; +CLI11_MODULE_INLINE constexpr enabler dummy = {}; } // namespace detail /// A copy of enable_if_t from C++14, compatible with C++11. @@ -1998,7 +2061,7 @@ inline std::string to_string(T &&) { /// convert a readable container to a string template ::value && !std::is_constructible::value && - !is_ostreamable::value && is_readable_container::value, + !is_ostreamable::value && is_readable_container::value && !is_tuple_like::value, detail::enabler> = detail::dummy> inline std::string to_string(T &&variable) { auto cval = variable.begin(); @@ -2155,7 +2218,8 @@ template } /// Get the type size of the sum of type sizes for all the individual tuple types -template struct type_count::value>::type> { +template +struct type_count::value && !is_complex::value>::type> { static constexpr int value{tuple_type_size()}; }; @@ -2204,7 +2268,8 @@ template } /// Get the type size of the sum of type sizes for all the individual tuple types -template struct type_count_min::value>::type> { +template +struct type_count_min::value && !is_complex::value>::type> { static constexpr int value{tuple_type_size_min()}; }; @@ -2239,7 +2304,7 @@ struct expected_count::value }; // Enumeration of the different supported categorizations of objects -enum class object_category : int { +enum class object_category : std::uint8_t { char_value = 1, integral_value = 2, unsigned_integral = 4, @@ -2572,13 +2637,18 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); return (static_cast(output) == output_sll); } - // remove separators - if(input.find_first_of("_'") != std::string::npos) { + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + } + } return integral_conversion(nstring, output); } + if(std::isspace(static_cast(input.back()))) { return integral_conversion(trim_copy(input), output); } @@ -2630,12 +2700,16 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(1); return true; } - // remove separators and trailing spaces - if(input.find_first_of("_'") != std::string::npos) { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); - return integral_conversion(nstring, output); + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return integral_conversion(nstring, output); + } + } } if(std::isspace(static_cast(input.back()))) { return integral_conversion(trim_copy(input), output); @@ -2732,7 +2806,14 @@ bool lexical_cast(const std::string &input, T &output) { output = static_cast(input[0]); return true; } - return integral_conversion(input, output); + std::int8_t res{0}; + // we do it this way as some systems have char as signed and not, this ensures consistency in the way things are + // handled + bool result = integral_conversion(input, res); + if(result) { + output = static_cast(res); + } + return result; } /// Boolean values @@ -2771,12 +2852,16 @@ bool lexical_cast(const std::string &input, T &output) { } } - // remove separators - if(input.find_first_of("_'") != std::string::npos) { - std::string nstring = input; - nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); - nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); - return lexical_cast(nstring, output); + // remove separators if present + auto group_separators = get_group_separators(); + if(input.find_first_of(group_separators) != std::string::npos) { + for(auto &separator : group_separators) { + if(input.find_first_of(separator) != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); + return lexical_cast(nstring, output); + } + } } return false; } @@ -3108,7 +3193,7 @@ bool lexical_conversion(const std::vector &strings, AssignTo &outp using FirstType = typename std::remove_const::type>::type; using SecondType = typename std::tuple_element<1, ConvertTo>::type; FirstType v1; - SecondType v2; + SecondType v2{}; bool retval = lexical_assign(strings[0], v1); retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); if(retval) { @@ -3567,7 +3652,7 @@ get_names(const std::vector &input, bool allow_non_standard) { std::vector long_names; std::string pos_name; for(std::string name : input) { - if(name.length() == 0) { + if(name.empty()) { continue; } if(name.length() > 1 && name[0] == '-' && name[1] != '-') { @@ -3661,7 +3746,12 @@ class Config { /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure CLI11_NODISCARD std::vector from_file(const std::string &name) const { +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 + std::ifstream input{to_path(name)}; +#else std::ifstream input{name}; +#endif + if(!input.good()) throw FileError::Missing(name); @@ -3928,21 +4018,22 @@ class Validator { void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); }; -/// Class wrapping some of the accessors of Validator -class CustomValidator : public Validator { - public: -}; +/// Alias for Validator for custom Validator for clarity +using CustomValidator = Validator; + // The implementation of the built in validators is using the Validator class; // the user is only expected to use the const (static) versions (since there's no setup). // Therefore, this is in detail. namespace detail { /// CLI enumeration of different file types -enum class path_type { nonexistent, file, directory }; +enum class path_type : std::uint8_t { nonexistent, file, directory }; /// get the type of the path from a file name CLI11_INLINE path_type check_path(const char *file) noexcept; +// Static is not needed here, because global const implies static. + /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: @@ -3967,12 +4058,6 @@ class NonexistentPathValidator : public Validator { NonexistentPathValidator(); }; -/// Validate the given string is a legal ipv4 address -class IPV4Validator : public Validator { - public: - IPV4Validator(); -}; - class EscapedStringTransformer : public Validator { public: EscapedStringTransformer(); @@ -3980,43 +4065,20 @@ class EscapedStringTransformer : public Validator { } // namespace detail -// Static is not needed here, because global const implies static. - /// Check for existing file (returns error message if check fails) -const detail::ExistingFileValidator ExistingFile; +CLI11_MODULE_INLINE const detail::ExistingFileValidator ExistingFile; /// Check for an existing directory (returns error message if check fails) -const detail::ExistingDirectoryValidator ExistingDirectory; +CLI11_MODULE_INLINE const detail::ExistingDirectoryValidator ExistingDirectory; /// Check for an existing path -const detail::ExistingPathValidator ExistingPath; +CLI11_MODULE_INLINE const detail::ExistingPathValidator ExistingPath; /// Check for an non-existing path -const detail::NonexistentPathValidator NonexistentPath; - -/// Check for an IP4 address -const detail::IPV4Validator ValidIPV4; +CLI11_MODULE_INLINE const detail::NonexistentPathValidator NonexistentPath; /// convert escaped characters into their associated values -const detail::EscapedStringTransformer EscapedString; - -/// Validate the input as a particular type -template class TypeValidator : public Validator { - public: - explicit TypeValidator(const std::string &validator_name) - : Validator(validator_name, [](std::string &input_string) { - using CLI::detail::lexical_cast; - auto val = DesiredType(); - if(!lexical_cast(input_string, val)) { - return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); - } - return std::string(); - }) {} - TypeValidator() : TypeValidator(detail::type_name()) {} -}; - -/// Check for a number -const TypeValidator Number("NUMBER"); +CLI11_MODULE_INLINE const detail::EscapedStringTransformer EscapedString; /// Modify a path if the file is a particular default location, can be used as Check or transform /// with the error return optionally disabled @@ -4061,138 +4123,13 @@ class Range : public Validator { }; /// Check for a non negative number -const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); +CLI11_MODULE_INLINE const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); /// Check for a positive valued number (val>0.0), ::min here is the smallest positive number -const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); - -/// Produce a bounded range (factory). Min and max are inclusive. -class Bound : public Validator { - public: - /// This bounds a value with min and max inclusive. - /// - /// Note that the constructor is templated, but the struct is not, so C++17 is not - /// needed to provide nice syntax for Range(a,b). - template Bound(T min_val, T max_val) { - std::stringstream out; - out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; - description(out.str()); - - func_ = [min_val, max_val](std::string &input) { - using CLI::detail::lexical_cast; - T val; - bool converted = lexical_cast(input, val); - if(!converted) { - return std::string("Value ") + input + " could not be converted"; - } - if(val < min_val) - input = detail::to_string(min_val); - else if(val > max_val) - input = detail::to_string(max_val); - - return std::string{}; - }; - } - - /// Range of one value is 0 to value - template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} -}; +CLI11_MODULE_INLINE const + Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); namespace detail { -template ::type>::value, detail::enabler> = detail::dummy> -auto smart_deref(T value) -> decltype(*value) { - return *value; -} - -template < - typename T, - enable_if_t::type>::value, detail::enabler> = detail::dummy> -typename std::remove_reference::type &smart_deref(T &value) { - return value; -} -/// Generate a string representation of a set -template std::string generate_set(const T &set) { - using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair - std::string out(1, '{'); - out.append(detail::join( - detail::smart_deref(set), - [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, - ",")); - out.push_back('}'); - return out; -} - -/// Generate a string representation of a map -template std::string generate_map(const T &map, bool key_only = false) { - using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair - std::string out(1, '{'); - out.append(detail::join( - detail::smart_deref(map), - [key_only](const iteration_type_t &v) { - std::string res{detail::to_string(detail::pair_adaptor::first(v))}; - - if(!key_only) { - res.append("->"); - res += detail::to_string(detail::pair_adaptor::second(v)); - } - return res; - }, - ",")); - out.push_back('}'); - return out; -} - -template struct has_find { - template - static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); - template static auto test(...) -> decltype(std::false_type()); - - static const auto value = decltype(test(0))::value; - using type = std::integral_constant; -}; - -/// A search function -template ::value, detail::enabler> = detail::dummy> -auto search(const T &set, const V &val) -> std::pair { - using element_t = typename detail::element_type::type; - auto &setref = detail::smart_deref(set); - auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { - return (detail::pair_adaptor::first(v) == val); - }); - return {(it != std::end(setref)), it}; -} - -/// A search function that uses the built in find function -template ::value, detail::enabler> = detail::dummy> -auto search(const T &set, const V &val) -> std::pair { - auto &setref = detail::smart_deref(set); - auto it = setref.find(val); - return {(it != std::end(setref)), it}; -} - -/// A search function with a filter function -template -auto search(const T &set, const V &val, const std::function &filter_function) - -> std::pair { - using element_t = typename detail::element_type::type; - // do the potentially faster first search - auto res = search(set, val); - if((res.first) || (!(filter_function))) { - return res; - } - // if we haven't found it do the longer linear search with all the element translations - auto &setref = detail::smart_deref(set); - auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { - V a{detail::pair_adaptor::first(v)}; - a = filter_function(a); - return (a == val); - }); - return {(it != std::end(setref)), it}; -} - // the following suggestion was made by Nikita Ofitserov(@himikof) // done in templates to prevent compiler warnings on negation of unsigned numbers @@ -4236,404 +4173,6 @@ typename std::enable_if::value, bool>::type checked_mu a = c; return true; } - -} // namespace detail -/// Verify items are in a set -class IsMember : public Validator { - public: - using filter_fn_t = std::function; - - /// This allows in-place construction using an initializer list - template - IsMember(std::initializer_list values, Args &&...args) - : IsMember(std::vector(values), std::forward(args)...) {} - - /// This checks to see if an item is in a set (empty function) - template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} - - /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter - /// both sides of the comparison before computing the comparison. - template explicit IsMember(T set, F filter_function) { - - // Get the type of the contained item - requires a container have ::value_type - // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map - - using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones - // (const char * to std::string) - - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; - - // This is the type name for help, it will take the current version of the set contents - desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; - - // This is the function that validates - // It stores a copy of the set pointer-like, so shared_ptr will stay alive - func_ = [set, filter_fn](std::string &input) { - using CLI::detail::lexical_cast; - local_item_t b; - if(!lexical_cast(input, b)) { - throw ValidationError(input); // name is added later - } - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(set, b, filter_fn); - if(res.first) { - // Make sure the version in the input string is identical to the one in the set - if(filter_fn) { - input = detail::value_string(detail::pair_adaptor::first(*(res.second))); - } - - // Return empty error string (success) - return std::string{}; - } - - // If you reach this point, the result was not found - return input + " not in " + detail::generate_set(detail::smart_deref(set)); - }; - } - - /// You can pass in as many filter functions as you like, they nest (string only currently) - template - IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) - : IsMember( - std::forward(set), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} -}; - -/// definition of the default transformation object -template using TransformPairs = std::vector>; - -/// Translate named items to other or a value set -class Transformer : public Validator { - public: - using filter_fn_t = std::function; - - /// This allows in-place construction - template - Transformer(std::initializer_list> values, Args &&...args) - : Transformer(TransformPairs(values), std::forward(args)...) {} - - /// direct map of std::string to std::string - template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} - - /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter - /// both sides of the comparison before computing the comparison. - template explicit Transformer(T mapping, F filter_function) { - - static_assert(detail::pair_adaptor::type>::value, - "mapping must produce value pairs"); - // Get the type of the contained item - requires a container have ::value_type - // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map - using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones - // (const char * to std::string) - - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; - - // This is the type name for help, it will take the current version of the set contents - desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; - - func_ = [mapping, filter_fn](std::string &input) { - using CLI::detail::lexical_cast; - local_item_t b; - if(!lexical_cast(input, b)) { - return std::string(); - // there is no possible way we can match anything in the mapping if we can't convert so just return - } - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(mapping, b, filter_fn); - if(res.first) { - input = detail::value_string(detail::pair_adaptor::second(*res.second)); - } - return std::string{}; - }; - } - - /// You can pass in as many filter functions as you like, they nest - template - Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) - : Transformer( - std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} -}; - -/// translate named items to other or a value set -class CheckedTransformer : public Validator { - public: - using filter_fn_t = std::function; - - /// This allows in-place construction - template - CheckedTransformer(std::initializer_list> values, Args &&...args) - : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} - - /// direct map of std::string to std::string - template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} - - /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter - /// both sides of the comparison before computing the comparison. - template explicit CheckedTransformer(T mapping, F filter_function) { - - static_assert(detail::pair_adaptor::type>::value, - "mapping must produce value pairs"); - // Get the type of the contained item - requires a container have ::value_type - // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map - using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones - // (const char * to std::string) - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair - - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; - - auto tfunc = [mapping]() { - std::string out("value in "); - out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; - out += detail::join( - detail::smart_deref(mapping), - [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, - ","); - out.push_back('}'); - return out; - }; - - desc_function_ = tfunc; - - func_ = [mapping, tfunc, filter_fn](std::string &input) { - using CLI::detail::lexical_cast; - local_item_t b; - bool converted = lexical_cast(input, b); - if(converted) { - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(mapping, b, filter_fn); - if(res.first) { - input = detail::value_string(detail::pair_adaptor::second(*res.second)); - return std::string{}; - } - } - for(const auto &v : detail::smart_deref(mapping)) { - auto output_string = detail::value_string(detail::pair_adaptor::second(v)); - if(output_string == input) { - return std::string(); - } - } - - return "Check " + input + " " + tfunc() + " FAILED"; - }; - } - - /// You can pass in as many filter functions as you like, they nest - template - CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) - : CheckedTransformer( - std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} -}; - -/// Helper function to allow ignore_case to be passed to IsMember or Transform -inline std::string ignore_case(std::string item) { return detail::to_lower(item); } - -/// Helper function to allow ignore_underscore to be passed to IsMember or Transform -inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } - -/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform -inline std::string ignore_space(std::string item) { - item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); - item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); - return item; -} - -/// Multiply a number by a factor using given mapping. -/// Can be used to write transforms for SIZE or DURATION inputs. -/// -/// Example: -/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` -/// one can recognize inputs like "100", "12kb", "100 MB", -/// that will be automatically transformed to 100, 14448, 104857600. -/// -/// Output number type matches the type in the provided mapping. -/// Therefore, if it is required to interpret real inputs like "0.42 s", -/// the mapping should be of a type or . -class AsNumberWithUnit : public Validator { - public: - /// Adjust AsNumberWithUnit behavior. - /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. - /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError - /// if UNIT_REQUIRED is set and unit literal is not found. - enum Options { - CASE_SENSITIVE = 0, - CASE_INSENSITIVE = 1, - UNIT_OPTIONAL = 0, - UNIT_REQUIRED = 2, - DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL - }; - - template - explicit AsNumberWithUnit(std::map mapping, - Options opts = DEFAULT, - const std::string &unit_name = "UNIT") { - description(generate_description(unit_name, opts)); - validate_mapping(mapping, opts); - - // transform function - func_ = [mapping, opts](std::string &input) -> std::string { - Number num{}; - - detail::rtrim(input); - if(input.empty()) { - throw ValidationError("Input is empty"); - } - - // Find split position between number and prefix - auto unit_begin = input.end(); - while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { - --unit_begin; - } - - std::string unit{unit_begin, input.end()}; - input.resize(static_cast(std::distance(input.begin(), unit_begin))); - detail::trim(input); - - if(opts & UNIT_REQUIRED && unit.empty()) { - throw ValidationError("Missing mandatory unit"); - } - if(opts & CASE_INSENSITIVE) { - unit = detail::to_lower(unit); - } - if(unit.empty()) { - using CLI::detail::lexical_cast; - if(!lexical_cast(input, num)) { - throw ValidationError(std::string("Value ") + input + " could not be converted to " + - detail::type_name()); - } - // No need to modify input if no unit passed - return {}; - } - - // find corresponding factor - auto it = mapping.find(unit); - if(it == mapping.end()) { - throw ValidationError(unit + - " unit not recognized. " - "Allowed values: " + - detail::generate_map(mapping, true)); - } - - if(!input.empty()) { - using CLI::detail::lexical_cast; - bool converted = lexical_cast(input, num); - if(!converted) { - throw ValidationError(std::string("Value ") + input + " could not be converted to " + - detail::type_name()); - } - // perform safe multiplication - bool ok = detail::checked_multiply(num, it->second); - if(!ok) { - throw ValidationError(detail::to_string(num) + " multiplied by " + unit + - " factor would cause number overflow. Use smaller value."); - } - } else { - num = static_cast(it->second); - } - - input = detail::to_string(num); - - return {}; - }; - } - - private: - /// Check that mapping contains valid units. - /// Update mapping for CASE_INSENSITIVE mode. - template static void validate_mapping(std::map &mapping, Options opts) { - for(auto &kv : mapping) { - if(kv.first.empty()) { - throw ValidationError("Unit must not be empty."); - } - if(!detail::isalpha(kv.first)) { - throw ValidationError("Unit must contain only letters."); - } - } - - // make all units lowercase if CASE_INSENSITIVE - if(opts & CASE_INSENSITIVE) { - std::map lower_mapping; - for(auto &kv : mapping) { - auto s = detail::to_lower(kv.first); - if(lower_mapping.count(s)) { - throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + - s); - } - lower_mapping[detail::to_lower(kv.first)] = kv.second; - } - mapping = std::move(lower_mapping); - } - } - - /// Generate description like this: NUMBER [UNIT] - template static std::string generate_description(const std::string &name, Options opts) { - std::stringstream out; - out << detail::type_name() << ' '; - if(opts & UNIT_REQUIRED) { - out << name; - } else { - out << '[' << name << ']'; - } - return out.str(); - } -}; - -inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { - return static_cast(static_cast(a) | static_cast(b)); -} - -/// Converts a human-readable size string (with unit literal) to uin64_t size. -/// Example: -/// "100" => 100 -/// "1 b" => 100 -/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) -/// "10 KB" => 10240 -/// "10 kb" => 10240 -/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) -/// "10kb" => 10240 -/// "2 MB" => 2097152 -/// "2 EiB" => 2^61 // Units up to exibyte are supported -class AsSizeValue : public AsNumberWithUnit { - public: - using result_t = std::uint64_t; - - /// If kb_is_1000 is true, - /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 - /// (same applies to higher order units as well). - /// Otherwise, interpret all literals as factors of 1024. - /// The first option is formally correct, but - /// the second interpretation is more wide-spread - /// (see https://en.wikipedia.org/wiki/Binary_prefix). - explicit AsSizeValue(bool kb_is_1000); - - private: - /// Get mapping - static std::map init_mapping(bool kb_is_1000); - - /// Cache calculated mapping - static std::map get_mapping(bool kb_is_1000); -}; - -namespace detail { /// Split a string into a program name and command line arguments /// the string is assumed to contain a file name followed by other arguments /// the return value contains is a pair with the first argument containing the program name and the second @@ -4836,27 +4375,6 @@ CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("P }; } -CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { - func_ = [](std::string &ip_addr) { - auto result = CLI::detail::split(ip_addr, '.'); - if(result.size() != 4) { - return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; - } - int num = 0; - for(const auto &var : result) { - using CLI::detail::lexical_cast; - bool retval = lexical_cast(var, num); - if(!retval) { - return std::string("Failed parsing number (") + var + ')'; - } - if(num < 0 || num > 255) { - return std::string("Each IP number must be between 0 and 255 ") + var; - } - } - return std::string{}; - }; -} - CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { func_ = [](std::string &str) { try { @@ -4902,41 +4420,6 @@ CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool }; } -CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { - if(kb_is_1000) { - description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); - } else { - description("SIZE [b, kb(=1024b), ...]"); - } -} - -CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { - std::map m; - result_t k_factor = kb_is_1000 ? 1000 : 1024; - result_t ki_factor = 1024; - result_t k = 1; - result_t ki = 1; - m["b"] = 1; - for(std::string p : {"k", "m", "g", "t", "p", "e"}) { - k *= k_factor; - ki *= ki_factor; - m[p] = k; - m[p + "b"] = k; - m[p + "i"] = ki; - m[p + "ib"] = ki; - } - return m; -} - -CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { - if(kb_is_1000) { - static auto m = init_mapping(true); - return m; - } - static auto m = init_mapping(false); - return m; -} - namespace detail { CLI11_INLINE std::pair split_program_name(std::string commandline) { @@ -4990,6 +4473,719 @@ CLI11_INLINE std::pair split_program_name(std::string +// The implementation of the extra validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +} // namespace detail + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string{}; + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +// Static is not needed here, because global const implies static. + +/// Check for an IP4 address +CLI11_MODULE_INLINE const detail::IPV4Validator ValidIPV4; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + // NOLINTNEXTLINE + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +} // namespace detail + /// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { + return detail::value_string(detail::pair_adaptor::second(v)); + }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options : std::uint8_t { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +#if CLI11_HAS_FILESYSTEM +namespace detail { +enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 }; +class PermissionValidator : public Validator { + public: + explicit PermissionValidator(Permission permission); +}; +} // namespace detail + +/// Check that the file exist and available for read +const detail::PermissionValidator ReadPermissions(detail::Permission::read); + +/// Check that the file exist and available for write +const detail::PermissionValidator WritePermissions(detail::Permission::write); + +/// Check that the file exist and available for write +const detail::PermissionValidator ExecPermissions(detail::Permission::exec); +#endif + +#endif + + + +namespace detail { + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.'); + if(cdot != 3u) { + return std::string("Invalid IPV4 address: must have 3 separators"); + } + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +} // namespace detail + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail {} // namespace detail +/// @} + +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE PermissionValidator::PermissionValidator(Permission permission) { + std::filesystem::perms permission_code = std::filesystem::perms::none; + std::string permission_name; + switch(permission) { + case Permission::read: + permission_code = std::filesystem::perms::owner_read | std::filesystem::perms::group_read | + std::filesystem::perms::others_read; + permission_name = "read"; + break; + case Permission::write: + permission_code = std::filesystem::perms::owner_write | std::filesystem::perms::group_write | + std::filesystem::perms::others_write; + permission_name = "write"; + break; + case Permission::exec: + permission_code = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | + std::filesystem::perms::others_exec; + permission_name = "exec"; + break; + case Permission::none: + default: + permission_code = std::filesystem::perms::none; + break; + } + func_ = [permission_code](std::string &path) { + std::error_code ec; + auto p = std::filesystem::path(path); + if(!std::filesystem::exists(p, ec)) { + return std::string("Path does not exist: ") + path; + } + if(ec) { + return std::string("Error checking path: ") + ec.message(); // LCOV_EXCL_LINE + } + if(permission_code == std::filesystem::perms::none) { + return std::string{}; + } + auto perms = std::filesystem::status(p, ec).permissions(); + if(ec) { + return std::string("Error checking path status: ") + ec.message(); // LCOV_EXCL_LINE + } + if((perms & permission_code) == std::filesystem::perms::none) { + return std::string("Path does not have required permissions: ") + path; + } + return std::string{}; + }; + description("Path with " + permission_name + " permission"); +} +#endif + +} // namespace detail +#endif + + + class Option; class App; @@ -4998,7 +5194,7 @@ class App; /// This is passed in by App; all user classes must accept this as /// the second argument. -enum class AppFormatMode { +enum class AppFormatMode : std::uint8_t { Normal, ///< The normal, detailed help All, ///< A fully expanded help Sub, ///< Used when printed as part of expanded subcommand @@ -5016,6 +5212,9 @@ class FormatterBase { /// The width of the left column (options/flags/subcommands) std::size_t column_width_{30}; + /// The alignment ratio for long options within the left column + float long_option_alignment_ratio_{1 / 3.f}; + /// The width of the right column (description of options/flags/subcommands) std::size_t right_column_width_{65}; @@ -5025,6 +5224,14 @@ class FormatterBase { /// The width of the footer paragraph std::size_t footer_paragraph_width_{80}; + /// options controlling formatting for footer and descriptions + bool enable_description_formatting_{true}; + bool enable_footer_formatting_{true}; + + /// options controlling formatting of options + bool enable_option_defaults_{true}; + bool enable_option_type_names_{true}; + bool enable_default_flag_values_{true}; /// @brief The required help printout labels (user changeable) /// Values are Needs, Excludes, etc. std::map labels_{}; @@ -5050,12 +5257,19 @@ class FormatterBase { /// @name Setters ///@{ - /// Set the "REQUIRED" label + /// Set the "REQUIRED" or other labels void label(std::string key, std::string val) { labels_[key] = val; } /// Set the left column width (options/flags/subcommands) void column_width(std::size_t val) { column_width_ = val; } + /// Set the alignment ratio for long options within the left column + /// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character) + void long_option_alignment_ratio(float ratio) { + long_option_alignment_ratio_ = + (ratio >= 0.0f) ? ((ratio <= 1.0f) ? ratio : 1.0f / ratio) : ((ratio < -1.0f) ? 1.0f / (-ratio) : -ratio); + } + /// Set the right column width (description of options/flags/subcommands) void right_column_width(std::size_t val) { right_column_width_ = val; } @@ -5064,7 +5278,17 @@ class FormatterBase { /// Set the footer paragraph width void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } + /// enable formatting for description paragraph + void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; } + /// disable formatting for footer paragraph + void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; } + /// enable option defaults to be printed + void enable_option_defaults(bool value = true) { enable_option_defaults_ = value; } + /// enable option type names to be printed + void enable_option_type_names(bool value = true) { enable_option_type_names_ = value; } + /// enable default flag values to be printed + void enable_default_flag_values(bool value = true) { enable_default_flag_values_ = value; } ///@} /// @name Getters ///@{ @@ -5088,6 +5312,25 @@ class FormatterBase { /// Get the current footer paragraph width CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } + /// @brief Get the current alignment ratio for long options within the left column + /// @return + CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; } + + /// Get the current status of description paragraph formatting + CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; } + + /// Get the current status of whether footer paragraph formatting is enabled + CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; } + + /// Get the current status of whether option defaults are printed + CLI11_NODISCARD bool is_option_defaults_enabled() const { return enable_option_defaults_; } + + /// Get the current status of whether option type names are printed + CLI11_NODISCARD bool is_option_type_names_enabled() const { return enable_option_type_names_; } + + /// Get the current status of whether default flag values are printed + CLI11_NODISCARD bool is_default_flag_values_enabled() const { return enable_default_flag_values_; } + ///@} }; @@ -5187,8 +5430,11 @@ using callback_t = std::function; class Option; class App; +class ConfigBase; using Option_p = std::unique_ptr