spacebox/lib/cli11/CLI11.hpp

10999 lines
411 KiB
C++

// CLI11: Version 2.4.2
// Originally designed by Henry Schreiner
// https://github.com/CLIUtils/CLI11
//
// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
// from: v2.4.2
//
// CLI11 2.4.2 Copyright (c) 2017-2024 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.
#pragma once
// Standard combined includes:
#include <algorithm>
#include <array>
#include <cctype>
#include <clocale>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <locale>
#include <map>
#include <memory>
#include <numeric>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#define CLI11_VERSION_MAJOR 2
#define CLI11_VERSION_MINOR 4
#define CLI11_VERSION_PATCH 2
#define CLI11_VERSION "2.4.2"
// 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
// GCC < 10 doesn't ignore this in unevaluated contexts
#if !defined(CLI11_CPP17) || \
(defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4)
#define CLI11_NODISCARD
#else
#define CLI11_NODISCARD [[nodiscard]]
#endif
/** detection of rtti */
#ifndef CLI11_USE_STATIC_RTTI
#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI)
#define CLI11_USE_STATIC_RTTI 1
#elif defined(__cpp_rtti)
#if(defined(_CPPRTTI) && _CPPRTTI == 0)
#define CLI11_USE_STATIC_RTTI 1
#else
#define CLI11_USE_STATIC_RTTI 0
#endif
#elif(defined(__GCC_RTTI) && __GXX_RTTI)
#define CLI11_USE_STATIC_RTTI 0
#else
#define CLI11_USE_STATIC_RTTI 1
#endif
#endif
/** <filesystem> availability */
#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
#if __has_include(<filesystem>)
// Filesystem cannot be used if targeting macOS < 10.15
#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
#define CLI11_HAS_FILESYSTEM 0
#elif defined(__wasi__)
// As of wasi-sdk-14, filesystem is not implemented
#define CLI11_HAS_FILESYSTEM 0
#else
#include <filesystem>
#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
#define CLI11_HAS_FILESYSTEM 1
#elif defined(__GLIBCXX__)
// if we are using gcc and Version <9 default to no filesystem
#define CLI11_HAS_FILESYSTEM 0
#else
#define CLI11_HAS_FILESYSTEM 1
#endif
#else
#define CLI11_HAS_FILESYSTEM 0
#endif
#endif
#endif
#endif
/** <codecvt> availability */
#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5
#define CLI11_HAS_CODECVT 0
#else
#define CLI11_HAS_CODECVT 1
#include <codecvt>
#endif
/** disable deprecations */
#if defined(__GNUC__) // GCC or clang
#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#elif defined(_MSC_VER)
#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push))
#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop))
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996))
#else
#define CLI11_DIAGNOSTIC_PUSH
#define CLI11_DIAGNOSTIC_POP
#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED
#endif
/** Inline macro **/
#ifdef CLI11_COMPILE
#define CLI11_INLINE
#else
#define CLI11_INLINE inline
#endif
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem> // NOLINT(build/include)
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif
#ifdef CLI11_CPP17
#include <string_view>
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
#include <string_view> // NOLINT(build/include)
#endif // CLI11_HAS_FILESYSTEM
#if defined(_WIN32)
#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_))
#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \
defined(_M_AMD64)
#define _AMD64_
#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86)
#define _X86_
#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT)
#define _ARM_
#elif defined(__aarch64__) || defined(_M_ARM64)
#define _ARM64_
#elif defined(_M_ARM64EC)
#define _ARM64EC_
#endif
#endif
// first
#ifndef NOMINMAX
// if NOMINMAX is already defined we don't want to mess with that either way
#define NOMINMAX
#include <windef.h>
#undef NOMINMAX
#else
#include <windef.h>
#endif
// second
#include <winbase.h>
// third
#include <processthreadsapi.h>
#include <shellapi.h>
#endif
namespace CLI {
/// Convert a wide string to a narrow string.
CLI11_INLINE std::string narrow(const std::wstring &str);
CLI11_INLINE std::string narrow(const wchar_t *str);
CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size);
/// Convert a narrow string to a wide string.
CLI11_INLINE std::wstring widen(const std::string &str);
CLI11_INLINE std::wstring widen(const char *str);
CLI11_INLINE std::wstring widen(const char *str, std::size_t size);
#ifdef CLI11_CPP17
CLI11_INLINE std::string narrow(std::wstring_view str);
CLI11_INLINE std::wstring widen(std::string_view str);
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
/// Convert a char-string to a native path correctly.
CLI11_INLINE std::filesystem::path to_path(std::string_view str);
#endif // CLI11_HAS_FILESYSTEM
namespace detail {
#if !CLI11_HAS_CODECVT
/// Attempt to set one of the acceptable unicode locales for conversion
CLI11_INLINE void set_unicode_locale() {
static const std::array<const char *, 3> unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}};
for(const auto &locale_name : unicode_locales) {
if(std::setlocale(LC_ALL, locale_name) != nullptr) {
return;
}
}
throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8");
}
template <typename F> struct scope_guard_t {
F closure;
explicit scope_guard_t(F closure_) : closure(closure_) {}
~scope_guard_t() { closure(); }
};
template <typename F> CLI11_NODISCARD CLI11_INLINE scope_guard_t<F> scope_guard(F &&closure) {
return scope_guard_t<F>{std::forward<F>(closure)};
}
#endif // !CLI11_HAS_CODECVT
CLI11_DIAGNOSTIC_PUSH
CLI11_DIAGNOSTIC_IGNORE_DEPRECATED
CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) {
#if CLI11_HAS_CODECVT
#ifdef _WIN32
return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(str, str + str_size);
#else
return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(str, str + str_size);
#endif // _WIN32
#else // CLI11_HAS_CODECVT
(void)str_size;
std::mbstate_t state = std::mbstate_t();
const wchar_t *it = str;
std::string old_locale = std::setlocale(LC_ALL, nullptr);
auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); });
set_unicode_locale();
std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state);
if(new_size == static_cast<std::size_t>(-1)) {
throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " +
std::to_string(it - str));
}
std::string result(new_size, '\0');
std::wcsrtombs(const_cast<char *>(result.data()), &str, new_size, &state);
return result;
#endif // CLI11_HAS_CODECVT
}
CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) {
#if CLI11_HAS_CODECVT
#ifdef _WIN32
return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(str, str + str_size);
#else
return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str, str + str_size);
#endif // _WIN32
#else // CLI11_HAS_CODECVT
(void)str_size;
std::mbstate_t state = std::mbstate_t();
const char *it = str;
std::string old_locale = std::setlocale(LC_ALL, nullptr);
auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); });
set_unicode_locale();
std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state);
if(new_size == static_cast<std::size_t>(-1)) {
throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " +
std::to_string(it - str));
}
std::wstring result(new_size, L'\0');
std::mbsrtowcs(const_cast<wchar_t *>(result.data()), &str, new_size, &state);
return result;
#endif // CLI11_HAS_CODECVT
}
CLI11_DIAGNOSTIC_POP
} // namespace detail
CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); }
CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); }
// Flawfinder: ignore
CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); }
CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); }
CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); }
// Flawfinder: ignore
CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); }
#ifdef CLI11_CPP17
CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); }
CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); }
#endif // CLI11_CPP17
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE std::filesystem::path to_path(std::string_view str) {
return std::filesystem::path{
#ifdef _WIN32
widen(str)
#else
str
#endif // _WIN32
};
}
#endif // CLI11_HAS_FILESYSTEM
namespace detail {
#ifdef _WIN32
/// Decode and return UTF-8 argv from GetCommandLineW.
CLI11_INLINE std::vector<std::string> compute_win32_argv();
#endif
} // namespace detail
namespace detail {
#ifdef _WIN32
CLI11_INLINE std::vector<std::string> compute_win32_argv() {
std::vector<std::string> result;
int argc = 0;
auto deleter = [](wchar_t **ptr) { LocalFree(ptr); };
// NOLINTBEGIN(*-avoid-c-arrays)
auto wargv = std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter);
// NOLINTEND(*-avoid-c-arrays)
if(wargv == nullptr) {
throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError()));
}
result.reserve(static_cast<size_t>(argc));
for(size_t i = 0; i < static_cast<size_t>(argc); ++i) {
result.push_back(narrow(wargv[i]));
}
return result;
}
#endif
} // namespace detail
/// 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);
}
} // namespace enums
/// Export to CLI namespace
using enums::operator<<;
namespace detail {
/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
/// produce overflow for some expected uses
constexpr int expected_max_vector_size{1 << 29};
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
/// Split a string by a delim
CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim);
/// 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);
auto loc = s.tellp();
while(beg != end) {
auto nloc = s.tellp();
if(nloc > loc) {
s << delim;
loc = nloc;
}
s << 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(std::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
CLI11_INLINE std::string &ltrim(std::string &str);
/// Trim anything from left of string
CLI11_INLINE std::string &ltrim(std::string &str, const std::string &filter);
/// Trim whitespace from right of string
CLI11_INLINE std::string &rtrim(std::string &str);
/// Trim anything from right of string
CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter);
/// 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);
}
/// remove quotes at the front and back of a string either '"' or '\''
CLI11_INLINE std::string &remove_quotes(std::string &str);
/// remove quotes from all elements of a string vector and process escaped components
CLI11_INLINE void remove_quotes(std::vector<std::string> &args);
/// 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.
CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input);
/// 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
CLI11_INLINE std::ostream &
format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid);
/// Print subcommand aliases
CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid);
/// Verify the first character of an option
/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
template <typename T> bool valid_first_char(T c) {
return ((c != '-') && (static_cast<unsigned char>(c) > 33)); // space and '!' not allowed
}
/// Verify following characters of an option
template <typename T> bool valid_later_char(T c) {
// = and : are value separators, { has special meaning for option defaults,
// and control codes other than tab would just be annoying to deal with in many places allowing space here has too
// much potential for inadvertent entry errors and bugs
return ((c != '=') && (c != ':') && (c != '{') && ((static_cast<unsigned char>(c) > 32) || c == '\t'));
}
/// Verify an option/subcommand name
CLI11_INLINE bool valid_name_string(const std::string &str);
/// Verify an app name
inline bool valid_alias_name_string(const std::string &str) {
static const std::string badChars(std::string("\n") + '\0');
return (str.find_first_of(badChars) == std::string::npos);
}
/// check if a string is a container segment separator (empty or "%%")
inline bool is_separator(const std::string &str) {
static const std::string sep("%%");
return (str.empty() || str == sep);
}
/// 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
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to);
/// 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);
}
CLI11_INLINE void remove_default_flag_values(std::string &flags);
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
CLI11_INLINE std::ptrdiff_t find_member(std::string name,
const std::vector<std::string> names,
bool ignore_case = false,
bool ignore_underscore = false);
/// 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) {
std::size_t start_pos = 0;
while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
start_pos = modify(str, start_pos);
}
return str;
}
/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences
/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings
CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char);
/// Split a string '"one two" "three"' into 'one two', 'three'
/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
/// get the value of an environmental variable or empty string if empty
CLI11_INLINE std::string get_environment_value(const std::string &env_name);
/// 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.
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
/// @brief detect if a string has escapable characters
/// @param str the string to do the detection on
/// @return true if the string has escapable characters
CLI11_INLINE bool has_escapable_character(const std::string &str);
/// @brief escape all escapable characters
/// @param str the string to escape
/// @return a string with the escapble characters escaped with '\'
CLI11_INLINE std::string add_escaped_characters(const std::string &str);
/// @brief replace the escaped characters with their equivalent
CLI11_INLINE std::string remove_escaped_characters(const std::string &str);
/// generate a string with all non printable characters escaped to hex codes
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape);
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string);
/// extract an escaped binary_string
CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string);
/// process a quoted string, remove the quotes and if appropriate handle escaped characters
CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\'');
} // namespace detail
namespace detail {
CLI11_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;
}
CLI11_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;
}
CLI11_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;
}
CLI11_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;
}
CLI11_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;
}
CLI11_INLINE std::string &remove_quotes(std::string &str) {
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) {
if(str.front() == str.back()) {
str.pop_back();
str.erase(str.begin(), str.begin() + 1);
}
}
return str;
}
CLI11_INLINE std::string &remove_outer(std::string &str, char key) {
if(str.length() > 1 && (str.front() == key)) {
if(str.front() == str.back()) {
str.pop_back();
str.erase(str.begin(), str.begin() + 1);
}
}
return str;
}
CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) {
std::string::size_type n = 0;
while(n != std::string::npos && n < input.size()) {
n = input.find('\n', n);
if(n != std::string::npos) {
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
n += leader.size();
}
}
return input;
}
CLI11_INLINE std::ostream &
format_help(std::ostream &out, std::string name, const std::string &description, std::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;
}
CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
if(!aliases.empty()) {
out << std::setw(static_cast<int>(wid)) << " aliases: ";
bool front = true;
for(const auto &alias : aliases) {
if(!front) {
out << ", ";
} else {
front = false;
}
out << detail::fix_newlines(" ", alias);
}
out << "\n";
}
return out;
}
CLI11_INLINE bool valid_name_string(const std::string &str) {
if(str.empty() || !valid_first_char(str[0])) {
return false;
}
auto e = str.end();
for(auto c = str.begin() + 1; c != e; ++c)
if(!valid_later_char(*c))
return false;
return true;
}
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) {
std::size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
return str;
}
CLI11_INLINE void remove_default_flag_values(std::string &flags) {
auto loc = flags.find_first_of('{', 2);
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());
}
CLI11_INLINE std::ptrdiff_t
find_member(std::string name, const std::vector<std::string> names, bool ignore_case, bool ignore_underscore) {
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);
}
static const std::string escapedChars("\b\t\n\f\r\"\\");
static const std::string escapedCharsCode("btnfr\"\\");
static const std::string bracketChars{"\"'`[(<{"};
static const std::string matchBracketChars("\"'`])>}");
CLI11_INLINE bool has_escapable_character(const std::string &str) {
return (str.find_first_of(escapedChars) != std::string::npos);
}
CLI11_INLINE std::string add_escaped_characters(const std::string &str) {
std::string out;
out.reserve(str.size() + 4);
for(char s : str) {
auto sloc = escapedChars.find_first_of(s);
if(sloc != std::string::npos) {
out.push_back('\\');
out.push_back(escapedCharsCode[sloc]);
} else {
out.push_back(s);
}
}
return out;
}
CLI11_INLINE std::uint32_t hexConvert(char hc) {
int hcode{0};
if(hc >= '0' && hc <= '9') {
hcode = (hc - '0');
} else if(hc >= 'A' && hc <= 'F') {
hcode = (hc - 'A' + 10);
} else if(hc >= 'a' && hc <= 'f') {
hcode = (hc - 'a' + 10);
} else {
hcode = -1;
}
return static_cast<uint32_t>(hcode);
}
CLI11_INLINE char make_char(std::uint32_t code) { return static_cast<char>(static_cast<unsigned char>(code)); }
CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) {
if(code < 0x80) { // ascii code equivalent
str.push_back(static_cast<char>(code));
} else if(code < 0x800) { // \u0080 to \u07FF
// 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111
str.push_back(make_char(0xC0 | code >> 6));
str.push_back(make_char(0x80 | (code & 0x3F)));
} else if(code < 0x10000) { // U+0800...U+FFFF
if(0xD800 <= code && code <= 0xDFFF) {
throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8.");
}
// 1110yyyy 10yxxxxx 10xxxxxx
str.push_back(make_char(0xE0 | code >> 12));
str.push_back(make_char(0x80 | (code >> 6 & 0x3F)));
str.push_back(make_char(0x80 | (code & 0x3F)));
} else if(code < 0x110000) { // U+010000 ... U+10FFFF
// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx
str.push_back(make_char(0xF0 | code >> 18));
str.push_back(make_char(0x80 | (code >> 12 & 0x3F)));
str.push_back(make_char(0x80 | (code >> 6 & 0x3F)));
str.push_back(make_char(0x80 | (code & 0x3F)));
}
}
CLI11_INLINE std::string remove_escaped_characters(const std::string &str) {
std::string out;
out.reserve(str.size());
for(auto loc = str.begin(); loc < str.end(); ++loc) {
if(*loc == '\\') {
if(str.end() - loc < 2) {
throw std::invalid_argument("invalid escape sequence " + str);
}
auto ecloc = escapedCharsCode.find_first_of(*(loc + 1));
if(ecloc != std::string::npos) {
out.push_back(escapedChars[ecloc]);
++loc;
} else if(*(loc + 1) == 'u') {
// must have 4 hex characters
if(str.end() - loc < 6) {
throw std::invalid_argument("unicode sequence must have 4 hex codes " + str);
}
std::uint32_t code{0};
std::uint32_t mplier{16 * 16 * 16};
for(int ii = 2; ii < 6; ++ii) {
std::uint32_t res = hexConvert(*(loc + ii));
if(res > 0x0F) {
throw std::invalid_argument("unicode sequence must have 4 hex codes " + str);
}
code += res * mplier;
mplier = mplier / 16;
}
append_codepoint(out, code);
loc += 5;
} else if(*(loc + 1) == 'U') {
// must have 8 hex characters
if(str.end() - loc < 10) {
throw std::invalid_argument("unicode sequence must have 8 hex codes " + str);
}
std::uint32_t code{0};
std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16};
for(int ii = 2; ii < 10; ++ii) {
std::uint32_t res = hexConvert(*(loc + ii));
if(res > 0x0F) {
throw std::invalid_argument("unicode sequence must have 8 hex codes " + str);
}
code += res * mplier;
mplier = mplier / 16;
}
append_codepoint(out, code);
loc += 9;
} else if(*(loc + 1) == '0') {
out.push_back('\0');
++loc;
} else {
throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str);
}
} else {
out.push_back(*loc);
}
}
return out;
}
CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) {
std::size_t loc{0};
for(loc = start + 1; loc < str.size(); ++loc) {
if(str[loc] == closure_char) {
break;
}
if(str[loc] == '\\') {
// skip the next character for escaped sequences
++loc;
}
}
return loc;
}
CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) {
auto loc = str.find_first_of(closure_char, start + 1);
return (loc != std::string::npos ? loc : str.size());
}
CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) {
auto bracket_loc = matchBracketChars.find(closure_char);
switch(bracket_loc) {
case 0:
return close_string_quote(str, start, closure_char);
case 1:
case 2:
case std::string::npos:
return close_literal_quote(str, start, closure_char);
default:
break;
}
std::string closures(1, closure_char);
auto loc = start + 1;
while(loc < str.size()) {
if(str[loc] == closures.back()) {
closures.pop_back();
if(closures.empty()) {
return loc;
}
}
bracket_loc = bracketChars.find(str[loc]);
if(bracket_loc != std::string::npos) {
switch(bracket_loc) {
case 0:
loc = close_string_quote(str, loc, str[loc]);
break;
case 1:
case 2:
loc = close_literal_quote(str, loc, str[loc]);
break;
default:
closures.push_back(matchBracketChars[bracket_loc]);
break;
}
}
++loc;
}
if(loc > str.size()) {
loc = str.size();
}
return loc;
}
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) {
auto find_ws = [delimiter](char ch) {
return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
};
trim(str);
std::vector<std::string> output;
while(!str.empty()) {
if(bracketChars.find_first_of(str[0]) != std::string::npos) {
auto bracketLoc = bracketChars.find_first_of(str[0]);
auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]);
if(end >= str.size()) {
output.push_back(std::move(str));
str.clear();
} else {
output.push_back(str.substr(0, end + 1));
if(end + 2 < str.size()) {
str = str.substr(end + 2);
} else {
str.clear();
}
}
} 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 + 1, str.end());
} else {
output.push_back(str);
str.clear();
}
}
trim(str);
}
return output;
}
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
auto next = str[offset + 1];
if((next == '\"') || (next == '\'') || (next == '`')) {
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
if(astart != std::string::npos) {
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
str[offset] = ' '; // interpret this as a space so the split_up works properly
}
}
return offset + 1;
}
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) {
// s is our escaped output string
std::string escaped_string{};
// loop through all characters
for(char c : string_to_escape) {
// check if a given character is printable
// the cast is necessary to avoid undefined behaviour
if(isprint(static_cast<unsigned char>(c)) == 0) {
std::stringstream stream;
// if the character is not printable
// we'll convert it to a hex string using a stringstream
// note that since char is signed we have to cast it to unsigned first
stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c));
std::string code = stream.str();
escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code;
} else {
escaped_string.push_back(c);
}
}
if(escaped_string != string_to_escape) {
auto sqLoc = escaped_string.find('\'');
while(sqLoc != std::string::npos) {
escaped_string.replace(sqLoc, sqLoc + 1, "\\x27");
sqLoc = escaped_string.find('\'');
}
escaped_string.insert(0, "'B\"(");
escaped_string.push_back(')');
escaped_string.push_back('"');
escaped_string.push_back('\'');
}
return escaped_string;
}
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) {
size_t ssize = escaped_string.size();
if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
return true;
}
return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0);
}
CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) {
std::size_t start{0};
std::size_t tail{0};
size_t ssize = escaped_string.size();
if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
start = 3;
tail = 2;
} else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) {
start = 4;
tail = 3;
}
if(start == 0) {
return escaped_string;
}
std::string outstring;
outstring.reserve(ssize - start - tail);
std::size_t loc = start;
while(loc < ssize - tail) {
// ssize-2 to skip )" at the end
if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) {
auto c1 = escaped_string[loc + 2];
auto c2 = escaped_string[loc + 3];
std::uint32_t res1 = hexConvert(c1);
std::uint32_t res2 = hexConvert(c2);
if(res1 <= 0x0F && res2 <= 0x0F) {
loc += 4;
outstring.push_back(static_cast<char>(res1 * 16 + res2));
continue;
}
}
outstring.push_back(escaped_string[loc]);
++loc;
}
return outstring;
}
CLI11_INLINE void remove_quotes(std::vector<std::string> &args) {
for(auto &arg : args) {
if(arg.front() == '\"' && arg.back() == '\"') {
remove_quotes(arg);
// only remove escaped for string arguments not literal strings
arg = remove_escaped_characters(arg);
} else {
remove_quotes(arg);
}
}
}
CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) {
if(str.size() <= 1) {
return false;
}
if(detail::is_binary_escaped_string(str)) {
str = detail::extract_binary_string(str);
return true;
}
if(str.front() == string_char && str.back() == string_char) {
detail::remove_outer(str, string_char);
if(str.find_first_of('\\') != std::string::npos) {
str = detail::remove_escaped_characters(str);
}
return true;
}
if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) {
detail::remove_outer(str, str.front());
return true;
}
return false;
}
std::string get_environment_value(const std::string &env_name) {
char *buffer = nullptr;
std::string ename_string;
#ifdef _MSC_VER
// Windows version
std::size_t sz = 0;
if(_dupenv_s(&buffer, &sz, env_name.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(env_name.c_str());
if(buffer != nullptr) {
ename_string = std::string(buffer);
}
#endif
return ename_string;
}
} // namespace detail
// 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:
CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; }
CLI11_NODISCARD 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 MissingDash(std::string name) {
return BadNameString("Long names strings require 2 dashes " + name);
}
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
static BadNameString BadPositionalName(std::string name) {
return BadNameString("Invalid positional 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 {name + " requires " + other, ExitCodes::OptionAlreadyAdded};
}
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
return {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 Success {
CLI11_ERROR_DEF(Success, 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 Success {
CLI11_ERROR_DEF(Success, CallForAllHelp)
CallForAllHelp()
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// -v or --version on command line
class CallForVersion : public Success {
CLI11_ERROR_DEF(Success, CallForVersion)
CallForVersion()
: CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {}
};
/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return 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(std::size_t min_subcom) {
if(min_subcom == 1) {
return RequiredError("A subcommand");
}
return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError};
}
static RequiredError
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
if((min_option == 1) && (max_option == 1) && (used == 0))
return RequiredError("Exactly 1 option from [" + option_list + "]");
if((min_option == 1) && (max_option == 1) && (used > 1)) {
return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) +
" were given",
ExitCodes::RequiredError};
}
if((min_option == 1) && (used == 0))
return RequiredError("At least 1 option from [" + option_list + "]");
if(used < min_option) {
return {"Requires at least " + std::to_string(min_option) + " options used but only " +
std::to_string(used) + " were given from [" + option_list + "]",
ExitCodes::RequiredError};
}
if(max_option == 1)
return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError};
return {"Requires at most " + std::to_string(max_option) + " options be used but " + 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, std::size_t received)
: ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
", got " + std::to_string(received))
: ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
", got " + std::to_string(received)),
ExitCodes::ArgumentMismatch) {}
static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
std::to_string(received));
}
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
}
static ArgumentMismatch FlagOverride(std::string name) {
return ArgumentMismatch(name + " was given a disallowed flag override");
}
static ArgumentMismatch PartialType(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) +
" required for each element");
}
};
/// 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) {}
ExtrasError(const std::string &name, std::vector<std::string> args)
: ExtrasError(name,
(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
/// @}
// 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/main/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 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 adl_detail {
/// Check for existence of user-supplied lexical_cast.
///
/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail.
/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this
/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem).
template <typename T, typename S = std::string> class is_lexical_castable {
template <typename TT, typename SS>
static auto test(int) -> decltype(lexical_cast(std::declval<const SS &>(), std::declval<TT &>()), std::true_type());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<T, S>(0))::value;
};
} // namespace adl_detail
namespace detail {
// These are utilities for IsMember and other transforming objects
/// 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.
/// not a pointer
template <typename T, typename Enable = void> struct element_type {
using type = T;
};
template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> {
using type = typename std::pointer_traits<T>::element_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));
}
};
// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in
// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a
// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
// suppressed
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
#endif
// check for constructibility from a specific type and copy assignable used in the parse detection
template <typename T, typename C> class is_direct_constructible {
template <typename TT, typename CC>
static auto test(int, std::true_type) -> decltype(
// NVCC warns about narrowing conversions here
#ifdef __CUDACC__
#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
#pragma nv_diag_suppress 2361
#else
#pragma diag_suppress 2361
#endif
#endif
TT{std::declval<CC>()}
#ifdef __CUDACC__
#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
#pragma nv_diag_default 2361
#else
#pragma diag_default 2361
#endif
#endif
,
std::is_move_assignable<TT>());
template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
template <typename, typename> static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
};
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
// Check for output streamability
// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
template <typename T, typename S = std::ostringstream> class is_ostreamable {
template <typename TT, typename SS>
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 constexpr bool value = decltype(test<T, S>(0))::value;
};
/// Check for input streamability
template <typename T, typename S = std::istringstream> class is_istreamable {
template <typename TT, typename SS>
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 constexpr bool value = decltype(test<T, S>(0))::value;
};
/// Check for complex
template <typename T> class is_complex {
template <typename TT>
static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type());
template <typename> static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
/// Templated operation to get a value from a stream
template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
bool from_stream(const std::string &istring, T &obj) {
std::istringstream is;
is.str(istring);
is >> obj;
return !is.fail() && !is.rdbuf()->in_avail();
}
template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
return false;
}
// check to see if an object is a mutable container (fail by default)
template <typename T, typename _ = void> struct is_mutable_container : std::false_type {};
/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and
/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed
/// from a std::string
template <typename T>
struct is_mutable_container<
T,
conditional_t<false,
void_t<typename T::value_type,
decltype(std::declval<T>().end()),
decltype(std::declval<T>().clear()),
decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
std::declval<const typename T::value_type &>()))>,
void>> : public conditional_t<std::is_constructible<T, std::string>::value ||
std::is_constructible<T, std::wstring>::value,
std::false_type,
std::true_type> {};
// check to see if an object is a mutable container (fail by default)
template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end
/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from
/// a std::string
template <typename T>
struct is_readable_container<
T,
conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>>
: public std::true_type {};
// check to see if an object is a wrapper (fail by default)
template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
// check if an object is a wrapper (it has a value_type defined)
template <typename T>
struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
// Check for tuple like types, as in classes with a tuple_size type trait
template <typename S> class is_tuple_like {
template <typename SS>
// static auto test(int)
// -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{});
template <typename> static auto test(...) -> std::false_type;
public:
static constexpr bool value = decltype(test<S>(0))::value;
};
/// Convert an object to a string (directly forward if this can become a string)
template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
return std::forward<T>(value);
}
/// Construct a string from the object
template <typename T,
enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
detail::enabler> = detail::dummy>
std::string to_string(const T &value) {
return std::string(value); // NOLINT(google-readability-casting)
}
/// Convert an object to a string (streaming must be supported for that type)
template <typename T,
enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
is_ostreamable<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_ostreamable<T>::value &&
!is_readable_container<typename std::remove_const<T>::type>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&) {
return {};
}
/// convert a readable container to a string
template <typename T,
enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
is_readable_container<T>::value,
detail::enabler> = detail::dummy>
std::string to_string(T &&variable) {
auto cval = variable.begin();
auto end = variable.end();
if(cval == end) {
return {"{}"};
}
std::vector<std::string> defaults;
while(cval != end) {
defaults.emplace_back(CLI::detail::to_string(*cval));
++cval;
}
return {"[" + detail::join(defaults) + "]"};
}
/// special template overload
template <typename T1,
typename T2,
typename T,
enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
return to_string(std::forward<T>(value));
}
/// special template overload
template <typename T1,
typename T2,
typename T,
enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
std::string checked_to_string(T &&) {
return std::string{};
}
/// get a string as a convertible value for arithmetic types
template <typename T, enable_if_t<std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
std::string value_string(const T &value) {
return std::to_string(value);
}
/// get a string as a convertible value for enumerations
template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
std::string value_string(const T &value) {
return std::to_string(static_cast<typename std::underlying_type<T>::type>(value));
}
/// for other types just use the regular to_string function
template <typename T,
enable_if_t<!std::is_enum<T>::value && !std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
auto value_string(const T &value) -> decltype(to_string(value)) {
return to_string(value);
}
/// template to get the underlying value type if it exists or use a default
template <typename T, typename def, typename Enable = void> struct wrapped_type {
using type = def;
};
/// Type size for regular object types that do not look like a tuple
template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
using type = typename T::value_type;
};
/// This will only trigger for actual void type
template <typename T, typename Enable = void> struct type_count_base {
static const int value{0};
};
/// Type size for regular object types that do not look like a tuple
template <typename T>
struct type_count_base<T,
typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
!std::is_void<T>::value>::type> {
static constexpr int value{1};
};
/// the base tuple size
template <typename T>
struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
static constexpr int value{std::tuple_size<T>::value};
};
/// Type count base for containers is the type_count_base of the individual element
template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
static constexpr int value{type_count_base<typename T::value_type>::value};
};
/// Set of overloads to get the type size of an object
/// forward declare the subtype_count structure
template <typename T> struct subtype_count;
/// forward declare the subtype_count_min structure
template <typename T> struct subtype_count_min;
/// This will only trigger for actual void type
template <typename T, typename Enable = void> struct type_count {
static const int value{0};
};
/// Type size for regular object types that do not look like a tuple
template <typename T>
struct type_count<T,
typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value &&
!std::is_void<T>::value>::type> {
static constexpr int value{1};
};
/// Type size for complex since it sometimes looks like a wrapper
template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> {
static constexpr int value{2};
};
/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
static constexpr int value{subtype_count<typename T::value_type>::value};
};
/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes)
template <typename T>
struct type_count<T,
typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value &&
!is_mutable_container<T>::value>::type> {
static constexpr int value{type_count<typename T::value_type>::value};
};
/// 0 if the index > tuple size
template <typename T, std::size_t I>
constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() {
return 0;
}
/// Recursively generate the tuple type name
template <typename T, std::size_t I>
constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() {
return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>();
}
/// Get the type size of the sum of type sizes for all the individual tuple types
template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
static constexpr int value{tuple_type_size<T, 0>()};
};
/// definition of subtype count
template <typename T> struct subtype_count {
static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value};
};
/// This will only trigger for actual void type
template <typename T, typename Enable = void> struct type_count_min {
static const int value{0};
};
/// Type size for regular object types that do not look like a tuple
template <typename T>
struct type_count_min<
T,
typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value &&
!is_complex<T>::value && !std::is_void<T>::value>::type> {
static constexpr int value{type_count<T>::value};
};
/// Type size for complex since it sometimes looks like a wrapper
template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> {
static constexpr int value{1};
};
/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
template <typename T>
struct type_count_min<
T,
typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> {
static constexpr int value{subtype_count_min<typename T::value_type>::value};
};
/// 0 if the index > tuple size
template <typename T, std::size_t I>
constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() {
return 0;
}
/// Recursively generate the tuple type name
template <typename T, std::size_t I>
constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() {
return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>();
}
/// Get the type size of the sum of type sizes for all the individual tuple types
template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
static constexpr int value{tuple_type_size_min<T, 0>()};
};
/// definition of subtype count
template <typename T> struct subtype_count_min {
static constexpr int value{is_mutable_container<T>::value
? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0)
: type_count_min<T>::value};
};
/// This will only trigger for actual void type
template <typename T, typename Enable = void> struct expected_count {
static const int value{0};
};
/// For most types the number of expected items is 1
template <typename T>
struct expected_count<T,
typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value &&
!std::is_void<T>::value>::type> {
static constexpr int value{1};
};
/// number of expected items in a vector
template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
static constexpr int value{expected_max_vector_size};
};
/// number of expected items in a vector
template <typename T>
struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> {
static constexpr int value{expected_count<typename T::value_type>::value};
};
// Enumeration of the different supported categorizations of objects
enum class object_category : int {
char_value = 1,
integral_value = 2,
unsigned_integral = 4,
enumeration = 6,
boolean_value = 8,
floating_point = 10,
number_constructible = 12,
double_constructible = 14,
integer_constructible = 16,
// string like types
string_assignable = 23,
string_constructible = 24,
wstring_assignable = 25,
wstring_constructible = 26,
other = 45,
// special wrapper or container types
wrapper_value = 50,
complex_number = 60,
tuple_value = 70,
container_value = 80,
};
/// Set of overloads to classify an object according to type
/// some type that is not otherwise recognized
template <typename T, typename Enable = void> struct classify_object {
static constexpr object_category value{object_category::other};
};
/// Signed integers
template <typename T>
struct classify_object<
T,
typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && std::is_signed<T>::value &&
!is_bool<T>::value && !std::is_enum<T>::value>::type> {
static constexpr object_category value{object_category::integral_value};
};
/// Unsigned integers
template <typename T>
struct classify_object<T,
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value &&
!std::is_same<T, char>::value && !is_bool<T>::value>::type> {
static constexpr object_category value{object_category::unsigned_integral};
};
/// single character values
template <typename T>
struct classify_object<T, typename std::enable_if<std::is_same<T, char>::value && !std::is_enum<T>::value>::type> {
static constexpr object_category value{object_category::char_value};
};
/// Boolean values
template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> {
static constexpr object_category value{object_category::boolean_value};
};
/// Floats
template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
static constexpr object_category value{object_category::floating_point};
};
#if defined _MSC_VER
// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of
// utf-8 encoding
#define WIDE_STRING_CHECK \
!std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value
#define STRING_CHECK true
#else
#define WIDE_STRING_CHECK true
#define STRING_CHECK !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value
#endif
/// String and similar direct assignment
template <typename T>
struct classify_object<
T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && WIDE_STRING_CHECK &&
std::is_assignable<T &, std::string>::value>::type> {
static constexpr object_category value{object_category::string_assignable};
};
/// String and similar constructible and copy assignment
template <typename T>
struct classify_object<
T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) &&
WIDE_STRING_CHECK && std::is_constructible<T, std::string>::value>::type> {
static constexpr object_category value{object_category::string_constructible};
};
/// Wide strings
template <typename T>
struct classify_object<T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
STRING_CHECK && std::is_assignable<T &, std::wstring>::value>::type> {
static constexpr object_category value{object_category::wstring_assignable};
};
template <typename T>
struct classify_object<
T,
typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) &&
STRING_CHECK && std::is_constructible<T, std::wstring>::value>::type> {
static constexpr object_category value{object_category::wstring_constructible};
};
/// Enumerations
template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> {
static constexpr object_category value{object_category::enumeration};
};
template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> {
static constexpr object_category value{object_category::complex_number};
};
/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
/// vectors, and enumerations
template <typename T> struct uncommon_type {
using type = typename std::conditional<
!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
!std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value &&
!std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value &&
!is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value,
std::true_type,
std::false_type>::type;
static constexpr bool value = type::value;
};
/// wrapper type
template <typename T>
struct classify_object<T,
typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value &&
!is_tuple_like<T>::value && uncommon_type<T>::value)>::type> {
static constexpr object_category value{object_category::wrapper_value};
};
/// Assignable from double or int
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
!is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value>::type> {
static constexpr object_category value{object_category::number_constructible};
};
/// Assignable from int
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
!is_wrapper<T>::value && !is_direct_constructible<T, double>::value &&
is_direct_constructible<T, int>::value>::type> {
static constexpr object_category value{object_category::integer_constructible};
};
/// Assignable from double
template <typename T>
struct classify_object<T,
typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
!is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
!is_direct_constructible<T, int>::value>::type> {
static constexpr object_category value{object_category::double_constructible};
};
/// Tuple type
template <typename T>
struct classify_object<
T,
typename std::enable_if<is_tuple_like<T>::value &&
((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
(uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
!is_direct_constructible<T, int>::value) ||
(uncommon_type<T>::value && type_count<T>::value >= 2))>::type> {
static constexpr object_category value{object_category::tuple_value};
// the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
// constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
// could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2
// mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out
// those cases that are caught by other object classifications
};
/// container type
template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
static constexpr object_category value{object_category::container_value};
};
// 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<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "CHAR";
}
template <typename T,
enable_if_t<classify_object<T>::value == object_category::integral_value ||
classify_object<T>::value == object_category::integer_constructible,
detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "INT";
}
template <typename T,
enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy>
constexpr const char *type_name() {
return "UINT";
}
template <typename T,
enable_if_t<classify_object<T>::value == object_category::floating_point ||
classify_object<T>::value == object_category::number_constructible ||
classify_object<T>::value == object_category::double_constructible,
detail::enabler> = detail::dummy>