10999 lines
411 KiB
C++
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 <rim(std::string &str);
|
|
|
|
/// Trim anything from left of string
|
|
CLI11_INLINE std::string <rim(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 <rim(std::string &str) {
|
|
auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
|
|
str.erase(str.begin(), it);
|
|
return str;
|
|
}
|
|
|
|
CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) {
|
|
auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
|
|
str.erase(str.begin(), it);
|
|
return str;
|
|
}
|
|
|
|
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>
|
|