From 060e4b91a0196e059d48817e81bd1e4349649104 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 | 2483 ++++++++++++++++++++++++-------------- 1 file changed, 1586 insertions(+), 897 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,49 +4173,512 @@ typename std::enable_if::value, bool>::type checked_mu a = c; return true; } +/// 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 +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); } // 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 +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} - using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones - // (const char * to std::string) +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; - // 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)); }; + newval._merge_description(*this, other, " AND "); - // 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); + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +// 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) { @@ -4332,7 +4732,7 @@ class Transformer : public Validator { 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) + // (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; @@ -4391,7 +4791,7 @@ class CheckedTransformer : public Validator { 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) + // (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 @@ -4402,7 +4802,9 @@ class CheckedTransformer : public Validator { 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)); }, + [](const iteration_type_t &v) { + return detail::value_string(detail::pair_adaptor::second(v)); + }, ","); out.push_back('}'); return out; @@ -4474,7 +4876,7 @@ class AsNumberWithUnit : public Validator { /// 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 { + enum Options : std::uint8_t { CASE_SENSITIVE = 0, CASE_INSENSITIVE = 1, UNIT_OPTIONAL = 0, @@ -4616,231 +5018,59 @@ 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 -/// everything else. -CLI11_INLINE std::pair split_program_name(std::string commandline); - -} // namespace detail -/// @} - - - - -CLI11_INLINE std::string Validator::operator()(std::string &str) const { - std::string retstring; - if(active_) { - if(non_modifying_) { - std::string value = str; - retstring = func_(value); - } else { - retstring = func_(str); - } - } - return retstring; -} - -CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { - Validator newval(*this); - newval.desc_function_ = [validator_desc]() { return validator_desc; }; - return newval; -} - -CLI11_INLINE Validator Validator::operator&(const Validator &other) const { - Validator newval; - - newval._merge_description(*this, other, " AND "); - - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - const std::function &f2 = other.func_; - - newval.func_ = [f1, f2](std::string &input) { - std::string s1 = f1(input); - std::string s2 = f2(input); - if(!s1.empty() && !s2.empty()) - return std::string("(") + s1 + ") AND (" + s2 + ")"; - return s1 + s2; - }; - - newval.active_ = active_ && other.active_; - newval.application_index_ = application_index_; - return newval; -} - -CLI11_INLINE Validator Validator::operator|(const Validator &other) const { - Validator newval; - - newval._merge_description(*this, other, " OR "); - - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - const std::function &f2 = other.func_; - - newval.func_ = [f1, f2](std::string &input) { - std::string s1 = f1(input); - std::string s2 = f2(input); - if(s1.empty() || s2.empty()) - return std::string(); - - return std::string("(") + s1 + ") OR (" + s2 + ")"; - }; - newval.active_ = active_ && other.active_; - newval.application_index_ = application_index_; - return newval; -} - -CLI11_INLINE Validator Validator::operator!() const { - Validator newval; - const std::function &dfunc1 = desc_function_; - newval.desc_function_ = [dfunc1]() { - auto str = dfunc1(); - return (!str.empty()) ? std::string("NOT ") + str : std::string{}; - }; - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - - newval.func_ = [f1, dfunc1](std::string &test) -> std::string { - std::string s1 = f1(test); - if(s1.empty()) { - return std::string("check ") + dfunc1() + " succeeded improperly"; - } - return std::string{}; - }; - newval.active_ = active_; - newval.application_index_ = application_index_; - return newval; -} - -CLI11_INLINE void -Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + /// 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); - const std::function &dfunc1 = val1.desc_function_; - const std::function &dfunc2 = val2.desc_function_; + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); - desc_function_ = [=]() { - std::string f1 = dfunc1(); - std::string f2 = dfunc2(); - if((f1.empty()) || (f2.empty())) { - return f1 + f2; - } - return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; - }; -} + /// 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 -#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -CLI11_INLINE path_type check_path(const char *file) noexcept { - std::error_code ec; - auto stat = std::filesystem::status(to_path(file), ec); - if(ec) { - return path_type::nonexistent; - } - switch(stat.type()) { - case std::filesystem::file_type::none: // LCOV_EXCL_LINE - case std::filesystem::file_type::not_found: - return path_type::nonexistent; // LCOV_EXCL_LINE - case std::filesystem::file_type::directory: - return path_type::directory; - case std::filesystem::file_type::symlink: - case std::filesystem::file_type::block: - case std::filesystem::file_type::character: - case std::filesystem::file_type::fifo: - case std::filesystem::file_type::socket: - case std::filesystem::file_type::regular: - case std::filesystem::file_type::unknown: - default: - return path_type::file; - } -} -#else -CLI11_INLINE path_type check_path(const char *file) noexcept { -#if defined(_MSC_VER) - struct __stat64 buffer; - if(_stat64(file, &buffer) == 0) { - return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; - } -#else - struct stat buffer; - if(stat(file, &buffer) == 0) { - return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; - } -#endif - return path_type::nonexistent; -} +/// 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 -CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "File does not exist: " + filename; - } - if(path_result == path_type::directory) { - return "File is actually a directory: " + filename; - } - return std::string(); - }; -} +#endif -CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "Directory does not exist: " + filename; - } - if(path_result == path_type::file) { - return "Directory is actually a file: " + filename; - } - return std::string(); - }; -} -CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "Path does not exist: " + filename; - } - return std::string(); - }; -} -CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result != path_type::nonexistent) { - return "Path already exists: " + filename; - } - return std::string(); - }; -} +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 + ')'; + return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')'; } int num = 0; for(const auto &var : result) { @@ -4857,51 +5087,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { }; } -CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { - func_ = [](std::string &str) { - try { - if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && - str.front() == str.back()) { - process_quoted_string(str); - } else if(str.find_first_of('\\') != std::string::npos) { - if(detail::is_binary_escaped_string(str)) { - str = detail::extract_binary_string(str); - } else { - str = remove_escaped_characters(str); - } - } - return std::string{}; - } catch(const std::invalid_argument &ia) { - return std::string(ia.what()); - } - }; -} } // namespace detail -CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) - : Validator("FILE") { - func_ = [default_path, enableErrorReturn](std::string &filename) { - auto path_result = detail::check_path(filename.c_str()); - if(path_result == detail::path_type::nonexistent) { - std::string test_file_path = default_path; - if(default_path.back() != '/' && default_path.back() != '\\') { - // Add folder separator - test_file_path += '/'; - } - test_file_path.append(filename); - path_result = detail::check_path(test_file_path.c_str()); - if(path_result == detail::path_type::file) { - filename = test_file_path; - } else { - if(enableErrorReturn) { - return "File does not exist: " + filename; - } - } - } - return std::string{}; - }; -} - 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), ...]"); @@ -4937,56 +5124,65 @@ CLI11_INLINE std::map AsSizeValue::get_mappi return m; } -namespace detail { +namespace detail {} // namespace detail +/// @} -CLI11_INLINE std::pair split_program_name(std::string commandline) { - // try to determine the programName - std::pair vals; - trim(commandline); - auto esp = commandline.find_first_of(' ', 1); - while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { - esp = commandline.find_first_of(' ', esp + 1); - if(esp == std::string::npos) { - // if we have reached the end and haven't found a valid file just assume the first argument is the - // program name - if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { - bool embeddedQuote = false; - auto keyChar = commandline[0]; - auto end = commandline.find_first_of(keyChar, 1); - while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes - end = commandline.find_first_of(keyChar, end + 1); - embeddedQuote = true; - } - if(end != std::string::npos) { - vals.first = commandline.substr(1, end - 1); - esp = end + 1; - if(embeddedQuote) { - vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); - } - } else { - esp = commandline.find_first_of(' ', 1); - } - } else { - esp = commandline.find_first_of(' ', 1); - } +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 +// new extra validators +namespace detail { - break; - } - } - if(vals.first.empty()) { - vals.first = commandline.substr(0, esp); - rtrim(vals.first); +#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; } - - // strip the program name - vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; - ltrim(vals.second); - return vals; + 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 @@ -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