diff --git a/algo/lib/CLI11.hpp b/algo/lib/CLI11.hpp index 7f1a8dbe7df03d76c00b7cfceaa31fd2bed56c5d..1e3f0044c54ad5f7f3ff93793f5b1f1172c576b6 100644 --- a/algo/lib/CLI11.hpp +++ b/algo/lib/CLI11.hpp @@ -1,15 +1,15 @@ #pragma once -// CLI11: Version 1.5.4 +// CLI11: Version 1.7.1 // 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.5.4 +// from: v1.7.1 // // From LICENSE: // -// CLI11 1.5 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry +// CLI11 1.7 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 @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -66,9 +67,9 @@ #define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 5 -#define CLI11_VERSION_PATCH 4 -#define CLI11_VERSION "1.5.4" +#define CLI11_VERSION_MINOR 7 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.7.1" @@ -101,7 +102,7 @@ #endif #endif -#if defined(PYBIND11_CPP14) +#if defined(CLI11_CPP14) #define CLI11_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(_MSC_VER) #define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) @@ -116,14 +117,21 @@ #ifdef __has_include +// You can explicitly enable or disable support +// by defining these to 1 or 0. #if defined(CLI11_CPP17) && __has_include() && \ !defined(CLI11_STD_OPTIONAL) #define CLI11_STD_OPTIONAL 1 +#elif !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 0 #endif #if defined(CLI11_CPP14) && __has_include() && \ - !defined(CLI11_EXPERIMENTAL_OPTIONAL) + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) #define CLI11_EXPERIMENTAL_OPTIONAL 1 +#elif !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 0 #endif #if __has_include() && !defined(CLI11_BOOST_OPTIONAL) @@ -131,6 +139,8 @@ #if BOOST_VERSION >= 105800 #define CLI11_BOOST_OPTIONAL 1 #endif +#elif !defined(CLI11_BOOST_OPTIONAL) +#define CLI11_BOOST_OPTIONAL 0 #endif #endif @@ -296,15 +306,16 @@ inline std::string trim_copy(const std::string &str, const std::string &filter) return trim(s, filter); } /// Print a two part "help" string -inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) { +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 << std::endl << std::setw(static_cast(wid)) << ""; + out << "\n" << std::setw(static_cast(wid)) << ""; out << description; } - out << std::endl; + out << "\n"; + return out; } /// Verify the first character of an option @@ -333,27 +344,54 @@ inline std::string to_lower(std::string str) { 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; +} + +/// 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) { - std::vector delims = {'\'', '\"'}; + 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(str[0] == '\'') { - auto end = str.find('\'', 1); - 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 = ""; + 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; } - } else if(str[0] == '\"') { - auto end = str.find('\"', 1); if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); str = str.substr(end + 1); @@ -361,7 +399,6 @@ inline std::vector split_up(std::string str) { 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)) { @@ -373,9 +410,13 @@ inline std::vector split_up(std::string 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; } @@ -395,6 +436,34 @@ inline std::string fix_newlines(std::string leader, std::string input) { 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 @@ -402,12 +471,13 @@ inline std::string fix_newlines(std::string leader, std::string input) { namespace CLI { -// Use one of these on all error classes +// 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 name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ - name(std::string name, std::string msg, ExitCodes exit_code) \ - : parent(std::move(name), std::move(msg), exit_code) {} \ + 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) {} \ @@ -431,7 +501,7 @@ enum class ExitCodes { RequiresError, ExcludesError, ExtrasError, - INIError, + ConfigError, InvalidError, HorribleError, OptionNotFound, @@ -449,16 +519,16 @@ enum class ExitCodes { /// All errors derive from this one class Error : public std::runtime_error { - int exit_code; - std::string name{"Error"}; + int actual_exit_code; + std::string error_name{"Error"}; public: - int get_exit_code() const { return exit_code; } + int get_exit_code() const { return actual_exit_code; } - std::string get_name() const { return name; } + 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), exit_code(exit_code), name(std::move(name)) {} + : 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)) {} }; @@ -550,6 +620,13 @@ class CallForAdvancedHelp : public ParseError { CLI11_ERROR_DEF(ParseError, CallForAdvancedHelp) CallForAdvancedHelp() : CallForAdvancedHelp("This should be caught in your main function, see examples", ExitCodes::Success /* , 2 */ ) {} }; + +/// 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 { @@ -644,12 +721,12 @@ class ExtrasError : public ParseError { }; /// Thrown when extra values are found in an INI file -class INIError : public ParseError { - CLI11_ERROR_DEF(ParseError, INIError) - CLI11_ERROR_SIMPLE(INIError) - static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); } - static INIError NotConfigurable(std::string item) { - return INIError(item + ": This option is not allowed in a configuration 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"); } }; @@ -676,6 +753,9 @@ class OptionNotFound : public Error { explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} }; +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + /// @} } // namespace CLI @@ -686,18 +766,24 @@ namespace CLI { // Type tools -// 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. +/// 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; +/// Check to see if something is a vector (fail check by default) template struct is_vector { static const bool value = false; }; +/// Check to see if something is a vector (true if actually a vector) template struct is_vector> { static bool const value = true; }; +/// Check to see if something is bool (fail check by default) template struct is_bool { static const bool value = false; }; +/// Check to see if something is bool (true if actually a bool) template <> struct is_bool { static bool const value = true; }; namespace detail { @@ -839,7 +925,7 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri // 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("="); + auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); value = current.substr(loc + 1); @@ -852,6 +938,22 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin return false; } +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows(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; @@ -902,12 +1004,16 @@ get_names(const std::vector &input) { } // namespace detail } // namespace CLI -// From CLI/Ini.hpp: +// From CLI/ConfigFwd.hpp: namespace CLI { + +class App; + namespace detail { -inline std::string inijoin(std::vector args) { +/// 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) { @@ -926,86 +1032,126 @@ inline std::string inijoin(std::vector args) { return s.str(); } -struct ini_ret_t { - /// This is the full name with dots - std::string fullname; +} // namespace detail - /// Listing of inputs - std::vector inputs; +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents; - /// Current parent level - size_t level = 0; + /// This is the name + std::string name; - /// Return parent or empty string, based on level - /// - /// Level 0, a.b.c would return a - /// Level 1, a.b.c could return b - std::string parent() const { - std::vector plist = detail::split(fullname, '.'); - if(plist.size() > (level + 1)) - return plist[level]; - else - return ""; - } + /// Listing of inputs + std::vector inputs; - /// Return name - std::string name() const { - std::vector plist = detail::split(fullname, '.'); - return plist.at(plist.size() - 1); + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); } }; -/// Internal parsing function -inline std::vector parse_ini(std::istream &input) { - std::string name, line; - std::string section = "default"; - - std::vector output; - - while(getline(input, line)) { - std::vector items; - - 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(); - ini_ret_t &out = output.back(); - - // Find = in string, split and recombine - auto pos = line.find("="); - if(pos != std::string::npos) { - name = detail::trim_copy(line.substr(0, pos)); - std::string item = detail::trim_copy(line.substr(pos + 1)); - items = detail::split_up(item); +/// 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; + + /// Convert a flag to a bool + virtual std::vector to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector(); } else { - name = detail::trim_copy(line); - items = {"ON"}; + try { + size_t ui = std::stoul(val); + return std::vector(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } - if(detail::to_lower(section) == "default") - out.fullname = name; - else - out.fullname = section + "." + name; + /// 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); - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); - } + return from_config(input); } - return output; -} -/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure -inline std::vector parse_ini(const std::string &name) { + /// 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"}; + } - std::ifstream input{name}; - if(!input.good()) - throw FileError::Missing(name); + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } - return parse_ini(input); -} + 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 detail } // namespace CLI // From CLI/Validators.hpp: @@ -1013,79 +1159,373 @@ inline std::vector parse_ini(const std::string &name) { namespace CLI { /// @defgroup validator_group Validators + /// @brief Some validators that are provided /// -/// These are simple `void(std::string&)` validators that are useful. They throw -/// a ValidationError if they fail (or the normally expected error if the cast fails) +/// 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. /// @{ -/// Check for an existing file -inline std::string ExistingFile(const 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(); -} +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func; + + /// 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 { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } -/// Check for an existing directory -inline std::string ExistingDirectory(const 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(); -} + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// 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) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const 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) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const 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 -inline std::string ExistingPath(const std::string &filename) { - struct stat buffer; - bool const exist = stat(filename.c_str(), &buffer) == 0; - if(!exist) { - return "Path does not exist: " + filename; +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const 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(); + }; } - return std::string(); -} +}; -/// Check for a non-existing path -inline std::string NonexistentPath(const std::string &filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - if(exist) { - return "Path already exists: " + filename; +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; } - return std::string(); -} +}; +} // namespace detail -/// Produce a range validator function -template std::function Range(T min, T max) { - return [min, max](std::string input) { - T val; - detail::lexical_cast(input, val); - if(val < min || val > max) - return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); +// Static is not needed here, because global const implies static. - return std::string(); - }; -} +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; -/// Range of one value is 0 to value -template std::function Range(T max) { - return Range(static_cast(0), max); -} +/// 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; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// 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 << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(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) {} +}; +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; + virtual ~FormatterBase() = 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)) {} + + /// 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 { @@ -1100,6 +1540,8 @@ using Option_p = std::unique_ptr