diff --git a/programs/cleos/CLI11.hpp b/programs/cleos/CLI11.hpp index 11dbe1ccfec..8f958076a81 100644 --- a/programs/cleos/CLI11.hpp +++ b/programs/cleos/CLI11.hpp @@ -1,315 +1,186 @@ #pragma once -// Distributed under the 3-Clause BSD License. See accompanying -// file LICENSE or https://github.com/CLIUtils/CLI11 for details. +// CLI11: Version 1.9.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.9.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: -// This file was generated using MakeSingleHeader.py in CLI11/scripts -// from: v1.1.0-17-gb88f1f2 - -// This has the complete CLI library in one file. - -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include -#include #include -#include +#include +#include +#include +#include #include +#include -// From CLI/Error.hpp - -namespace CLI { - -/// 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, - File, - Conversion, - Validation, - Required, - Requires, - Excludes, - Extras, - ExtrasINI, - Invalid, - Horrible, - OptionNotFound, - BaseClass = 255 -}; - -// 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 -struct Error : public std::runtime_error { - int exit_code; - bool print_help; - int get_exit_code() const { return exit_code; } - Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) - : runtime_error(parent + ": " + name), exit_code(static_cast(exit_code)), print_help(print_help) {} - Error(std::string parent, - std::string name, - int exit_code = static_cast(ExitCodes::BaseClass), - bool print_help = true) - : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {} -}; - -/// Construction errors (not in parsing) -struct ConstructionError : public Error { - // Using Error::Error constructors seem to not work on GCC 4.7 - ConstructionError(std::string parent, - std::string name, - ExitCodes exit_code = ExitCodes::BaseClass, - bool print_help = true) - : Error(parent, name, exit_code, print_help) {} -}; - -/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) -struct IncorrectConstruction : public ConstructionError { - IncorrectConstruction(std::string name) - : ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {} -}; - -/// Thrown on construction of a bad name -struct BadNameString : public ConstructionError { - BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {} -}; - -/// Thrown when an option already exists -struct OptionAlreadyAdded : public ConstructionError { - OptionAlreadyAdded(std::string name) - : ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {} -}; - -// Parsing errors - -/// Anything that can error in Parse -struct ParseError : public Error { - ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true) - : Error(parent, name, exit_code, print_help) {} -}; - -// Not really "errors" - -/// This is a successful completion on parsing, supposed to exit -struct Success : public ParseError { - Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {} -}; - -/// -h or --help on command line -struct CallForHelp : public ParseError { - CallForHelp() - : ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {} -}; - -/// Thrown when parsing an INI file and it is missing -struct FileError : public ParseError { - FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {} -}; - -/// Thrown when conversion call back fails, such as when an int fails to coerse to a string -struct ConversionError : public ParseError { - ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {} -}; -/// Thrown when validation of results fails -struct ValidationError : public ParseError { - ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {} -}; +// Verbatim copy from CLI/Version.hpp: -/// Thrown when a required option is missing -struct RequiredError : public ParseError { - RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {} -}; -/// Thrown when a requires option is missing -struct RequiresError : public ParseError { - RequiresError(std::string name, std::string subname) - : ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {} -}; +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 9 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "1.9.0" -/// Thrown when a exludes option is present -struct ExcludesError : public ParseError { - ExcludesError(std::string name, std::string subname) - : ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {} -}; -/// Thrown when too many positionals or options are found -struct ExtrasError : public ParseError { - ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {} -}; -/// Thrown when extra values are found in an INI file -struct ExtrasINIError : public ParseError { - ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {} -}; -/// Thrown when validation fails before parsing -struct InvalidError : public ParseError { - InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {} -}; +// Verbatim copy from CLI/Macros.hpp: -/// This is just a safety check to verify selection and parsing match -struct HorribleError : public ParseError { - HorribleError(std::string name) - : ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {} -}; -// After parsing +// 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 -/// Thrown when counting a non-existent option -struct OptionNotFound : public Error { - OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {} -}; +#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 -/// @} -} // namespace CLI -// From CLI/TypeTools.hpp -namespace CLI { +// Verbatim copy from CLI/Validators.hpp: -// Type tools -// Copied from C++14 -#if __cplusplus < 201402L -template using enable_if_t = typename std::enable_if::type; +// C standard library +// Only needed for existence checking +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 #else -// If your compiler supports C++14, you can use that definition instead -using std::enable_if_t; +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#define CLI11_HAS_FILESYSTEM 1 +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif #endif -template struct is_vector { static const bool value = false; }; - -template struct is_vector> { static bool const value = true; }; - -template struct is_bool { static const bool value = false; }; - -template <> struct is_bool { static bool const value = true; }; - -namespace detail { -// Based generally on https://rmf.io/cxx11/almost-static-if -/// Simple empty scoped class -enum class enabler {}; +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif -/// An instance to use in EnableIf -constexpr enabler dummy = {}; -// 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 +// From CLI/Version.hpp: -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"; -} +// From CLI/Macros.hpp: -/// 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"; -} -template ::value && !std::is_integral::value && !is_vector::value, - detail::enabler> = detail::dummy> -constexpr const char *type_name() { - return "TEXT"; -} -// Lexical cast +// From CLI/StringTools.hpp: -/// Integers / enums -template ::value - || std::is_enum::value - , detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - try { - output = static_cast(std::stoll(input)); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } -} +namespace CLI { -/// Floats -template ::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - try { - output = static_cast(std::stold(input)); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } -} +/// 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 { -/// String and similar -template < - typename T, - enable_if_t::value - && !std::is_integral::value - && !std::is_enum::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - output = input; - return true; +/// 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); } -} // namespace detail -} // namespace CLI +} // namespace enums -// From CLI/StringTools.hpp +/// Export to CLI namespace +using enums::operator<<; -namespace CLI { namespace detail { - +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; // 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 emtpy string, give consistent result - if(s == "") - elems.emplace_back(""); + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(); else { std::stringstream ss; ss.str(s); @@ -324,11 +195,28 @@ inline std::vector split(const std::string &s, char delim) { /// Simple function to join a string template std::string join(const T &v, std::string delim = ",") { std::ostringstream s; - size_t start = 0; - for(const auto &i : v) { - if(start++ > 0) - s << delim; - s << i; + 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(); } @@ -336,7 +224,7 @@ template std::string join(const T &v, std::string delim = ",") { /// 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++) { + for(std::size_t start = 0; start < v.size(); start++) { if(start > 0) s << delim; s << v[v.size() - start - 1]; @@ -387,30 +275,47 @@ inline std::string trim_copy(const std::string &str) { return trim(s); } +/// remove quotes at the front and back of a string either '"' or '\'' +inline std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + /// 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 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, std::size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; - if(description != "") { + if(!description.empty()) { if(name.length() >= wid) - out << std::endl << std::setw(static_cast(wid)) << ""; - out << description; + 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 << std::endl; + out << "\n"; + return out; } /// Verify the first character of an option -template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } +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 std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; -} +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) { @@ -422,6 +327,11 @@ inline bool valid_name_string(const std::string &str) { 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) { @@ -430,27 +340,104 @@ 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) { + + std::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) { + std::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' -inline std::vector split_up(std::string str) { +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str, char delimiter = '\0') { - std::vector delims = {'\'', '\"'}; - auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + const std::string delims("\'\"`"); + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); + }; 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); @@ -458,1979 +445,7779 @@ 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)) { std::string value = std::string(str.begin(), it); output.push_back(value); - str = std::string(it, str.end()); + str = std::string(it + 1, 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(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline std::size_t escape_detect(std::string &str, std::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/Split.hpp +// From CLI/Error.hpp: namespace CLI { -namespace detail { -// Returns false if not a short option. Otherwise, sets opt name and rest and returns true -inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { - if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { - name = current.substr(1, 1); - rest = current.substr(2); - return true; - } else - return false; -} +// 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) {} -// 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("="); - 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; -} +/// 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 +}; -// 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(current.substr(0, val)); - current = current.substr(val + 1); - } - output.push_back(current); - return output; -} +// Error definitions -/// 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) { +/// @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. +/// @{ - std::vector short_names; - std::vector long_names; - std::string pos_name; +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; - 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("Invalid one char name: " + 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("Bad long name: " + name); - } else if(name == "-" || name == "--") { - throw BadNameString("Must have a name, not just dashes"); - } else { - if(pos_name.length() > 0) - throw BadNameString("Only one positional name allowed, remove: " + name); - pos_name = name; - } - } + public: + int get_exit_code() const { return actual_exit_code; } - return std::tuple, std::vector, std::string>( - short_names, long_names, pos_name); -} + std::string get_name() const { return error_name; } -} // namespace detail -} // namespace CLI + 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)) {} -// From CLI/Ini.hpp + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; -namespace CLI { -namespace detail { +// Note: Using Error::Error constructors does not work on GCC 4.7 -inline std::string inijoin(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(R"(")") == std::string::npos) - s << R"(")" << arg << R"(")"; - else - s << R"(')" << arg << R"(')"; +/// 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"); + } +}; - return s.str(); -} +/// 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); + } +}; -struct ini_ret_t { - /// This is the full name with dots - std::string fullname; +/// 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); + } +}; - /// Listing of inputs - std::vector inputs; +// Parsing errors - /// Current parent level - size_t level = 0; +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; - /// 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 ""; - } +// Not really "errors" - /// Return name - std::string name() const { - std::vector plist = detail::split(fullname, '.'); - return plist.at(plist.size() - 1); - } +/// 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) {} }; -/// Internal parsing function -inline std::vector parse_ini(std::istream &input) { - std::string name, line; - std::string section = "default"; - - std::vector output; +/// -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) {} +}; - while(getline(input, line)) { - std::vector items; +/// 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) {} +}; - 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); - } else { - name = detail::trim_copy(line); - items = {"ON"}; - } +/// 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) {} +}; - if(detail::to_lower(section) == "default") - out.fullname = name; - else - out.fullname = section + "." + name; +/// 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?)"); } +}; - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); - } +/// 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"); } - return output; -} + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; -/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure -inline std::vector parse_ini(const std::string &name) { +/// 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) {} +}; - std::ifstream input{name}; - if(!input.good()) - throw FileError(name); +/// 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(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::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 + "]"); + 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); + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + 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); + } + if(max_option == 1) + return RequiredError("Requires at most 1 options be given from [" + option_list + "]", + ExitCodes::RequiredError); - return parse_ini(input); -} + 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); + } +}; -} // namespace detail -} // namespace CLI +/// 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, std::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, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + 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"); + } +}; -// From CLI/Validators.hpp +/// 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) {} +}; -namespace CLI { +/// 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) {} +}; -/// @defgroup validator_group Validators -/// @brief Some validators that are provided -/// -/// These are simple `bool(std::string)` validators that are useful. -/// @{ +/// 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) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; -/// Check for an existing file -inline bool ExistingFile(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) { - std::cerr << "File does not exist: " << filename << std::endl; - return false; - } else if(is_dir) { - std::cerr << "File is actually a directory: " << filename << std::endl; - return false; - } else { - return true; +/// 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"); } -} +}; -/// Check for an existing directory -inline bool ExistingDirectory(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) { - std::cerr << "Directory does not exist: " << filename << std::endl; - return false; - } else if(is_dir) { - return true; - } else { - std::cerr << "Directory is actually a file: " << filename << std::endl; - return false; +/// 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) { } -} +}; -/// Check for a non-existing path -inline bool NonexistentPath(std::string filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - if(!exist) { - return true; - } else { - std::cerr << "Path exists: " << filename << std::endl; - return false; - } -} +/// 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) +}; -/// 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); - return val >= min && val <= max; - }; -} +// 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) {} +}; -/// Range of one value is 0 to value -template std::function Range(T max) { return Range(static_cast(0), max); } +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE /// @} } // namespace CLI -// From CLI/Option.hpp +// From CLI/TypeTools.hpp: namespace CLI { -using results_t = std::vector; -using callback_t = std::function; - -class Option; -class App; +// Type tools -using Option_p = std::unique_ptr