summaryrefslogtreecommitdiff
path: root/thirdparty
diff options
context:
space:
mode:
authorDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2024-04-01 16:11:30 +0300
committerDirk-Jan C. Binnema <djcb@djcbsoftware.nl>2024-04-01 16:11:30 +0300
commit8e981e3f469e5d903f0212a95a55cbe559e60382 (patch)
tree13e3f5bce196a46db83a2d9b0530c8c6175ca318 /thirdparty
parentf31684bfa68c80ea03040c704667a2465283743c (diff)
thirdparty: use CLI11 v2.4.1
Diffstat (limited to 'thirdparty')
-rw-r--r--thirdparty/CLI11.hpp1942
1 files changed, 1611 insertions, 331 deletions
diff --git a/thirdparty/CLI11.hpp b/thirdparty/CLI11.hpp
index 3913fa9..41027f0 100644
--- a/thirdparty/CLI11.hpp
+++ b/thirdparty/CLI11.hpp
@@ -1,11 +1,11 @@
-// CLI11: Version 2.3.2
+// CLI11: Version 2.4.1
// 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.3.2
+// from: v2.4.1
//
-// CLI11 2.3.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry
+// CLI11 2.4.1 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
@@ -34,34 +34,40 @@
#pragma once
// Standard combined includes:
-#include <iomanip>
-#include <set>
-#include <memory>
-#include <vector>
-#include <utility>
-#include <stdexcept>
-#include <locale>
+#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 <exception>
+#include <limits>
+#include <locale>
+#include <map>
+#include <memory>
#include <numeric>
-#include <fstream>
+#include <set>
+#include <sstream>
+#include <stdexcept>
#include <string>
-#include <type_traits>
#include <tuple>
-#include <map>
-#include <algorithm>
-#include <cstdint>
-#include <sstream>
-#include <cmath>
-#include <limits>
+#include <type_traits>
+#include <utility>
+#include <vector>
#define CLI11_VERSION_MAJOR 2
-#define CLI11_VERSION_MINOR 3
-#define CLI11_VERSION_PATCH 2
-#define CLI11_VERSION "2.3.2"
+#define CLI11_VERSION_MINOR 4
+#define CLI11_VERSION_PATCH 1
+#define CLI11_VERSION "2.4.1"
@@ -124,17 +130,7 @@
#endif
#endif
-/** Inline macro **/
-#ifdef CLI11_COMPILE
-#define CLI11_INLINE
-#else
-#define CLI11_INLINE inline
-#endif
-
-
-
-// C standard library
-// Only needed for existence checking
+/** <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
@@ -161,6 +157,44 @@
#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
@@ -170,9 +204,244 @@
+
+#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 {
@@ -270,6 +539,9 @@ inline std::string trim_copy(const std::string &str) {
/// 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
///
@@ -290,14 +562,16 @@ CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<s
/// 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 != '-') && (c != '!') && (c != ' ') && c != '\n'); }
+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 \n 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 != '{') && (c != ' ') && c != '\n');
+ // 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
@@ -360,18 +634,46 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
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 "
+/// 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);
-/// Add quotes if the string contains spaces
-CLI11_INLINE std::string &add_quotes_if_needed(std::string &str);
+/// @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
@@ -421,7 +723,17 @@ CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) {
}
CLI11_INLINE std::string &remove_quotes(std::string &str) {
- if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
+ 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);
@@ -541,37 +853,220 @@ find_member(std::string name, const std::vector<std::string> names, bool ignore_
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) {
- const std::string delims("\'\"`");
auto find_ws = [delimiter](char ch) {
return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
};
trim(str);
std::vector<std::string> output;
- bool embeddedQuote = false;
- char keyChar = ' ';
while(!str.empty()) {
- if(delims.find_first_of(str[0]) != std::string::npos) {
- keyChar = str[0];
- auto end = str.find_first_of(keyChar, 1);
- while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
- end = str.find_first_of(keyChar, end + 1);
- embeddedQuote = true;
- }
- if(end != std::string::npos) {
- output.push_back(str.substr(1, end - 1));
+ 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 {
- output.push_back(str.substr(1));
- str = "";
}
+
} else {
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
if(it != std::end(str)) {
@@ -580,14 +1075,9 @@ CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter)
str = std::string(it + 1, str.end());
} else {
output.push_back(str);
- str = "";
+ str.clear();
}
}
- // transform any embedded quotes into the regular character
- if(embeddedQuote) {
- output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
- embeddedQuote = false;
- }
trim(str);
}
return output;
@@ -605,15 +1095,140 @@ CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
return offset + 1;
}
-CLI11_INLINE std::string &add_quotes_if_needed(std::string &str) {
- if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
- char quote = str.find('"') < str.find('\'') ? '\'' : '"';
- if(str.find(' ') != std::string::npos) {
- str.insert(0, 1, quote);
- str.append(1, quote);
+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);
}
}
- return str;
+ 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
@@ -722,7 +1337,13 @@ 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);
}
@@ -1087,12 +1708,20 @@ template <typename T, typename C> class is_direct_constructible {
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>());
@@ -1171,8 +1800,10 @@ struct is_mutable_container<
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::false_type, std::true_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 {};
@@ -1475,6 +2106,8 @@ enum class object_category : int {
// 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,
@@ -1523,12 +2156,23 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_bool<
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 &&
- std::is_assignable<T &, std::string>::value>::type> {
+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};
};
@@ -1538,10 +2182,27 @@ 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) &&
- std::is_constructible<T, std::string>::value>::type> {
+ 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};
@@ -1554,12 +2215,13 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_compl
/// 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 && !is_complex<T>::value &&
- !is_mutable_container<T>::value && !std::is_enum<T>::value,
- std::true_type,
- std::false_type>::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;
};
@@ -1748,7 +2410,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
if(input.empty() || input.front() == '-') {
return false;
}
- char *val = nullptr;
+ char *val{nullptr};
errno = 0;
std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
if(errno == ERANGE) {
@@ -1764,6 +2426,33 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
return (static_cast<std::int64_t>(output) == output_sll);
}
+ // remove separators
+ if(input.find_first_of("_'") != std::string::npos) {
+ std::string nstring = input;
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
+ return integral_conversion(nstring, output);
+ }
+ if(input.compare(0, 2, "0o") == 0) {
+ val = nullptr;
+ errno = 0;
+ output_ll = std::strtoull(input.c_str() + 2, &val, 8);
+ if(errno == ERANGE) {
+ return false;
+ }
+ output = static_cast<T>(output_ll);
+ return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll);
+ }
+ if(input.compare(0, 2, "0b") == 0) {
+ val = nullptr;
+ errno = 0;
+ output_ll = std::strtoull(input.c_str() + 2, &val, 2);
+ if(errno == ERANGE) {
+ return false;
+ }
+ output = static_cast<T>(output_ll);
+ return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll);
+ }
return false;
}
@@ -1788,11 +2477,38 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
output = static_cast<T>(1);
return true;
}
+ // remove separators
+ if(input.find_first_of("_'") != std::string::npos) {
+ std::string nstring = input;
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
+ return integral_conversion(nstring, output);
+ }
+ if(input.compare(0, 2, "0o") == 0) {
+ val = nullptr;
+ errno = 0;
+ output_ll = std::strtoll(input.c_str() + 2, &val, 8);
+ if(errno == ERANGE) {
+ return false;
+ }
+ output = static_cast<T>(output_ll);
+ return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll);
+ }
+ if(input.compare(0, 2, "0b") == 0) {
+ val = nullptr;
+ errno = 0;
+ output_ll = std::strtoll(input.c_str() + 2, &val, 2);
+ if(errno == ERANGE) {
+ return false;
+ }
+ output = static_cast<T>(output_ll);
+ return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll);
+ }
return false;
}
-/// Convert a flag into an integer value typically binary flags
-inline std::int64_t to_flag_value(std::string val) {
+/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed
+inline std::int64_t to_flag_value(std::string val) noexcept {
static const std::string trueString("true");
static const std::string falseString("false");
if(val == trueString) {
@@ -1820,7 +2536,8 @@ inline std::int64_t to_flag_value(std::string val) {
ret = 1;
break;
default:
- throw std::invalid_argument("unrecognized character");
+ errno = EINVAL;
+ return -1;
}
return ret;
}
@@ -1829,7 +2546,11 @@ inline std::int64_t to_flag_value(std::string val) {
} else if(val == falseString || val == "off" || val == "no" || val == "disable") {
ret = -1;
} else {
- ret = std::stoll(val);
+ char *loc_ptr{nullptr};
+ ret = std::strtoll(val.c_str(), &loc_ptr, 0);
+ if(loc_ptr != (val.c_str() + val.size()) && errno == 0) {
+ errno = EINVAL;
+ }
}
return ret;
}
@@ -1858,18 +2579,16 @@ bool lexical_cast(const std::string &input, T &output) {
template <typename T,
enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
bool lexical_cast(const std::string &input, T &output) {
- try {
- auto out = to_flag_value(input);
+ errno = 0;
+ auto out = to_flag_value(input);
+ if(errno == 0) {
output = (out > 0);
- return true;
- } catch(const std::invalid_argument &) {
- return false;
- } catch(const std::out_of_range &) {
- // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still
- // valid all we care about the sign
+ } else if(errno == ERANGE) {
output = (input[0] != '-');
- return true;
+ } else {
+ return false;
}
+ return true;
}
/// Floats
@@ -1882,7 +2601,17 @@ bool lexical_cast(const std::string &input, T &output) {
char *val = nullptr;
auto output_ld = std::strtold(input.c_str(), &val);
output = static_cast<T>(output_ld);
- return val == (input.c_str() + input.size());
+ if(val == (input.c_str() + input.size())) {
+ return true;
+ }
+ // remove separators
+ if(input.find_first_of("_'") != std::string::npos) {
+ std::string nstring = input;
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
+ nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
+ return lexical_cast(nstring, output);
+ }
+ return false;
}
/// complex
@@ -1934,6 +2663,23 @@ bool lexical_cast(const std::string &input, T &output) {
return true;
}
+/// Wide strings
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::wstring_assignable, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ output = widen(input);
+ return true;
+}
+
+template <
+ typename T,
+ enable_if_t<classify_object<T>::value == object_category::wstring_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+ output = T{widen(input)};
+ return true;
+}
+
/// Enumerations
template <typename T,
enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
@@ -2062,7 +2808,9 @@ template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
(classify_object<AssignTo>::value == object_category::string_assignable ||
- classify_object<AssignTo>::value == object_category::string_constructible),
+ classify_object<AssignTo>::value == object_category::string_constructible ||
+ classify_object<AssignTo>::value == object_category::wstring_assignable ||
+ classify_object<AssignTo>::value == object_category::wstring_constructible),
detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) {
return lexical_cast(input, output);
@@ -2073,7 +2821,9 @@ template <typename AssignTo,
typename ConvertTo,
enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
classify_object<AssignTo>::value != object_category::string_assignable &&
- classify_object<AssignTo>::value != object_category::string_constructible,
+ classify_object<AssignTo>::value != object_category::string_constructible &&
+ classify_object<AssignTo>::value != object_category::wstring_assignable &&
+ classify_object<AssignTo>::value != object_category::wstring_constructible,
detail::enabler> = detail::dummy>
bool lexical_assign(const std::string &input, AssignTo &output) {
if(input.empty()) {
@@ -2112,9 +2862,17 @@ bool lexical_assign(const std::string &input, AssignTo &output) {
output = 0;
return true;
}
- int val = 0;
+ int val{0};
if(lexical_cast(input, val)) {
+#if defined(__clang__)
+/* on some older clang compilers */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#endif
output = val;
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
return true;
}
return false;
@@ -2169,12 +2927,12 @@ template <typename AssignTo,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
// the remove const is to handle pair types coming from a container
- typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1;
- typename std::tuple_element<1, ConvertTo>::type v2;
- bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
- if(strings.size() > 1) {
- retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
- }
+ using FirstType = typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type;
+ using SecondType = typename std::tuple_element<1, ConvertTo>::type;
+ FirstType v1;
+ SecondType v2;
+ bool retval = lexical_assign<FirstType, FirstType>(strings[0], v1);
+ retval = retval && lexical_assign<SecondType, SecondType>((strings.size() > 1) ? strings[1] : std::string{}, v2);
if(retval) {
output = AssignTo{v1, v2};
}
@@ -2189,6 +2947,9 @@ template <class AssignTo,
detail::enabler> = detail::dummy>
bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
output.erase(output.begin(), output.end());
+ if(strings.empty()) {
+ return true;
+ }
if(strings.size() == 1 && strings[0] == "{}") {
return true;
}
@@ -2333,7 +3094,7 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
std::size_t index{subtype_count_min<ConvertTo>::value};
const std::size_t mx_count{subtype_count<ConvertTo>::value};
- const std::size_t mx{(std::max)(mx_count, strings.size())};
+ const std::size_t mx{(std::min)(mx_count, strings.size() - 1)};
while(index < mx) {
if(is_separator(strings[index])) {
@@ -2343,7 +3104,11 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
}
bool retval = lexical_conversion<AssignTo, ConvertTo>(
std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output);
- strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
+ if(strings.size() > index) {
+ strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
+ } else {
+ strings.clear();
+ }
return retval;
}
@@ -2487,12 +3252,13 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
double tv{0.0};
auto comp = lexical_cast(arg, tv);
if(!comp) {
- try {
- tv = static_cast<double>(detail::to_flag_value(arg));
- } catch(const std::exception &) {
- fail = true;
+ errno = 0;
+ auto fv = detail::to_flag_value(arg);
+ fail = (errno != 0);
+ if(fail) {
break;
}
+ tv = static_cast<double>(fv);
}
val += tv;
}
@@ -2501,13 +3267,10 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
output.append(arg);
}
} else {
- if(val <= static_cast<double>((std::numeric_limits<std::int64_t>::min)()) ||
- val >= static_cast<double>((std::numeric_limits<std::int64_t>::max)()) ||
- std::ceil(val) == std::floor(val)) {
- output = detail::value_string(static_cast<int64_t>(val));
- } else {
- output = detail::value_string(val);
- }
+ std::ostringstream out;
+ out.precision(16);
+ out << val;
+ output = out.str();
}
return output;
}
@@ -2553,7 +3316,7 @@ CLI11_INLINE bool split_short(const std::string &current, std::string &name, std
}
CLI11_INLINE bool split_long(const std::string &current, std::string &name, std::string &value) {
- if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
+ if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) {
auto loc = current.find_first_of('=');
if(loc != std::string::npos) {
name = current.substr(2, loc - 2);
@@ -2625,7 +3388,6 @@ get_names(const std::vector<std::string> &input) {
std::vector<std::string> short_names;
std::vector<std::string> long_names;
std::string pos_name;
-
for(std::string name : input) {
if(name.length() == 0) {
continue;
@@ -2633,6 +3395,8 @@ get_names(const std::vector<std::string> &input) {
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
if(name.length() == 2 && valid_first_char(name[1]))
short_names.emplace_back(1, name[1]);
+ else if(name.length() > 2)
+ throw BadNameString::MissingDash(name);
else
throw BadNameString::OneCharName(name);
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
@@ -2644,12 +3408,15 @@ get_names(const std::vector<std::string> &input) {
} else if(name == "-" || name == "--") {
throw BadNameString::DashesOnly(name);
} else {
- if(pos_name.length() > 0)
+ if(!pos_name.empty())
throw BadNameString::MultiPositionalNames(name);
- pos_name = name;
+ if(valid_name_string(name)) {
+ pos_name = name;
+ } else {
+ throw BadNameString::BadPositionalName(name);
+ }
}
}
-
return std::make_tuple(short_names, long_names, pos_name);
}
@@ -2666,7 +3433,6 @@ struct ConfigItem {
/// This is the name
std::string name{};
-
/// Listing of inputs
std::vector<std::string> inputs{};
@@ -2729,8 +3495,8 @@ class ConfigBase : public Config {
char valueDelimiter = '=';
/// the character to use around strings
char stringQuote = '"';
- /// the character to use around single characters
- char characterQuote = '\'';
+ /// the character to use around single characters and literal strings
+ char literalQuote = '\'';
/// the maximum number of layers to allow
uint8_t maximumLayers{255};
/// the separator used to separator parent layers
@@ -2766,10 +3532,10 @@ class ConfigBase : public Config {
valueDelimiter = vSep;
return this;
}
- /// Specify the quote characters used around strings and characters
- ConfigBase *quoteCharacter(char qString, char qChar) {
+ /// Specify the quote characters used around strings and literal strings
+ ConfigBase *quoteCharacter(char qString, char literalChar) {
stringQuote = qString;
- characterQuote = qChar;
+ literalQuote = literalChar;
return this;
}
/// Specify the maximum number of parents
@@ -3001,6 +3767,11 @@ class IPV4Validator : public Validator {
IPV4Validator();
};
+class EscapedStringTransformer : public Validator {
+ public:
+ EscapedStringTransformer();
+};
+
} // namespace detail
// Static is not needed here, because global const implies static.
@@ -3020,6 +3791,9 @@ const detail::NonexistentPathValidator NonexistentPath;
/// Check for an IP4 address
const detail::IPV4Validator ValidIPV4;
+/// convert escaped characters into their associated values
+const detail::EscapedStringTransformer EscapedString;
+
/// Validate the input as a particular type
template <typename DesiredType> class TypeValidator : public Validator {
public:
@@ -3772,14 +4546,14 @@ namespace detail {
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE path_type check_path(const char *file) noexcept {
std::error_code ec;
- auto stat = std::filesystem::status(file, ec);
+ auto stat = std::filesystem::status(to_path(file), ec);
if(ec) {
return path_type::nonexistent;
}
switch(stat.type()) {
case std::filesystem::file_type::none: // LCOV_EXCL_LINE
case std::filesystem::file_type::not_found:
- return path_type::nonexistent;
+ return path_type::nonexistent; // LCOV_EXCL_LINE
case std::filesystem::file_type::directory:
return path_type::directory;
case std::filesystem::file_type::symlink:
@@ -3873,10 +4647,29 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") {
return std::string("Each IP number must be between 0 and 255 ") + var;
}
}
- return std::string();
+ return std::string{};
};
}
+CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() {
+ func_ = [](std::string &str) {
+ try {
+ if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') &&
+ str.front() == str.back()) {
+ process_quoted_string(str);
+ } else if(str.find_first_of('\\') != std::string::npos) {
+ if(detail::is_binary_escaped_string(str)) {
+ str = detail::extract_binary_string(str);
+ } else {
+ str = remove_escaped_characters(str);
+ }
+ }
+ return std::string{};
+ } catch(const std::invalid_argument &ia) {
+ return std::string(ia.what());
+ }
+ };
+}
} // namespace detail
CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn)
@@ -4175,7 +4968,8 @@ enum class MultiOptionPolicy : char {
TakeFirst, //!< take only the first Expected number of arguments
Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
TakeAll, //!< just get all the passed argument regardless
- Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter
+ Sum, //!< sum all the arguments together if numerical or concatenate directly without delimiter
+ Reverse, //!< take only the last Expected number of arguments in reverse order
};
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
@@ -4683,12 +5477,12 @@ class Option : public OptionBase<Option> {
if(!lnames_.empty()) {
return lnames_[0];
}
- if(!pname_.empty()) {
- return pname_;
- }
if(!snames_.empty()) {
return snames_[0];
}
+ if(!pname_.empty()) {
+ return pname_;
+ }
return envname_;
}
/// The number of times the option expects to be included
@@ -4711,13 +5505,13 @@ class Option : public OptionBase<Option> {
CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); }
/// True if the argument can be given directly
- CLI11_NODISCARD bool get_positional() const { return pname_.length() > 0; }
+ CLI11_NODISCARD bool get_positional() const { return !pname_.empty(); }
/// True if option has at least one non-positional name
- CLI11_NODISCARD bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
+ CLI11_NODISCARD bool nonpositional() const { return (!lnames_.empty() || !snames_.empty()); }
/// True if option has description
- CLI11_NODISCARD bool has_description() const { return description_.length() > 0; }
+ CLI11_NODISCARD bool has_description() const { return !description_.empty(); }
/// Get the description
CLI11_NODISCARD const std::string &get_description() const { return description_; }
@@ -5226,13 +6020,29 @@ CLI11_INLINE void Option::run_callback() {
CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const {
static const std::string estring;
- for(const std::string &sname : snames_)
+ for(const std::string &sname : snames_) {
if(other.check_sname(sname))
return sname;
- for(const std::string &lname : lnames_)
+ if(other.check_lname(sname))
+ return sname;
+ }
+ for(const std::string &lname : lnames_) {
if(other.check_lname(lname))
return lname;
-
+ if(lname.size() == 1) {
+ if(other.check_sname(lname)) {
+ return lname;
+ }
+ }
+ }
+ if(snames_.empty() && lnames_.empty() && !pname_.empty()) {
+ if(other.check_sname(pname_) || other.check_lname(pname_) || pname_ == other.pname_)
+ return pname_;
+ }
+ if(other.snames_.empty() && other.fnames_.empty() && !other.pname_.empty()) {
+ if(check_sname(other.pname_) || check_lname(other.pname_) || (pname_ == other.pname_))
+ return other.pname_;
+ }
if(ignore_case_ ||
ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
for(const std::string &sname : other.snames_)
@@ -5286,6 +6096,9 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin
if(default_ind >= 0) {
// We can static cast this to std::size_t because it is more than 0 in this block
if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
+ if(input_value == default_str_ && force_callback_) {
+ return input_value;
+ }
throw(ArgumentMismatch::FlagOverride(name));
}
} else {
@@ -5306,15 +6119,15 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin
return input_value;
}
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
- try {
- auto val = detail::to_flag_value(input_value);
- return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
- } catch(const std::invalid_argument &) {
+ errno = 0;
+ auto val = detail::to_flag_value(input_value);
+ if(errno != 0) {
+ errno = 0;
return input_value;
}
- } else {
- return input_value;
+ return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
}
+ return input_value;
}
CLI11_INLINE Option *Option::add_result(std::string s) {
@@ -5417,7 +6230,8 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const {
if(type_size_max_ > 1) { // in this context index refers to the index in the type
int index = 0;
if(get_items_expected_max() < static_cast<int>(res.size()) &&
- multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+ (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast ||
+ multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) {
// create a negative index for the earliest ones
index = get_items_expected_max() - static_cast<int>(res.size());
}
@@ -5435,7 +6249,8 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const {
} else {
int index = 0;
if(expected_max_ < static_cast<int>(res.size()) &&
- multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+ (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast ||
+ multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) {
// create a negative index for the earliest ones
index = expected_max_ - static_cast<int>(res.size());
}
@@ -5467,6 +6282,15 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi
out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
}
} break;
+ case MultiOptionPolicy::Reverse: {
+ // Allow multi-option sizes (including 0)
+ std::size_t trim_size = std::min<std::size_t>(
+ static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+ if(original.size() != trim_size || trim_size > 1) {
+ out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
+ }
+ std::reverse(out.begin(), out.end());
+ } break;
case MultiOptionPolicy::TakeFirst: {
std::size_t trim_size = std::min<std::size_t>(
static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
@@ -5496,7 +6320,12 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi
throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
}
if(original.size() > num_max) {
- throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
+ if(original.size() == 2 && num_max == 1 && original[1] == "%%" && original[0] == "{}") {
+ // this condition is a trap for the following empty indicator check on config files
+ out = original;
+ } else {
+ throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
+ }
}
break;
}
@@ -5505,11 +6334,11 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi
// {} is the indicator for an empty container
if(out.empty()) {
if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) {
- out.push_back("{}");
- out.push_back("%%");
+ out.emplace_back("{}");
+ out.emplace_back("%%");
}
} else if(out.size() == 1 && out[0] == "{}" && get_items_expected_min() > 0) {
- out.push_back("%%");
+ out.emplace_back("%%");
}
}
@@ -5570,9 +6399,9 @@ CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::stri
#ifndef CLI11_PARSE
-#define CLI11_PARSE(app, argc, argv) \
+#define CLI11_PARSE(app, ...) \
try { \
- (app).parse((argc), (argv)); \
+ (app).parse(__VA_ARGS__); \
} catch(const CLI::ParseError &e) { \
return (app).exit(e); \
}
@@ -5685,6 +6514,12 @@ class App {
/// @name Help
///@{
+ /// Usage to put after program/subcommand description in the help output INHERITABLE
+ std::string usage_{};
+
+ /// This is a function that generates a usage to put after program/subcommand description in help output
+ std::function<std::string()> usage_callback_{};
+
/// Footer to put after all options in the help output INHERITABLE
std::string footer_{};
@@ -5819,6 +6654,14 @@ class App {
///@}
+#ifdef _WIN32
+ /// When normalizing argv to UTF-8 on Windows, this is the storage for normalized args.
+ std::vector<std::string> normalized_argv_{};
+
+ /// When normalizing argv to UTF-8 on Windows, this is the `char**` value returned to the user.
+ std::vector<char *> normalized_argv_view_{};
+#endif
+
/// Special private constructor for subcommand
App(std::string app_description, std::string app_name, App *parent);
@@ -5838,6 +6681,9 @@ class App {
/// virtual destructor
virtual ~App() = default;
+ /// Convert the contents of argv to UTF-8. Only does something on Windows, does nothing elsewhere.
+ CLI11_NODISCARD char **ensure_utf8(char **argv);
+
/// Set a callback for execution when all parsing and processing has completed
///
/// Due to a bug in c++11,
@@ -6369,12 +7215,18 @@ class App {
/// Parses the command line - throws errors.
/// This must be called after the options are in but before the rest of the program.
void parse(int argc, const char *const *argv);
+ void parse(int argc, const wchar_t *const *argv);
+
+ private:
+ template <class CharT> void parse_char_t(int argc, const CharT *const *argv);
+ public:
/// Parse a single string as if it contained command line arguments.
/// This function splits the string into arguments then calls parse(std::vector<std::string> &)
/// the function takes an optional boolean argument specifying if the programName is included in the string to
/// process
void parse(std::string commandline, bool program_name_included = false);
+ void parse(std::wstring commandline, bool program_name_included = false);
/// The real work is done here. Expects a reversed vector.
/// Changes the vector to the remaining options.
@@ -6482,6 +7334,16 @@ class App {
/// @name Help
///@{
+ /// Set usage.
+ App *usage(std::string usage_string) {
+ usage_ = std::move(usage_string);
+ return this;
+ }
+ /// Set usage.
+ App *usage(std::function<std::string()> usage_function) {
+ usage_callback_ = std::move(usage_function);
+ return this;
+ }
/// Set footer.
App *footer(std::string footer_string) {
footer_ = std::move(footer_string);
@@ -6590,6 +7452,11 @@ class App {
/// Get the group of this subcommand
CLI11_NODISCARD const std::string &get_group() const { return group_; }
+ /// Generate and return the usage.
+ CLI11_NODISCARD std::string get_usage() const {
+ return (usage_callback_) ? usage_callback_() + '\n' + usage_ : usage_;
+ }
+
/// Generate and return the footer.
CLI11_NODISCARD std::string get_footer() const {
return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_;
@@ -6727,6 +7594,9 @@ class App {
/// Read and process a configuration file (main app only)
void _process_config_file();
+ /// Read and process a particular configuration file
+ bool _process_config_file(const std::string &config_file, bool throw_error);
+
/// Get envname options if not yet passed. Runs on *all* subcommands.
void _process_env();
@@ -6799,8 +7669,9 @@ class App {
bool _parse_subcommand(std::vector<std::string> &args);
/// Parse a short (false) or long (true) argument, must be at the top of the list
+ /// if local_processing_only is set to true then fallthrough is disabled will return false if not found
/// return true if the argument was processed or false if nothing was done
- bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type);
+ bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only);
/// Trigger the pre_parse callback if needed
void _trigger_pre_parse(std::size_t remaining_args);
@@ -6955,6 +7826,7 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa
configurable_ = parent_->configurable_;
allow_windows_style_options_ = parent_->allow_windows_style_options_;
group_ = parent_->group_;
+ usage_ = parent_->usage_;
footer_ = parent_->footer_;
formatter_ = parent_->formatter_;
config_formatter_ = parent_->config_formatter_;
@@ -6962,10 +7834,32 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa
}
}
+CLI11_NODISCARD CLI11_INLINE char **App::ensure_utf8(char **argv) {
+#ifdef _WIN32
+ (void)argv;
+
+ normalized_argv_ = detail::compute_win32_argv();
+
+ if(!normalized_argv_view_.empty()) {
+ normalized_argv_view_.clear();
+ }
+
+ normalized_argv_view_.reserve(normalized_argv_.size());
+ for(auto &arg : normalized_argv_) {
+ // using const_cast is well-defined, string is known to not be const.
+ normalized_argv_view_.push_back(const_cast<char *>(arg.data()));
+ }
+
+ return normalized_argv_view_.data();
+#else
+ return argv;
+#endif
+}
+
CLI11_INLINE App *App::name(std::string app_name) {
if(parent_ != nullptr) {
- auto oname = name_;
+ std::string oname = name_;
name_ = app_name;
const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
if(!res.empty()) {
@@ -7046,6 +7940,32 @@ CLI11_INLINE Option *App::add_option(std::string option_name,
if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) ==
std::end(options_)) {
+ if(myopt.lnames_.empty() && myopt.snames_.empty()) {
+ // if the option is positional only there is additional potential for ambiguities in config files and needs
+ // to be checked
+ std::string test_name = "--" + myopt.get_single_name();
+ if(test_name.size() == 3) {
+ test_name.erase(0, 1);
+ }
+
+ auto *op = get_option_no_throw(test_name);
+ if(op != nullptr) {
+ throw(OptionAlreadyAdded("added option positional name matches existing option: " + test_name));
+ }
+ } else if(parent_ != nullptr) {
+ for(auto &ln : myopt.lnames_) {
+ auto *op = parent_->get_option_no_throw(ln);
+ if(op != nullptr) {
+ throw(OptionAlreadyAdded("added option matches existing positional option: " + ln));
+ }
+ }
+ for(auto &sn : myopt.snames_) {
+ auto *op = parent_->get_option_no_throw(sn);
+ if(op != nullptr) {
+ throw(OptionAlreadyAdded("added option matches existing positional option: " + sn));
+ }
+ }
+ }
options_.emplace_back();
Option_p &option = options_.back();
option.reset(new Option(option_name, option_description, option_callback, this));
@@ -7220,8 +8140,11 @@ CLI11_INLINE Option *App::set_config(std::string option_name,
}
if(!default_filename.empty()) {
config_ptr_->default_str(std::move(default_filename));
+ config_ptr_->force_callback_ = true;
}
config_ptr_->configurable(false);
+ // set the option to take the last value and reverse given by default
+ config_ptr_->multi_option_policy(MultiOptionPolicy::Reverse);
}
return config_ptr_;
@@ -7251,13 +8174,14 @@ CLI11_INLINE bool App::remove_option(Option *opt) {
CLI11_INLINE App *App::add_subcommand(std::string subcommand_name, std::string subcommand_description) {
if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
if(!detail::valid_first_char(subcommand_name[0])) {
- throw IncorrectConstruction("Subcommand name starts with invalid character, '!' and '-' are not allowed");
+ throw IncorrectConstruction(
+ "Subcommand name starts with invalid character, '!' and '-' and control characters");
}
for(auto c : subcommand_name) {
if(!detail::valid_later_char(c)) {
throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
"'), all characters are allowed except"
- "'=',':','{','}', and ' '");
+ "'=',':','{','}', ' ', and control characters");
}
}
}
@@ -7382,17 +8306,29 @@ CLI11_INLINE void App::clear() {
}
}
-CLI11_INLINE void App::parse(int argc, const char *const *argv) {
+CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); }
+CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); }
+
+namespace detail {
+
+// Do nothing or perform narrowing
+CLI11_INLINE const char *maybe_narrow(const char *str) { return str; }
+CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); }
+
+} // namespace detail
+
+template <class CharT> CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) {
// If the name is not set, read from command line
if(name_.empty() || has_automatic_name_) {
has_automatic_name_ = true;
- name_ = argv[0];
+ name_ = detail::maybe_narrow(argv[0]);
}
std::vector<std::string> args;
args.reserve(static_cast<std::size_t>(argc) - 1U);
for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i)
- args.emplace_back(argv[i]);
+ args.emplace_back(detail::maybe_narrow(argv[i]));
+
parse(std::move(args));
}
@@ -7418,11 +8354,19 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included
auto args = detail::split_up(std::move(commandline));
// remove all empty strings
args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
+ try {
+ detail::remove_quotes(args);
+ } catch(const std::invalid_argument &arg) {
+ throw CLI::ParseError(arg.what(), CLI::ExitCodes::InvalidError);
+ }
std::reverse(args.begin(), args.end());
-
parse(std::move(args));
}
+CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) {
+ parse(narrow(commandline), program_name_included);
+}
+
CLI11_INLINE void App::parse(std::vector<std::string> &args) {
// Clear if parsed
if(parsed_ > 0)
@@ -7489,7 +8433,7 @@ CLI11_INLINE int App::exit(const Error &e, std::ostream &out, std::ostream &err)
}
if(e.get_name() == "CallForVersion") {
- out << e.what() << std::endl;
+ out << e.what() << '\n';
return e.get_exit_code();
}
@@ -7585,7 +8529,8 @@ CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMo
CLI11_NODISCARD CLI11_INLINE std::string App::version() const {
std::string val;
if(version_ptr_ != nullptr) {
- auto rv = version_ptr_->results();
+ // copy the results for reuse later
+ results_t rv = version_ptr_->results();
version_ptr_->clear();
version_ptr_->add_result("true");
try {
@@ -7694,7 +8639,7 @@ CLI11_NODISCARD CLI11_INLINE bool App::check_name(std::string name_to_check) con
if(local_name == name_to_check) {
return true;
}
- for(auto les : aliases_) { // NOLINT(performance-for-range-copy)
+ for(std::string les : aliases_) { // NOLINT(performance-for-range-copy)
if(ignore_underscore_) {
les = detail::remove_underscore(les);
}
@@ -7884,70 +8829,96 @@ CLI11_NODISCARD CLI11_INLINE detail::Classifier App::_recognize(const std::strin
return detail::Classifier::WINDOWS_STYLE;
if((current == "++") && !name_.empty() && parent_ != nullptr)
return detail::Classifier::SUBCOMMAND_TERMINATOR;
+ auto dotloc = current.find_first_of('.');
+ if(dotloc != std::string::npos) {
+ auto *cm = _find_subcommand(current.substr(0, dotloc), true, ignore_used_subcommands);
+ if(cm != nullptr) {
+ auto res = cm->_recognize(current.substr(dotloc + 1), ignore_used_subcommands);
+ if(res == detail::Classifier::SUBCOMMAND) {
+ return res;
+ }
+ }
+ }
return detail::Classifier::NONE;
}
+CLI11_INLINE bool App::_process_config_file(const std::string &config_file, bool throw_error) {
+ auto path_result = detail::check_path(config_file.c_str());
+ if(path_result == detail::path_type::file) {
+ try {
+ std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
+ _parse_config(values);
+ return true;
+ } catch(const FileError &) {
+ if(throw_error) {
+ throw;
+ }
+ return false;
+ }
+ } else if(throw_error) {
+ throw FileError::Missing(config_file);
+ } else {
+ return false;
+ }
+}
+
CLI11_INLINE void App::_process_config_file() {
if(config_ptr_ != nullptr) {
bool config_required = config_ptr_->get_required();
auto file_given = config_ptr_->count() > 0;
+ if(!(file_given || config_ptr_->envname_.empty())) {
+ std::string ename_string = detail::get_environment_value(config_ptr_->envname_);
+ if(!ename_string.empty()) {
+ config_ptr_->add_result(ename_string);
+ }
+ }
+ config_ptr_->run_callback();
+
auto config_files = config_ptr_->as<std::vector<std::string>>();
+ bool files_used{file_given};
if(config_files.empty() || config_files.front().empty()) {
if(config_required) {
- throw FileError::Missing("no specified config file");
+ throw FileError("config file is required but none was given");
}
return;
}
- for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
- const auto &config_file = *rit;
- auto path_result = detail::check_path(config_file.c_str());
- if(path_result == detail::path_type::file) {
- try {
- std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
- _parse_config(values);
- if(!file_given) {
- config_ptr_->add_result(config_file);
- }
- } catch(const FileError &) {
- if(config_required || file_given)
- throw;
- }
- } else if(config_required || file_given) {
- throw FileError::Missing(config_file);
+ for(const auto &config_file : config_files) {
+ if(_process_config_file(config_file, config_required || file_given)) {
+ files_used = true;
}
}
+ if(!files_used) {
+ // this is done so the count shows as 0 if no callbacks were processed
+ config_ptr_->clear();
+ bool force = config_ptr_->force_callback_;
+ config_ptr_->force_callback_ = false;
+ config_ptr_->run_callback();
+ config_ptr_->force_callback_ = force;
+ }
}
}
CLI11_INLINE void App::_process_env() {
for(const Option_p &opt : options_) {
if(opt->count() == 0 && !opt->envname_.empty()) {
- char *buffer = nullptr;
- std::string ename_string;
-
-#ifdef _MSC_VER
- // Windows version
- std::size_t sz = 0;
- if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {
- ename_string = std::string(buffer);
- free(buffer);
- }
-#else
- // This also works on Windows, but gives a warning
- buffer = std::getenv(opt->envname_.c_str());
- if(buffer != nullptr)
- ename_string = std::string(buffer);
-#endif
-
+ std::string ename_string = detail::get_environment_value(opt->envname_);
if(!ename_string.empty()) {
- opt->add_result(ename_string);
+ std::string result = ename_string;
+ result = opt->_validate(result, 0);
+ if(result.empty()) {
+ opt->add_result(ename_string);
+ }
}
}
}
for(App_p &sub : subcommands_) {
- if(sub->get_name().empty() || !sub->parse_complete_callback_)
- sub->_process_env();
+ if(sub->get_name().empty() || !sub->parse_complete_callback_) {
+ if(sub->count_all() > 0) {
+ // only process environment variables if the callback has actually been triggered already
+ sub->_process_env();
+ }
+ }
}
}
@@ -8247,12 +9218,11 @@ CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) {
}
CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) {
+
if(level < item.parents.size()) {
try {
auto *subcom = get_subcommand(item.parents.at(level));
- auto result = subcom->_parse_single_config(item, level + 1);
-
- return result;
+ return subcom->_parse_single_config(item, level + 1);
} catch(const OptionNotFound &) {
return false;
}
@@ -8282,15 +9252,19 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
if(item.name.size() == 1) {
op = get_option_no_throw("-" + item.name);
}
+ if(op == nullptr) {
+ op = get_option_no_throw(item.name);
+ }
}
- if(op == nullptr) {
- op = get_option_no_throw(item.name);
- }
+
if(op == nullptr) {
// If the option was not present
if(get_allow_config_extras() == config_extras_mode::capture)
// Should we worry about classifying the extras properly?
missing_.emplace_back(detail::Classifier::NONE, item.fullname());
+ for(const auto &input : item.inputs) {
+ missing_.emplace_back(detail::Classifier::NONE, input);
+ }
return false;
}
@@ -8309,29 +9283,54 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
auto res = config_formatter_->to_flag(item);
bool converted{false};
if(op->get_disable_flag_override()) {
-
- try {
- auto val = detail::to_flag_value(res);
- if(val == 1) {
- res = op->get_flag_value(item.name, "{}");
- converted = true;
- }
- } catch(...) {
+ auto val = detail::to_flag_value(res);
+ if(val == 1) {
+ res = op->get_flag_value(item.name, "{}");
+ converted = true;
}
}
if(!converted) {
+ errno = 0;
res = op->get_flag_value(item.name, res);
}
op->add_result(res);
return true;
}
- if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) {
+ if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max() &&
+ op->get_multi_option_policy() != MultiOptionPolicy::TakeAll) {
if(op->get_items_expected_max() > 1) {
throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size());
}
- throw ConversionError::TooManyInputsFlag(item.fullname());
+
+ if(!op->get_disable_flag_override()) {
+ throw ConversionError::TooManyInputsFlag(item.fullname());
+ }
+ // if the disable flag override is set then we must have the flag values match a known flag value
+ // this is true regardless of the output value, so an array input is possible and must be accounted for
+ for(const auto &res : item.inputs) {
+ bool valid_value{false};
+ if(op->default_flag_values_.empty()) {
+ if(res == "true" || res == "false" || res == "1" || res == "0") {
+ valid_value = true;
+ }
+ } else {
+ for(const auto &valid_res : op->default_flag_values_) {
+ if(valid_res.second == res) {
+ valid_value = true;
+ break;
+ }
+ }
+ }
+
+ if(valid_value) {
+ op->add_result(res);
+ } else {
+ throw InvalidError("invalid flag argument given");
+ }
+ }
+ return true;
}
}
op->add_result(item.inputs);
@@ -8366,7 +9365,7 @@ CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &posit
case detail::Classifier::SHORT:
case detail::Classifier::WINDOWS_STYLE:
// If already parsed a subcommand, don't accept options_
- _parse_arg(args, classifier);
+ retval = _parse_arg(args, classifier, false);
break;
case detail::Classifier::NONE:
// Probably a positional or something for a parent (sub)command
@@ -8408,6 +9407,7 @@ CLI11_NODISCARD CLI11_INLINE bool App::_has_remaining_positionals() const {
CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
const std::string &positional = args.back();
+ Option *posOpt{nullptr};
if(positionals_at_end_) {
// deal with the case of required arguments at the end which should take precedence over other arguments
@@ -8424,56 +9424,47 @@ CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool ha
continue;
}
}
-
- parse_order_.push_back(opt.get());
- /// if we require a separator add it here
- if(opt->get_inject_separator()) {
- if(!opt->results().empty() && !opt->results().back().empty()) {
- opt->add_result(std::string{});
- }
- }
- if(opt->get_trigger_on_parse() &&
- opt->current_option_state_ == Option::option_state::callback_run) {
- opt->clear();
- }
- opt->add_result(positional);
- if(opt->get_trigger_on_parse()) {
- opt->run_callback();
- }
- args.pop_back();
- return true;
+ posOpt = opt.get();
+ break;
}
}
}
}
}
- for(const Option_p &opt : options_) {
- // Eat options, one by one, until done
- if(opt->get_positional() &&
- (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
- if(validate_positionals_) {
- std::string pos = positional;
- pos = opt->_validate(pos, 0);
- if(!pos.empty()) {
- continue;
- }
- }
- if(opt->get_inject_separator()) {
- if(!opt->results().empty() && !opt->results().back().empty()) {
- opt->add_result(std::string{});
+ if(posOpt == nullptr) {
+ for(const Option_p &opt : options_) {
+ // Eat options, one by one, until done
+ if(opt->get_positional() &&
+ (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
+ if(validate_positionals_) {
+ std::string pos = positional;
+ pos = opt->_validate(pos, 0);
+ if(!pos.empty()) {
+ continue;
+ }
}
+ posOpt = opt.get();
+ break;
}
- if(opt->get_trigger_on_parse() && opt->current_option_state_ == Option::option_state::callback_run) {
- opt->clear();
- }
- opt->add_result(positional);
- if(opt->get_trigger_on_parse()) {
- opt->run_callback();
+ }
+ }
+ if(posOpt != nullptr) {
+ parse_order_.push_back(posOpt);
+ if(posOpt->get_inject_separator()) {
+ if(!posOpt->results().empty() && !posOpt->results().back().empty()) {
+ posOpt->add_result(std::string{});
}
- parse_order_.push_back(opt.get());
- args.pop_back();
- return true;
}
+ if(posOpt->get_trigger_on_parse() && posOpt->current_option_state_ == Option::option_state::callback_run) {
+ posOpt->clear();
+ }
+ posOpt->add_result(positional);
+ if(posOpt->get_trigger_on_parse()) {
+ posOpt->run_callback();
+ }
+
+ args.pop_back();
+ return true;
}
for(auto &subc : subcommands_) {
@@ -8554,6 +9545,17 @@ CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) {
return true;
}
auto *com = _find_subcommand(args.back(), true, true);
+ if(com == nullptr) {
+ // the main way to get here is using .notation
+ auto dotloc = args.back().find_first_of('.');
+ if(dotloc != std::string::npos) {
+ com = _find_subcommand(args.back().substr(0, dotloc), true, true);
+ if(com != nullptr) {
+ args.back() = args.back().substr(dotloc + 1);
+ args.push_back(com->get_display_name());
+ }
+ }
+ }
if(com != nullptr) {
args.pop_back();
if(!com->silent_) {
@@ -8576,7 +9578,8 @@ CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) {
return false;
}
-CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
+CLI11_INLINE bool
+App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only) {
std::string current = args.back();
@@ -8618,7 +9621,7 @@ CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classi
if(op_ptr == std::end(options_)) {
for(auto &subc : subcommands_) {
if(subc->name_.empty() && !subc->disabled_) {
- if(subc->_parse_arg(args, current_type)) {
+ if(subc->_parse_arg(args, current_type, local_processing_only)) {
if(!subc->pre_parse_called_) {
subc->_trigger_pre_parse(args.size());
}
@@ -8632,9 +9635,57 @@ CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classi
return false;
}
+ // now check for '.' notation of subcommands
+ auto dotloc = arg_name.find_first_of('.', 1);
+ if(dotloc != std::string::npos) {
+ // using dot notation is equivalent to single argument subcommand
+ auto *sub = _find_subcommand(arg_name.substr(0, dotloc), true, false);
+ if(sub != nullptr) {
+ auto v = args.back();
+ args.pop_back();
+ arg_name = arg_name.substr(dotloc + 1);
+ if(arg_name.size() > 1) {
+ args.push_back(std::string("--") + v.substr(dotloc + 3));
+ current_type = detail::Classifier::LONG;
+ } else {
+ auto nval = v.substr(dotloc + 2);
+ nval.front() = '-';
+ if(nval.size() > 2) {
+ // '=' not allowed in short form arguments
+ args.push_back(nval.substr(3));
+ nval.resize(2);
+ }
+ args.push_back(nval);
+ current_type = detail::Classifier::SHORT;
+ }
+ auto val = sub->_parse_arg(args, current_type, true);
+ if(val) {
+ if(!sub->silent_) {
+ parsed_subcommands_.push_back(sub);
+ }
+ // deal with preparsing
+ increment_parsed();
+ _trigger_pre_parse(args.size());
+ // run the parse complete callback since the subcommand processing is now complete
+ if(sub->parse_complete_callback_) {
+ sub->_process_env();
+ sub->_process_callbacks();
+ sub->_process_help_flags();
+ sub->_process_requirements();
+ sub->run_callback(false, true);
+ }
+ return true;
+ }
+ args.pop_back();
+ args.push_back(v);
+ }
+ }
+ if(local_processing_only) {
+ return false;
+ }
// If a subcommand, try the main command
if(parent_ != nullptr && fallthrough_)
- return _get_fallthrough_parent()->_parse_arg(args, current_type);
+ return _get_fallthrough_parent()->_parse_arg(args, current_type, false);
// Otherwise, add to missing
args.pop_back();
@@ -8756,7 +9807,7 @@ CLI11_INLINE void App::_trigger_pre_parse(std::size_t remaining_args) {
} else if(immediate_callback_) {
if(!name_.empty()) {
auto pcnt = parsed_;
- auto extras = std::move(missing_);
+ missing_t extras = std::move(missing_);
clear();
parsed_ = pcnt;
pre_parse_called_ = true;
@@ -8942,12 +9993,12 @@ CLI11_INLINE void retire_option(App *app, Option *opt) {
->allow_extra_args(opt->get_allow_extra_args());
app->remove_option(opt);
- auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
- ->type_name("RETIRED")
- ->default_str("RETIRED")
- ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
- ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
- ->allow_extra_args(option_copy->get_allow_extra_args());
+ auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect");
+ opt2->type_name("RETIRED")
+ ->default_str("RETIRED")
+ ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
+ ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
+ ->allow_extra_args(option_copy->get_allow_extra_args());
Validator retired_warning{[opt2](std::string &) {
std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
@@ -9015,7 +10066,10 @@ CLI11_INLINE std::string help(const App *app, const Error &e) {
namespace detail {
-std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'');
+std::string convert_arg_for_ini(const std::string &arg,
+ char stringQuote = '"',
+ char literalQuote = '\'',
+ bool disable_multi_line = false);
/// Comma separated join, adds quotes if needed
std::string ini_join(const std::vector<std::string> &args,
@@ -9023,7 +10077,9 @@ std::string ini_join(const std::vector<std::string> &args,
char arrayStart = '[',
char arrayEnd = ']',
char stringQuote = '"',
- char characterQuote = '\'');
+ char literalQuote = '\'');
+
+void clean_name_string(std::string &name, const std::string &keyChars);
std::vector<std::string> generate_parents(const std::string &section, std::string &name, char parentSeparator);
@@ -9034,9 +10090,19 @@ void checkParentSegments(std::vector<ConfigItem> &output, const std::string &cur
+static constexpr auto multiline_literal_quote = R"(''')";
+static constexpr auto multiline_string_quote = R"(""")";
+
namespace detail {
-CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) {
+CLI11_INLINE bool is_printable(const std::string &test_string) {
+ return std::all_of(test_string.begin(), test_string.end(), [](char x) {
+ return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n' || x == '\t');
+ });
+}
+
+CLI11_INLINE std::string
+convert_arg_for_ini(const std::string &arg, char stringQuote, char literalQuote, bool disable_multi_line) {
if(arg.empty()) {
return std::string(2, stringQuote);
}
@@ -9049,12 +10115,20 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string
using CLI::detail::lexical_cast;
double val = 0.0;
if(lexical_cast(arg, val)) {
- return arg;
+ if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) {
+ return arg;
+ }
}
}
// just quote a single non numeric character
if(arg.size() == 1) {
- return std::string(1, characterQuote) + arg + characterQuote;
+ if(isprint(static_cast<unsigned char>(arg.front())) == 0) {
+ return binary_escape_string(arg);
+ }
+ if(arg == "'") {
+ return std::string(1, stringQuote) + "'" + stringQuote;
+ }
+ return std::string(1, literalQuote) + arg + literalQuote;
}
// handle hex, binary or octal arguments
if(arg.front() == '0') {
@@ -9074,10 +10148,16 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string
}
}
}
- if(arg.find_first_of(stringQuote) == std::string::npos) {
- return std::string(1, stringQuote) + arg + stringQuote;
+ if(!is_printable(arg)) {
+ return binary_escape_string(arg);
}
- return characterQuote + arg + characterQuote;
+ if(detail::has_escapable_character(arg)) {
+ if(arg.size() > 100 && !disable_multi_line) {
+ return std::string(multiline_literal_quote) + arg + multiline_literal_quote;
+ }
+ return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
+ }
+ return std::string(1, stringQuote) + arg + stringQuote;
}
CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
@@ -9085,10 +10165,12 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
char arrayStart,
char arrayEnd,
char stringQuote,
- char characterQuote) {
+ char literalQuote) {
+ bool disable_multi_line{false};
std::string joined;
if(args.size() > 1 && arrayStart != '\0') {
joined.push_back(arrayStart);
+ disable_multi_line = true;
}
std::size_t start = 0;
for(const auto &arg : args) {
@@ -9098,7 +10180,7 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
joined.push_back(' ');
}
}
- joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
+ joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line));
}
if(args.size() > 1 && arrayEnd != '\0') {
joined.push_back(arrayEnd);
@@ -9111,22 +10193,22 @@ generate_parents(const std::string &section, std::string &name, char parentSepar
std::vector<std::string> parents;
if(detail::to_lower(section) != "default") {
if(section.find(parentSeparator) != std::string::npos) {
- parents = detail::split(section, parentSeparator);
+ parents = detail::split_up(section, parentSeparator);
} else {
parents = {section};
}
}
if(name.find(parentSeparator) != std::string::npos) {
- std::vector<std::string> plist = detail::split(name, parentSeparator);
+ std::vector<std::string> plist = detail::split_up(name, parentSeparator);
name = plist.back();
- detail::remove_quotes(name);
plist.pop_back();
parents.insert(parents.end(), plist.begin(), plist.end());
}
-
// clean up quotes on the parents
- for(auto &parent : parents) {
- detail::remove_quotes(parent);
+ try {
+ detail::remove_quotes(parents);
+ } catch(const std::invalid_argument &iarg) {
+ throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
}
return parents;
}
@@ -9179,30 +10261,59 @@ checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentS
output.back().parents = std::move(parents);
output.back().name = "++";
}
+
+/// @brief checks if a string represents a multiline comment
+CLI11_INLINE bool hasMLString(std::string const &fullString, char check) {
+ if(fullString.length() < 3) {
+ return false;
+ }
+ auto it = fullString.rbegin();
+ return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check);
+}
} // namespace detail
inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
std::string line;
+ std::string buffer;
std::string currentSection = "default";
std::string previousSection = "default";
std::vector<ConfigItem> output;
bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
bool inSection{false};
+ bool inMLineComment{false};
+ bool inMLineValue{false};
+
char aStart = (isINIArray) ? '[' : arrayStart;
char aEnd = (isINIArray) ? ']' : arrayEnd;
char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
int currentSectionIndex{0};
- while(getline(input, line)) {
+
+ std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter};
+ while(getline(input, buffer)) {
std::vector<std::string> items_buffer;
std::string name;
-
- detail::trim(line);
+ line = detail::trim_copy(buffer);
std::size_t len = line.length();
// lines have to be at least 3 characters to have any meaning to CLI just skip the rest
if(len < 3) {
continue;
}
+ if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) {
+ inMLineComment = true;
+ auto cchar = line.front();
+ while(inMLineComment) {
+ if(getline(input, line)) {
+ detail::trim(line);
+ } else {
+ break;
+ }
+ if(detail::hasMLString(line, cchar)) {
+ inMLineComment = false;
+ }
+ }
+ continue;
+ }
if(line.front() == '[' && line.back() == ']') {
if(currentSection != "default") {
// insert a section end which is just an empty items_buffer
@@ -9234,49 +10345,130 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
continue;
}
-
+ std::size_t search_start = 0;
+ if(line.find_first_of("\"'`") != std::string::npos) {
+ while(search_start < line.size()) {
+ auto test_char = line[search_start];
+ if(test_char == '\"' || test_char == '\'' || test_char == '`') {
+ search_start = detail::close_sequence(line, search_start, line[search_start]);
+ ++search_start;
+ } else if(test_char == valueDelimiter || test_char == commentChar) {
+ --search_start;
+ break;
+ } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) {
+ ++search_start;
+ } else {
+ search_start = line.find_first_of(line_sep_chars, search_start);
+ }
+ }
+ }
// Find = in string, split and recombine
- auto pos = line.find(valueDelimiter);
- if(pos != std::string::npos) {
- name = detail::trim_copy(line.substr(0, pos));
- std::string item = detail::trim_copy(line.substr(pos + 1));
- auto cloc = item.find(commentChar);
- if(cloc != std::string::npos) {
- item.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument)
- detail::trim(item);
- }
- if(item.size() > 1 && item.front() == aStart) {
+ auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1);
+ auto comment_pos = line.find_first_of(commentChar, search_start);
+ if(comment_pos < delimiter_pos) {
+ delimiter_pos = std::string::npos;
+ }
+ if(delimiter_pos != std::string::npos) {
+
+ name = detail::trim_copy(line.substr(0, delimiter_pos));
+ std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
+ bool mlquote =
+ (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0);
+ if(!mlquote && comment_pos != std::string::npos) {
+ auto citems = detail::split_up(item, commentChar);
+ item = detail::trim_copy(citems.front());
+ }
+ if(mlquote) {
+ // mutliline string
+ auto keyChar = item.front();
+ item = buffer.substr(delimiter_pos + 1, std::string::npos);
+ detail::ltrim(item);
+ item.erase(0, 3);
+ inMLineValue = true;
+ bool lineExtension{false};
+ bool firstLine = true;
+ if(!item.empty() && item.back() == '\\') {
+ item.pop_back();
+ lineExtension = true;
+ }
+ while(inMLineValue) {
+ std::string l2;
+ if(!std::getline(input, l2)) {
+ break;
+ }
+ line = l2;
+ detail::rtrim(line);
+ if(detail::hasMLString(line, keyChar)) {
+ line.pop_back();
+ line.pop_back();
+ line.pop_back();
+ if(lineExtension) {
+ detail::ltrim(line);
+ } else if(!(firstLine && item.empty())) {
+ item.push_back('\n');
+ }
+ firstLine = false;
+ item += line;
+ inMLineValue = false;
+ if(!item.empty() && item.back() == '\n') {
+ item.pop_back();
+ }
+ if(keyChar == '\"') {
+ try {
+ item = detail::remove_escaped_characters(item);
+ } catch(const std::invalid_argument &iarg) {
+ throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
+ }
+ }
+ } else {
+ if(lineExtension) {
+ detail::trim(l2);
+ } else if(!(firstLine && item.empty())) {
+ item.push_back('\n');
+ }
+ lineExtension = false;
+ firstLine = false;
+ if(!l2.empty() && l2.back() == '\\') {
+ lineExtension = true;
+ l2.pop_back();
+ }
+ item += l2;
+ }
+ }
+ items_buffer = {item};
+ } else if(item.size() > 1 && item.front() == aStart) {
for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
detail::trim(multiline);
item += multiline;
}
- items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
+ if(item.back() == aEnd) {
+ items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
+ } else {
+ items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
+ }
} else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
items_buffer = detail::split_up(item, aSep);
} else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
- items_buffer = detail::split_up(item);
+ items_buffer = detail::split_up(item, '\0');
} else {
items_buffer = {item};
}
} else {
- name = detail::trim_copy(line);
- auto cloc = name.find(commentChar);
- if(cloc != std::string::npos) {
- name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument)
- detail::trim(name);
- }
-
+ name = detail::trim_copy(line.substr(0, comment_pos));
items_buffer = {"true"};
}
- if(name.find(parentSeparatorChar) == std::string::npos) {
- detail::remove_quotes(name);
- }
- // clean up quotes on the items
- for(auto &it : items_buffer) {
- detail::remove_quotes(it);
+ std::vector<std::string> parents;
+ try {
+ parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
+ detail::process_quoted_string(name);
+ // clean up quotes on the items and check for escaped strings
+ for(auto &it : items_buffer) {
+ detail::process_quoted_string(it, stringQuote, literalQuote);
+ }
+ } catch(const std::invalid_argument &ia) {
+ throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError);
}
- std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
if(parents.size() > maximumLayers) {
continue;
}
@@ -9313,6 +10505,23 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
return output;
}
+CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string &keyChars) {
+ if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') ||
+ (name.find_first_of("'`\"\\") != std::string::npos)) {
+ if(name.find_first_of('\'') == std::string::npos) {
+ name.insert(0, 1, '\'');
+ name.push_back('\'');
+ } else {
+ if(detail::has_escapable_character(name)) {
+ name = detail::add_escaped_characters(name);
+ }
+ name.insert(0, 1, '\"');
+ name.push_back('\"');
+ }
+ }
+ return name;
+}
+
CLI11_INLINE std::string
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
std::stringstream out;
@@ -9320,6 +10529,18 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
commentLead.push_back(commentChar);
commentLead.push_back(' ');
+ std::string commentTest = "#;";
+ commentTest.push_back(commentChar);
+ commentTest.push_back(parentSeparatorChar);
+
+ std::string keyChars = commentTest;
+ keyChars.push_back(literalQuote);
+ keyChars.push_back(stringQuote);
+ keyChars.push_back(arrayStart);
+ keyChars.push_back(arrayEnd);
+ keyChars.push_back(valueDelimiter);
+ keyChars.push_back(arraySeparator);
+
std::vector<std::string> groups = app->get_groups();
bool defaultUsed = false;
groups.insert(groups.begin(), std::string("Options"));
@@ -9345,13 +10566,17 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
continue;
}
}
- std::string name = prefix + opt->get_single_name();
+ std::string single_name = opt->get_single_name();
+ if(single_name.empty()) {
+ continue;
+ }
+
std::string value = detail::ini_join(
- opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
+ opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
if(value.empty() && default_also) {
if(!opt->get_default_str().empty()) {
- value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
+ value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, false);
} else if(opt->get_expected_min() == 0) {
value = "false";
} else if(opt->get_run_callback_for_default()) {
@@ -9360,13 +10585,35 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
}
if(!value.empty()) {
+
if(!opt->get_fnames().empty()) {
- value = opt->get_flag_value(name, value);
+ try {
+ value = opt->get_flag_value(single_name, value);
+ } catch(const CLI::ArgumentMismatch &) {
+ bool valid{false};
+ for(const auto &test_name : opt->get_fnames()) {
+ try {
+ value = opt->get_flag_value(test_name, value);
+ single_name = test_name;
+ valid = true;
+ } catch(const CLI::ArgumentMismatch &) {
+ continue;
+ }
+ }
+ if(!valid) {
+ value = detail::ini_join(
+ opt->results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
+ }
+ }
}
if(write_description && opt->has_description()) {
out << '\n';
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
}
+ clean_name_string(single_name, keyChars);
+
+ std::string name = prefix + single_name;
+
out << name << valueDelimiter << value << '\n';
}
}
@@ -9375,31 +10622,56 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
auto subcommands = app->get_subcommands({});
for(const App *subcom : subcommands) {
if(subcom->get_name().empty()) {
+ if(!default_also && (subcom->count_all() == 0)) {
+ continue;
+ }
if(write_description && !subcom->get_group().empty()) {
out << '\n' << commentLead << subcom->get_group() << " Options\n";
}
+ /*if (!prefix.empty() || app->get_parent() == nullptr) {
+ out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
+ } else {
+ std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
+ const auto *p = app->get_parent();
+ while(p->get_parent() != nullptr) {
+ subname = p->get_name() + parentSeparatorChar +subname;
+ p = p->get_parent();
+ }
+ out << '[' << subname << "]\n";
+ }
+ */
out << to_config(subcom, default_also, write_description, prefix);
}
}
for(const App *subcom : subcommands) {
if(!subcom->get_name().empty()) {
+ if(!default_also && (subcom->count_all() == 0)) {
+ continue;
+ }
+ std::string subname = subcom->get_name();
+ clean_name_string(subname, keyChars);
+
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
if(!prefix.empty() || app->get_parent() == nullptr) {
- out << '[' << prefix << subcom->get_name() << "]\n";
+
+ out << '[' << prefix << subname << "]\n";
} else {
- std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
+ std::string appname = app->get_name();
+ clean_name_string(appname, keyChars);
+ subname = appname + parentSeparatorChar + subname;
const auto *p = app->get_parent();
while(p->get_parent() != nullptr) {
- subname = p->get_name() + parentSeparatorChar + subname;
+ std::string pname = p->get_name();
+ clean_name_string(pname, keyChars);
+ subname = pname + parentSeparatorChar + subname;
p = p->get_parent();
}
out << '[' << subname << "]\n";
}
out << to_config(subcom, default_also, write_description, "");
} else {
- out << to_config(
- subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
+ out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar);
}
}
}
@@ -9463,7 +10735,7 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
auto min_options = app->get_require_option_min();
auto max_options = app->get_require_option_max();
if(app->get_required()) {
- desc += " REQUIRED ";
+ desc += " " + get_label("REQUIRED") + " ";
}
if((max_options == min_options) && (min_options > 0)) {
if(min_options == 1) {
@@ -9485,6 +10757,11 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const {
}
CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const {
+ std::string usage = app->get_usage();
+ if(!usage.empty()) {
+ return usage + "\n";
+ }
+
std::stringstream out;
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
@@ -9521,7 +10798,7 @@ CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name)
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
}
- out << std::endl;
+ out << '\n';
return out.str();
}
@@ -9602,7 +10879,10 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo
CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const {
std::stringstream out;
- detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
+ detail::format_help(out,
+ sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : ""),
+ sub->get_description(),
+ column_width_);
return out.str();
}