diff --git a/dep/CLI11/CLI11.hpp b/dep/CLI11/CLI11.hpp new file mode 100644 index 000000000..5ba22d325 --- /dev/null +++ b/dep/CLI11/CLI11.hpp @@ -0,0 +1,7821 @@ +#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& ltrim(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& ltrim(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& current, 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& current, 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& current, 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