#pragma once // CLI11: Version 1.8.0 // 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.8.0 // // From LICENSE: // // CLI11 1.8 Copyright (c) 2017-2019 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, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // 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 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // 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 // Verbatim copy from CLI/Version.hpp: #define CLI11_VERSION_MAJOR 1 #define CLI11_VERSION_MINOR 8 #define CLI11_VERSION_PATCH 0 #define CLI11_VERSION "1.8.0" // Verbatim copy from CLI/Macros.hpp: // 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 #if __cplusplus >= 201703L #define CLI11_CPP17 #if __cplusplus > 201703L #define CLI11_CPP20 #endif #endif #endif #elif defined(_MSC_VER) && __cplusplus == 199711L // MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) // Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer #if _MSVC_LANG >= 201402L #define CLI11_CPP14 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 #define CLI11_CPP17 #if __MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 #endif #endif #endif #endif #if defined(CLI11_CPP14) #define CLI11_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(_MSC_VER) #define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) #else #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif // Verbatim copy from CLI/Optional.hpp: // You can explicitly enable or disable support // by defining to 1 or 0. Extra check here to ensure it's in the stdlib too. // We nest the check for __has_include and it's usage #ifndef CLI11_STD_OPTIONAL #ifdef __has_include #if defined(CLI11_CPP17) && __has_include() #define CLI11_STD_OPTIONAL 1 #else #define CLI11_STD_OPTIONAL 0 #endif #else #define CLI11_STD_OPTIONAL 0 #endif #endif #ifndef CLI11_EXPERIMENTAL_OPTIONAL #define CLI11_EXPERIMENTAL_OPTIONAL 0 #endif #ifndef CLI11_BOOST_OPTIONAL #define CLI11_BOOST_OPTIONAL 0 #endif #if CLI11_BOOST_OPTIONAL #include #if BOOST_VERSION < 106100 #error "This boost::optional version is not supported, use 1.61 or better" #endif #endif #if CLI11_STD_OPTIONAL #include #endif #if CLI11_EXPERIMENTAL_OPTIONAL #include #endif #if CLI11_BOOST_OPTIONAL #include #include #endif // From CLI/Version.hpp: // From CLI/Macros.hpp: // From CLI/Optional.hpp: namespace CLI { #if CLI11_STD_OPTIONAL template std::istream &operator>>(std::istream &in, std::optional &val) { T v; in >> v; val = v; return in; } #endif #if CLI11_EXPERIMENTAL_OPTIONAL template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { T v; in >> v; val = v; return in; } #endif #if CLI11_BOOST_OPTIONAL template std::istream &operator>>(std::istream &in, boost::optional &val) { T v; in >> v; val = v; return in; } #endif // Export the best optional to the CLI namespace #if CLI11_STD_OPTIONAL using std::optional; #elif CLI11_EXPERIMENTAL_OPTIONAL using std::experimental::optional; #elif CLI11_BOOST_OPTIONAL using boost::optional; #endif // This is true if any optional is found #if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL #define CLI11_OPTIONAL 1 #endif } // namespace CLI // 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 { /// output streaming for enumerations 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); } /// input streaming for enumerations template ::value>::type> std::istream &operator>>(std::istream &in, T &item) { typename std::underlying_type::type i; in >> i; item = static_cast(i); return in; } } // namespace enums /// Export to CLI namespace using namespace enums; namespace detail { // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim inline std::vector split(const std::string &s, char delim) { std::vector elems; // Check to see if empty string, give consistent result if(s.empty()) elems.emplace_back(); else { std::stringstream ss; ss.str(s); std::string item; while(std::getline(ss, item, delim)) { elems.push_back(item); } } return elems; } /// simple utility to convert various types to a string template inline std::string as_string(const T &v) { std::ostringstream s; s << v; return s.str(); } // if the data type is already a string just forward it template ::value>::type> inline auto as_string(T &&v) -> decltype(std::forward(v)) { return std::forward(v); } /// Simple function to join a string template std::string join(const T &v, std::string delim = ",") { std::ostringstream s; auto beg = std::begin(v); auto end = std::end(v); if(beg != end) s << *beg++; while(beg != end) { s << delim << *beg++; } return s.str(); } /// Simple function to join a string from processed elements template ::value>::type> 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++); while(beg != end) { s << delim << func(*beg++); } return s.str(); } /// Join a string in reverse order template std::string rjoin(const T &v, std::string delim = ",") { std::ostringstream s; for(size_t start = 0; start < v.size(); start++) { if(start > 0) s << delim; s << v[v.size() - start - 1]; } return s.str(); } // Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string /// Trim whitespace from left of string inline std::string <rim(std::string &str) { auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); str.erase(str.begin(), it); return str; } /// Trim anything from left of string inline std::string <rim(std::string &str, const std::string &filter) { auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); str.erase(str.begin(), it); return str; } /// Trim whitespace from right of string inline std::string &rtrim(std::string &str) { auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); str.erase(it.base(), str.end()); return str; } /// Trim anything from right of string inline std::string &rtrim(std::string &str, const std::string &filter) { auto it = std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); str.erase(it.base(), str.end()); return str; } /// Trim whitespace from string inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } /// Trim anything from string inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } /// Make a copy of the string and then trim it inline std::string trim_copy(const std::string &str) { std::string s = str; return trim(s); } /// 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, size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; if(!description.empty()) { if(name.length() >= wid) out << "\n" << std::setw(static_cast(wid)) << ""; for(const char c : description) { out.put(c); if(c == '\n') { out << std::setw(static_cast(wid)) << ""; } } } out << "\n"; 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 == '@'; } /// Verify following characters of an option template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } /// Verify an option name inline bool valid_name_string(const std::string &str) { if(str.empty() || !valid_first_char(str[0])) return false; for(auto c : str.substr(1)) if(!valid_later_char(c)) return false; return true; } /// Verify that str consists of letters only inline bool isalpha(const std::string &str) { return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); } /// Return a lower case version of a string inline std::string to_lower(std::string str) { std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { return std::tolower(x, std::locale()); }); return str; } /// remove underscores from a string inline std::string remove_underscore(std::string str) { str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); return str; } /// Find and replace a substring with another substring inline std::string find_and_replace(std::string str, std::string from, std::string to) { size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); } return str; } /// check if the flag definitions has possible false flags inline bool has_default_flag_values(const std::string &flags) { return (flags.find_first_of("{!") != std::string::npos); } inline void remove_default_flag_values(std::string &flags) { auto loc = flags.find_first_of('{'); while(loc != std::string::npos) { auto finish = flags.find_first_of("},", loc + 1); if((finish != std::string::npos) && (flags[finish] == '}')) { flags.erase(flags.begin() + static_cast(loc), flags.begin() + static_cast(finish) + 1); } loc = flags.find_first_of('{', loc + 1); } flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); } /// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores inline std::ptrdiff_t find_member(std::string name, const std::vector names, bool ignore_case = false, bool ignore_underscore = false) { auto it = std::end(names); if(ignore_case) { if(ignore_underscore) { name = detail::to_lower(detail::remove_underscore(name)); it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { return detail::to_lower(detail::remove_underscore(local_name)) == name; }); } else { name = detail::to_lower(name); it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { return detail::to_lower(local_name) == name; }); } } else if(ignore_underscore) { name = detail::remove_underscore(name); it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { return detail::remove_underscore(local_name) == name; }); } else it = std::find(std::begin(names), std::end(names), name); return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } /// Find a trigger string and call a modify callable function that takes the current string and starting position of the /// trigger and returns the position in the string to search for the next trigger string template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { size_t start_pos = 0; while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { start_pos = modify(str, start_pos); } return str; } /// Split a string '"one two" "three"' into 'one two', 'three' /// Quote characters can be ` ' or " inline std::vector split_up(std::string str) { const std::string delims("\'\"`"); auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; trim(str); std::vector output; bool embeddedQuote = false; char keyChar = ' '; while(!str.empty()) { if(delims.find_first_of(str[0]) != std::string::npos) { keyChar = str[0]; auto end = str.find_first_of(keyChar, 1); while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes end = str.find_first_of(keyChar, end + 1); embeddedQuote = true; } if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); str = str.substr(end + 1); } else { output.push_back(str.substr(1)); str = ""; } } else { auto it = std::find_if(std::begin(str), std::end(str), find_ws); if(it != std::end(str)) { std::string value = std::string(str.begin(), it); output.push_back(value); str = std::string(it, str.end()); } else { output.push_back(str); str = ""; } } // transform any embedded quotes into the regular character if(embeddedQuote) { output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); embeddedQuote = false; } trim(str); } 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(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 /// the return value is the offset+1 which is required by the find_and_modify function. inline size_t escape_detect(std::string &str, size_t offset) { auto next = str[offset + 1]; if((next == '\"') || (next == '\'') || (next == '`')) { auto astart = str.find_last_of("-/ \"\'`", offset - 1); if(astart != std::string::npos) { if(str[astart] == ((str[offset] == '=') ? '-' : '/')) str[offset] = ' '; // interpret this as a space so the split_up works properly } } return offset + 1; } /// Add quotes if the string contains spaces inline std::string &add_quotes_if_needed(std::string &str) { if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { char quote = str.find('"') < str.find('\'') ? '\'' : '"'; if(str.find(' ') != std::string::npos) { str.insert(0, 1, quote); str.append(1, quote); } } return 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. #define CLI11_ERROR_DEF(parent, name) \ protected: \ name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ name(std::string ename, std::string msg, ExitCodes exit_code) \ : parent(std::move(ename), std::move(msg), exit_code) {} \ \ public: \ name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} // This is added after the one above if a class is used directly and builds its own message #define CLI11_ERROR_SIMPLE(name) \ explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} /// 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 { Success = 0, IncorrectConstruction = 100, BadNameString, OptionAlreadyAdded, FileError, ConversionError, ValidationError, RequiredError, RequiresError, ExcludesError, ExtrasError, ConfigError, InvalidError, HorribleError, OptionNotFound, ArgumentMismatch, BaseClass = 127 }; // Error definitions /// @defgroup error_group Errors /// @brief Errors thrown by CLI11 /// /// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. /// @{ /// All errors derive from this one class Error : public std::runtime_error { int actual_exit_code; std::string error_name{"Error"}; public: int get_exit_code() const { return actual_exit_code; } std::string get_name() const { return error_name; } Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} }; // Note: Using Error::Error constructors does not work on GCC 4.7 /// Construction errors (not in parsing) class ConstructionError : public Error { CLI11_ERROR_DEF(Error, ConstructionError) }; /// Thrown when an option is set to conflicting values (non-vector and multi args, for example) class IncorrectConstruction : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) CLI11_ERROR_SIMPLE(IncorrectConstruction) static IncorrectConstruction PositionalFlag(std::string name) { return IncorrectConstruction(name + ": Flags cannot be positional"); } static IncorrectConstruction Set0Opt(std::string name) { return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); } static IncorrectConstruction SetFlag(std::string name) { return IncorrectConstruction(name + ": Cannot set an expected number for flags"); } static IncorrectConstruction ChangeNotVector(std::string name) { return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); } static IncorrectConstruction AfterMultiOpt(std::string name) { return IncorrectConstruction( name + ": You can't change expected arguments after you've changed the multi option policy!"); } static IncorrectConstruction MissingOption(std::string name) { return IncorrectConstruction("Option " + name + " is not defined"); } static IncorrectConstruction MultiOptionPolicy(std::string name) { return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); } }; /// Thrown on construction of a bad name class BadNameString : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } static BadNameString DashesOnly(std::string name) { return BadNameString("Must have a name, not just dashes: " + name); } static BadNameString MultiPositionalNames(std::string name) { return BadNameString("Only one positional name allowed, remove: " + name); } }; /// Thrown when an option already exists class OptionAlreadyAdded : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) explicit OptionAlreadyAdded(std::string name) : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} static OptionAlreadyAdded Requires(std::string name, std::string other) { return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); } static OptionAlreadyAdded Excludes(std::string name, std::string other) { return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); } }; // Parsing errors /// Anything that can error in Parse class ParseError : public Error { CLI11_ERROR_DEF(Error, ParseError) }; // Not really "errors" /// This is a successful completion on parsing, supposed to exit class Success : public ParseError { CLI11_ERROR_DEF(ParseError, Success) Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} }; /// -h or --help on command line class CallForHelp : public ParseError { CLI11_ERROR_DEF(ParseError, CallForHelp) CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; /// Usually something like --help-all on command line class CallForAllHelp : public ParseError { CLI11_ERROR_DEF(ParseError, CallForAllHelp) CallForAllHelp() : CallForAllHelp("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. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} }; /// Thrown when parsing an INI file and it is missing class FileError : public ParseError { CLI11_ERROR_DEF(ParseError, FileError) CLI11_ERROR_SIMPLE(FileError) static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } }; /// Thrown when conversion call back fails, such as when an int fails to coerce to a string class ConversionError : public ParseError { CLI11_ERROR_DEF(ParseError, ConversionError) CLI11_ERROR_SIMPLE(ConversionError) ConversionError(std::string member, std::string name) : ConversionError("The value " + member + " is not an allowed value for " + name) {} ConversionError(std::string name, std::vector results) : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} static ConversionError TooManyInputsFlag(std::string name) { return ConversionError(name + ": too many inputs for a flag"); } static ConversionError TrueFalse(std::string name) { return ConversionError(name + ": Should be true/false or a number"); } }; /// Thrown when validation of results fails class ValidationError : public ParseError { CLI11_ERROR_DEF(ParseError, ValidationError) CLI11_ERROR_SIMPLE(ValidationError) explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} }; /// Thrown when a required option is missing class RequiredError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} static RequiredError Subcommand(size_t min_subcom) { if(min_subcom == 1) return RequiredError("A subcommand"); else return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError); } static RequiredError Option(size_t min_option, size_t max_option, size_t used, const std::string &option_list) { if((min_option == 1) && (max_option == 1) && (used == 0)) return RequiredError("Exactly 1 option from [" + option_list + "]"); else if((min_option == 1) && (max_option == 1) && (used > 1)) return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + " were given", ExitCodes::RequiredError); else if((min_option == 1) && (used == 0)) return RequiredError("At least 1 option from [" + option_list + "]"); else if(used < min_option) return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + std::to_string(used) + "were given from [" + option_list + "]", ExitCodes::RequiredError); else if(max_option == 1) return RequiredError("Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError); else return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + std::to_string(used) + "were given from [" + option_list + "]", ExitCodes::RequiredError); } }; /// Thrown when the wrong number of arguments has been received class ArgumentMismatch : public ParseError { CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) ArgumentMismatch(std::string name, int expected, size_t recieved) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + ", got " + std::to_string(recieved)) : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + ", got " + std::to_string(recieved)), ExitCodes::ArgumentMismatch) {} static ArgumentMismatch AtLeast(std::string name, int num) { return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); } static ArgumentMismatch FlagOverride(std::string name) { return ArgumentMismatch(name + " was given a disallowed flag override"); } }; /// Thrown when a requires option is missing class RequiresError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiresError) RequiresError(std::string curname, std::string subname) : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} }; /// Thrown when an excludes option is present class ExcludesError : public ParseError { CLI11_ERROR_DEF(ParseError, ExcludesError) ExcludesError(std::string curname, std::string subname) : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} }; /// Thrown when too many positionals or options are found class ExtrasError : public ParseError { CLI11_ERROR_DEF(ParseError, ExtrasError) explicit ExtrasError(std::vector args) : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} }; /// Thrown when extra values are found in an INI file class ConfigError : public ParseError { CLI11_ERROR_DEF(ParseError, ConfigError) CLI11_ERROR_SIMPLE(ConfigError) static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } static ConfigError NotConfigurable(std::string item) { return ConfigError(item + ": This option is not allowed in a configuration file"); } }; /// Thrown when validation fails before parsing class InvalidError : public ParseError { CLI11_ERROR_DEF(ParseError, InvalidError) explicit InvalidError(std::string name) : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { } }; /// This is just a safety check to verify selection and parsing match - you should not ever see it /// Strings are directly added to this error, but again, it should never be seen. class HorribleError : public ParseError { CLI11_ERROR_DEF(ParseError, HorribleError) CLI11_ERROR_SIMPLE(HorribleError) }; // After parsing /// Thrown when counting a non-existent option class OptionNotFound : public Error { CLI11_ERROR_DEF(Error, OptionNotFound) explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} }; #undef CLI11_ERROR_DEF #undef CLI11_ERROR_SIMPLE /// @} } // namespace CLI // From CLI/TypeTools.hpp: namespace CLI { // Type tools // Utilities for type enabling namespace detail { // Based generally on https://rmf.io/cxx11/almost-static-if /// Simple empty scoped class enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; } // namespace detail /// 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) /// It is not in the std namespace anyway, so no harm done. template using enable_if_t = typename std::enable_if::type; /// A copy of std::void_t from C++17 (helper for C++11 and C++14) template struct make_void { using type = void; }; /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine template using void_t = typename make_void::type; /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine template using conditional_t = typename std::conditional::type; /// Check to see if something is a vector (fail check by default) template struct is_vector : std::false_type {}; /// Check to see if something is a vector (true if actually a vector) template struct is_vector> : std::true_type {}; /// Check to see if something is bool (fail check by default) template struct is_bool : std::false_type {}; /// Check to see if something is bool (true if actually a bool) template <> struct is_bool : std::true_type {}; /// Check to see if something is a shared pointer template struct is_shared_ptr : std::false_type {}; /// Check to see if something is a shared pointer (True if really a shared pointer) template struct is_shared_ptr> : std::true_type {}; /// Check to see if something is a shared pointer (True if really a shared pointer) template struct is_shared_ptr> : std::true_type {}; /// Check to see if something is copyable pointer template struct is_copyable_ptr { static bool const value = is_shared_ptr::value || std::is_pointer::value; }; /// This can be specialized to override the type deduction for IsMember. template struct IsMemberType { using type = T; }; /// The main custom type needed here is const char * should be a string. template <> struct IsMemberType { using type = std::string; }; namespace detail { // These are utilities for IsMember /// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that /// pointer_traits be valid. template struct element_type { using type = typename std::conditional::value, typename std::pointer_traits::element_type, T>::type; }; /// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of /// the container template struct element_value_type { using type = typename element_type::type::value_type; }; /// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. template struct pair_adaptor : std::false_type { using value_type = typename T::value_type; using first_type = typename std::remove_const::type; using second_type = typename std::remove_const::type; /// Get the first value (really just the underlying value) template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { return std::forward(pair_value); } /// Get the second value (really just the underlying value) template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { return std::forward(pair_value); } }; /// Adaptor for map-like structure (true version, must have key_type and mapped_type). /// This wraps a mapped container in a few utilities access it in a general way. template struct pair_adaptor< T, conditional_t, void>> : std::true_type { using value_type = typename T::value_type; using first_type = typename std::remove_const::type; using second_type = typename std::remove_const::type; /// Get the first value (really just the underlying value) template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { return std::get<0>(std::forward(pair_value)); } /// Get the second value (really just the underlying value) template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { return std::get<1>(std::forward(pair_value)); } }; // Check for streamability // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream template class is_streamable { template static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; /// Convert an object to a string (directly forward if this can become a string) template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(value)) { return std::forward(value); } /// Convert an object to a string (streaming must be supported for that type) template ::value && is_streamable::value, detail::enabler> = detail::dummy> std::string to_string(T &&value) { std::stringstream stream; stream << value; return stream.str(); } /// If conversion is not supported, return an empty string (streaming is not supported for that type) template ::value && !is_streamable::value, detail::enabler> = detail::dummy> std::string to_string(T &&) { return std::string{}; } // Type name print /// Was going to be based on /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template /// But this is cleaner and works better in this case template ::value && std::is_signed::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "INT"; } template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "UINT"; } template ::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "FLOAT"; } /// This one should not be used, since vector types print the internal type template ::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "VECTOR"; } /// Print name for enumeration types template ::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "ENUM"; } /// Print for all other types template ::value && !std::is_integral::value && !is_vector::value && !std::is_enum::value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; } // Lexical cast /// Convert a flag into an integer value typically binary flags inline int64_t to_flag_value(std::string val) { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { return 1; } if(val == falseString) { return -1; } val = detail::to_lower(val); int64_t ret; if(val.size() == 1) { switch(val[0]) { case '0': case 'f': case 'n': case '-': ret = -1; break; case '1': case 't': case 'y': case '+': ret = 1; break; case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ret = val[0] - '0'; break; default: throw std::invalid_argument("unrecognized character"); } return ret; } if(val == trueString || val == "on" || val == "yes" || val == "enable") { ret = 1; } else if(val == falseString || val == "off" || val == "no" || val == "disable") { ret = -1; } else { ret = std::stoll(val); } return ret; } /// Signed integers template < typename T, enable_if_t::value && std::is_signed::value && !is_bool::value && !std::is_enum::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; long long output_ll = std::stoll(input, &n, 0); output = static_cast(output_ll); return n == input.size() && static_cast(output) == output_ll; } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// Unsigned integers template ::value && std::is_unsigned::value && !is_bool::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { if(!input.empty() && input.front() == '-') return false; // std::stoull happily converts negative values to junk without any errors. try { size_t n = 0; unsigned long long output_ll = std::stoull(input, &n, 0); output = static_cast(output_ll); return n == input.size() && static_cast(output) == output_ll; } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// Boolean values template ::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { auto out = to_flag_value(input); output = (out > 0); return true; } catch(const std::invalid_argument &) { return false; } } /// Floats template ::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { try { size_t n = 0; output = static_cast(std::stold(input, &n)); return n == input.size(); } catch(const std::invalid_argument &) { return false; } catch(const std::out_of_range &) { return false; } } /// String and similar template ::value && !std::is_integral::value && std::is_assignable::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { output = input; return true; } /// Enumerations template ::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { typename std::underlying_type::type val; bool retval = detail::lexical_cast(input, val); if(!retval) { return false; } output = static_cast(val); return true; } /// Non-string parsable template ::value && !std::is_integral::value && !std::is_assignable::value && !std::is_enum::value, detail::enabler> = detail::dummy> bool lexical_cast(std::string input, T &output) { std::istringstream is; is.str(input); is >> output; return !is.fail() && !is.rdbuf()->in_avail(); } /// 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 && std::is_unsigned::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector &flags, T &output) { int64_t count{0}; for(auto &flag : flags) { count += detail::to_flag_value(flag); } output = (count > 0) ? static_cast(count) : T{0}; } /// 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 && std::is_signed::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector &flags, T &output) { int64_t count{0}; for(auto &flag : flags) { count += detail::to_flag_value(flag); } output = static_cast(count); } } // 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 inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { name = current.substr(1, 1); rest = current.substr(2); return true; } else return false; } // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); value = current.substr(loc + 1); } else { name = current.substr(2); value = ""; } return true; } else return false; } // Returns false if not a windows style option. Otherwise, sets opt name and value and returns true inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { auto loc = current.find_first_of(':'); if(loc != std::string::npos) { name = current.substr(1, loc - 1); value = current.substr(loc + 1); } else { name = current.substr(1); value = ""; } return true; } else return false; } // Splits a string into multiple long and short names inline std::vector split_names(std::string current) { std::vector output; size_t val; while((val = current.find(",")) != std::string::npos) { output.push_back(trim_copy(current.substr(0, val))); current = current.substr(val + 1); } output.push_back(trim_copy(current)); return output; } /// extract default flag values either {def} or starting with a ! inline std::vector> get_default_flag_values(const std::string &str) { std::vector flags = split_names(str); flags.erase(std::remove_if(flags.begin(), flags.end(), [](const std::string &name) { return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && (name.back() == '}')) || (name[0] == '!')))); }), flags.end()); std::vector> output; output.reserve(flags.size()); for(auto &flag : flags) { auto def_start = flag.find_first_of('{'); std::string defval = "false"; if((def_start != std::string::npos) && (flag.back() == '}')) { defval = flag.substr(def_start + 1); defval.pop_back(); flag.erase(def_start, std::string::npos); } flag.erase(0, flag.find_first_not_of("-!")); output.emplace_back(flag, defval); } return output; } /// Get a vector of short names, one of long names, and a single name inline std::tuple, std::vector, std::string> get_names(const std::vector &input) { std::vector short_names; std::vector long_names; std::string pos_name; for(std::string name : input) { if(name.length() == 0) continue; else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { if(name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); else throw BadNameString::OneCharName(name); } else if(name.length() > 2 && name.substr(0, 2) == "--") { name = name.substr(2); if(valid_name_string(name)) long_names.push_back(name); else throw BadNameString::BadLongName(name); } else if(name == "-" || name == "--") { throw BadNameString::DashesOnly(name); } else { if(pos_name.length() > 0) throw BadNameString::MultiPositionalNames(name); pos_name = name; } } return std::tuple, std::vector, std::string>( short_names, long_names, pos_name); } } // namespace detail } // namespace CLI // From CLI/ConfigFwd.hpp: namespace CLI { class App; namespace detail { /// Comma separated join, adds quotes if needed inline std::string ini_join(std::vector args) { std::ostringstream s; size_t start = 0; for(const auto &arg : args) { if(start++ > 0) s << " "; auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); if(it == arg.end()) s << arg; else if(arg.find_first_of('\"') == std::string::npos) s << '\"' << arg << '\"'; else s << '\'' << arg << '\''; } return s.str(); } } // namespace detail /// Holds values to load into Options struct ConfigItem { /// This is the list of parents std::vector parents; /// This is the name std::string name; /// Listing of inputs std::vector inputs; /// The list of parents and name joined by "." std::string fullname() const { std::vector tmp = parents; tmp.emplace_back(name); return detail::join(tmp, "."); } }; /// This class provides a converter for configuration files. class Config { protected: std::vector items; public: /// Convert an app into a configuration virtual std::string to_config(const App *, bool, bool, std::string) const = 0; /// Convert a configuration into an app virtual std::vector from_config(std::istream &) const = 0; /// Get a flag value virtual std::string to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { return item.inputs.at(0); } throw ConversionError::TooManyInputsFlag(item.fullname()); } /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure std::vector from_file(const std::string &name) { std::ifstream input{name}; if(!input.good()) throw FileError::Missing(name); return from_config(input); } /// Virtual destructor virtual ~Config() = default; }; /// This converter works with INI files class ConfigINI : public Config { public: std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; std::vector from_config(std::istream &input) const override { std::string line; std::string section = "default"; std::vector output; while(getline(input, line)) { std::vector items_buffer; detail::trim(line); size_t len = line.length(); if(len > 1 && line[0] == '[' && line[len - 1] == ']') { section = line.substr(1, len - 2); } else if(len > 0 && line[0] != ';') { output.emplace_back(); ConfigItem &out = output.back(); // Find = in string, split and recombine auto pos = line.find('='); if(pos != std::string::npos) { out.name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); items_buffer = detail::split_up(item); } else { out.name = detail::trim_copy(line); items_buffer = {"ON"}; } if(detail::to_lower(section) != "default") { out.parents = {section}; } if(out.name.find('.') != std::string::npos) { std::vector plist = detail::split(out.name, '.'); out.name = plist.back(); plist.pop_back(); out.parents.insert(out.parents.end(), plist.begin(), plist.end()); } out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); } } return output; } }; } // namespace CLI // From CLI/Validators.hpp: namespace CLI { class Option; /// @defgroup validator_group Validators /// @brief Some validators that are provided /// /// These are simple `std::string(const std::string&)` validators that are useful. They return /// a string if the validation fails. A custom struct is provided, as well, with the same user /// semantics, but with the ability to provide a new type name. /// @{ /// class Validator { protected: /// This is the description function, if empty the description_ will be used std::function desc_function_{[]() { return std::string{}; }}; /// This it the base function that is to be called. /// Returns a string error message if validation fails. std::function func_{[](std::string &) { return std::string{}; }}; /// The name for search purposes of the Validator std::string name_; /// Enable for Validator to allow it to be disabled if need be bool active_{true}; /// specify that a validator should not modify the input bool non_modifying_{false}; public: Validator() = default; /// Construct a Validator with just the description string explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} // Construct Validator from basic information Validator(std::function op, std::string validator_desc, std::string validator_name = "") : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), name_(std::move(validator_name)) {} /// Set the Validator operation function Validator &operation(std::function op) { func_ = std::move(op); return *this; } /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) std::string 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; }; /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) std::string operator()(const std::string &str) const { std::string value = str; return (active_) ? func_(value) : std::string{}; }; /// Specify the type string Validator &description(std::string validator_desc) { desc_function_ = [validator_desc]() { return validator_desc; }; return *this; } /// Generate type description information for the Validator std::string get_description() const { if(active_) { return desc_function_(); } return std::string{}; } /// Specify the type string Validator &name(std::string validator_name) { name_ = std::move(validator_name); return *this; } /// Get the name of the Validator const std::string &get_name() const { return name_; } /// Specify whether the Validator is active or not Validator &active(bool active_val = true) { active_ = active_val; return *this; } /// Specify whether the Validator can be modifying or not Validator &non_modifying(bool no_modify = true) { non_modifying_ = no_modify; return *this; } /// Get a boolean if the validator is active bool get_active() const { return active_; } /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input bool get_modifying() const { return !non_modifying_; } /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the /// same. 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 + ")"; else return s1 + s2; }; newval.active_ = (active_ & other.active_); return newval; } /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the /// same. 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(); else return std::string("(") + s1 + ") OR (" + s2 + ")"; }; newval.active_ = (active_ & other.active_); return newval; } /// Create a validator that fails when a given validator succeeds 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"; } else return std::string{}; }; newval.active_ = active_; return newval; } private: void _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("(") + f1 + ")" + merger + "(" + f2 + ")"; }; } }; /// Class wrapping some of the accessors of Validator class CustomValidator : public Validator { public: }; // 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 { /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: ExistingFileValidator() : Validator("FILE") { func_ = [](std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; bool is_dir = (buffer.st_mode & S_IFDIR) != 0; if(!exist) { return "File does not exist: " + filename; } else if(is_dir) { return "File is actually a directory: " + filename; } return std::string(); }; } }; /// Check for an existing directory (returns error message if check fails) class ExistingDirectoryValidator : public Validator { public: ExistingDirectoryValidator() : Validator("DIR") { func_ = [](std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; bool is_dir = (buffer.st_mode & S_IFDIR) != 0; if(!exist) { return "Directory does not exist: " + filename; } else if(!is_dir) { return "Directory is actually a file: " + filename; } return std::string(); }; } }; /// Check for an existing path class ExistingPathValidator : public Validator { public: ExistingPathValidator() : Validator("PATH(existing)") { func_ = [](std::string &filename) { struct stat buffer; bool const exist = stat(filename.c_str(), &buffer) == 0; if(!exist) { return "Path does not exist: " + filename; } return std::string(); }; } }; /// Check for an non-existing path class NonexistentPathValidator : public Validator { public: NonexistentPathValidator() : Validator("PATH(non-existing)") { func_ = [](std::string &filename) { struct stat buffer; bool exist = stat(filename.c_str(), &buffer) == 0; if(exist) { return "Path already exists: " + filename; } return std::string(); }; } }; /// Validate the given string is a legal ipv4 address class IPV4Validator : public Validator { public: IPV4Validator() : Validator("IPV4") { func_ = [](std::string &ip_addr) { auto result = CLI::detail::split(ip_addr, '.'); if(result.size() != 4) { return "Invalid IPV4 address must have four parts " + ip_addr; } int num; bool retval = true; for(const auto &var : result) { retval &= detail::lexical_cast(var, num); if(!retval) { return "Failed parsing number " + var; } if(num < 0 || num > 255) { return "Each IP number must be between 0 and 255 " + var; } } return std::string(); }; } }; /// Validate the argument is a number and greater than or equal to 0 class PositiveNumber : public Validator { public: PositiveNumber() : Validator("POSITIVE") { func_ = [](std::string &number_str) { int number; if(!detail::lexical_cast(number_str, number)) { return "Failed parsing number " + number_str; } if(number < 0) { return "Number less then 0 " + number_str; } return std::string(); }; } }; /// Validate the argument is a number and greater than or equal to 0 class Number : public Validator { public: Number() : Validator("NUMBER") { func_ = [](std::string &number_str) { double number; if(!detail::lexical_cast(number_str, number)) { return "Failed parsing as a number " + number_str; } return std::string(); }; } }; } // 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; /// Check for an existing directory (returns error message if check fails) const detail::ExistingDirectoryValidator ExistingDirectory; /// Check for an existing path const detail::ExistingPathValidator ExistingPath; /// Check for an non-existing path 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 number const detail::Number Number; /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { public: /// This produces a range 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 Range(T min, T max) { std::stringstream out; out << detail::type_name() << " in [" << min << " - " << max << "]"; description(out.str()); func_ = [min, max](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); if((!converted) || (val < min || val > max)) return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); return std::string(); }; } /// Range of one value is 0 to value template explicit Range(T max) : Range(static_cast(0), max) {} }; /// 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, T max) { std::stringstream out; out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; description(out.str()); func_ = [min, max](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); if(!converted) { return "Value " + input + " could not be converted"; } if(val < min) input = detail::as_string(min); else if(val > max) input = detail::as_string(max); return std::string(); }; } /// Range of one value is 0 to value template explicit Bound(T max) : Bound(static_cast(0), max) {} }; 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) { auto res = detail::as_string(detail::pair_adaptor::first(v)); if(!key_only) { res += "->" + detail::as_string(detail::pair_adaptor::second(v)); } return res; }, ",")); out.push_back('}'); return out; } template struct sfinae_true : std::true_type {}; /// Function to check for the existence of a member find function which presumably is more efficient than looping over /// everything template static auto test_find(int) -> sfinae_true().find(std::declval()))>; template static auto test_find(long) -> std::false_type; template struct has_find : decltype(test_find(0)) {}; /// 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}; } /// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { if(a == 0 || b == 0) { a *= b; return true; } T c = a * b; if(c / a != b) { return false; } a = c; return true; } /// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { T c = a * b; if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { return false; } a = c; return true; } } // namespace detail /// Verify items are in a set class IsMember : public Validator { public: using filter_fn_t = std::function; /// This allows in-place construction using an initializer list template explicit 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) { local_item_t b; if(!detail::lexical_cast(input, b)) { throw ValidationError(input); // name is added later } if(filter_fn) { b = filter_fn(b); } auto res = detail::search(set, b, filter_fn); if(res.first) { // Make sure the version in the input string is identical to the one in the set if(filter_fn) { input = detail::as_string(detail::pair_adaptor::first(*(res.second))); } // Return empty error string (success) return std::string{}; } // If you reach this point, the result was not found std::string out(" not in "); out += detail::generate_set(detail::smart_deref(set)); return out; }; } /// You can pass in as many filter functions as you like, they nest (string only currently) template IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) : IsMember(std::forward(set), [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, other...) {} }; /// definition of the default transformation object template using TransformPairs = std::vector>; /// Translate named items to other or a value set class Transformer : public Validator { public: using filter_fn_t = std::function; /// This allows in-place construction template explicit Transformer(std::initializer_list> values, Args &&... args) : Transformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter /// both sides of the comparison before computing the comparison. template explicit Transformer(T mapping, F filter_function) { static_assert(detail::pair_adaptor::type>::value, "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // if the type does not have first_type and second_type, these are both value_type using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // 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_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; func_ = [mapping, filter_fn](std::string &input) { local_item_t b; if(!detail::lexical_cast(input, b)) { return std::string(); // there is no possible way we can match anything in the mapping if we can't convert so just return } if(filter_fn) { b = filter_fn(b); } auto res = detail::search(mapping, b, filter_fn); if(res.first) { input = detail::as_string(detail::pair_adaptor::second(*res.second)); } return std::string{}; }; } /// You can pass in as many filter functions as you like, they nest template Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) : Transformer(std::forward(mapping), [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, other...) {} }; /// translate named items to other or a value set class CheckedTransformer : public Validator { public: using filter_fn_t = std::function; /// This allows in-place construction template explicit CheckedTransformer(std::initializer_list> values, Args &&... args) : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter /// both sides of the comparison before computing the comparison. template explicit CheckedTransformer(T mapping, F filter_function) { static_assert(detail::pair_adaptor::type>::value, "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // if the type does not have first_type and second_type, these are both value_type using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones // (const char * to std::string) using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair // // the type of the object pair // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; auto tfunc = [mapping]() { std::string out("value in "); out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; out += detail::join( detail::smart_deref(mapping), [](const iteration_type_t &v) { return detail::as_string(detail::pair_adaptor::second(v)); }, ","); out.push_back('}'); return out; }; desc_function_ = tfunc; func_ = [mapping, tfunc, filter_fn](std::string &input) { local_item_t b; bool converted = detail::lexical_cast(input, b); if(converted) { if(filter_fn) { b = filter_fn(b); } auto res = detail::search(mapping, b, filter_fn); if(res.first) { input = detail::as_string(detail::pair_adaptor::second(*res.second)); return std::string{}; } } for(const auto &v : detail::smart_deref(mapping)) { auto output_string = detail::as_string(detail::pair_adaptor::second(v)); if(output_string == input) { return std::string(); } } return "Check " + input + " " + tfunc() + " FAILED"; }; } /// You can pass in as many filter functions as you like, they nest template CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) : CheckedTransformer(std::forward(mapping), [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, other...) {} }; /// Helper function to allow ignore_case to be passed to IsMember or Transform inline std::string ignore_case(std::string item) { return detail::to_lower(item); } /// Helper function to allow ignore_underscore to be passed to IsMember or Transform inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } /// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform inline std::string ignore_space(std::string item) { item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); return item; } /// Multiply a number by a factor using given mapping. /// Can be used to write transforms for SIZE or DURATION inputs. /// /// Example: /// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` /// one can recognize inputs like "100", "12kb", "100 MB", /// that will be automatically transformed to 100, 14448, 104857600. /// /// Output number type matches the type in the provided mapping. /// Therefore, if it is required to interpret real inputs like "0.42 s", /// the mapping should be of a type or . class AsNumberWithUnit : public Validator { public: /// Adjust AsNumberWithUnit behavior. /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError /// if UNIT_REQUIRED is set and unit literal is not found. enum Options { CASE_SENSITIVE = 0, CASE_INSENSITIVE = 1, UNIT_OPTIONAL = 0, UNIT_REQUIRED = 2, DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL }; template explicit AsNumberWithUnit(std::map mapping, Options opts = DEFAULT, const std::string &unit_name = "UNIT") { description(generate_description(unit_name, opts)); validate_mapping(mapping, opts); // transform function func_ = [mapping, opts](std::string &input) -> std::string { Number num; detail::rtrim(input); if(input.empty()) { throw ValidationError("Input is empty"); } // Find split position between number and prefix auto unit_begin = input.end(); while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { --unit_begin; } std::string unit{unit_begin, input.end()}; input.resize(static_cast(std::distance(input.begin(), unit_begin))); detail::trim(input); if(opts & UNIT_REQUIRED && unit.empty()) { throw ValidationError("Missing mandatory unit"); } if(opts & CASE_INSENSITIVE) { unit = detail::to_lower(unit); } bool converted = detail::lexical_cast(input, num); if(!converted) { throw ValidationError("Value " + input + " could not be converted to " + detail::type_name()); } if(unit.empty()) { // No need to modify input if no unit passed return {}; } // find corresponding factor auto it = mapping.find(unit); if(it == mapping.end()) { throw ValidationError(unit + " unit not recognized. " "Allowed values: " + detail::generate_map(mapping, true)); } // perform safe multiplication bool ok = detail::checked_multiply(num, it->second); if(!ok) { throw ValidationError(detail::as_string(num) + " multiplied by " + unit + " factor would cause number overflow. Use smaller value."); } input = detail::as_string(num); return {}; }; } private: /// Check that mapping contains valid units. /// Update mapping for CASE_INSENSITIVE mode. template static void validate_mapping(std::map &mapping, Options opts) { for(auto &kv : mapping) { if(kv.first.empty()) { throw ValidationError("Unit must not be empty."); } if(!detail::isalpha(kv.first)) { throw ValidationError("Unit must contain only letters."); } } // make all units lowercase if CASE_INSENSITIVE if(opts & CASE_INSENSITIVE) { std::map lower_mapping; for(auto &kv : mapping) { auto s = detail::to_lower(kv.first); if(lower_mapping.count(s)) { throw ValidationError("Several matching lowercase unit representations are found: " + s); } lower_mapping[detail::to_lower(kv.first)] = kv.second; } mapping = std::move(lower_mapping); } } /// Generate description like this: NUMBER [UNIT] template static std::string generate_description(const std::string &name, Options opts) { std::stringstream out; out << detail::type_name() << ' '; if(opts & UNIT_REQUIRED) { out << name; } else { out << '[' << name << ']'; } return out.str(); } }; /// Converts a human-readable size string (with unit literal) to uin64_t size. /// Example: /// "100" => 100 /// "1 b" => 100 /// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) /// "10 KB" => 10240 /// "10 kb" => 10240 /// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) /// "10kb" => 10240 /// "2 MB" => 2097152 /// "2 EiB" => 2^61 // Units up to exibyte are supported class AsSizeValue : public AsNumberWithUnit { public: using result_t = 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) : AsNumberWithUnit(get_mapping(kb_is_1000)) { if(kb_is_1000) { description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); } else { description("SIZE [b, kb(=1024b), ...]"); } } private: /// Get mapping static std::map init_mapping(bool kb_is_1000) { std::map m; result_t k_factor = kb_is_1000 ? 1000 : 1024; result_t ki_factor = 1024; result_t k = 1; result_t ki = 1; m["b"] = 1; for(std::string p : {"k", "m", "g", "t", "p", "e"}) { k *= k_factor; ki *= ki_factor; m[p] = k; m[p + "b"] = k; m[p + "i"] = ki; m[p + "ib"] = ki; } return m; } /// Cache calculated mapping static std::map get_mapping(bool kb_is_1000) { if(kb_is_1000) { static auto m = init_mapping(true); return m; } else { static auto m = init_mapping(false); return m; } } }; 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. 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(!ExistingFile(commandline.substr(0, esp)).empty()) { 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 esp = commandline.find_first_of(' ', 1); break; } } 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); return vals; } } // namespace detail /// @} } // namespace CLI // From CLI/FormatterFwd.hpp: namespace CLI { class Option; class App; /// This enum signifies the type of help requested /// /// This is passed in by App; all user classes must accept this as /// the second argument. enum class AppFormatMode { Normal, //< The normal, detailed help All, //< A fully expanded help Sub, //< Used when printed as part of expanded subcommand }; /// This is the minimum requirements to run a formatter. /// /// A user can subclass this is if they do not care at all /// about the structure in CLI::Formatter. class FormatterBase { protected: /// @name Options ///@{ /// The width of the first column size_t column_width_{30}; /// @brief The required help printout labels (user changeable) /// Values are Needs, Excludes, etc. std::map labels_; ///@} /// @name Basic ///@{ public: FormatterBase() = default; FormatterBase(const FormatterBase &) = default; FormatterBase(FormatterBase &&) = default; /// Adding a destructor in this form to work around bug in GCC 4.7 virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) /// This is the key method that puts together help virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; ///@} /// @name Setters ///@{ /// Set the "REQUIRED" label void label(std::string key, std::string val) { labels_[key] = val; } /// Set the column width void column_width(size_t val) { column_width_ = val; } ///@} /// @name Getters ///@{ /// Get the current value of a name (REQUIRED, etc.) std::string get_label(std::string key) const { if(labels_.find(key) == labels_.end()) return key; else return labels_.at(key); } /// Get the current column width size_t get_column_width() const { return column_width_; } ///@} }; /// This is a specialty override for lambda functions class FormatterLambda final : public FormatterBase { using funct_t = std::function; /// The lambda to hold and run funct_t lambda_; public: /// Create a FormatterLambda with a lambda function explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} /// Adding a destructor (mostly to make GCC 4.7 happy) ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) /// This will simply call the lambda function std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { return lambda_(app, name, mode); } }; /// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few /// overridable methods, to be highly customizable with minimal effort. class Formatter : public FormatterBase { public: Formatter() = default; Formatter(const Formatter &) = default; Formatter(Formatter &&) = default; /// @name Overridables ///@{ /// This prints out a group of options with title /// virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; /// This prints out just the positionals "group" virtual std::string make_positionals(const App *app) const; /// This prints out all the groups of options std::string make_groups(const App *app, AppFormatMode mode) const; /// This prints out all the subcommands virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; /// This prints out a subcommand virtual std::string make_subcommand(const App *sub) const; /// This prints out a subcommand in help-all virtual std::string make_expanded(const App *sub) const; /// This prints out all the groups of options virtual std::string make_footer(const App *app) const; /// This displays the description line virtual std::string make_description(const App *app) const; /// This displays the usage line virtual std::string make_usage(const App *app, std::string name) const; /// This puts everything together std::string make_help(const App *, std::string, AppFormatMode) const override; ///@} /// @name Options ///@{ /// This prints out an option help line, either positional or optional form virtual std::string make_option(const Option *opt, bool is_positional) const { std::stringstream out; detail::format_help( out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); return out.str(); } /// @brief This is the name part of an option, Default: left column virtual std::string make_option_name(const Option *, bool) const; /// @brief This is the options part of the name, Default: combined into left column virtual std::string make_option_opts(const Option *) const; /// @brief This is the description. Default: Right column, on new line if left column too large virtual std::string make_option_desc(const Option *) const; /// @brief This is used to print the name on the USAGE line virtual std::string make_option_usage(const Option *opt) const; ///@} }; } // namespace CLI // From CLI/Option.hpp: namespace CLI { using results_t = std::vector; using callback_t = std::function; class Option; class App; using Option_p = std::unique_ptr