Created
September 9, 2019 22:39
-
-
Save rafaelperez/5e2067b14a1202ed74479056082580d7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 <algorithm> | |
#include <cmath> | |
#include <deque> | |
#include <exception> | |
#include <fstream> | |
#include <functional> | |
#include <iomanip> | |
#include <iostream> | |
#include <istream> | |
#include <iterator> | |
#include <locale> | |
#include <map> | |
#include <memory> | |
#include <numeric> | |
#include <set> | |
#include <sstream> | |
#include <stdexcept> | |
#include <string> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
#include <vector> | |
// 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(<optional>) | |
#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 <boost/version.hpp> | |
#if BOOST_VERSION < 106100 | |
#error "This boost::optional version is not supported, use 1.61 or better" | |
#endif | |
#endif | |
#if CLI11_STD_OPTIONAL | |
#include <optional> | |
#endif | |
#if CLI11_EXPERIMENTAL_OPTIONAL | |
#include <experimental/optional> | |
#endif | |
#if CLI11_BOOST_OPTIONAL | |
#include <boost/optional.hpp> | |
#include <boost/optional/optional_io.hpp> | |
#endif | |
// From CLI/Version.hpp: | |
// From CLI/Macros.hpp: | |
// From CLI/Optional.hpp: | |
namespace CLI { | |
#if CLI11_STD_OPTIONAL | |
template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) { | |
T v; | |
in >> v; | |
val = v; | |
return in; | |
} | |
#endif | |
#if CLI11_EXPERIMENTAL_OPTIONAL | |
template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) { | |
T v; | |
in >> v; | |
val = v; | |
return in; | |
} | |
#endif | |
#if CLI11_BOOST_OPTIONAL | |
template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &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 <typename T, typename = typename std::enable_if<std::is_enum<T>::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<typename std::underlying_type<T>::type>(item); | |
} | |
/// input streaming for enumerations | |
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> | |
std::istream &operator>>(std::istream &in, T &item) { | |
typename std::underlying_type<T>::type i; | |
in >> i; | |
item = static_cast<T>(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<std::string> split(const std::string &s, char delim) { | |
std::vector<std::string> 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 <typename T> 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 <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type> | |
inline auto as_string(T &&v) -> decltype(std::forward<T>(v)) { | |
return std::forward<T>(v); | |
} | |
/// Simple function to join a string | |
template <typename T> 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 <typename T, | |
typename Callable, | |
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::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 <typename T> std::string rjoin(const T &v, std::string delim = ",") { | |
std::ostringstream s; | |
for(size_t start = 0; start < v.size(); start++) { | |
if(start > 0) | |
s << delim; | |
s << v[v.size() - start - 1]; | |
} | |
return s.str(); | |
} | |
// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string | |
/// Trim whitespace from left of string | |
inline std::string <rim(std::string &str) { | |
auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); }); | |
str.erase(str.begin(), it); | |
return str; | |
} | |
/// Trim anything from left of string | |
inline std::string <rim(std::string &str, const std::string &filter) { | |
auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); | |
str.erase(str.begin(), it); | |
return str; | |
} | |
/// Trim whitespace from right of string | |
inline std::string &rtrim(std::string &str) { | |
auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(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<int>(wid)) << std::left << name; | |
if(!description.empty()) { | |
if(name.length() >= wid) | |
out << "\n" << std::setw(static_cast<int>(wid)) << ""; | |
for(const char c : description) { | |
out.put(c); | |
if(c == '\n') { | |
out << std::setw(static_cast<int>(wid)) << ""; | |
} | |
} | |
} | |
out << "\n"; | |
return out; | |
} | |
/// Verify the first character of an option | |
template <typename T> bool valid_first_char(T c) { | |
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; | |
} | |
/// Verify following characters of an option | |
template <typename T> 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<std::ptrdiff_t>(loc), | |
flags.begin() + static_cast<std::ptrdiff_t>(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<std::string> 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 <typename Callable> 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<std::string> split_up(std::string str) { | |
const std::string delims("\'\"`"); | |
auto find_ws = [](char ch) { return std::isspace<char>(ch, std::locale()); }; | |
trim(str); | |
std::vector<std::string> 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<int>(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<int>(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<std::string> 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<std::string> 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 <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; | |
/// A copy of std::void_t from C++17 (helper for C++11 and C++14) | |
template <typename... Ts> 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 <typename... Ts> using void_t = typename make_void<Ts...>::type; | |
/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine | |
template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; | |
/// Check to see if something is a vector (fail check by default) | |
template <typename T> struct is_vector : std::false_type {}; | |
/// Check to see if something is a vector (true if actually a vector) | |
template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {}; | |
/// Check to see if something is bool (fail check by default) | |
template <typename T> struct is_bool : std::false_type {}; | |
/// Check to see if something is bool (true if actually a bool) | |
template <> struct is_bool<bool> : std::true_type {}; | |
/// Check to see if something is a shared pointer | |
template <typename T> struct is_shared_ptr : std::false_type {}; | |
/// Check to see if something is a shared pointer (True if really a shared pointer) | |
template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {}; | |
/// Check to see if something is a shared pointer (True if really a shared pointer) | |
template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {}; | |
/// Check to see if something is copyable pointer | |
template <typename T> struct is_copyable_ptr { | |
static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value; | |
}; | |
/// This can be specialized to override the type deduction for IsMember. | |
template <typename T> struct IsMemberType { using type = T; }; | |
/// The main custom type needed here is const char * should be a string. | |
template <> struct IsMemberType<const char *> { 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<T> be valid. | |
template <typename T> struct element_type { | |
using type = | |
typename std::conditional<is_copyable_ptr<T>::value, typename std::pointer_traits<T>::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 <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; }; | |
/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. | |
template <typename T, typename _ = void> struct pair_adaptor : std::false_type { | |
using value_type = typename T::value_type; | |
using first_type = typename std::remove_const<value_type>::type; | |
using second_type = typename std::remove_const<value_type>::type; | |
/// Get the first value (really just the underlying value) | |
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) { | |
return std::forward<Q>(pair_value); | |
} | |
/// Get the second value (really just the underlying value) | |
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) { | |
return std::forward<Q>(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 <typename T> | |
struct pair_adaptor< | |
T, | |
conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>> | |
: std::true_type { | |
using value_type = typename T::value_type; | |
using first_type = typename std::remove_const<typename value_type::first_type>::type; | |
using second_type = typename std::remove_const<typename value_type::second_type>::type; | |
/// Get the first value (really just the underlying value) | |
template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) { | |
return std::get<0>(std::forward<Q>(pair_value)); | |
} | |
/// Get the second value (really just the underlying value) | |
template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) { | |
return std::get<1>(std::forward<Q>(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 <typename S, typename T> class is_streamable { | |
template <typename SS, typename TT> | |
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type()); | |
template <typename, typename> static auto test(...) -> std::false_type; | |
public: | |
static const bool value = decltype(test<S, T>(0))::value; | |
}; | |
/// Convert an object to a string (directly forward if this can become a string) | |
template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy> | |
auto to_string(T &&value) -> decltype(std::forward<T>(value)) { | |
return std::forward<T>(value); | |
} | |
/// Convert an object to a string (streaming must be supported for that type) | |
template <typename T, | |
enable_if_t<!std::is_constructible<std::string, T>::value && is_streamable<std::stringstream, T>::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 <typename T, | |
enable_if_t<!std::is_constructible<std::string, T>::value && !is_streamable<std::stringstream, T>::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 <typename T, | |
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
constexpr const char *type_name() { | |
return "INT"; | |
} | |
template <typename T, | |
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
constexpr const char *type_name() { | |
return "UINT"; | |
} | |
template <typename T, enable_if_t<std::is_floating_point<T>::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 <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
constexpr const char *type_name() { | |
return "VECTOR"; | |
} | |
/// Print name for enumeration types | |
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | |
constexpr const char *type_name() { | |
return "ENUM"; | |
} | |
/// Print for all other types | |
template <typename T, | |
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value && | |
!std::is_enum<T>::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<std::is_integral<T>::value && std::is_signed<T>::value && !is_bool<T>::value && !std::is_enum<T>::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<T>(output_ll); | |
return n == input.size() && static_cast<long long>(output) == output_ll; | |
} catch(const std::invalid_argument &) { | |
return false; | |
} catch(const std::out_of_range &) { | |
return false; | |
} | |
} | |
/// Unsigned integers | |
template <typename T, | |
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::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<T>(output_ll); | |
return n == input.size() && static_cast<unsigned long long>(output) == output_ll; | |
} catch(const std::invalid_argument &) { | |
return false; | |
} catch(const std::out_of_range &) { | |
return false; | |
} | |
} | |
/// Boolean values | |
template <typename T, enable_if_t<is_bool<T>::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 <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> | |
bool lexical_cast(std::string input, T &output) { | |
try { | |
size_t n = 0; | |
output = static_cast<T>(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 <typename T, | |
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
std::is_assignable<T &, std::string>::value, | |
detail::enabler> = detail::dummy> | |
bool lexical_cast(std::string input, T &output) { | |
output = input; | |
return true; | |
} | |
/// Enumerations | |
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> | |
bool lexical_cast(std::string input, T &output) { | |
typename std::underlying_type<T>::type val; | |
bool retval = detail::lexical_cast(input, val); | |
if(!retval) { | |
return false; | |
} | |
output = static_cast<T>(val); | |
return true; | |
} | |
/// Non-string parsable | |
template <typename T, | |
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
!std::is_assignable<T &, std::string>::value && !std::is_enum<T>::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 <typename T, | |
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> | |
void sum_flag_vector(const std::vector<std::string> &flags, T &output) { | |
int64_t count{0}; | |
for(auto &flag : flags) { | |
count += detail::to_flag_value(flag); | |
} | |
output = (count > 0) ? static_cast<T>(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 <typename T, | |
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> | |
void sum_flag_vector(const std::vector<std::string> &flags, T &output) { | |
int64_t count{0}; | |
for(auto &flag : flags) { | |
count += detail::to_flag_value(flag); | |
} | |
output = static_cast<T>(count); | |
} | |
} // namespace detail | |
} // namespace CLI | |
// From CLI/Split.hpp: | |
namespace CLI { | |
namespace detail { | |
// Returns false if not a short option. Otherwise, sets opt name and rest and returns true | |
inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { | |
if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { | |
name = current.substr(1, 1); | |
rest = current.substr(2); | |
return true; | |
} else | |
return false; | |
} | |
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true | |
inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { | |
if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { | |
auto loc = current.find_first_of('='); | |
if(loc != std::string::npos) { | |
name = current.substr(2, loc - 2); | |
value = current.substr(loc + 1); | |
} else { | |
name = current.substr(2); | |
value = ""; | |
} | |
return true; | |
} else | |
return false; | |
} | |
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true | |
inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { | |
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { | |
auto loc = current.find_first_of(':'); | |
if(loc != std::string::npos) { | |
name = current.substr(1, loc - 1); | |
value = current.substr(loc + 1); | |
} else { | |
name = current.substr(1); | |
value = ""; | |
} | |
return true; | |
} else | |
return false; | |
} | |
// Splits a string into multiple long and short names | |
inline std::vector<std::string> split_names(std::string current) { | |
std::vector<std::string> 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<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) { | |
std::vector<std::string> 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<std::pair<std::string, std::string>> 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>, std::vector<std::string>, std::string> | |
get_names(const std::vector<std::string> &input) { | |
std::vector<std::string> short_names; | |
std::vector<std::string> 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>, std::vector<std::string>, 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<std::string> 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<char>(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<std::string> parents; | |
/// This is the name | |
std::string name; | |
/// Listing of inputs | |
std::vector<std::string> inputs; | |
/// The list of parents and name joined by "." | |
std::string fullname() const { | |
std::vector<std::string> tmp = parents; | |
tmp.emplace_back(name); | |
return detail::join(tmp, "."); | |
} | |
}; | |
/// This class provides a converter for configuration files. | |
class Config { | |
protected: | |
std::vector<ConfigItem> 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<ConfigItem> 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<ConfigItem> 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<ConfigItem> from_config(std::istream &input) const override { | |
std::string line; | |
std::string section = "default"; | |
std::vector<ConfigItem> output; | |
while(getline(input, line)) { | |
std::vector<std::string> 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<std::string> 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<std::string()> 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<std::string(std::string &)> 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<std::string(std::string &)> 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<std::string(std::string &)> 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<std::string(std::string & filename)> &f1 = func_; | |
const std::function<std::string(std::string & filename)> &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<std::string(std::string &)> &f1 = func_; | |
const std::function<std::string(std::string &)> &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<std::string()> &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<std::string(std::string & res)> &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<std::string()> &dfunc1 = val1.desc_function_; | |
const std::function<std::string()> &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 <typename T> Range(T min, T max) { | |
std::stringstream out; | |
out << detail::type_name<T>() << " 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 <typename T> explicit Range(T max) : Range(static_cast<T>(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 <typename T> Bound(T min, T max) { | |
std::stringstream out; | |
out << detail::type_name<T>() << " 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 <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {} | |
}; | |
namespace detail { | |
template <typename T, | |
enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy> | |
auto smart_deref(T value) -> decltype(*value) { | |
return *value; | |
} | |
template < | |
typename T, | |
enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy> | |
typename std::remove_reference<T>::type &smart_deref(T &value) { | |
return value; | |
} | |
/// Generate a string representation of a set | |
template <typename T> std::string generate_set(const T &set) { | |
using element_t = typename detail::element_type<T>::type; | |
using iteration_type_t = typename detail::pair_adaptor<element_t>::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<element_t>::first(v); }, | |
",")); | |
out.push_back('}'); | |
return out; | |
} | |
/// Generate a string representation of a map | |
template <typename T> std::string generate_map(const T &map, bool key_only = false) { | |
using element_t = typename detail::element_type<T>::type; | |
using iteration_type_t = typename detail::pair_adaptor<element_t>::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<element_t>::first(v)); | |
if(!key_only) { | |
res += "->" + detail::as_string(detail::pair_adaptor<element_t>::second(v)); | |
} | |
return res; | |
}, | |
",")); | |
out.push_back('}'); | |
return out; | |
} | |
template <typename> 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 <typename T, typename V> | |
static auto test_find(int) -> sfinae_true<decltype(std::declval<T>().find(std::declval<V>()))>; | |
template <typename, typename V> static auto test_find(long) -> std::false_type; | |
template <typename T, typename V> struct has_find : decltype(test_find<T, V>(0)) {}; | |
/// A search function | |
template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy> | |
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { | |
using element_t = typename detail::element_type<T>::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<element_t>::first(v) == val); | |
}); | |
return {(it != std::end(setref)), it}; | |
} | |
/// A search function that uses the built in find function | |
template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy> | |
auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { | |
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 <typename T, typename V> | |
auto search(const T &set, const V &val, const std::function<V(V)> &filter_function) | |
-> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { | |
using element_t = typename detail::element_type<T>::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<element_t>::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 T> typename std::enable_if<std::is_integral<T>::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 T> | |
typename std::enable_if<std::is_floating_point<T>::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<std::string(std::string)>; | |
/// This allows in-place construction using an initializer list | |
template <typename T, typename... Args> | |
explicit IsMember(std::initializer_list<T> values, Args &&... args) | |
: IsMember(std::vector<T>(values), std::forward<Args>(args)...) {} | |
/// This checks to see if an item is in a set (empty function) | |
template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(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 <typename T, typename F> 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<T>::type; // Removes (smart) pointers if needed | |
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map | |
using local_item_t = typename IsMemberType<item_t>::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<local_item_t(local_item_t)> 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<element_t>::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 <typename T, typename... Args> | |
IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) | |
: IsMember(std::forward<T>(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 <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>; | |
/// Translate named items to other or a value set | |
class Transformer : public Validator { | |
public: | |
using filter_fn_t = std::function<std::string(std::string)>; | |
/// This allows in-place construction | |
template <typename... Args> | |
explicit Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args) | |
: Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {} | |
/// direct map of std::string to std::string | |
template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(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 <typename T, typename F> explicit Transformer(T mapping, F filter_function) { | |
static_assert(detail::pair_adaptor<typename detail::element_type<T>::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<T>::type; // Removes (smart) pointers if needed | |
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map | |
using local_item_t = typename IsMemberType<item_t>::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<local_item_t(local_item_t)> 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<element_t>::second(*res.second)); | |
} | |
return std::string{}; | |
}; | |
} | |
/// You can pass in as many filter functions as you like, they nest | |
template <typename T, typename... Args> | |
Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) | |
: Transformer(std::forward<T>(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<std::string(std::string)>; | |
/// This allows in-place construction | |
template <typename... Args> | |
explicit CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&... args) | |
: CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {} | |
/// direct map of std::string to std::string | |
template <typename T> 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 <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) { | |
static_assert(detail::pair_adaptor<typename detail::element_type<T>::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<T>::type; // Removes (smart) pointers if needed | |
using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map | |
using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones | |
// (const char * to std::string) | |
using iteration_type_t = typename detail::pair_adaptor<element_t>::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<local_item_t(local_item_t)> 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<element_t>::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<element_t>::second(*res.second)); | |
return std::string{}; | |
} | |
} | |
for(const auto &v : detail::smart_deref(mapping)) { | |
auto output_string = detail::as_string(detail::pair_adaptor<element_t>::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 <typename T, typename... Args> | |
CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) | |
: CheckedTransformer(std::forward<T>(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 <string, float> or <string, double>. | |
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 <typename Number> | |
explicit AsNumberWithUnit(std::map<std::string, Number> mapping, | |
Options opts = DEFAULT, | |
const std::string &unit_name = "UNIT") { | |
description(generate_description<Number>(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<size_t>(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<Number>()); | |
} | |
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 <typename Number> static void validate_mapping(std::map<std::string, Number> &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<std::string, Number> 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 <typename Number> static std::string generate_description(const std::string &name, Options opts) { | |
std::stringstream out; | |
out << detail::type_name<Number>() << ' '; | |
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 <size unit, factor> mapping | |
static std::map<std::string, result_t> init_mapping(bool kb_is_1000) { | |
std::map<std::string, result_t> 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<std::string, result_t> 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<std::string, std::string> split_program_name(std::string commandline) { | |
// try to determine the programName | |
std::pair<std::string, std::string> 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<std::string, std::string> 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<std::string(const App *, std::string, AppFormatMode)>; | |
/// 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<const Option *> 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<std::string>; | |
using callback_t = std::function<bool(results_t)>; | |
class Option; | |
class App; | |
using Option_p = std::unique_ptr<Option>; | |
enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join }; | |
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way | |
/// to share parts of the class; an OptionDefaults can copy to an Option. | |
template <typename CRTP> class OptionBase { | |
friend App; | |
protected: | |
/// The group membership | |
std::string group_ = std::string("Options"); | |
/// True if this is a required option | |
bool required_{false}; | |
/// Ignore the case when matching (option, not value) | |
bool ignore_case_{false}; | |
/// Ignore underscores when matching (option, not value) | |
bool ignore_underscore_{false}; | |
/// Allow this option to be given in a configuration file | |
bool configurable_{true}; | |
/// Disable overriding flag values with '=value' | |
bool disable_flag_override_{false}; | |
/// Specify a delimiter character for vector arguments | |
char delimiter_{'\0'}; | |
/// Automatically capture default value | |
bool always_capture_default_{false}; | |
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) | |
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; | |
/// Copy the contents to another similar class (one based on OptionBase) | |
template <typename T> void copy_to(T *other) const { | |
other->group(group_); | |
other->required(required_); | |
other->ignore_case(ignore_case_); | |
other->ignore_underscore(ignore_underscore_); | |
other->configurable(configurable_); | |
other->disable_flag_override(disable_flag_override_); | |
other->delimiter(delimiter_); | |
other->always_capture_default(always_capture_default_); | |
other->multi_option_policy(multi_option_policy_); | |
} | |
public: | |
// setters | |
/// Changes the group membership | |
CRTP *group(std::string name) { | |
group_ = name; | |
return static_cast<CRTP *>(this); | |
} | |
/// Set the option as required | |
CRTP *required(bool value = true) { | |
required_ = value; | |
return static_cast<CRTP *>(this); | |
} | |
/// Support Plumbum term | |
CRTP *mandatory(bool value = true) { return required(value); } | |
CRTP *always_capture_default(bool value = true) { | |
always_capture_default_ = value; | |
return static_cast<CRTP *>(this); | |
} | |
// Getters | |
/// Get the group of this option | |
const std::string &get_group() const { return group_; } | |
/// True if this is a required option | |
bool get_required() const { return required_; } | |
/// The status of ignore case | |
bool get_ignore_case() const { return ignore_case_; } | |
/// The status of ignore_underscore | |
bool get_ignore_underscore() const { return ignore_underscore_; } | |
/// The status of configurable | |
bool get_configurable() const { return configurable_; } | |
/// The status of configurable | |
bool get_disable_flag_override() const { return disable_flag_override_; } | |
/// Get the current delimeter char | |
char get_delimiter() const { return delimiter_; } | |
/// Return true if this will automatically capture the default value for help printing | |
bool get_always_capture_default() const { return always_capture_default_; } | |
/// The status of the multi option policy | |
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } | |
// Shortcuts for multi option policy | |
/// Set the multi option policy to take last | |
CRTP *take_last() { | |
auto self = static_cast<CRTP *>(this); | |
self->multi_option_policy(MultiOptionPolicy::TakeLast); | |
return self; | |
} | |
/// Set the multi option policy to take last | |
CRTP *take_first() { | |
auto self = static_cast<CRTP *>(this); | |
self->multi_option_policy(MultiOptionPolicy::TakeFirst); | |
return self; | |
} | |
/// Set the multi option policy to take last | |
CRTP *join() { | |
auto self = static_cast<CRTP *>(this); | |
self->multi_option_policy(MultiOptionPolicy::Join); | |
return self; | |
} | |
/// Allow in a configuration file | |
CRTP *configurable(bool value = true) { | |
configurable_ = value; | |
return static_cast<CRTP *>(this); | |
} | |
/// Allow in a configuration file | |
CRTP *delimiter(char value = '\0') { | |
delimiter_ = value; | |
return static_cast<CRTP *>(this); | |
} | |
}; | |
/// This is a version of OptionBase that only supports setting values, | |
/// for defaults. It is stored as the default option in an App. | |
class OptionDefaults : public OptionBase<OptionDefaults> { | |
public: | |
OptionDefaults() = default; | |
// Methods here need a different implementation if they are Option vs. OptionDefault | |
/// Take the last argument if given multiple times | |
OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { | |
multi_option_policy_ = value; | |
return this; | |
} | |
/// Ignore the case of the option name | |
OptionDefaults *ignore_case(bool value = true) { | |
ignore_case_ = value; | |
return this; | |
} | |
/// Ignore underscores in the option name | |
OptionDefaults *ignore_underscore(bool value = true) { | |
ignore_underscore_ = value; | |
return this; | |
} | |
/// Disable overriding flag values with an '=<value>' segment | |
OptionDefaults *disable_flag_override(bool value = true) { | |
disable_flag_override_ = value; | |
return this; | |
} | |
/// set a delimiter character to split up single arguments to treat as multiple inputs | |
OptionDefaults *delimiter(char value = '\0') { | |
delimiter_ = value; | |
return this; | |
} | |
}; | |
class Option : public OptionBase<Option> { | |
friend App; | |
protected: | |
/// @name Names | |
///@{ | |
/// A list of the short names (`-a`) without the leading dashes | |
std::vector<std::string> snames_; | |
/// A list of the long names (`--a`) without the leading dashes | |
std::vector<std::string> lnames_; | |
/// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of | |
/// what is in snames or lnames but will trigger a particular response on a flag | |
std::vector<std::pair<std::string, std::string>> default_flag_values_; | |
/// a list of flag names with specified default values; | |
std::vector<std::string> fnames_; | |
/// A positional name | |
std::string pname_; | |
/// If given, check the environment for this option | |
std::string envname_; | |
///@} | |
/// @name Help | |
///@{ | |
/// The description for help strings | |
std::string description_; | |
/// A human readable default value, either manually set, captured, or captured by default | |
std::string default_str_; | |
/// A human readable type value, set when App creates this | |
/// | |
/// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. | |
std::function<std::string()> type_name_{[]() { return std::string(); }}; | |
/// Run this function to capture a default (ignore if empty) | |
std::function<std::string()> default_function_; | |
///@} | |
/// @name Configuration | |
///@{ | |
/// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option, | |
/// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean | |
/// vector of pairs. | |
int type_size_{1}; | |
/// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values. | |
int expected_{1}; | |
/// A list of validators to run on each value parsed | |
std::vector<Validator> validators_; | |
/// A list of options that are required with this option | |
std::set<Option *> needs_; | |
/// A list of options that are excluded with this option | |
std::set<Option *> excludes_; | |
///@} | |
/// @name Other | |
///@{ | |
/// Remember the parent app | |
App *parent_; | |
/// Options store a callback to do all the work | |
callback_t callback_; | |
///@} | |
/// @name Parsing results | |
///@{ | |
/// Results of parsing | |
results_t results_; | |
/// Whether the callback has run (needed for INI parsing) | |
bool callback_run_{false}; | |
///@} | |
/// Making an option by hand is not defined, it must be made by the App class | |
Option(std::string option_name, | |
std::string option_description, | |
std::function<bool(results_t)> callback, | |
App *parent) | |
: description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) { | |
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name)); | |
} | |
public: | |
/// @name Basic | |
///@{ | |
/// Count the total number of times an option was passed | |
size_t count() const { return results_.size(); } | |
/// True if the option was not passed | |
size_t empty() const { return results_.empty(); } | |
/// This class is true if option is passed. | |
operator bool() const { return !empty(); } | |
/// Clear the parsed results (mostly for testing) | |
void clear() { results_.clear(); } | |
///@} | |
/// @name Setting options | |
///@{ | |
/// Set the number of expected arguments (Flags don't use this) | |
Option *expected(int value) { | |
// Break if this is a flag | |
if(type_size_ == 0) | |
throw IncorrectConstruction::SetFlag(get_name(true, true)); | |
// Setting 0 is not allowed | |
else if(value == 0) | |
throw IncorrectConstruction::Set0Opt(get_name()); | |
// No change is okay, quit now | |
else if(expected_ == value) | |
return this; | |
// Type must be a vector | |
else if(type_size_ >= 0) | |
throw IncorrectConstruction::ChangeNotVector(get_name()); | |
// TODO: Can support multioption for non-1 values (except for join) | |
else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) | |
throw IncorrectConstruction::AfterMultiOpt(get_name()); | |
expected_ = value; | |
return this; | |
} | |
/// Adds a Validator with a built in type name | |
Option *check(Validator validator, std::string validator_name = "") { | |
validator.non_modifying(); | |
validators_.push_back(std::move(validator)); | |
if(!validator_name.empty()) | |
validators_.front().name(validator_name); | |
return this; | |
} | |
/// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay). | |
Option *check(std::function<std::string(const std::string &)> validator, | |
std::string validator_description = "", | |
std::string validator_name = "") { | |
validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name)); | |
validators_.back().non_modifying(); | |
return this; | |
} | |
/// Adds a transforming validator with a built in type name | |
Option *transform(Validator validator, std::string validator_name = "") { | |
validators_.insert(validators_.begin(), std::move(validator)); | |
if(!validator_name.empty()) | |
validators_.front().name(validator_name); | |
return this; | |
} | |
/// Adds a validator-like function that can change result | |
Option *transform(std::function<std::string(std::string)> func, | |
std::string transform_description = "", | |
std::string transform_name = "") { | |
validators_.insert(validators_.begin(), | |
Validator( | |
[func](std::string &val) { | |
val = func(val); | |
return std::string{}; | |
}, | |
std::move(transform_description), | |
std::move(transform_name))); | |
return this; | |
} | |
/// Adds a user supplied function to run on each item passed in (communicate though lambda capture) | |
Option *each(std::function<void(std::string)> func) { | |
validators_.emplace_back( | |
[func](std::string &inout) { | |
func(inout); | |
return std::string{}; | |
}, | |
std::string{}); | |
return this; | |
} | |
/// Get a named Validator | |
Validator *get_validator(const std::string &validator_name = "") { | |
for(auto &validator : validators_) { | |
if(validator_name == validator.get_name()) { | |
return &validator; | |
} | |
} | |
if((validator_name.empty()) && (!validators_.empty())) { | |
return &(validators_.front()); | |
} | |
throw OptionNotFound(std::string("Validator ") + validator_name + " Not Found"); | |
} | |
/// Sets required options | |
Option *needs(Option *opt) { | |
auto tup = needs_.insert(opt); | |
if(!tup.second) | |
throw OptionAlreadyAdded::Requires(get_name(), opt->get_name()); | |
return this; | |
} | |
/// Can find a string if needed | |
template <typename T = App> Option *needs(std::string opt_name) { | |
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) | |
if(opt.get() != this && opt->check_name(opt_name)) | |
return needs(opt.get()); | |
throw IncorrectConstruction::MissingOption(opt_name); | |
} | |
/// Any number supported, any mix of string and Opt | |
template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) { | |
needs(opt); | |
return needs(opt1, args...); | |
} | |
/// Remove needs link from an option. Returns true if the option really was in the needs list. | |
bool remove_needs(Option *opt) { | |
auto iterator = std::find(std::begin(needs_), std::end(needs_), opt); | |
if(iterator != std::end(needs_)) { | |
needs_.erase(iterator); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/// Sets excluded options | |
Option *excludes(Option *opt) { | |
excludes_.insert(opt); | |
// Help text should be symmetric - excluding a should exclude b | |
opt->excludes_.insert(this); | |
// Ignoring the insert return value, excluding twice is now allowed. | |
// (Mostly to allow both directions to be excluded by user, even though the library does it for you.) | |
return this; | |
} | |
/// Can find a string if needed | |
template <typename T = App> Option *excludes(std::string opt_name) { | |
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) | |
if(opt.get() != this && opt->check_name(opt_name)) | |
return excludes(opt.get()); | |
throw IncorrectConstruction::MissingOption(opt_name); | |
} | |
/// Any number supported, any mix of string and Opt | |
template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) { | |
excludes(opt); | |
return excludes(opt1, args...); | |
} | |
/// Remove needs link from an option. Returns true if the option really was in the needs list. | |
bool remove_excludes(Option *opt) { | |
auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt); | |
if(iterator != std::end(excludes_)) { | |
excludes_.erase(iterator); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/// Sets environment variable to read if no option given | |
Option *envname(std::string name) { | |
envname_ = name; | |
return this; | |
} | |
/// Ignore case | |
/// | |
/// The template hides the fact that we don't have the definition of App yet. | |
/// You are never expected to add an argument to the template here. | |
template <typename T = App> Option *ignore_case(bool value = true) { | |
ignore_case_ = value; | |
auto *parent = dynamic_cast<T *>(parent_); | |
for(const Option_p &opt : parent->options_) | |
if(opt.get() != this && *opt == *this) | |
throw OptionAlreadyAdded(opt->get_name(true, true)); | |
return this; | |
} | |
/// Ignore underscores in the option names | |
/// | |
/// The template hides the fact that we don't have the definition of App yet. | |
/// You are never expected to add an argument to the template here. | |
template <typename T = App> Option *ignore_underscore(bool value = true) { | |
ignore_underscore_ = value; | |
auto *parent = dynamic_cast<T *>(parent_); | |
for(const Option_p &opt : parent->options_) | |
if(opt.get() != this && *opt == *this) | |
throw OptionAlreadyAdded(opt->get_name(true, true)); | |
return this; | |
} | |
/// Take the last argument if given multiple times (or another policy) | |
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { | |
if(get_items_expected() < 0) | |
throw IncorrectConstruction::MultiOptionPolicy(get_name()); | |
multi_option_policy_ = value; | |
return this; | |
} | |
/// disable flag overrides | |
Option *disable_flag_override(bool value = true) { | |
disable_flag_override_ = value; | |
return this; | |
} | |
///@} | |
/// @name Accessors | |
///@{ | |
/// The number of arguments the option expects | |
int get_type_size() const { return type_size_; } | |
/// The environment variable associated to this value | |
std::string get_envname() const { return envname_; } | |
/// The set of options needed | |
std::set<Option *> get_needs() const { return needs_; } | |
/// The set of options excluded | |
std::set<Option *> get_excludes() const { return excludes_; } | |
/// The default value (for help printing) DEPRECATED Use get_default_str() instead | |
CLI11_DEPRECATED("Use get_default_str() instead") | |
std::string get_defaultval() const { return default_str_; } | |
/// The default value (for help printing) | |
std::string get_default_str() const { return default_str_; } | |
/// Get the callback function | |
callback_t get_callback() const { return callback_; } | |
/// Get the long names | |
const std::vector<std::string> get_lnames() const { return lnames_; } | |
/// Get the short names | |
const std::vector<std::string> get_snames() const { return snames_; } | |
/// get the flag names with specified default values | |
const std::vector<std::string> get_fnames() const { return fnames_; } | |
/// The number of times the option expects to be included | |
int get_expected() const { return expected_; } | |
/// \brief The total number of expected values (including the type) | |
/// This is positive if exactly this number is expected, and negative for at least N values | |
/// | |
/// v = fabs(size_type*expected) | |
/// !MultiOptionPolicy::Throw | |
/// | Expected < 0 | Expected == 0 | Expected > 0 | |
/// Size < 0 | -v | 0 | -v | |
/// Size == 0 | 0 | 0 | 0 | |
/// Size > 0 | -v | 0 | -v // Expected must be 1 | |
/// | |
/// MultiOptionPolicy::Throw | |
/// | Expected < 0 | Expected == 0 | Expected > 0 | |
/// Size < 0 | -v | 0 | v | |
/// Size == 0 | 0 | 0 | 0 | |
/// Size > 0 | v | 0 | v // Expected must be 1 | |
/// | |
int get_items_expected() const { | |
return std::abs(type_size_ * expected_) * | |
((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); | |
} | |
/// True if the argument can be given directly | |
bool get_positional() const { return pname_.length() > 0; } | |
/// True if option has at least one non-positional name | |
bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; } | |
/// True if option has description | |
bool has_description() const { return description_.length() > 0; } | |
/// Get the description | |
const std::string &get_description() const { return description_; } | |
/// Set the description | |
Option *description(std::string option_description) { | |
description_ = std::move(option_description); | |
return this; | |
} | |
///@} | |
/// @name Help tools | |
///@{ | |
/// \brief Gets a comma separated list of names. | |
/// Will include / prefer the positional name if positional is true. | |
/// If all_options is false, pick just the most descriptive name to show. | |
/// Use `get_name(true)` to get the positional name (replaces `get_pname`) | |
std::string get_name(bool positional = false, //<[input] Show the positional name | |
bool all_options = false //<[input] Show every option | |
) const { | |
if(all_options) { | |
std::vector<std::string> name_list; | |
/// The all list will never include a positional unless asked or that's the only name. | |
if((positional && pname_.length()) || (snames_.empty() && lnames_.empty())) | |
name_list.push_back(pname_); | |
if((get_items_expected() == 0) && (!fnames_.empty())) { | |
for(const std::string &sname : snames_) { | |
name_list.push_back("-" + sname); | |
if(check_fname(sname)) { | |
name_list.back() += "{" + get_flag_value(sname, "") + "}"; | |
} | |
} | |
for(const std::string &lname : lnames_) { | |
name_list.push_back("--" + lname); | |
if(check_fname(lname)) { | |
name_list.back() += "{" + get_flag_value(lname, "") + "}"; | |
} | |
} | |
} else { | |
for(const std::string &sname : snames_) | |
name_list.push_back("-" + sname); | |
for(const std::string &lname : lnames_) | |
name_list.push_back("--" + lname); | |
} | |
return detail::join(name_list); | |
} else { | |
// This returns the positional name no matter what | |
if(positional) | |
return pname_; | |
// Prefer long name | |
else if(!lnames_.empty()) | |
return std::string("--") + lnames_[0]; | |
// Or short name if no long name | |
else if(!snames_.empty()) | |
return std::string("-") + snames_[0]; | |
// If positional is the only name, it's okay to use that | |
else | |
return pname_; | |
} | |
} | |
///@} | |
/// @name Parser tools | |
///@{ | |
/// Process the callback | |
void run_callback() { | |
callback_run_ = true; | |
// Run the validators (can change the string) | |
if(!validators_.empty()) { | |
for(std::string &result : results_) { | |
auto err_msg = _validate(result); | |
if(!err_msg.empty()) | |
throw ValidationError(get_name(), err_msg); | |
} | |
} | |
if(!(callback_)) { | |
return; | |
} | |
bool local_result; | |
// Num items expected or length of vector, always at least 1 | |
// Only valid for a trimming policy | |
int trim_size = | |
std::min<int>(std::max<int>(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); | |
// Operation depends on the policy setting | |
if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { | |
// Allow multi-option sizes (including 0) | |
results_t partial_result{results_.end() - trim_size, results_.end()}; | |
local_result = !callback_(partial_result); | |
} else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { | |
results_t partial_result{results_.begin(), results_.begin() + trim_size}; | |
local_result = !callback_(partial_result); | |
} else if(multi_option_policy_ == MultiOptionPolicy::Join) { | |
results_t partial_result = {detail::join(results_, "\n")}; | |
local_result = !callback_(partial_result); | |
} else { | |
// Exact number required | |
if(get_items_expected() > 0) { | |
if(results_.size() != static_cast<size_t>(get_items_expected())) | |
throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); | |
// Variable length list | |
} else if(get_items_expected() < 0) { | |
// Require that this be a multiple of expected size and at least as many as expected | |
if(results_.size() < static_cast<size_t>(-get_items_expected()) || | |
results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0u) | |
throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); | |
} | |
local_result = !callback_(results_); | |
} | |
if(local_result) | |
throw ConversionError(get_name(), results_); | |
} | |
/// If options share any of the same names, they are equal (not counting positional) | |
bool operator==(const Option &other) const { | |
for(const std::string &sname : snames_) | |
if(other.check_sname(sname)) | |
return true; | |
for(const std::string &lname : lnames_) | |
if(other.check_lname(lname)) | |
return true; | |
if(ignore_case_ || | |
ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore | |
for(const std::string &sname : other.snames_) | |
if(check_sname(sname)) | |
return true; | |
for(const std::string &lname : other.lnames_) | |
if(check_lname(lname)) | |
return true; | |
} | |
return false; | |
} | |
/// Check a name. Requires "-" or "--" for short / long, supports positional name | |
bool check_name(std::string name) const { | |
if(name.length() > 2 && name[0] == '-' && name[1] == '-') | |
return check_lname(name.substr(2)); | |
else if(name.length() > 1 && name.front() == '-') | |
return check_sname(name.substr(1)); | |
else { | |
std::string local_pname = pname_; | |
if(ignore_underscore_) { | |
local_pname = detail::remove_underscore(local_pname); | |
name = detail::remove_underscore(name); | |
} | |
if(ignore_case_) { | |
local_pname = detail::to_lower(local_pname); | |
name = detail::to_lower(name); | |
} | |
return name == local_pname; | |
} | |
} | |
/// Requires "-" to be removed from string | |
bool check_sname(std::string name) const { return (detail::find_member(name, snames_, ignore_case_) >= 0); } | |
/// Requires "--" to be removed from string | |
bool check_lname(std::string name) const { | |
return (detail::find_member(name, lnames_, ignore_case_, ignore_underscore_) >= 0); | |
} | |
/// Requires "--" to be removed from string | |
bool check_fname(std::string name) const { | |
if(fnames_.empty()) { | |
return false; | |
} | |
return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0); | |
} | |
std::string get_flag_value(std::string name, std::string input_value) const { | |
static const std::string trueString{"true"}; | |
static const std::string falseString{"false"}; | |
static const std::string emptyString{"{}"}; | |
// check for disable flag override_ | |
if(disable_flag_override_) { | |
if(!((input_value.empty()) || (input_value == emptyString))) { | |
auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_); | |
if(default_ind >= 0) { | |
// We can static cast this to size_t because it is more than 0 in this block | |
if(default_flag_values_[static_cast<size_t>(default_ind)].second != input_value) { | |
throw(ArgumentMismatch::FlagOverride(name)); | |
} | |
} else { | |
if(input_value != trueString) { | |
throw(ArgumentMismatch::FlagOverride(name)); | |
} | |
} | |
} | |
} | |
auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_); | |
if((input_value.empty()) || (input_value == emptyString)) { | |
return (ind < 0) ? trueString : default_flag_values_[static_cast<size_t>(ind)].second; | |
} | |
if(ind < 0) { | |
return input_value; | |
} | |
if(default_flag_values_[static_cast<size_t>(ind)].second == falseString) { | |
try { | |
auto val = detail::to_flag_value(input_value); | |
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); | |
} catch(const std::invalid_argument &) { | |
return input_value; | |
} | |
} else { | |
return input_value; | |
} | |
} | |
/// Puts a result at the end | |
Option *add_result(std::string s) { | |
_add_result(std::move(s)); | |
callback_run_ = false; | |
return this; | |
} | |
/// Puts a result at the end and get a count of the number of arguments actually added | |
Option *add_result(std::string s, int &results_added) { | |
results_added = _add_result(std::move(s)); | |
callback_run_ = false; | |
return this; | |
} | |
/// Puts a result at the end | |
Option *add_result(std::vector<std::string> s) { | |
for(auto &str : s) { | |
_add_result(std::move(str)); | |
} | |
callback_run_ = false; | |
return this; | |
} | |
/// Get a copy of the results | |
std::vector<std::string> results() const { return results_; } | |
/// get the results as a particular type | |
template <typename T, | |
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy> | |
void results(T &output) const { | |
bool retval; | |
if(results_.empty()) { | |
retval = detail::lexical_cast(default_str_, output); | |
} else if(results_.size() == 1) { | |
retval = detail::lexical_cast(results_[0], output); | |
} else { | |
switch(multi_option_policy_) { | |
case MultiOptionPolicy::TakeFirst: | |
retval = detail::lexical_cast(results_.front(), output); | |
break; | |
case MultiOptionPolicy::TakeLast: | |
default: | |
retval = detail::lexical_cast(results_.back(), output); | |
break; | |
case MultiOptionPolicy::Throw: | |
throw ConversionError(get_name(), results_); | |
case MultiOptionPolicy::Join: | |
retval = detail::lexical_cast(detail::join(results_), output); | |
break; | |
} | |
} | |
if(!retval) { | |
throw ConversionError(get_name(), results_); | |
} | |
} | |
/// get the results as a vector of a particular type | |
template <typename T> void results(std::vector<T> &output) const { | |
output.clear(); | |
bool retval = true; | |
for(const auto &elem : results_) { | |
output.emplace_back(); | |
retval &= detail::lexical_cast(elem, output.back()); | |
} | |
if(!retval) { | |
throw ConversionError(get_name(), results_); | |
} | |
} | |
/// return the results as a particular type | |
template <typename T> T as() const { | |
T output; | |
results(output); | |
return output; | |
} | |
/// See if the callback has been run already | |
bool get_callback_run() const { return callback_run_; } | |
///@} | |
/// @name Custom options | |
///@{ | |
/// Set the type function to run when displayed on this option | |
Option *type_name_fn(std::function<std::string()> typefun) { | |
type_name_ = typefun; | |
return this; | |
} | |
/// Set a custom option typestring | |
Option *type_name(std::string typeval) { | |
type_name_fn([typeval]() { return typeval; }); | |
return this; | |
} | |
/// Set a custom option size | |
Option *type_size(int option_type_size) { | |
type_size_ = option_type_size; | |
if(type_size_ == 0) | |
required_ = false; | |
if(option_type_size < 0) | |
expected_ = -1; | |
return this; | |
} | |
/// Set a capture function for the default. Mostly used by App. | |
Option *default_function(const std::function<std::string()> &func) { | |
default_function_ = func; | |
return this; | |
} | |
/// Capture the default value from the original value (if it can be captured) | |
Option *capture_default_str() { | |
if(default_function_) { | |
default_str_ = default_function_(); | |
} | |
return this; | |
} | |
/// Set the default value string representation (does not change the contained value) | |
Option *default_str(std::string val) { | |
default_str_ = val; | |
return this; | |
} | |
/// Set the default value string representation and evaluate into the bound value | |
Option *default_val(std::string val) { | |
default_str(val); | |
auto old_results = results_; | |
results_ = {val}; | |
run_callback(); | |
results_ = std::move(old_results); | |
return this; | |
} | |
/// Get the full typename for this option | |
std::string get_type_name() const { | |
std::string full_type_name = type_name_(); | |
if(!validators_.empty()) { | |
for(auto &validator : validators_) { | |
std::string vtype = validator.get_description(); | |
if(!vtype.empty()) { | |
full_type_name += ":" + vtype; | |
} | |
} | |
} | |
return full_type_name; | |
} | |
private: | |
// run through the validators | |
std::string _validate(std::string &result) { | |
std::string err_msg; | |
for(const auto &vali : validators_) { | |
try { | |
err_msg = vali(result); | |
} catch(const ValidationError &err) { | |
err_msg = err.what(); | |
} | |
if(!err_msg.empty()) | |
break; | |
} | |
return err_msg; | |
} | |
int _add_result(std::string &&result) { | |
int result_count = 0; | |
if(delimiter_ == '\0') { | |
results_.push_back(std::move(result)); | |
++result_count; | |
} else { | |
if((result.find_first_of(delimiter_) != std::string::npos)) { | |
for(const auto &var : CLI::detail::split(result, delimiter_)) { | |
if(!var.empty()) { | |
results_.push_back(var); | |
++result_count; | |
} | |
} | |
} else { | |
results_.push_back(std::move(result)); | |
++result_count; | |
} | |
} | |
return result_count; | |
} | |
}; | |
} // namespace CLI | |
// From CLI/App.hpp: | |
namespace CLI { | |
#ifndef CLI11_PARSE | |
#define CLI11_PARSE(app, argc, argv) \ | |
try { \ | |
(app).parse((argc), (argv)); \ | |
} catch(const CLI::ParseError &e) { \ | |
return (app).exit(e); \ | |
} | |
#endif | |
namespace detail { | |
enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS, SUBCOMMAND, SUBCOMMAND_TERMINATOR }; | |
struct AppFriend; | |
} // namespace detail | |
namespace FailureMessage { | |
std::string simple(const App *app, const Error &e); | |
std::string help(const App *app, const Error &e); | |
} // namespace FailureMessage | |
class App; | |
using App_p = std::shared_ptr<App>; | |
class Option_group; | |
/// Creates a command line program, with very few defaults. | |
/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated | |
* add_option methods make it easy to prepare options. Remember to call `.start` before starting your | |
* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ | |
class App { | |
friend Option; | |
friend detail::AppFriend; | |
protected: | |
// This library follows the Google style guide for member names ending in underscores | |
/// @name Basics | |
///@{ | |
/// Subcommand name or program name (from parser if name is empty) | |
std::string name_; | |
/// Description of the current program/subcommand | |
std::string description_; | |
/// If true, allow extra arguments (ie, don't throw an error). INHERITABLE | |
bool allow_extras_{false}; | |
/// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE | |
bool allow_config_extras_{false}; | |
/// If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE | |
bool prefix_command_{false}; | |
/// If set to true the name was automatically generated from the command line vs a user set name | |
bool has_automatic_name_{false}; | |
/// If set to true the subcommand is required to be processed and used, ignored for main app | |
bool required_{false}; | |
/// If set to true the subcommand is disabled and cannot be used, ignored for main app | |
bool disabled_{false}; | |
/// Flag indicating that the pre_parse_callback has been triggered | |
bool pre_parse_called_{false}; | |
/// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is | |
/// before help or ini files are processed. INHERITABLE | |
bool immediate_callback_{false}; | |
/// This is a function that runs prior to the start of parsing | |
std::function<void(size_t)> pre_parse_callback_; | |
/// This is a function that runs when complete. Great for subcommands. Can throw. | |
std::function<void()> callback_; | |
///@} | |
/// @name Options | |
///@{ | |
/// The default values for options, customizable and changeable INHERITABLE | |
OptionDefaults option_defaults_; | |
/// The list of options, stored locally | |
std::vector<Option_p> options_; | |
///@} | |
/// @name Help | |
///@{ | |
/// Footer to put after all options in the help output INHERITABLE | |
std::string footer_; | |
/// A pointer to the help flag if there is one INHERITABLE | |
Option *help_ptr_{nullptr}; | |
/// A pointer to the help all flag if there is one INHERITABLE | |
Option *help_all_ptr_{nullptr}; | |
/// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) | |
std::shared_ptr<FormatterBase> formatter_{new Formatter()}; | |
/// The error message printing function INHERITABLE | |
std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple; | |
///@} | |
/// @name Parsing | |
///@{ | |
using missing_t = std::vector<std::pair<detail::Classifier, std::string>>; | |
/// Pair of classifier, string for missing options. (extra detail is removed on returning from parse) | |
/// | |
/// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. | |
missing_t missing_; | |
/// This is a list of pointers to options with the original parse order | |
std::vector<Option *> parse_order_; | |
/// This is a list of the subcommands collected, in order | |
std::vector<App *> parsed_subcommands_; | |
/// this is a list of subcommands that are exclusionary to this one | |
std::set<App *> exclude_subcommands_; | |
/// This is a list of options which are exclusionary to this App, if the options were used this subcommand should | |
/// not be | |
std::set<Option *> exclude_options_; | |
///@} | |
/// @name Subcommands | |
///@{ | |
/// Storage for subcommand list | |
std::vector<App_p> subcommands_; | |
/// If true, the program name is not case sensitive INHERITABLE | |
bool ignore_case_{false}; | |
/// If true, the program should ignore underscores INHERITABLE | |
bool ignore_underscore_{false}; | |
/// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE | |
bool fallthrough_{false}; | |
/// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE | |
bool allow_windows_style_options_{ | |
#ifdef _WIN32 | |
true | |
#else | |
false | |
#endif | |
}; | |
/// specify that positional arguments come at the end of the argument sequence not inheritable | |
bool positionals_at_end_{false}; | |
/// If set to true the subcommand will start each parse disabled | |
bool disabled_by_default_{false}; | |
/// If set to true the subcommand will be reenabled at the start of each parse | |
bool enabled_by_default_{false}; | |
/// If set to true positional options are validated before assigning INHERITABLE | |
bool validate_positionals_{false}; | |
/// A pointer to the parent if this is a subcommand | |
App *parent_{nullptr}; | |
/// Counts the number of times this command/subcommand was parsed | |
size_t parsed_ = 0; | |
/// Minimum required subcommands (not inheritable!) | |
size_t require_subcommand_min_ = 0; | |
/// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE | |
size_t require_subcommand_max_ = 0; | |
/// Minimum required options (not inheritable!) | |
size_t require_option_min_ = 0; | |
/// Max number of options allowed. 0 is unlimited (not inheritable) | |
size_t require_option_max_ = 0; | |
/// The group membership INHERITABLE | |
std::string group_{"Subcommands"}; | |
///@} | |
/// @name Config | |
///@{ | |
/// The name of the connected config file | |
std::string config_name_; | |
/// True if ini is required (throws if not present), if false simply keep going. | |
bool config_required_{false}; | |
/// Pointer to the config option | |
Option *config_ptr_{nullptr}; | |
/// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) | |
std::shared_ptr<Config> config_formatter_{new ConfigINI()}; | |
///@} | |
/// Special private constructor for subcommand | |
App(std::string app_description, std::string app_name, App *parent) | |
: name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) { | |
// Inherit if not from a nullptr | |
if(parent_ != nullptr) { | |
if(parent_->help_ptr_ != nullptr) | |
set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description()); | |
if(parent_->help_all_ptr_ != nullptr) | |
set_help_all_flag(parent_->help_all_ptr_->get_name(false, true), | |
parent_->help_all_ptr_->get_description()); | |
/// OptionDefaults | |
option_defaults_ = parent_->option_defaults_; | |
// INHERITABLE | |
failure_message_ = parent_->failure_message_; | |
allow_extras_ = parent_->allow_extras_; | |
allow_config_extras_ = parent_->allow_config_extras_; | |
prefix_command_ = parent_->prefix_command_; | |
immediate_callback_ = parent_->immediate_callback_; | |
ignore_case_ = parent_->ignore_case_; | |
ignore_underscore_ = parent_->ignore_underscore_; | |
fallthrough_ = parent_->fallthrough_; | |
validate_positionals_ = parent_->validate_positionals_; | |
allow_windows_style_options_ = parent_->allow_windows_style_options_; | |
group_ = parent_->group_; | |
footer_ = parent_->footer_; | |
formatter_ = parent_->formatter_; | |
config_formatter_ = parent_->config_formatter_; | |
require_subcommand_max_ = parent_->require_subcommand_max_; | |
} | |
} | |
public: | |
/// @name Basic | |
///@{ | |
/// Create a new program. Pass in the same arguments as main(), along with a help string. | |
explicit App(std::string app_description = "", std::string app_name = "") | |
: App(app_description, app_name, nullptr) { | |
set_help_flag("-h,--help", "Print this help message and exit"); | |
} | |
/// virtual destructor | |
virtual ~App() = default; | |
/// Set a callback for the end of parsing. | |
/// | |
/// Due to a bug in c++11, | |
/// it is not possible to overload on std::function (fixed in c++14 | |
/// and backported to c++11 on newer compilers). Use capture by reference | |
/// to get a pointer to App if needed. | |
App *callback(std::function<void()> app_callback) { | |
callback_ = std::move(app_callback); | |
return this; | |
} | |
/// Set a callback to execute prior to parsing. | |
/// | |
App *preparse_callback(std::function<void(size_t)> pp_callback) { | |
pre_parse_callback_ = std::move(pp_callback); | |
return this; | |
} | |
/// Set a name for the app (empty will use parser to set the name) | |
App *name(std::string app_name = "") { | |
name_ = app_name; | |
has_automatic_name_ = false; | |
return this; | |
} | |
/// Remove the error when extras are left over on the command line. | |
App *allow_extras(bool allow = true) { | |
allow_extras_ = allow; | |
return this; | |
} | |
/// Remove the error when extras are left over on the command line. | |
App *required(bool require = true) { | |
required_ = require; | |
return this; | |
} | |
/// Disable the subcommand or option group | |
App *disabled(bool disable = true) { | |
disabled_ = disable; | |
return this; | |
} | |
/// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled | |
App *disabled_by_default(bool disable = true) { | |
disabled_by_default_ = disable; | |
return this; | |
} | |
/// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled (not | |
/// disabled) | |
App *enabled_by_default(bool enable = true) { | |
enabled_by_default_ = enable; | |
return this; | |
} | |
/// Set the subcommand callback to be executed immediately on subcommand completion | |
App *immediate_callback(bool immediate = true) { | |
immediate_callback_ = immediate; | |
return this; | |
} | |
/// Set the subcommand to validate positional arguments before assigning | |
App *validate_positionals(bool validate = true) { | |
validate_positionals_ = validate; | |
return this; | |
} | |
/// Remove the error when extras are left over on the command line. | |
/// Will also call App::allow_extras(). | |
App *allow_config_extras(bool allow = true) { | |
allow_extras(allow); | |
allow_config_extras_ = allow; | |
return this; | |
} | |
/// Do not parse anything after the first unrecognized option and return | |
App *prefix_command(bool allow = true) { | |
prefix_command_ = allow; | |
return this; | |
} | |
/// Ignore case. Subcommands inherit value. | |
App *ignore_case(bool value = true) { | |
ignore_case_ = value; | |
if(parent_ != nullptr && !name_.empty()) { | |
for(const auto &subc : parent_->subcommands_) { | |
if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_))) | |
throw OptionAlreadyAdded(subc->name_); | |
} | |
} | |
return this; | |
} | |
/// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit value. | |
App *allow_windows_style_options(bool value = true) { | |
allow_windows_style_options_ = value; | |
return this; | |
} | |
/// Specify that the positional arguments are only at the end of the sequence | |
App *positionals_at_end(bool value = true) { | |
positionals_at_end_ = value; | |
return this; | |
} | |
/// Ignore underscore. Subcommands inherit value. | |
App *ignore_underscore(bool value = true) { | |
ignore_underscore_ = value; | |
if(parent_ != nullptr && !name_.empty()) { | |
for(const auto &subc : parent_->subcommands_) { | |
if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_))) | |
throw OptionAlreadyAdded(subc->name_); | |
} | |
} | |
return this; | |
} | |
/// Set the help formatter | |
App *formatter(std::shared_ptr<FormatterBase> fmt) { | |
formatter_ = fmt; | |
return this; | |
} | |
/// Set the help formatter | |
App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) { | |
formatter_ = std::make_shared<FormatterLambda>(fmt); | |
return this; | |
} | |
/// Set the config formatter | |
App *config_formatter(std::shared_ptr<Config> fmt) { | |
config_formatter_ = fmt; | |
return this; | |
} | |
/// Check to see if this subcommand was parsed, true only if received on command line. | |
bool parsed() const { return parsed_ > 0; } | |
/// Get the OptionDefault object, to set option defaults | |
OptionDefaults *option_defaults() { return &option_defaults_; } | |
///@} | |
/// @name Adding options | |
///@{ | |
/// Add an option, will automatically understand the type for common types. | |
/// | |
/// To use, create a variable with the expected type, and pass it in after the name. | |
/// After start is called, you can use count to see if the value was passed, and | |
/// the value will be initialized properly. Numbers, vectors, and strings are supported. | |
/// | |
/// ->required(), ->default, and the validators are options, | |
/// The positional options take an optional number of arguments. | |
/// | |
/// For example, | |
/// | |
/// std::string filename; | |
/// program.add_option("filename", filename, "description of filename"); | |
/// | |
Option *add_option(std::string option_name, | |
callback_t option_callback, | |
std::string option_description = "", | |
bool defaulted = false, | |
std::function<std::string()> func = {}) { | |
Option myopt{option_name, option_description, option_callback, this}; | |
if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { | |
return *v == myopt; | |
}) == std::end(options_)) { | |
options_.emplace_back(); | |
Option_p &option = options_.back(); | |
option.reset(new Option(option_name, option_description, option_callback, this)); | |
// Set the default string capture function | |
option->default_function(func); | |
// For compatibility with CLI11 1.7 and before, capture the default string here | |
if(defaulted) | |
option->capture_default_str(); | |
// Transfer defaults to the new option | |
option_defaults_.copy_to(option.get()); | |
// Don't bother to capture if we already did | |
if(!defaulted && option->get_always_capture_default()) | |
option->capture_default_str(); | |
return option.get(); | |
} else | |
throw OptionAlreadyAdded(myopt.get_name()); | |
} | |
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) | |
template <typename T, enable_if_t<!is_vector<T>::value & !std::is_const<T>::value, detail::enabler> = detail::dummy> | |
Option *add_option(std::string option_name, | |
T &variable, ///< The variable to set | |
std::string option_description = "", | |
bool defaulted = false) { | |
auto fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; | |
Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { | |
return std::string(CLI::detail::to_string(variable)); | |
}); | |
opt->type_name(detail::type_name<T>()); | |
return opt; | |
} | |
/// Add option for a callback of a specific type | |
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> | |
Option *add_option_function(std::string option_name, | |
const std::function<void(const T &)> &func, ///< the callback to execute | |
std::string option_description = "") { | |
auto fun = [func](CLI::results_t res) { | |
T variable; | |
bool result = detail::lexical_cast(res[0], variable); | |
if(result) { | |
func(variable); | |
} | |
return result; | |
}; | |
Option *opt = add_option(option_name, std::move(fun), option_description, false); | |
opt->type_name(detail::type_name<T>()); | |
return opt; | |
} | |
/// Add option with no description or variable assignment | |
Option *add_option(std::string option_name) { | |
return add_option(option_name, CLI::callback_t(), std::string{}, false); | |
} | |
/// Add option with description but with no variable assignment or callback | |
template <typename T, | |
enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> = | |
detail::dummy> | |
Option *add_option(std::string option_name, T &option_description) { | |
return add_option(option_name, CLI::callback_t(), option_description, false); | |
} | |
/// Add option for vectors | |
template <typename T> | |
Option *add_option(std::string option_name, | |
std::vector<T> &variable, ///< The variable vector to set | |
std::string option_description = "", | |
bool defaulted = false) { | |
auto fun = [&variable](CLI::results_t res) { | |
bool retval = true; | |
variable.clear(); | |
variable.reserve(res.size()); | |
for(const auto &elem : res) { | |
variable.emplace_back(); | |
retval &= detail::lexical_cast(elem, variable.back()); | |
} | |
return (!variable.empty()) && retval; | |
}; | |
auto default_function = [&variable]() { | |
std::vector<std::string> defaults; | |
defaults.resize(variable.size()); | |
std::transform(variable.begin(), variable.end(), defaults.begin(), [](T &val) { | |
return std::string(CLI::detail::to_string(val)); | |
}); | |
return std::string("[" + detail::join(defaults) + "]"); | |
}; | |
Option *opt = add_option(option_name, fun, option_description, defaulted, default_function); | |
opt->type_name(detail::type_name<T>())->type_size(-1); | |
return opt; | |
} | |
/// Add option for a vector callback of a specific type | |
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> | |
Option *add_option_function(std::string option_name, | |
const std::function<void(const T &)> &func, ///< the callback to execute | |
std::string option_description = "") { | |
CLI::callback_t fun = [func](CLI::results_t res) { | |
T values; | |
bool retval = true; | |
values.reserve(res.size()); | |
for(const auto &elem : res) { | |
values.emplace_back(); | |
retval &= detail::lexical_cast(elem, values.back()); | |
} | |
if(retval) { | |
func(values); | |
} | |
return retval; | |
}; | |
Option *opt = add_option(option_name, std::move(fun), std::move(option_description), false); | |
opt->type_name(detail::type_name<T>())->type_size(-1); | |
return opt; | |
} | |
/// Set a help flag, replace the existing one if present | |
Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") { | |
// take flag_description by const reference otherwise add_flag tries to assign to help_description | |
if(help_ptr_ != nullptr) { | |
remove_option(help_ptr_); | |
help_ptr_ = nullptr; | |
} | |
// Empty name will simply remove the help flag | |
if(!flag_name.empty()) { | |
help_ptr_ = add_flag(flag_name, help_description); | |
help_ptr_->configurable(false); | |
} | |
return help_ptr_; | |
} | |
/// Set a help all flag, replaced the existing one if present | |
Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") { | |
// take flag_description by const reference otherwise add_flag tries to assign to flag_description | |
if(help_all_ptr_ != nullptr) { | |
remove_option(help_all_ptr_); | |
help_all_ptr_ = nullptr; | |
} | |
// Empty name will simply remove the help all flag | |
if(!help_name.empty()) { | |
help_all_ptr_ = add_flag(help_name, help_description); | |
help_all_ptr_->configurable(false); | |
} | |
return help_all_ptr_; | |
} | |
private: | |
/// Internal function for adding a flag | |
Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { | |
Option *opt; | |
if(detail::has_default_flag_values(flag_name)) { | |
// check for default values and if it has them | |
auto flag_defaults = detail::get_default_flag_values(flag_name); | |
detail::remove_default_flag_values(flag_name); | |
opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); | |
for(const auto &fname : flag_defaults) | |
opt->fnames_.push_back(fname.first); | |
opt->default_flag_values_ = std::move(flag_defaults); | |
} else { | |
opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); | |
} | |
// flags cannot have positional values | |
if(opt->get_positional()) { | |
auto pos_name = opt->get_name(true); | |
remove_option(opt); | |
throw IncorrectConstruction::PositionalFlag(pos_name); | |
} | |
opt->type_size(0); | |
return opt; | |
} | |
public: | |
/// Add a flag with no description or variable assignment | |
Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); } | |
/// Add flag with description but with no variable assignment or callback | |
/// takes a constant string, if a variable string is passed that variable will be assigned the results from the | |
/// flag | |
template <typename T, | |
enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> = | |
detail::dummy> | |
Option *add_flag(std::string flag_name, T &flag_description) { | |
return _add_flag_internal(flag_name, CLI::callback_t(), flag_description); | |
} | |
/// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one if | |
/// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. | |
template <typename T, | |
enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy> | |
Option *add_flag(std::string flag_name, | |
T &flag_count, ///< A variable holding the count | |
std::string flag_description = "") { | |
flag_count = 0; | |
CLI::callback_t fun = [&flag_count](CLI::results_t res) { | |
try { | |
detail::sum_flag_vector(res, flag_count); | |
} catch(const std::invalid_argument &) { | |
return false; | |
} | |
return true; | |
}; | |
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | |
} | |
/// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes that | |
/// can be converted from a string | |
template <typename T, | |
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value && | |
(!std::is_integral<T>::value || is_bool<T>::value) && | |
!std::is_constructible<std::function<void(int)>, T>::value, | |
detail::enabler> = detail::dummy> | |
Option *add_flag(std::string flag_name, | |
T &flag_result, ///< A variable holding true if passed | |
std::string flag_description = "") { | |
CLI::callback_t fun = [&flag_result](CLI::results_t res) { | |
if(res.size() != 1) { | |
return false; | |
} | |
return CLI::detail::lexical_cast(res[0], flag_result); | |
}; | |
Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | |
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); | |
return opt; | |
} | |
/// Vector version to capture multiple flags. | |
template <typename T, | |
enable_if_t<!std::is_assignable<std::function<void(int64_t)>, T>::value, detail::enabler> = detail::dummy> | |
Option *add_flag(std::string flag_name, | |
std::vector<T> &flag_results, ///< A vector of values with the flag results | |
std::string flag_description = "") { | |
CLI::callback_t fun = [&flag_results](CLI::results_t res) { | |
bool retval = true; | |
for(const auto &elem : res) { | |
flag_results.emplace_back(); | |
retval &= detail::lexical_cast(elem, flag_results.back()); | |
} | |
return retval; | |
}; | |
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | |
} | |
/// Add option for callback that is triggered with a true flag and takes no arguments | |
Option *add_flag_callback(std::string flag_name, | |
std::function<void(void)> function, ///< A function to call, void(void) | |
std::string flag_description = "") { | |
CLI::callback_t fun = [function](CLI::results_t res) { | |
if(res.size() != 1) { | |
return false; | |
} | |
bool trigger; | |
auto result = CLI::detail::lexical_cast(res[0], trigger); | |
if(trigger) | |
function(); | |
return result; | |
}; | |
Option *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | |
opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); | |
return opt; | |
} | |
/// Add option for callback with an integer value | |
Option *add_flag_function(std::string flag_name, | |
std::function<void(int64_t)> function, ///< A function to call, void(int) | |
std::string flag_description = "") { | |
CLI::callback_t fun = [function](CLI::results_t res) { | |
int64_t flag_count = 0; | |
detail::sum_flag_vector(res, flag_count); | |
function(flag_count); | |
return true; | |
}; | |
return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); | |
} | |
#ifdef CLI11_CPP14 | |
/// Add option for callback (C++14 or better only) | |
Option *add_flag(std::string flag_name, | |
std::function<void(int64_t)> function, ///< A function to call, void(int64_t) | |
std::string flag_description = "") { | |
return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description)); | |
} | |
#endif | |
/// Add set of options (No default, temp reference, such as an inline set) DEPRECATED | |
template <typename T> | |
Option *add_set(std::string option_name, | |
T &member, ///< The selected member of the set | |
std::set<T> options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->check(IsMember{options}); | |
return opt; | |
} | |
/// Add set of options (No default, set can be changed afterwards - do not destroy the set) DEPRECATED | |
template <typename T> | |
Option *add_mutable_set(std::string option_name, | |
T &member, ///< The selected member of the set | |
const std::set<T> &options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->check(IsMember{&options}); | |
return opt; | |
} | |
/// Add set of options (with default, static set, such as an inline set) DEPRECATED | |
template <typename T> | |
Option *add_set(std::string option_name, | |
T &member, ///< The selected member of the set | |
std::set<T> options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->check(IsMember{options}); | |
return opt; | |
} | |
/// Add set of options (with default, set can be changed afterwards - do not destroy the set) DEPRECATED | |
template <typename T> | |
Option *add_mutable_set(std::string option_name, | |
T &member, ///< The selected member of the set | |
const std::set<T> &options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->check(IsMember{&options}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore case (no default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") | |
Option *add_set_ignore_case(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{options, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the | |
/// set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_case(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{&options, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore case (default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") | |
Option *add_set_ignore_case(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{options, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set) | |
/// DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_case(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{&options, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
Option *add_set_ignore_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{options, CLI::ignore_underscore}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy | |
/// the set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{options, CLI::ignore_underscore}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore (default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
Option *add_set_ignore_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{options, CLI::ignore_underscore}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the | |
/// set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{&options, CLI::ignore_underscore}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
Option *add_set_ignore_case_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not | |
/// destroy the set) DEPRECATED | |
CLI11_DEPRECATED( | |
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_case_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description = "") { | |
Option *opt = add_option(option_name, member, std::move(option_description)); | |
opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED | |
CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
Option *add_set_ignore_case_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
std::set<std::string> options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not | |
/// destroy the set) DEPRECATED | |
CLI11_DEPRECATED( | |
"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
Option *add_mutable_set_ignore_case_underscore(std::string option_name, | |
std::string &member, ///< The selected member of the set | |
const std::set<std::string> &options, ///< The set of possibilities | |
std::string option_description, | |
bool defaulted) { | |
Option *opt = add_option(option_name, member, std::move(option_description), defaulted); | |
opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
return opt; | |
} | |
/// Add a complex number | |
template <typename T> | |
Option *add_complex(std::string option_name, | |
T &variable, | |
std::string option_description = "", | |
bool defaulted = false, | |
std::string label = "COMPLEX") { | |
std::string simple_name = CLI::detail::split(option_name, ',').at(0); | |
CLI::callback_t fun = [&variable, simple_name, label](results_t res) { | |
if(res[1].back() == 'i') | |
res[1].pop_back(); | |
double x, y; | |
bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y); | |
if(worked) | |
variable = T(x, y); | |
return worked; | |
}; | |
auto default_function = [&variable]() { | |
std::stringstream out; | |
out << variable; | |
return out.str(); | |
}; | |
CLI::Option *opt = | |
add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function); | |
opt->type_name(label)->type_size(2); | |
return opt; | |
} | |
/// Set a configuration ini file option, or clear it if no name passed | |
Option *set_config(std::string option_name = "", | |
std::string default_filename = "", | |
std::string help_message = "Read an ini file", | |
bool config_required = false) { | |
// Remove existing config if present | |
if(config_ptr_ != nullptr) | |
remove_option(config_ptr_); | |
// Only add config if option passed | |
if(!option_name.empty()) { | |
config_name_ = default_filename; | |
config_required_ = config_required; | |
config_ptr_ = add_option(option_name, config_name_, help_message, !default_filename.empty()); | |
config_ptr_->configurable(false); | |
} | |
return config_ptr_; | |
} | |
/// Removes an option from the App. Takes an option pointer. Returns true if found and removed. | |
bool remove_option(Option *opt) { | |
// Make sure no links exist | |
for(Option_p &op : options_) { | |
op->remove_needs(opt); | |
op->remove_excludes(opt); | |
} | |
if(help_ptr_ == opt) | |
help_ptr_ = nullptr; | |
if(help_all_ptr_ == opt) | |
help_all_ptr_ = nullptr; | |
auto iterator = | |
std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); | |
if(iterator != std::end(options_)) { | |
options_.erase(iterator); | |
return true; | |
} | |
return false; | |
} | |
/// creates an option group as part of the given app | |
template <typename T = Option_group> | |
T *add_option_group(std::string group_name, std::string group_description = "") { | |
auto option_group = std::make_shared<T>(std::move(group_description), group_name, nullptr); | |
auto ptr = option_group.get(); | |
// move to App_p for overload resolution on older gcc versions | |
App_p app_ptr = std::dynamic_pointer_cast<App>(option_group); | |
add_subcommand(std::move(app_ptr)); | |
return ptr; | |
} | |
///@} | |
/// @name Subcommmands | |
///@{ | |
/// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag | |
App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") { | |
CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this)); | |
return add_subcommand(std::move(subcom)); | |
} | |
/// Add a previously created app as a subcommand | |
App *add_subcommand(CLI::App_p subcom) { | |
if(!subcom) | |
throw IncorrectConstruction("passed App is not valid"); | |
if(!subcom->name_.empty()) { | |
for(const auto &subc : subcommands_) | |
if(subc->check_name(subcom->name_) || subcom->check_name(subc->name_)) | |
throw OptionAlreadyAdded(subc->name_); | |
} | |
subcom->parent_ = this; | |
subcommands_.push_back(std::move(subcom)); | |
return subcommands_.back().get(); | |
} | |
/// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed. | |
bool remove_subcommand(App *subcom) { | |
// Make sure no links exist | |
for(App_p &sub : subcommands_) { | |
sub->remove_excludes(subcom); | |
} | |
auto iterator = std::find_if( | |
std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; }); | |
if(iterator != std::end(subcommands_)) { | |
subcommands_.erase(iterator); | |
return true; | |
} | |
return false; | |
} | |
/// Check to see if a subcommand is part of this command (doesn't have to be in command line) | |
/// returns the first subcommand if passed a nullptr | |
App *get_subcommand(App *subcom) const { | |
if(subcom == nullptr) | |
throw OptionNotFound("nullptr passed"); | |
for(const App_p &subcomptr : subcommands_) | |
if(subcomptr.get() == subcom) | |
return subcom; | |
throw OptionNotFound(subcom->get_name()); | |
} | |
/// Check to see if a subcommand is part of this command (text version) | |
App *get_subcommand(std::string subcom) const { | |
auto subc = _find_subcommand(subcom, false, false); | |
if(subc == nullptr) | |
throw OptionNotFound(subcom); | |
return subc; | |
} | |
/// Get a pointer to subcommand by index | |
App *get_subcommand(int index = 0) const { | |
if(index >= 0) { | |
auto uindex = static_cast<unsigned>(index); | |
if(uindex < subcommands_.size()) | |
return subcommands_[uindex].get(); | |
} | |
throw OptionNotFound(std::to_string(index)); | |
} | |
/// Check to see if a subcommand is part of this command and get a shared_ptr to it | |
CLI::App_p get_subcommand_ptr(App *subcom) const { | |
if(subcom == nullptr) | |
throw OptionNotFound("nullptr passed"); | |
for(const App_p &subcomptr : subcommands_) | |
if(subcomptr.get() == subcom) | |
return subcomptr; | |
throw OptionNotFound(subcom->get_name()); | |
} | |
/// Check to see if a subcommand is part of this command (text version) | |
CLI::App_p get_subcommand_ptr(std::string subcom) const { | |
for(const App_p &subcomptr : subcommands_) | |
if(subcomptr->check_name(subcom)) | |
return subcomptr; | |
throw OptionNotFound(subcom); | |
} | |
/// Get an owning pointer to subcommand by index | |
CLI::App_p get_subcommand_ptr(int index = 0) const { | |
if(index >= 0) { | |
auto uindex = static_cast<unsigned>(index); | |
if(uindex < subcommands_.size()) | |
return subcommands_[uindex]; | |
} | |
throw OptionNotFound(std::to_string(index)); | |
} | |
/// Check to see if an option group is part of this App | |
App *get_option_group(std::string group_name) const { | |
for(const App_p &app : subcommands_) { | |
if(app->name_.empty() && app->group_ == group_name) { | |
return app.get(); | |
} | |
} | |
throw OptionNotFound(group_name); | |
} | |
/// No argument version of count counts the number of times this subcommand was | |
/// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless | |
/// otherwise modified in a callback | |
size_t count() const { return parsed_; } | |
/// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were | |
/// treated as extras. | |
size_t count_all() const { | |
size_t cnt{0}; | |
for(auto &opt : options_) { | |
cnt += opt->count(); | |
} | |
for(auto &sub : subcommands_) { | |
cnt += sub->count_all(); | |
} | |
if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called | |
cnt += parsed_; | |
} | |
return cnt; | |
} | |
/// Changes the group membership | |
App *group(std::string group_name) { | |
group_ = group_name; | |
return this; | |
} | |
/// The argumentless form of require subcommand requires 1 or more subcommands | |
App *require_subcommand() { | |
require_subcommand_min_ = 1; | |
require_subcommand_max_ = 0; | |
return this; | |
} | |
/// Require a subcommand to be given (does not affect help call) | |
/// The number required can be given. Negative values indicate maximum | |
/// number allowed (0 for any number). Max number inheritable. | |
App *require_subcommand(int value) { | |
if(value < 0) { | |
require_subcommand_min_ = 0; | |
require_subcommand_max_ = static_cast<size_t>(-value); | |
} else { | |
require_subcommand_min_ = static_cast<size_t>(value); | |
require_subcommand_max_ = static_cast<size_t>(value); | |
} | |
return this; | |
} | |
/// Explicitly control the number of subcommands required. Setting 0 | |
/// for the max means unlimited number allowed. Max number inheritable. | |
App *require_subcommand(size_t min, size_t max) { | |
require_subcommand_min_ = min; | |
require_subcommand_max_ = max; | |
return this; | |
} | |
/// The argumentless form of require option requires 1 or more options be used | |
App *require_option() { | |
require_option_min_ = 1; | |
require_option_max_ = 0; | |
return this; | |
} | |
/// Require an option to be given (does not affect help call) | |
/// The number required can be given. Negative values indicate maximum | |
/// number allowed (0 for any number). | |
App *require_option(int value) { | |
if(value < 0) { | |
require_option_min_ = 0; | |
require_option_max_ = static_cast<size_t>(-value); | |
} else { | |
require_option_min_ = static_cast<size_t>(value); | |
require_option_max_ = static_cast<size_t>(value); | |
} | |
return this; | |
} | |
/// Explicitly control the number of options required. Setting 0 | |
/// for the max means unlimited number allowed. Max number inheritable. | |
App *require_option(size_t min, size_t max) { | |
require_option_min_ = min; | |
require_option_max_ = max; | |
return this; | |
} | |
/// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand. | |
/// Default from parent, usually set on parent. | |
App *fallthrough(bool value = true) { | |
fallthrough_ = value; | |
return this; | |
} | |
/// Check to see if this subcommand was parsed, true only if received on command line. | |
/// This allows the subcommand to be directly checked. | |
operator bool() const { return parsed_ > 0; } | |
///@} | |
/// @name Extras for subclassing | |
///@{ | |
/// This allows subclasses to inject code before callbacks but after parse. | |
/// | |
/// This does not run if any errors or help is thrown. | |
virtual void pre_callback() {} | |
///@} | |
/// @name Parsing | |
///@{ | |
// | |
/// Reset the parsed data | |
void clear() { | |
parsed_ = 0; | |
pre_parse_called_ = false; | |
missing_.clear(); | |
parsed_subcommands_.clear(); | |
for(const Option_p &opt : options_) { | |
opt->clear(); | |
} | |
for(const App_p &subc : subcommands_) { | |
subc->clear(); | |
} | |
} | |
/// Parses the command line - throws errors. | |
/// This must be called after the options are in but before the rest of the program. | |
void parse(int argc, const char *const *argv) { | |
// If the name is not set, read from command line | |
if(name_.empty() || has_automatic_name_) { | |
has_automatic_name_ = true; | |
name_ = argv[0]; | |
} | |
std::vector<std::string> args; | |
args.reserve(static_cast<size_t>(argc - 1)); | |
for(int i = argc - 1; i > 0; i--) | |
args.emplace_back(argv[i]); | |
parse(std::move(args)); | |
} | |
/// Parse a single string as if it contained command line arguments. | |
/// This function splits the string into arguments then calls parse(std::vector<std::string> &) | |
/// the function takes an optional boolean argument specifying if the programName is included in the string to | |
/// process | |
void parse(std::string commandline, bool program_name_included = false) { | |
if(program_name_included) { | |
auto nstr = detail::split_program_name(commandline); | |
if((name_.empty()) || (has_automatic_name_)) { | |
has_automatic_name_ = true; | |
name_ = nstr.first; | |
} | |
commandline = std::move(nstr.second); | |
} else | |
detail::trim(commandline); | |
// the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations | |
if(!commandline.empty()) { | |
commandline = detail::find_and_modify(commandline, "=", detail::escape_detect); | |
if(allow_windows_style_options_) | |
commandline = detail::find_and_modify(commandline, ":", detail::escape_detect); | |
} | |
auto args = detail::split_up(std::move(commandline)); | |
// remove all empty strings | |
args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); | |
std::reverse(args.begin(), args.end()); | |
parse(std::move(args)); | |
} | |
/// The real work is done here. Expects a reversed vector. | |
/// Changes the vector to the remaining options. | |
void parse(std::vector<std::string> &args) { | |
// Clear if parsed | |
if(parsed_ > 0) | |
clear(); | |
// parsed_ is incremented in commands/subcommands, | |
// but placed here to make sure this is cleared when | |
// running parse after an error is thrown, even by _validate or _configure. | |
parsed_ = 1; | |
_validate(); | |
_configure(); | |
// set the parent as nullptr as this object should be the top now | |
parent_ = nullptr; | |
parsed_ = 0; | |
_parse(args); | |
run_callback(); | |
} | |
/// The real work is done here. Expects a reversed vector. | |
void parse(std::vector<std::string> &&args) { | |
// Clear if parsed | |
if(parsed_ > 0) | |
clear(); | |
// parsed_ is incremented in commands/subcommands, | |
// but placed here to make sure this is cleared when | |
// running parse after an error is thrown, even by _validate or _configure. | |
parsed_ = 1; | |
_validate(); | |
_configure(); | |
// set the parent as nullptr as this object should be the top now | |
parent_ = nullptr; | |
parsed_ = 0; | |
_parse(std::move(args)); | |
run_callback(); | |
} | |
/// Provide a function to print a help message. The function gets access to the App pointer and error. | |
void failure_message(std::function<std::string(const App *, const Error &e)> function) { | |
failure_message_ = function; | |
} | |
/// Print a nice error message and return the exit code | |
int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const { | |
/// Avoid printing anything if this is a CLI::RuntimeError | |
if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr) | |
return e.get_exit_code(); | |
if(dynamic_cast<const CLI::CallForHelp *>(&e) != nullptr) { | |
out << help(); | |
return e.get_exit_code(); | |
} | |
if(dynamic_cast<const CLI::CallForAllHelp *>(&e) != nullptr) { | |
out << help("", AppFormatMode::All); | |
return e.get_exit_code(); | |
} | |
if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) { | |
if(failure_message_) | |
err << failure_message_(this, e) << std::flush; | |
} | |
return e.get_exit_code(); | |
} | |
///@} | |
/// @name Post parsing | |
///@{ | |
/// Counts the number of times the given option was passed. | |
size_t count(std::string option_name) const { return get_option(option_name)->count(); } | |
/// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command | |
/// line order; use parsed = false to get the original definition list.) | |
std::vector<App *> get_subcommands() const { return parsed_subcommands_; } | |
/// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all | |
/// subcommands (const) | |
std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const { | |
std::vector<const App *> subcomms(subcommands_.size()); | |
std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { | |
return v.get(); | |
}); | |
if(filter) { | |
subcomms.erase(std::remove_if(std::begin(subcomms), | |
std::end(subcomms), | |
[&filter](const App *app) { return !filter(app); }), | |
std::end(subcomms)); | |
} | |
return subcomms; | |
} | |
/// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all | |
/// subcommands | |
std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) { | |
std::vector<App *> subcomms(subcommands_.size()); | |
std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { | |
return v.get(); | |
}); | |
if(filter) { | |
subcomms.erase( | |
std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }), | |
std::end(subcomms)); | |
} | |
return subcomms; | |
} | |
/// Check to see if given subcommand was selected | |
bool got_subcommand(App *subcom) const { | |
// get subcom needed to verify that this was a real subcommand | |
return get_subcommand(subcom)->parsed_ > 0; | |
} | |
/// Check with name instead of pointer to see if subcommand was selected | |
bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; } | |
/// Sets excluded options for the subcommand | |
App *excludes(Option *opt) { | |
if(opt == nullptr) { | |
throw OptionNotFound("nullptr passed"); | |
} | |
exclude_options_.insert(opt); | |
return this; | |
} | |
/// Sets excluded subcommands for the subcommand | |
App *excludes(App *app) { | |
if((app == this) || (app == nullptr)) { | |
throw OptionNotFound("nullptr passed"); | |
} | |
auto res = exclude_subcommands_.insert(app); | |
// subcommand exclusion should be symmetric | |
if(res.second) { | |
app->exclude_subcommands_.insert(this); | |
} | |
return this; | |
} | |
/// Removes an option from the excludes list of this subcommand | |
bool remove_excludes(Option *opt) { | |
auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt); | |
if(iterator != std::end(exclude_options_)) { | |
exclude_options_.erase(iterator); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/// Removes a subcommand from this excludes list of this subcommand | |
bool remove_excludes(App *app) { | |
auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app); | |
if(iterator != std::end(exclude_subcommands_)) { | |
auto other_app = *iterator; | |
exclude_subcommands_.erase(iterator); | |
other_app->remove_excludes(this); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
///@} | |
/// @name Help | |
///@{ | |
/// Set footer. | |
App *footer(std::string footer_string) { | |
footer_ = std::move(footer_string); | |
return this; | |
} | |
/// Produce a string that could be read in as a config of the current values of the App. Set default_also to | |
/// include default arguments. Prefix will add a string to the beginning of each option. | |
std::string config_to_str(bool default_also = false, bool write_description = false) const { | |
return config_formatter_->to_config(this, default_also, write_description, ""); | |
} | |
/// Makes a help message, using the currently configured formatter | |
/// Will only do one subcommand at a time | |
std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const { | |
if(prev.empty()) | |
prev = get_name(); | |
else | |
prev += " " + get_name(); | |
// Delegate to subcommand if needed | |
auto selected_subcommands = get_subcommands(); | |
if(!selected_subcommands.empty()) | |
return selected_subcommands.at(0)->help(prev, mode); | |
else | |
return formatter_->make_help(this, prev, mode); | |
} | |
///@} | |
/// @name Getters | |
///@{ | |
/// Access the formatter | |
std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; } | |
/// Access the config formatter | |
std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; } | |
/// Get the app or subcommand description | |
std::string get_description() const { return description_; } | |
/// Set the description of the app | |
App *description(std::string app_description) { | |
description_ = std::move(app_description); | |
return this; | |
} | |
/// Get the list of options (user facing function, so returns raw pointers), has optional filter function | |
std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const { | |
std::vector<const Option *> options(options_.size()); | |
std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { | |
return val.get(); | |
}); | |
if(filter) { | |
options.erase(std::remove_if(std::begin(options), | |
std::end(options), | |
[&filter](const Option *opt) { return !filter(opt); }), | |
std::end(options)); | |
} | |
return options; | |
} | |
/// Get an option by name (noexcept non-const version) | |
Option *get_option_no_throw(std::string option_name) noexcept { | |
for(Option_p &opt : options_) { | |
if(opt->check_name(option_name)) { | |
return opt.get(); | |
} | |
} | |
for(auto &subc : subcommands_) { | |
// also check down into nameless subcommands | |
if(subc->get_name().empty()) { | |
auto opt = subc->get_option_no_throw(option_name); | |
if(opt != nullptr) { | |
return opt; | |
} | |
} | |
} | |
return nullptr; | |
} | |
/// Get an option by name (noexcept const version) | |
const Option *get_option_no_throw(std::string option_name) const noexcept { | |
for(const Option_p &opt : options_) { | |
if(opt->check_name(option_name)) { | |
return opt.get(); | |
} | |
} | |
for(const auto &subc : subcommands_) { | |
// also check down into nameless subcommands | |
if(subc->get_name().empty()) { | |
auto opt = subc->get_option_no_throw(option_name); | |
if(opt != nullptr) { | |
return opt; | |
} | |
} | |
} | |
return nullptr; | |
} | |
/// Get an option by name | |
const Option *get_option(std::string option_name) const { | |
auto opt = get_option_no_throw(option_name); | |
if(opt == nullptr) { | |
throw OptionNotFound(option_name); | |
} | |
return opt; | |
} | |
/// Get an option by name (non-const version) | |
Option *get_option(std::string option_name) { | |
auto opt = get_option_no_throw(option_name); | |
if(opt == nullptr) { | |
throw OptionNotFound(option_name); | |
} | |
return opt; | |
} | |
/// Shortcut bracket operator for getting a pointer to an option | |
const Option *operator[](const std::string &option_name) const { return get_option(option_name); } | |
/// Shortcut bracket operator for getting a pointer to an option | |
const Option *operator[](const char *option_name) const { return get_option(option_name); } | |
/// Check the status of ignore_case | |
bool get_ignore_case() const { return ignore_case_; } | |
/// Check the status of ignore_underscore | |
bool get_ignore_underscore() const { return ignore_underscore_; } | |
/// Check the status of fallthrough | |
bool get_fallthrough() const { return fallthrough_; } | |
/// Check the status of the allow windows style options | |
bool get_allow_windows_style_options() const { return allow_windows_style_options_; } | |
/// Check the status of the allow windows style options | |
bool get_positionals_at_end() const { return positionals_at_end_; } | |
/// Get the group of this subcommand | |
const std::string &get_group() const { return group_; } | |
/// Get footer. | |
const std::string &get_footer() const { return footer_; } | |
/// Get the required min subcommand value | |
size_t get_require_subcommand_min() const { return require_subcommand_min_; } | |
/// Get the required max subcommand value | |
size_t get_require_subcommand_max() const { return require_subcommand_max_; } | |
/// Get the required min option value | |
size_t get_require_option_min() const { return require_option_min_; } | |
/// Get the required max option value | |
size_t get_require_option_max() const { return require_option_max_; } | |
/// Get the prefix command status | |
bool get_prefix_command() const { return prefix_command_; } | |
/// Get the status of allow extras | |
bool get_allow_extras() const { return allow_extras_; } | |
/// Get the status of required | |
bool get_required() const { return required_; } | |
/// Get the status of disabled | |
bool get_disabled() const { return disabled_; } | |
/// Get the status of disabled | |
bool get_immediate_callback() const { return immediate_callback_; } | |
/// Get the status of disabled by default | |
bool get_disabled_by_default() const { return disabled_by_default_; } | |
/// Get the status of disabled by default | |
bool get_enabled_by_default() const { return enabled_by_default_; } | |
/// Get the status of validating positionals | |
bool get_validate_positionals() const { return validate_positionals_; } | |
/// Get the status of allow extras | |
bool get_allow_config_extras() const { return allow_config_extras_; } | |
/// Get a pointer to the help flag. | |
Option *get_help_ptr() { return help_ptr_; } | |
/// Get a pointer to the help flag. (const) | |
const Option *get_help_ptr() const { return help_ptr_; } | |
/// Get a pointer to the help all flag. (const) | |
const Option *get_help_all_ptr() const { return help_all_ptr_; } | |
/// Get a pointer to the config option. | |
Option *get_config_ptr() { return config_ptr_; } | |
/// Get a pointer to the config option. (const) | |
const Option *get_config_ptr() const { return config_ptr_; } | |
/// Get the parent of this subcommand (or nullptr if master app) | |
App *get_parent() { return parent_; } | |
/// Get the parent of this subcommand (or nullptr if master app) (const version) | |
const App *get_parent() const { return parent_; } | |
/// Get the name of the current app | |
std::string get_name() const { return name_; } | |
/// Get a display name for an app | |
std::string get_display_name() const { return (!name_.empty()) ? name_ : "[Option Group: " + get_group() + "]"; } | |
/// Check the name, case insensitive and underscore insensitive if set | |
bool check_name(std::string name_to_check) const { | |
std::string local_name = name_; | |
if(ignore_underscore_) { | |
local_name = detail::remove_underscore(name_); | |
name_to_check = detail::remove_underscore(name_to_check); | |
} | |
if(ignore_case_) { | |
local_name = detail::to_lower(name_); | |
name_to_check = detail::to_lower(name_to_check); | |
} | |
return local_name == name_to_check; | |
} | |
/// Get the groups available directly from this option (in order) | |
std::vector<std::string> get_groups() const { | |
std::vector<std::string> groups; | |
for(const Option_p &opt : options_) { | |
// Add group if it is not already in there | |
if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) { | |
groups.push_back(opt->get_group()); | |
} | |
} | |
return groups; | |
} | |
/// This gets a vector of pointers with the original parse order | |
const std::vector<Option *> &parse_order() const { return parse_order_; } | |
/// This returns the missing options from the current subcommand | |
std::vector<std::string> remaining(bool recurse = false) const { | |
std::vector<std::string> miss_list; | |
for(const std::pair<detail::Classifier, std::string> &miss : missing_) { | |
miss_list.push_back(std::get<1>(miss)); | |
} | |
// Get from a subcommand that may allow extras | |
if(recurse) { | |
if(!allow_extras_) { | |
for(const auto &sub : subcommands_) { | |
if(sub->name_.empty() && !sub->missing_.empty()) { | |
for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) { | |
miss_list.push_back(std::get<1>(miss)); | |
} | |
} | |
} | |
} | |
// Recurse into subcommands | |
for(const App *sub : parsed_subcommands_) { | |
std::vector<std::string> output = sub->remaining(recurse); | |
std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list)); | |
} | |
} | |
return miss_list; | |
} | |
/// This returns the missing options in a form ready for processing by another command line program | |
std::vector<std::string> remaining_for_passthrough(bool recurse = false) const { | |
std::vector<std::string> miss_list = remaining(recurse); | |
std::reverse(std::begin(miss_list), std::end(miss_list)); | |
return miss_list; | |
} | |
/// This returns the number of remaining options, minus the -- separator | |
size_t remaining_size(bool recurse = false) const { | |
auto remaining_options = static_cast<size_t>(std::count_if( | |
std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) { | |
return val.first != detail::Classifier::POSITIONAL_MARK; | |
})); | |
if(recurse) { | |
for(const App_p &sub : subcommands_) { | |
remaining_options += sub->remaining_size(recurse); | |
} | |
} | |
return remaining_options; | |
} | |
///@} | |
protected: | |
/// Check the options to make sure there are no conflicts. | |
/// | |
/// Currently checks to see if multiple positionals exist with -1 args and checks if the min and max options are | |
/// feasible | |
void _validate() const { | |
auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { | |
return opt->get_items_expected() < 0 && opt->get_positional(); | |
}); | |
if(pcount > 1) | |
throw InvalidError(name_); | |
size_t nameless_subs{0}; | |
for(const App_p &app : subcommands_) { | |
app->_validate(); | |
if(app->get_name().empty()) | |
++nameless_subs; | |
} | |
if(require_option_min_ > 0) { | |
if(require_option_max_ > 0) { | |
if(require_option_max_ < require_option_min_) { | |
throw(InvalidError("Required min options greater than required max options", | |
ExitCodes::InvalidError)); | |
} | |
} | |
if(require_option_min_ > (options_.size() + nameless_subs)) { | |
throw(InvalidError("Required min options greater than number of available options", | |
ExitCodes::InvalidError)); | |
} | |
} | |
} | |
/// configure subcommands to enable parsing through the current object | |
/// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable | |
/// makes sure parent is set correctly | |
void _configure() { | |
if(disabled_by_default_) { | |
disabled_ = true; | |
} | |
if(enabled_by_default_) { | |
disabled_ = false; | |
} | |
for(const App_p &app : subcommands_) { | |
if(app->has_automatic_name_) { | |
app->name_.clear(); | |
} | |
if(app->name_.empty()) { | |
app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop | |
app->prefix_command_ = false; | |
} | |
// make sure the parent is set to be this object in preparation for parse | |
app->parent_ = this; | |
app->_configure(); | |
} | |
} | |
/// Internal function to run (App) callback, bottom up | |
void run_callback() { | |
pre_callback(); | |
// run the callbacks for the received subcommands | |
for(App *subc : get_subcommands()) { | |
if(!subc->immediate_callback_) | |
subc->run_callback(); | |
} | |
// now run callbacks for option_groups | |
for(auto &subc : subcommands_) { | |
if(!subc->immediate_callback_ && subc->name_.empty() && subc->count_all() > 0) { | |
subc->run_callback(); | |
} | |
} | |
// finally run the main callback | |
if(callback_ && (parsed_ > 0)) { | |
if(!name_.empty() || count_all() > 0) { | |
callback_(); | |
} | |
} | |
} | |
/// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached. | |
bool _valid_subcommand(const std::string ¤t, bool ignore_used = true) const { | |
// Don't match if max has been reached - but still check parents | |
if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) { | |
return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used); | |
} | |
auto com = _find_subcommand(current, true, ignore_used); | |
if(com != nullptr) { | |
return true; | |
} | |
// Check parent if exists, else return false | |
return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used); | |
} | |
/// Selects a Classifier enum based on the type of the current argument | |
detail::Classifier _recognize(const std::string ¤t, bool ignore_used_subcommands = true) const { | |
std::string dummy1, dummy2; | |
if(current == "--") | |
return detail::Classifier::POSITIONAL_MARK; | |
if(_valid_subcommand(current, ignore_used_subcommands)) | |
return detail::Classifier::SUBCOMMAND; | |
if(detail::split_long(current, dummy1, dummy2)) | |
return detail::Classifier::LONG; | |
if(detail::split_short(current, dummy1, dummy2)) | |
return detail::Classifier::SHORT; | |
if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) | |
return detail::Classifier::WINDOWS; | |
if((current == "++") && !name_.empty() && parent_ != nullptr) | |
return detail::Classifier::SUBCOMMAND_TERMINATOR; | |
return detail::Classifier::NONE; | |
} | |
// The parse function is now broken into several parts, and part of process | |
/// Read and process an ini file (main app only) | |
void _process_ini() { | |
// Process an INI file | |
if(config_ptr_ != nullptr) { | |
if(*config_ptr_) { | |
config_ptr_->run_callback(); | |
config_required_ = true; | |
} | |
if(!config_name_.empty()) { | |
try { | |
std::vector<ConfigItem> values = config_formatter_->from_file(config_name_); | |
_parse_config(values); | |
} catch(const FileError &) { | |
if(config_required_) | |
throw; | |
} | |
} | |
} | |
} | |
/// Get envname options if not yet passed. Runs on *all* subcommands. | |
void _process_env() { | |
for(const Option_p &opt : options_) { | |
if(opt->count() == 0 && !opt->envname_.empty()) { | |
char *buffer = nullptr; | |
std::string ename_string; | |
#ifdef _MSC_VER | |
// Windows version | |
size_t sz = 0; | |
if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) { | |
ename_string = std::string(buffer); | |
free(buffer); | |
} | |
#else | |
// This also works on Windows, but gives a warning | |
buffer = std::getenv(opt->envname_.c_str()); | |
if(buffer != nullptr) | |
ename_string = std::string(buffer); | |
#endif | |
if(!ename_string.empty()) { | |
opt->add_result(ename_string); | |
} | |
} | |
} | |
for(App_p &sub : subcommands_) { | |
if(sub->get_name().empty() || !sub->immediate_callback_) | |
sub->_process_env(); | |
} | |
} | |
/// Process callbacks. Runs on *all* subcommands. | |
void _process_callbacks() { | |
for(App_p &sub : subcommands_) { | |
// process the priority option_groups first | |
if(sub->get_name().empty() && sub->immediate_callback_) { | |
if(sub->count_all() > 0) { | |
sub->_process_callbacks(); | |
sub->run_callback(); | |
} | |
} | |
} | |
for(const Option_p &opt : options_) { | |
if(opt->count() > 0 && !opt->get_callback_run()) { | |
opt->run_callback(); | |
} | |
} | |
for(App_p &sub : subcommands_) { | |
if(!sub->immediate_callback_) { | |
sub->_process_callbacks(); | |
} | |
} | |
} | |
/// Run help flag processing if any are found. | |
/// | |
/// The flags allow recursive calls to remember if there was a help flag on a parent. | |
void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const { | |
const Option *help_ptr = get_help_ptr(); | |
const Option *help_all_ptr = get_help_all_ptr(); | |
if(help_ptr != nullptr && help_ptr->count() > 0) | |
trigger_help = true; | |
if(help_all_ptr != nullptr && help_all_ptr->count() > 0) | |
trigger_all_help = true; | |
// If there were parsed subcommands, call those. First subcommand wins if there are multiple ones. | |
if(!parsed_subcommands_.empty()) { | |
for(const App *sub : parsed_subcommands_) | |
sub->_process_help_flags(trigger_help, trigger_all_help); | |
// Only the final subcommand should call for help. All help wins over help. | |
} else if(trigger_all_help) { | |
throw CallForAllHelp(); | |
} else if(trigger_help) { | |
throw CallForHelp(); | |
} | |
} | |
/// Verify required options and cross requirements. Subcommands too (only if selected). | |
void _process_requirements() { | |
// check excludes | |
bool excluded{false}; | |
std::string excluder; | |
for(auto &opt : exclude_options_) { | |
if(opt->count() > 0) { | |
excluded = true; | |
excluder = opt->get_name(); | |
} | |
} | |
for(auto &subc : exclude_subcommands_) { | |
if(subc->count_all() > 0) { | |
excluded = true; | |
excluder = subc->get_display_name(); | |
} | |
} | |
if(excluded) { | |
if(count_all() > 0) { | |
throw ExcludesError(get_display_name(), excluder); | |
} | |
// if we are excluded but didn't receive anything, just return | |
return; | |
} | |
size_t used_options = 0; | |
for(const Option_p &opt : options_) { | |
if(opt->count() != 0) { | |
++used_options; | |
} | |
// Required or partially filled | |
if(opt->get_required() || opt->count() != 0) { | |
// Make sure enough -N arguments parsed (+N is already handled in parsing function) | |
if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) | |
throw ArgumentMismatch::AtLeast(opt->get_name(), -opt->get_items_expected()); | |
// Required but empty | |
if(opt->get_required() && opt->count() == 0) | |
throw RequiredError(opt->get_name()); | |
} | |
// Requires | |
for(const Option *opt_req : opt->needs_) | |
if(opt->count() > 0 && opt_req->count() == 0) | |
throw RequiresError(opt->get_name(), opt_req->get_name()); | |
// Excludes | |
for(const Option *opt_ex : opt->excludes_) | |
if(opt->count() > 0 && opt_ex->count() != 0) | |
throw ExcludesError(opt->get_name(), opt_ex->get_name()); | |
} | |
// check for the required number of subcommands | |
if(require_subcommand_min_ > 0) { | |
auto selected_subcommands = get_subcommands(); | |
if(require_subcommand_min_ > selected_subcommands.size()) | |
throw RequiredError::Subcommand(require_subcommand_min_); | |
} | |
// Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item. | |
// run this loop to check how many unnamed subcommands were actually used since they are considered options from | |
// the perspective of an App | |
for(App_p &sub : subcommands_) { | |
if(sub->disabled_) | |
continue; | |
if(sub->name_.empty() && sub->count_all() > 0) { | |
++used_options; | |
} | |
} | |
if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) { | |
auto option_list = detail::join(options_, [](const Option_p &ptr) { return ptr->get_name(false, true); }); | |
if(option_list.compare(0, 10, "-h,--help,") == 0) { | |
option_list.erase(0, 10); | |
} | |
auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); }); | |
if(!subc_list.empty()) { | |
option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); }); | |
} | |
throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list); | |
} | |
// now process the requirements for subcommands if needed | |
for(App_p &sub : subcommands_) { | |
if(sub->disabled_) | |
continue; | |
if(sub->name_.empty() && sub->required_ == false) { | |
if(sub->count_all() == 0) { | |
if(require_option_min_ > 0 && require_option_min_ <= used_options) { | |
continue; | |
// if we have met the requirement and there is nothing in this option group skip checking | |
// requirements | |
} | |
if(require_option_max_ > 0 && used_options >= require_option_min_) { | |
continue; | |
// if we have met the requirement and there is nothing in this option group skip checking | |
// requirements | |
} | |
} | |
} | |
if(sub->count() > 0 || sub->name_.empty()) { | |
sub->_process_requirements(); | |
} | |
if(sub->required_ && sub->count_all() == 0) { | |
throw(CLI::RequiredError(sub->get_display_name())); | |
} | |
} | |
} | |
/// Process callbacks and such. | |
void _process() { | |
_process_ini(); | |
_process_env(); | |
_process_callbacks(); | |
_process_help_flags(); | |
_process_requirements(); | |
} | |
/// Throw an error if anything is left over and should not be. | |
void _process_extras() { | |
if(!(allow_extras_ || prefix_command_)) { | |
size_t num_left_over = remaining_size(); | |
if(num_left_over > 0) { | |
throw ExtrasError(remaining(false)); | |
} | |
} | |
for(App_p &sub : subcommands_) { | |
if(sub->count() > 0) | |
sub->_process_extras(); | |
} | |
} | |
/// Throw an error if anything is left over and should not be. | |
/// Modifies the args to fill in the missing items before throwing. | |
void _process_extras(std::vector<std::string> &args) { | |
if(!(allow_extras_ || prefix_command_)) { | |
size_t num_left_over = remaining_size(); | |
if(num_left_over > 0) { | |
args = remaining(false); | |
throw ExtrasError(args); | |
} | |
} | |
for(App_p &sub : subcommands_) { | |
if(sub->count() > 0) | |
sub->_process_extras(args); | |
} | |
} | |
/// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands | |
void increment_parsed() { | |
++parsed_; | |
for(App_p &sub : subcommands_) { | |
if(sub->get_name().empty()) | |
sub->increment_parsed(); | |
} | |
} | |
/// Internal parse function | |
void _parse(std::vector<std::string> &args) { | |
increment_parsed(); | |
_trigger_pre_parse(args.size()); | |
bool positional_only = false; | |
while(!args.empty()) { | |
if(!_parse_single(args, positional_only)) { | |
break; | |
} | |
} | |
if(parent_ == nullptr) { | |
_process(); | |
// Throw error if any items are left over (depending on settings) | |
_process_extras(args); | |
// Convert missing (pairs) to extras (string only) ready for processing in another app | |
args = remaining_for_passthrough(false); | |
} else if(immediate_callback_) { | |
_process_env(); | |
_process_callbacks(); | |
_process_help_flags(); | |
_process_requirements(); | |
run_callback(); | |
} | |
} | |
/// Internal parse function | |
void _parse(std::vector<std::string> &&args) { | |
// this can only be called by the top level in which case parent == nullptr by definition | |
// operation is simplified | |
increment_parsed(); | |
_trigger_pre_parse(args.size()); | |
bool positional_only = false; | |
while(!args.empty()) { | |
_parse_single(args, positional_only); | |
} | |
_process(); | |
// Throw error if any items are left over (depending on settings) | |
_process_extras(); | |
} | |
/// Parse one config param, return false if not found in any subcommand, remove if it is | |
/// | |
/// If this has more than one dot.separated.name, go into the subcommand matching it | |
/// Returns true if it managed to find the option, if false you'll need to remove the arg manually. | |
void _parse_config(std::vector<ConfigItem> &args) { | |
for(ConfigItem item : args) { | |
if(!_parse_single_config(item) && !allow_config_extras_) | |
throw ConfigError::Extras(item.fullname()); | |
} | |
} | |
/// Fill in a single config option | |
bool _parse_single_config(const ConfigItem &item, size_t level = 0) { | |
if(level < item.parents.size()) { | |
try { | |
auto subcom = get_subcommand(item.parents.at(level)); | |
return subcom->_parse_single_config(item, level + 1); | |
} catch(const OptionNotFound &) { | |
return false; | |
} | |
} | |
Option *op = get_option_no_throw("--" + item.name); | |
if(op == nullptr) { | |
// If the option was not present | |
if(get_allow_config_extras()) | |
// Should we worry about classifying the extras properly? | |
missing_.emplace_back(detail::Classifier::NONE, item.fullname()); | |
return false; | |
} | |
if(!op->get_configurable()) | |
throw ConfigError::NotConfigurable(item.fullname()); | |
if(op->empty()) { | |
// Flag parsing | |
if(op->get_type_size() == 0) { | |
auto res = config_formatter_->to_flag(item); | |
res = op->get_flag_value(item.name, res); | |
op->add_result(res); | |
} else { | |
op->add_result(item.inputs); | |
op->run_callback(); | |
} | |
} | |
return true; | |
} | |
/// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing | |
/// from master return false if the parse has failed and needs to return to parent | |
bool _parse_single(std::vector<std::string> &args, bool &positional_only) { | |
bool retval = true; | |
detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back()); | |
switch(classifier) { | |
case detail::Classifier::POSITIONAL_MARK: | |
args.pop_back(); | |
positional_only = true; | |
if((!_has_remaining_positionals()) && (parent_ != nullptr)) { | |
retval = false; | |
} else { | |
_move_to_missing(classifier, "--"); | |
} | |
break; | |
case detail::Classifier::SUBCOMMAND_TERMINATOR: | |
// treat this like a positional mark if in the parent app | |
args.pop_back(); | |
retval = false; | |
break; | |
case detail::Classifier::SUBCOMMAND: | |
retval = _parse_subcommand(args); | |
break; | |
case detail::Classifier::LONG: | |
case detail::Classifier::SHORT: | |
case detail::Classifier::WINDOWS: | |
// If already parsed a subcommand, don't accept options_ | |
_parse_arg(args, classifier); | |
break; | |
case detail::Classifier::NONE: | |
// Probably a positional or something for a parent (sub)command | |
retval = _parse_positional(args); | |
if(retval && positionals_at_end_) { | |
positional_only = true; | |
} | |
break; | |
// LCOV_EXCL_START | |
default: | |
HorribleError("unrecognized classifier (you should not see this!)"); | |
// LCOV_EXCL_END | |
} | |
return retval; | |
} | |
/// Count the required remaining positional arguments | |
size_t _count_remaining_positionals(bool required_only = false) const { | |
size_t retval = 0; | |
for(const Option_p &opt : options_) | |
if(opt->get_positional() && (!required_only || opt->get_required()) && opt->get_items_expected() > 0 && | |
static_cast<int>(opt->count()) < opt->get_items_expected()) | |
retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); | |
return retval; | |
} | |
/// Count the required remaining positional arguments | |
bool _has_remaining_positionals() const { | |
for(const Option_p &opt : options_) | |
if(opt->get_positional() && | |
((opt->get_items_expected() < 0) || ((static_cast<int>(opt->count()) < opt->get_items_expected())))) | |
return true; | |
return false; | |
} | |
/// Parse a positional, go up the tree to check | |
/// Return true if the positional was used false otherwise | |
bool _parse_positional(std::vector<std::string> &args) { | |
const std::string &positional = args.back(); | |
for(const Option_p &opt : options_) { | |
// Eat options, one by one, until done | |
if(opt->get_positional() && | |
(static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { | |
if(validate_positionals_) { | |
std::string pos = positional; | |
pos = opt->_validate(pos); | |
if(!pos.empty()) { | |
continue; | |
} | |
} | |
opt->add_result(positional); | |
parse_order_.push_back(opt.get()); | |
args.pop_back(); | |
return true; | |
} | |
} | |
for(auto &subc : subcommands_) { | |
if((subc->name_.empty()) && (!subc->disabled_)) { | |
if(subc->_parse_positional(args)) { | |
if(!subc->pre_parse_called_) { | |
subc->_trigger_pre_parse(args.size()); | |
} | |
return true; | |
} | |
} | |
} | |
// let the parent deal with it if possible | |
if(parent_ != nullptr && fallthrough_) | |
return _get_fallthrough_parent()->_parse_positional(args); | |
/// Try to find a local subcommand that is repeated | |
auto com = _find_subcommand(args.back(), true, false); | |
if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) { | |
args.pop_back(); | |
com->_parse(args); | |
return true; | |
} | |
/// now try one last gasp at subcommands that have been executed before, go to root app and try to find a | |
/// subcommand in a broader way, if one exists let the parent deal with it | |
auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this; | |
com = parent_app->_find_subcommand(args.back(), true, false); | |
if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 || | |
com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) { | |
return false; | |
} | |
if(positionals_at_end_) { | |
throw CLI::ExtrasError(args); | |
} | |
/// If this is an option group don't deal with it | |
if(parent_ != nullptr && name_.empty()) { | |
return false; | |
} | |
/// We are out of other options this goes to missing | |
_move_to_missing(detail::Classifier::NONE, positional); | |
args.pop_back(); | |
if(prefix_command_) { | |
while(!args.empty()) { | |
_move_to_missing(detail::Classifier::NONE, args.back()); | |
args.pop_back(); | |
} | |
} | |
return true; | |
} | |
/// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used | |
/// subcommands be ignored | |
App *_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept { | |
for(const App_p &com : subcommands_) { | |
if(com->disabled_ && ignore_disabled) | |
continue; | |
if(com->get_name().empty()) { | |
auto subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used); | |
if(subc != nullptr) { | |
return subc; | |
} | |
} else if(com->check_name(subc_name)) { | |
if((!*com) || !ignore_used) | |
return com.get(); | |
} | |
} | |
return nullptr; | |
} | |
/// Parse a subcommand, modify args and continue | |
/// | |
/// Unlike the others, this one will always allow fallthrough | |
/// return true if the subcommand was processed false otherwise | |
bool _parse_subcommand(std::vector<std::string> &args) { | |
if(_count_remaining_positionals(/* required */ true) > 0) { | |
_parse_positional(args); | |
return true; | |
} | |
auto com = _find_subcommand(args.back(), true, true); | |
if(com != nullptr) { | |
args.pop_back(); | |
parsed_subcommands_.push_back(com); | |
com->_parse(args); | |
auto parent_app = com->parent_; | |
while(parent_app != this) { | |
parent_app->_trigger_pre_parse(args.size()); | |
parent_app->parsed_subcommands_.push_back(com); | |
parent_app = parent_app->parent_; | |
} | |
return true; | |
} | |
if(parent_ == nullptr) | |
throw HorribleError("Subcommand " + args.back() + " missing"); | |
return false; | |
} | |
/// Parse a short (false) or long (true) argument, must be at the top of the list | |
/// return true if the argument was processed or false if nothing was done | |
bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) { | |
std::string current = args.back(); | |
std::string arg_name; | |
std::string value; | |
std::string rest; | |
switch(current_type) { | |
case detail::Classifier::LONG: | |
if(!detail::split_long(current, arg_name, value)) | |
throw HorribleError("Long parsed but missing (you should not see this):" + args.back()); | |
break; | |
case detail::Classifier::SHORT: | |
if(!detail::split_short(current, arg_name, rest)) | |
throw HorribleError("Short parsed but missing! You should not see this"); | |
break; | |
case detail::Classifier::WINDOWS: | |
if(!detail::split_windows_style(current, arg_name, value)) | |
throw HorribleError("windows option parsed but missing! You should not see this"); | |
break; | |
case detail::Classifier::SUBCOMMAND: | |
case detail::Classifier::POSITIONAL_MARK: | |
case detail::Classifier::NONE: | |
default: | |
throw HorribleError("parsing got called with invalid option! You should not see this"); | |
} | |
auto op_ptr = | |
std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) { | |
if(current_type == detail::Classifier::LONG) | |
return opt->check_lname(arg_name); | |
if(current_type == detail::Classifier::SHORT) | |
return opt->check_sname(arg_name); | |
// this will only get called for detail::Classifier::WINDOWS | |
return opt->check_lname(arg_name) || opt->check_sname(arg_name); | |
}); | |
// Option not found | |
if(op_ptr == std::end(options_)) { | |
for(auto &subc : subcommands_) { | |
if(subc->name_.empty() && !subc->disabled_) { | |
if(subc->_parse_arg(args, current_type)) { | |
if(!subc->pre_parse_called_) { | |
subc->_trigger_pre_parse(args.size()); | |
} | |
return true; | |
} | |
} | |
} | |
// If a subcommand, try the master command | |
if(parent_ != nullptr && fallthrough_) | |
return _get_fallthrough_parent()->_parse_arg(args, current_type); | |
// don't capture missing if this is a nameless subcommand | |
if(parent_ != nullptr && name_.empty()) { | |
return false; | |
} | |
// Otherwise, add to missing | |
args.pop_back(); | |
_move_to_missing(current_type, current); | |
return true; | |
} | |
args.pop_back(); | |
// Get a reference to the pointer to make syntax bearable | |
Option_p &op = *op_ptr; | |
int num = op->get_items_expected(); | |
// Make sure we always eat the minimum for unlimited vectors | |
int collected = 0; | |
int result_count = 0; | |
// deal with flag like things | |
if(num == 0) { | |
auto res = op->get_flag_value(arg_name, value); | |
op->add_result(res); | |
parse_order_.push_back(op.get()); | |
} | |
// --this=value | |
else if(!value.empty()) { | |
op->add_result(value, result_count); | |
parse_order_.push_back(op.get()); | |
collected += result_count; | |
// If exact number expected | |
if(num > 0) | |
num = (num >= result_count) ? num - result_count : 0; | |
// -Trest | |
} else if(!rest.empty()) { | |
op->add_result(rest, result_count); | |
parse_order_.push_back(op.get()); | |
rest = ""; | |
collected += result_count; | |
// If exact number expected | |
if(num > 0) | |
num = (num >= result_count) ? num - result_count : 0; | |
} | |
// Unlimited vector parser | |
if(num < 0) { | |
while(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) { | |
if(collected >= -num) { | |
// We could break here for allow extras, but we don't | |
// If any positionals remain, don't keep eating | |
if(_count_remaining_positionals() > 0) | |
break; | |
} | |
op->add_result(args.back(), result_count); | |
parse_order_.push_back(op.get()); | |
args.pop_back(); | |
collected += result_count; | |
} | |
// Allow -- to end an unlimited list and "eat" it | |
if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK) | |
args.pop_back(); | |
} else { | |
while(num > 0 && !args.empty()) { | |
std::string current_ = args.back(); | |
args.pop_back(); | |
op->add_result(current_, result_count); | |
parse_order_.push_back(op.get()); | |
num -= result_count; | |
} | |
if(num > 0) { | |
throw ArgumentMismatch::TypedAtLeast(op->get_name(), num, op->get_type_name()); | |
} | |
} | |
if(!rest.empty()) { | |
rest = "-" + rest; | |
args.push_back(rest); | |
} | |
return true; | |
} | |
/// Trigger the pre_parse callback if needed | |
void _trigger_pre_parse(size_t remaining_args) { | |
if(!pre_parse_called_) { | |
pre_parse_called_ = true; | |
if(pre_parse_callback_) { | |
pre_parse_callback_(remaining_args); | |
} | |
} else if(immediate_callback_) { | |
if(!name_.empty()) { | |
auto pcnt = parsed_; | |
auto extras = std::move(missing_); | |
clear(); | |
parsed_ = pcnt; | |
pre_parse_called_ = true; | |
missing_ = std::move(extras); | |
} | |
} | |
} | |
/// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app | |
App *_get_fallthrough_parent() { | |
if(parent_ == nullptr) { | |
throw(HorribleError("No Valid parent")); | |
} | |
auto fallthrough_parent = parent_; | |
while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) { | |
fallthrough_parent = fallthrough_parent->parent_; | |
} | |
return fallthrough_parent; | |
} | |
/// Helper function to place extra values in the most appropriate position | |
void _move_to_missing(detail::Classifier val_type, const std::string &val) { | |
if(allow_extras_ || subcommands_.empty()) { | |
missing_.emplace_back(val_type, val); | |
return; | |
} | |
// allow extra arguments to be places in an option group if it is allowed there | |
for(auto &subc : subcommands_) { | |
if(subc->name_.empty() && subc->allow_extras_) { | |
subc->missing_.emplace_back(val_type, val); | |
return; | |
} | |
} | |
// if we haven't found any place to put them yet put them in missing | |
missing_.emplace_back(val_type, val); | |
} | |
public: | |
/// function that could be used by subclasses of App to shift options around into subcommands | |
void _move_option(Option *opt, App *app) { | |
if(opt == nullptr) { | |
throw OptionNotFound("the option is NULL"); | |
} | |
// verify that the give app is actually a subcommand | |
bool found = false; | |
for(auto &subc : subcommands_) { | |
if(app == subc.get()) { | |
found = true; | |
} | |
} | |
if(!found) { | |
throw OptionNotFound("The Given app is not a subcommand"); | |
} | |
if((help_ptr_ == opt) || (help_all_ptr_ == opt)) | |
throw OptionAlreadyAdded("cannot move help options"); | |
if(config_ptr_ == opt) | |
throw OptionAlreadyAdded("cannot move config file options"); | |
auto iterator = | |
std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); | |
if(iterator != std::end(options_)) { | |
const auto &opt_p = *iterator; | |
if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) { | |
return (*v == *opt_p); | |
}) == std::end(app->options_)) { | |
// only erase after the insertion was successful | |
app->options_.push_back(std::move(*iterator)); | |
options_.erase(iterator); | |
} else { | |
throw OptionAlreadyAdded(opt->get_name()); | |
} | |
} else { | |
throw OptionNotFound("could not locate the given App"); | |
} | |
} | |
}; | |
/// Extension of App to better manage groups of options | |
class Option_group : public App { | |
public: | |
Option_group(std::string group_description, std::string group_name, App *parent) | |
: App(std::move(group_description), "", parent) { | |
group(group_name); | |
// option groups should have automatic fallthrough | |
} | |
using App::add_option; | |
/// Add an existing option to the Option_group | |
Option *add_option(Option *opt) { | |
if(get_parent() == nullptr) { | |
throw OptionNotFound("Unable to locate the specified option"); | |
} | |
get_parent()->_move_option(opt, this); | |
return opt; | |
} | |
/// Add an existing option to the Option_group | |
void add_options(Option *opt) { add_option(opt); } | |
/// Add a bunch of options to the group | |
template <typename... Args> void add_options(Option *opt, Args... args) { | |
add_option(opt); | |
add_options(args...); | |
} | |
using App::add_subcommand; | |
/// Add an existing subcommand to be a member of an option_group | |
App *add_subcommand(App *subcom) { | |
App_p subc = subcom->get_parent()->get_subcommand_ptr(subcom); | |
subc->get_parent()->remove_subcommand(subcom); | |
add_subcommand(std::move(subc)); | |
return subcom; | |
} | |
}; | |
/// Helper function to enable one option group/subcommand when another is used | |
inline void TriggerOn(App *trigger_app, App *app_to_enable) { | |
app_to_enable->enabled_by_default(false); | |
app_to_enable->disabled_by_default(); | |
trigger_app->preparse_callback([app_to_enable](size_t) { app_to_enable->disabled(false); }); | |
} | |
/// Helper function to enable one option group/subcommand when another is used | |
inline void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) { | |
for(auto &app : apps_to_enable) { | |
app->enabled_by_default(false); | |
app->disabled_by_default(); | |
} | |
trigger_app->preparse_callback([apps_to_enable](size_t) { | |
for(auto &app : apps_to_enable) { | |
app->disabled(false); | |
} | |
}); | |
} | |
/// Helper function to disable one option group/subcommand when another is used | |
inline void TriggerOff(App *trigger_app, App *app_to_enable) { | |
app_to_enable->disabled_by_default(false); | |
app_to_enable->enabled_by_default(); | |
trigger_app->preparse_callback([app_to_enable](size_t) { app_to_enable->disabled(); }); | |
} | |
/// Helper function to disable one option group/subcommand when another is used | |
inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) { | |
for(auto &app : apps_to_enable) { | |
app->disabled_by_default(false); | |
app->enabled_by_default(); | |
} | |
trigger_app->preparse_callback([apps_to_enable](size_t) { | |
for(auto &app : apps_to_enable) { | |
app->disabled(); | |
} | |
}); | |
} | |
namespace FailureMessage { | |
/// Printout a clean, simple message on error (the default in CLI11 1.5+) | |
inline std::string simple(const App *app, const Error &e) { | |
std::string header = std::string(e.what()) + "\n"; | |
std::vector<std::string> names; | |
// Collect names | |
if(app->get_help_ptr() != nullptr) | |
names.push_back(app->get_help_ptr()->get_name()); | |
if(app->get_help_all_ptr() != nullptr) | |
names.push_back(app->get_help_all_ptr()->get_name()); | |
// If any names found, suggest those | |
if(!names.empty()) | |
header += "Run with " + detail::join(names, " or ") + " for more information.\n"; | |
return header; | |
} | |
/// Printout the full help string on error (if this fn is set, the old default for CLI11) | |
inline std::string help(const App *app, const Error &e) { | |
std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n"; | |
header += app->help(); | |
return header; | |
} | |
} // namespace FailureMessage | |
namespace detail { | |
/// This class is simply to allow tests access to App's protected functions | |
struct AppFriend { | |
/// Wrap _parse_short, perfectly forward arguments and return | |
template <typename... Args> | |
static auto parse_arg(App *app, Args &&... args) -> | |
typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type { | |
return app->_parse_arg(std::forward<Args>(args)...); | |
} | |
/// Wrap _parse_subcommand, perfectly forward arguments and return | |
template <typename... Args> | |
static auto parse_subcommand(App *app, Args &&... args) -> | |
typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type { | |
return app->_parse_subcommand(std::forward<Args>(args)...); | |
} | |
/// Wrap the fallthrough parent function to make sure that is working correctly | |
static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); } | |
}; | |
} // namespace detail | |
} // namespace CLI | |
// From CLI/Config.hpp: | |
namespace CLI { | |
inline std::string | |
ConfigINI::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { | |
std::stringstream out; | |
for(const Option *opt : app->get_options({})) { | |
// Only process option with a long-name and configurable | |
if(!opt->get_lnames().empty() && opt->get_configurable()) { | |
std::string name = prefix + opt->get_lnames()[0]; | |
std::string value; | |
// Non-flags | |
if(opt->get_type_size() != 0) { | |
// If the option was found on command line | |
if(opt->count() > 0) | |
value = detail::ini_join(opt->results()); | |
// If the option has a default and is requested by optional argument | |
else if(default_also && !opt->get_default_str().empty()) | |
value = opt->get_default_str(); | |
// Flag, one passed | |
} else if(opt->count() == 1) { | |
value = "true"; | |
// Flag, multiple passed | |
} else if(opt->count() > 1) { | |
value = std::to_string(opt->count()); | |
// Flag, not present | |
} else if(opt->count() == 0 && default_also) { | |
value = "false"; | |
} | |
if(!value.empty()) { | |
if(write_description && opt->has_description()) { | |
if(static_cast<int>(out.tellp()) != 0) { | |
out << std::endl; | |
} | |
out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl; | |
} | |
// Don't try to quote anything that is not size 1 | |
if(opt->get_items_expected() != 1) | |
out << name << "=" << value << std::endl; | |
else | |
out << name << "=" << detail::add_quotes_if_needed(value) << std::endl; | |
} | |
} | |
} | |
for(const App *subcom : app->get_subcommands({})) | |
out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + "."); | |
return out.str(); | |
} | |
} // namespace CLI | |
// From CLI/Formatter.hpp: | |
namespace CLI { | |
inline std::string | |
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const { | |
std::stringstream out; | |
out << "\n" << group << ":\n"; | |
for(const Option *opt : opts) { | |
out << make_option(opt, is_positional); | |
} | |
return out.str(); | |
} | |
inline std::string Formatter::make_positionals(const App *app) const { | |
std::vector<const Option *> opts = | |
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); }); | |
if(opts.empty()) | |
return std::string(); | |
else | |
return make_group(get_label("Positionals"), true, opts); | |
} | |
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { | |
std::stringstream out; | |
std::vector<std::string> groups = app->get_groups(); | |
// Options | |
for(const std::string &group : groups) { | |
std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) { | |
return opt->get_group() == group // Must be in the right group | |
&& opt->nonpositional() // Must not be a positional | |
&& (mode != AppFormatMode::Sub // If mode is Sub, then | |
|| (app->get_help_ptr() != opt // Ignore help pointer | |
&& app->get_help_all_ptr() != opt)); // Ignore help all pointer | |
}); | |
if(!group.empty() && !opts.empty()) { | |
out << make_group(group, false, opts); | |
if(group != groups.back()) | |
out << "\n"; | |
} | |
} | |
return out.str(); | |
} | |
inline std::string Formatter::make_description(const App *app) const { | |
std::string desc = app->get_description(); | |
auto min_options = app->get_require_option_min(); | |
auto max_options = app->get_require_option_max(); | |
if(app->get_required()) { | |
desc += " REQUIRED "; | |
} | |
if((max_options == min_options) && (min_options > 0)) { | |
if(min_options == 1) { | |
desc += " \n[Exactly 1 of the following options is required]"; | |
} else { | |
desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]"; | |
} | |
} else if(max_options > 0) { | |
if(min_options > 0) { | |
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) + | |
" of the follow options are required]"; | |
} else { | |
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]"; | |
} | |
} else if(min_options > 0) { | |
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]"; | |
} | |
return (!desc.empty()) ? desc + "\n" : std::string{}; | |
} | |
inline std::string Formatter::make_usage(const App *app, std::string name) const { | |
std::stringstream out; | |
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; | |
std::vector<std::string> groups = app->get_groups(); | |
// Print an Options badge if any options exist | |
std::vector<const Option *> non_pos_options = | |
app->get_options([](const Option *opt) { return opt->nonpositional(); }); | |
if(!non_pos_options.empty()) | |
out << " [" << get_label("OPTIONS") << "]"; | |
// Positionals need to be listed here | |
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); }); | |
// Print out positionals if any are left | |
if(!positionals.empty()) { | |
// Convert to help names | |
std::vector<std::string> positional_names(positionals.size()); | |
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) { | |
return make_option_usage(opt); | |
}); | |
out << " " << detail::join(positional_names, " "); | |
} | |
// Add a marker if subcommands are expected or optional | |
if(!app->get_subcommands( | |
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); }) | |
.empty()) { | |
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") | |
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" | |
: "SUBCOMMANDS") | |
<< (app->get_require_subcommand_min() == 0 ? "]" : ""); | |
} | |
out << std::endl; | |
return out.str(); | |
} | |
inline std::string Formatter::make_footer(const App *app) const { | |
std::string footer = app->get_footer(); | |
if(!footer.empty()) | |
return footer + "\n"; | |
else | |
return ""; | |
} | |
inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { | |
// This immediately forwards to the make_expanded method. This is done this way so that subcommands can | |
// have overridden formatters | |
if(mode == AppFormatMode::Sub) | |
return make_expanded(app); | |
std::stringstream out; | |
if((app->get_name().empty()) && (app->get_parent() != nullptr)) { | |
if(app->get_group() != "Subcommands") { | |
out << app->get_group() << ':'; | |
} | |
} | |
out << make_description(app); | |
out << make_usage(app, name); | |
out << make_positionals(app); | |
out << make_groups(app, mode); | |
out << make_subcommands(app, mode); | |
out << make_footer(app); | |
return out.str(); | |
} | |
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const { | |
std::stringstream out; | |
std::vector<const App *> subcommands = app->get_subcommands({}); | |
// Make a list in definition order of the groups seen | |
std::vector<std::string> subcmd_groups_seen; | |
for(const App *com : subcommands) { | |
if(com->get_name().empty()) { | |
out << make_expanded(com); | |
continue; | |
} | |
std::string group_key = com->get_group(); | |
if(!group_key.empty() && | |
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { | |
return detail::to_lower(a) == detail::to_lower(group_key); | |
}) == subcmd_groups_seen.end()) | |
subcmd_groups_seen.push_back(group_key); | |
} | |
// For each group, filter out and print subcommands | |
for(const std::string &group : subcmd_groups_seen) { | |
out << "\n" << group << ":\n"; | |
std::vector<const App *> subcommands_group = app->get_subcommands( | |
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); }); | |
for(const App *new_com : subcommands_group) { | |
if(new_com->get_name().empty()) | |
continue; | |
if(mode != AppFormatMode::All) { | |
out << make_subcommand(new_com); | |
} else { | |
out << new_com->help(new_com->get_name(), AppFormatMode::Sub); | |
out << "\n"; | |
} | |
} | |
} | |
return out.str(); | |
} | |
inline std::string Formatter::make_subcommand(const App *sub) const { | |
std::stringstream out; | |
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_); | |
return out.str(); | |
} | |
inline std::string Formatter::make_expanded(const App *sub) const { | |
std::stringstream out; | |
out << sub->get_display_name() << "\n"; | |
out << make_description(sub); | |
out << make_positionals(sub); | |
out << make_groups(sub, AppFormatMode::Sub); | |
out << make_subcommands(sub, AppFormatMode::Sub); | |
// Drop blank spaces | |
std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n"); | |
tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n' | |
// Indent all but the first line (the name) | |
return detail::find_and_replace(tmp, "\n", "\n ") + "\n"; | |
} | |
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { | |
if(is_positional) | |
return opt->get_name(true, false); | |
else | |
return opt->get_name(false, true); | |
} | |
inline std::string Formatter::make_option_opts(const Option *opt) const { | |
std::stringstream out; | |
if(opt->get_type_size() != 0) { | |
if(!opt->get_type_name().empty()) | |
out << " " << get_label(opt->get_type_name()); | |
if(!opt->get_default_str().empty()) | |
out << "=" << opt->get_default_str(); | |
if(opt->get_expected() > 1) | |
out << " x " << opt->get_expected(); | |
if(opt->get_expected() == -1) | |
out << " ..."; | |
if(opt->get_required()) | |
out << " " << get_label("REQUIRED"); | |
} | |
if(!opt->get_envname().empty()) | |
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; | |
if(!opt->get_needs().empty()) { | |
out << " " << get_label("Needs") << ":"; | |
for(const Option *op : opt->get_needs()) | |
out << " " << op->get_name(); | |
} | |
if(!opt->get_excludes().empty()) { | |
out << " " << get_label("Excludes") << ":"; | |
for(const Option *op : opt->get_excludes()) | |
out << " " << op->get_name(); | |
} | |
return out.str(); | |
} | |
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); } | |
inline std::string Formatter::make_option_usage(const Option *opt) const { | |
// Note that these are positionals usages | |
std::stringstream out; | |
out << make_option_name(opt, true); | |
if(opt->get_expected() > 1) | |
out << "(" << std::to_string(opt->get_expected()) << "x)"; | |
else if(opt->get_expected() < 0) | |
out << "..."; | |
return opt->get_required() ? out.str() : "[" + out.str() + "]"; | |
} | |
} // namespace CLI | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment