terminal/dep/CLI11/CLI11.hpp
Mike Griese 830c22b73e Add support for commandline args to wt.exe (#4023)
## Summary of the Pull Request

Adds support for commandline arguments to the Windows Terminal, in accordance with the spec in #3495

## References

* Original issue: #607
* Original spec: #3495

## PR Checklist
* [x] Closes #607
* [x] I work here
* [x] Tests added/passed
* [ ] We should probably add some docs on these commands
* [x] The spec (#3495) needs to be merged first!

## Detailed Description of the Pull Request / Additional comments

🛑 **STOP** 🛑 - have you read #3495 yet? If you haven't, go do that now.

This PR adds support for three initial sub-commands to the `wt.exe` application:
* `new-tab`: Used to create a new tab.
* `split-pane`: Used to create a new split.
* `focus-tab`: Moves focus to another tab.

These commands are largely POC to prove that the commandlines work. They're not totally finished, but they work well enough. Follow up work items will be filed to track adding support for additional parameters and subcommands

Important scenarios added:
* `wt -d .`: Open a new wt instance in the current working directory #878
* `wt -p <profile name>`: Create a wt instance running the given profile, to unblock  #576, #1357, #2339
* `wt ; new-tab ; split-pane -V`: Launch the terminal with multiple tabs, splits, to unblock #756 

## Validation Steps Performed

* Ran tests
* Played with it a bunch
2020-01-27 15:34:12 +00:00

7822 lines
295 KiB
C++

#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& ltrim(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& ltrim(std::string& str, const std::string& filter)
{
auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
str.erase(str.begin(), it);
return str;
}
/// Trim whitespace from right of string
inline std::string& rtrim(std::string& str)
{
auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<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& current, std::string& name, std::string& rest)
{
if (current.size() > 1 && current[0] == '-' && valid_first_char(current[1]))
{
name = current.substr(1, 1);
rest = current.substr(2);
return true;
}
else
return false;
}
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
inline bool split_long(const std::string& current, std::string& name, std::string& value)
{
if (current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2]))
{
auto loc = current.find_first_of('=');
if (loc != std::string::npos)
{
name = current.substr(2, loc - 2);
value = current.substr(loc + 1);
}
else
{
name = current.substr(2);
value = "";
}
return true;
}
else
return false;
}
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
inline bool split_windows_style(const std::string& current, std::string& name, std::string& value)
{
if (current.size() > 1 && current[0] == '/' && valid_first_char(current[1]))
{
auto loc = current.find_first_of(':');
if (loc != std::string::npos)
{
name = current.substr(1, loc - 1);
value = current.substr(loc + 1);
}
else
{
name = current.substr(1);
value = "";
}
return true;
}
else
return false;
}
// Splits a string into multiple long and short names
inline std::vector<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& current, 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& current, 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