From d93539a1865c7daa3f11b629296d4d580f77b7f2 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 28 Mar 2022 10:55:36 -0700 Subject: [PATCH] update third party libs and workflow files to checkout v3 --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/static-analyzers.yml | 4 +- .github/workflows/tests.yml | 2 +- ThirdParty/CLI11.hpp | 1355 +- ThirdParty/json.hpp | 29289 ++++++++++++----------- 5 files changed, 16330 insertions(+), 14322 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 24cb9ef0..8816f7a9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/static-analyzers.yml b/.github/workflows/static-analyzers.yml index 89fbd4bc..ff03c9c6 100644 --- a/.github/workflows/static-analyzers.yml +++ b/.github/workflows/static-analyzers.yml @@ -14,7 +14,7 @@ jobs: container: helics/buildenv:cpplint steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run cpplint run: cpplint --counting=detailed --recursive units test webserver converter cppcheck: @@ -22,6 +22,6 @@ jobs: container: helics/buildenv:cppcheck2 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run cppcheck run: cppcheck --enable=performance,portability,unusedFunction --suppressions-list=config/cppcheck_suppressions.txt --error-exitcode=-4 -i ThirdParty -i FuzzTargets -i docs -i config . \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f0a67348..1743c653 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: CMake config check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Check CMake 3.4 with: diff --git a/ThirdParty/CLI11.hpp b/ThirdParty/CLI11.hpp index e603022b..368c70f2 100644 --- a/ThirdParty/CLI11.hpp +++ b/ThirdParty/CLI11.hpp @@ -1,20 +1,16 @@ -#pragma once - -// CLI11: Version 1.9.0 +// CLI11: Version 2.1.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: v1.9.0-58-g825291a +// from: v2.2.0 // -// From LICENSE: -// -// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// CLI11 2.1.2 Copyright (c) 2017-2022 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 // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, @@ -23,7 +19,7 @@ // 3. Neither the name of the copyright holder nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -35,48 +31,42 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once // Standard combined includes: - +#include #include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include +#include +#include #include +#include +#include #include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include #include -// Verbatim copy from CLI/Version.hpp: - - -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 9 +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 2 #define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.9.0" +#define CLI11_VERSION "2.2.0" -// Verbatim copy from CLI/Macros.hpp: - - -// The following version macro is very similar to the one in PyBind11 +// The following version macro is very similar to the one in pybind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) #if __cplusplus >= 201402L #define CLI11_CPP14 @@ -94,7 +84,7 @@ #define CLI11_CPP14 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 #define CLI11_CPP17 -#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 #endif #endif @@ -109,12 +99,25 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif -// Verbatim copy from CLI/Validators.hpp: - - // C standard library // Only needed for existence checking #if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM @@ -122,6 +125,9 @@ // Filesystem cannot be used if targeting macOS < 10.15 #if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 #define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 #else #include #if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 @@ -149,18 +155,9 @@ -// From CLI/Version.hpp: - - - -// From CLI/Macros.hpp: - - - -// From CLI/StringTools.hpp: - namespace CLI { + /// Include the items in this namespace to get free conversion of enums to/from streams. /// (This is available inside CLI as well, so CLI11 will use this without a using statement). namespace enums { @@ -220,10 +217,14 @@ std::string join(const T &v, Callable func, std::string delim = ",") { std::ostringstream s; auto beg = std::begin(v); auto end = std::end(v); - if(beg != end) - s << func(*beg++); + auto loc = s.tellp(); while(beg != end) { - s << delim << func(*beg++); + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); } return s.str(); } @@ -293,13 +294,29 @@ inline std::string &remove_quotes(std::string &str) { return str; } +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +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); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) inline std::string trim_copy(const std::string &str, const std::string &filter) { std::string s = str; return trim(s, filter); } /// Print a two part "help" string -inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) { +inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; if(!description.empty()) { @@ -316,24 +333,54 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin return out; } -/// Verify the first character of an option -template bool valid_first_char(T c) { - return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; +/// Print subcommand aliases +inline std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; } +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); } + /// Verify following characters of an option -template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and \n would just be annoying to deal with in many places allowing space here has too much potential for + // inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n'); +} -/// Verify an option name +/// Verify an option/subcommand name inline bool valid_name_string(const std::string &str) { - if(str.empty() || !valid_first_char(str[0])) + if(str.empty() || !valid_first_char(str[0])) { return false; - for(auto c : str.substr(1)) - if(!valid_later_char(c)) + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) return false; return true; } +/// 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); +} + /// check if a string is a container segment separator (empty or "%%") inline bool is_separator(const std::string &str) { static const std::string sep("%%"); @@ -378,7 +425,7 @@ inline bool has_default_flag_values(const std::string &flags) { } inline void remove_default_flag_values(std::string &flags) { - auto loc = flags.find_first_of('{'); + auto loc = flags.find_first_of('{', 2); while(loc != std::string::npos) { auto finish = flags.find_first_of("},", loc + 1); if((finish != std::string::npos) && (flags[finish] == '}')) { @@ -454,7 +501,12 @@ inline std::vector split_up(std::string str, char delimiter = '\0') } if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } else { output.push_back(str.substr(1)); str = ""; @@ -480,22 +532,6 @@ inline std::vector split_up(std::string str, char delimiter = '\0') return output; } -/// Add a leader to the beginning of all new lines (nothing is added -/// at the start of the first line). `"; "` would be for ini files -/// -/// Can't use Regex, or this would be a subs. -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); - if(n != std::string::npos) { - input = input.substr(0, n + 1) + leader + input.substr(n + 1); - n += leader.size(); - } - } - return input; -} - /// This function detects an equal or colon followed by an escaped quote after an argument /// then modifies the string to replace the equality with a space. This is needed /// to allow the split up function to work properly and is intended to be used with the find_and_modify function @@ -526,11 +562,8 @@ inline std::string &add_quotes_if_needed(std::string &str) { } // namespace detail -} // namespace CLI -// From CLI/Error.hpp: -namespace CLI { // Use one of these on all error classes. // These are temporary and are undef'd at the end of this file. @@ -691,7 +724,7 @@ class CallForVersion : public Success { : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} }; -/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} @@ -789,6 +822,10 @@ class ArgumentMismatch : public ParseError { static ArgumentMismatch FlagOverride(std::string name) { return ArgumentMismatch(name + " was given a disallowed flag override"); } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } }; /// Thrown when a requires option is missing @@ -859,11 +896,8 @@ class OptionNotFound : public Error { /// @} -} // namespace CLI -// From CLI/TypeTools.hpp: -namespace CLI { // Type tools @@ -880,7 +914,7 @@ constexpr enabler dummy = {}; /// A copy of enable_if_t from C++14, compatible with C++11. /// /// We could check to see if C++14 is being used, but it does not hurt to redefine this -/// (even Google does this: /~https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// (even Google does this: /~https://github.com/google/skia/blob/main/include/private/SkTLogic.h) /// It is not in the std namespace anyway, so no harm done. template using enable_if_t = typename std::enable_if::type; @@ -1148,9 +1182,12 @@ template ::value, detail::enabler> = detail::dummy> std::string to_string(T &&variable) { - std::vector defaults; auto cval = variable.begin(); auto end = variable.end(); + if(cval == end) { + return std::string("{}"); + } + std::vector defaults; while(cval != end) { defaults.emplace_back(CLI::detail::to_string(*cval)); ++cval; @@ -1640,7 +1677,16 @@ bool integral_conversion(const std::string &input, T &output) noexcept { char *val = nullptr; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); output = static_cast(output_ll); - return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + return false; } /// Convert to a signed integral @@ -1652,7 +1698,15 @@ bool integral_conversion(const std::string &input, T &output) noexcept { char *val = nullptr; std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); output = static_cast(output_ll); - return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + return false; } /// Convert a flag into an integer value typically binary flags @@ -1812,7 +1866,22 @@ bool lexical_cast(const std::string &input, T &output) { /// wrapper types template ::value == object_category::wrapper_value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { typename T::value_type val; if(lexical_cast(input, val)) { @@ -1867,8 +1936,36 @@ bool lexical_cast(const std::string &input, T &output) { return from_stream(input, output); } +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + /// Non-string parsable by a stream -template ::value == object_category::other, detail::enabler> = detail::dummy> +template ::value == object_category::other && !std::is_assignable::value, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { static_assert(is_istreamable::value, "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " @@ -1891,7 +1988,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) { /// Assign a value through lexical cast operations template ::value && + enable_if_t::value && std::is_assignable::value && classify_object::value != object_category::string_assignable && classify_object::value != object_category::string_constructible, detail::enabler> = detail::dummy> @@ -1900,9 +1997,46 @@ bool lexical_assign(const std::string &input, AssignTo &output) { output = AssignTo{}; return true; } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } return lexical_cast(input, output); } +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return false; +} + /// Assign a value converted from a string in lexical cast to the output value directly template = detail::dummy> bool lexical_conversion(const std::vector &strings, AssignTo &output) { output.erase(output.begin(), output.end()); + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } for(const auto &elem : strings) { typename AssignTo::value_type out; bool retval = lexical_assign(elem, out); @@ -1979,6 +2120,9 @@ bool lexical_conversion(const std::vector &strings, AssignTo &outp return false; } output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } } return (!output.empty()); } @@ -2214,10 +2358,11 @@ bool lexical_conversion(const std::vector &strings, AssignTo &outp } /// conversion for wrapper types -template < - typename AssignTo, - class ConvertTo, - enable_if_t::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector &strings, AssignTo &output) { if(strings.empty() || strings.front().empty()) { output = ConvertTo{}; @@ -2231,40 +2376,64 @@ bool lexical_conversion(const std::vector &strings, AssignTo &outpu return false; } -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; } - output = (count > 0) ? static_cast(count) : T{0}; + return false; } -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = detail::lexical_cast(arg, tv); + if(!comp) { + try { + tv = static_cast(detail::to_flag_value(arg)); + } catch(const std::exception &) { + fail = true; + break; + } + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + if(val <= static_cast(std::numeric_limits::min()) || + val >= static_cast(std::numeric_limits::max()) || + val == static_cast(val)) { + output = detail::value_string(static_cast(val)); + } else { + output = detail::value_string(val); + } } - output = static_cast(count); + return output; } } // namespace detail -} // namespace CLI -// From CLI/Split.hpp: -namespace CLI { + namespace detail { // Returns false if not a short option. Otherwise, sets opt name and rest and returns true @@ -2385,11 +2554,8 @@ get_names(const std::vector &input) { } } // namespace detail -} // namespace CLI -// From CLI/ConfigFwd.hpp: -namespace CLI { class App; @@ -2429,6 +2595,9 @@ class Config { if(item.inputs.size() == 1) { return item.inputs.at(0); } + if(item.inputs.empty()) { + return "{}"; + } throw ConversionError::TooManyInputsFlag(item.fullname()); } @@ -2458,12 +2627,18 @@ class ConfigBase : public Config { char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; /// the maximum number of layers to allow - uint8_t maxLayers_{255}; + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; /// Specify the configuration index to use for arrayed sections - uint16_t configIndex{0}; + int16_t configIndex{-1}; /// Specify the configuration section that should be used - std::string configSection; + std::string configSection{}; public: std::string @@ -2491,15 +2666,26 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; + } /// Specify the maximum number of parents ConfigBase *maxLayers(uint8_t layers) { - maxLayers_ = layers; + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; return this; } /// get a reference to the configuration section std::string §ionRef() { return configSection; } /// get the section - const std::string& section() const { return configSection; } + const std::string §ion() const { return configSection; } /// specify a particular section of the configuration file to use ConfigBase *section(const std::string §ionName) { configSection = sectionName; @@ -2507,11 +2693,11 @@ class ConfigBase : public Config { } /// get a reference to the configuration index - uint16_t& indexRef() { return configIndex; } + int16_t &indexRef() { return configIndex; } /// get the section index - uint16_t index() const { return configIndex; } - /// specify a particular index in the section to use - ConfigBase *index(uint16_t sectionIndex) { + int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { configIndex = sectionIndex; return this; } @@ -2532,11 +2718,8 @@ class ConfigINI : public ConfigTOML { valueDelimiter = '='; } }; -} // namespace CLI -// From CLI/Validators.hpp: -namespace CLI { class Option; @@ -2898,53 +3081,6 @@ class IPV4Validator : public Validator { } }; -/// Validate the argument is a number and greater than 0 -class PositiveNumber : public Validator { - public: - PositiveNumber() : Validator("POSITIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number <= 0) { - return std::string("Number less or equal to 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; -/// Validate the argument is a number and greater than or equal to 0 -class NonNegativeNumber : public Validator { - public: - NonNegativeNumber() : Validator("NONNEGATIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number < 0) { - return std::string("Number less than 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; - -/// Validate the argument is a number -class Number : public Validator { - public: - Number() : Validator("NUMBER") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing as a number (") + number_str + ')'; - } - return std::string(); - }; - } -}; - } // namespace detail // Static is not needed here, because global const implies static. @@ -2964,14 +3100,51 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; -/// Check for a positive number -const detail::PositiveNumber PositiveNumber; - -/// Check for a non-negative number -const detail::NonNegativeNumber NonNegativeNumber; +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { + func_ = [](std::string &input_string) { + auto val = DesiredType(); + if(!detail::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 detail::Number Number; +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true) : 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{}; + }; + } +}; /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { @@ -2980,26 +3153,39 @@ class Range : public Validator { /// /// 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 Range(T min, T max) { - std::stringstream out; - out << detail::type_name() << " in [" << min << " - " << max << "]"; - description(out.str()); + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); - if((!converted) || (val < min || val > max)) - return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + - std::to_string(max); - - return std::string(); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; }; } /// Range of one value is 0 to value - template explicit Range(T max) : Range(static_cast(0), max) {} + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} }; +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), min() her 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: @@ -3007,28 +3193,28 @@ class Bound : public Validator { /// /// 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, T max) { + template Bound(T min_val, T max_val) { std::stringstream out; - out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; description(out.str()); - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); if(!converted) { return std::string("Value ") + input + " could not be converted"; } - if(val < min) - input = detail::to_string(min); - else if(val > max) - input = detail::to_string(max); + 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) : Bound(static_cast(0), max) {} + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} }; namespace detail { @@ -3179,7 +3365,7 @@ class IsMember : public Validator { /// This allows in-place construction using an initializer list template - IsMember(std::initializer_list values, Args &&... args) + 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) @@ -3225,15 +3411,13 @@ class IsMember : public Validator { } // If you reach this point, the result was not found - std::string out(" not in "); - out += detail::generate_set(detail::smart_deref(set)); - return out; + 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(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)); }, @@ -3250,7 +3434,7 @@ class Transformer : public Validator { /// This allows in-place construction template - Transformer(std::initializer_list> values, Args &&... args) + Transformer(std::initializer_list> values, Args &&...args) : Transformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string @@ -3294,7 +3478,7 @@ class Transformer : public Validator { /// 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(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)); }, @@ -3308,7 +3492,7 @@ class CheckedTransformer : public Validator { /// This allows in-place construction template - CheckedTransformer(std::initializer_list> values, Args &&... args) + CheckedTransformer(std::initializer_list> values, Args &&...args) : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string @@ -3370,7 +3554,7 @@ class CheckedTransformer : public Validator { /// 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(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)); }, @@ -3605,12 +3789,35 @@ inline std::pair split_program_name(std::string comman 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 - esp = commandline.find_first_of(' ', 1); + 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; } } - vals.first = commandline.substr(0, esp); - rtrim(vals.first); + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + // strip the program name vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; ltrim(vals.second); @@ -3620,11 +3827,8 @@ inline std::pair split_program_name(std::string comman } // namespace detail /// @} -} // namespace CLI -// From CLI/FormatterFwd.hpp: -namespace CLI { class Option; class App; @@ -3788,11 +3992,8 @@ class Formatter : public FormatterBase { ///@} }; -} // namespace CLI -// From CLI/Option.hpp: -namespace CLI { using results_t = std::vector; /// callback function definition @@ -3808,7 +4009,8 @@ enum class MultiOptionPolicy : char { TakeLast, //!< take only the last Expected number of arguments TakeFirst, //!< take only the first Expected number of arguments Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') - TakeAll //!< just get all the passed argument regardless + TakeAll, //!< just get all the passed argument regardless + Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter }; /// This is the CRTP base class for Option and OptionDefaults. It was designed this way @@ -3862,6 +4064,9 @@ template class OptionBase { /// Changes the group membership CRTP *group(const std::string &name) { + if(!detail::valid_alias_name_string(name)) { + throw IncorrectConstruction("Group names may not contain newlines or null characters"); + } group_ = name; return static_cast(this); } @@ -4035,6 +4240,9 @@ class Option : public OptionBase