CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
5024 lines
144 KiB
C++
5024 lines
144 KiB
C++
/* A simple header-only C++ argument parser library.
|
|
*
|
|
* https://github.com/Taywee/args
|
|
*
|
|
* Copyright (c) 2016-2024 Taylor Richberger <taylor@axfive.net> and Pavel
|
|
* Belikov
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
/** \file args.hxx
|
|
* \brief this single-header lets you use all of the args functionality
|
|
*
|
|
* The important stuff is done inside the args namespace
|
|
*/
|
|
|
|
#ifndef ARGS_HXX
|
|
#define ARGS_HXX
|
|
#pragma push_macro("min")
|
|
#pragma push_macro("max")
|
|
#undef min
|
|
#undef max
|
|
|
|
#define ARGS_VERSION "6.4.16"
|
|
#define ARGS_VERSION_MAJOR 6
|
|
#define ARGS_VERSION_MINOR 4
|
|
#define ARGS_VERSION_PATCH 16
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <type_traits>
|
|
#include <cstddef>
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <iostream>
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
|
#define noexcept
|
|
#endif
|
|
|
|
/** \namespace args
|
|
* \brief contains all the functionality of the args library
|
|
*/
|
|
namespace args {
|
|
/** Getter to grab the value from the argument type.
|
|
*
|
|
* If the Get() function of the type returns a reference, so does this, and
|
|
* the value will be modifiable.
|
|
*/
|
|
template <typename Option>
|
|
auto
|
|
get(Option &option_) -> decltype(option_.Get())
|
|
{
|
|
return option_.Get();
|
|
}
|
|
|
|
/** (INTERNAL) Count UTF-8 glyphs
|
|
*
|
|
* This is not reliable, and will fail for combinatory glyphs, but it's
|
|
* good enough here for now.
|
|
*
|
|
* \param string The string to count glyphs from
|
|
* \return The UTF-8 glyphs in the string
|
|
*/
|
|
inline std::string::size_type
|
|
Glyphs(const std::string &string_)
|
|
{
|
|
std::string::size_type length = 0;
|
|
for (const char c : string_) {
|
|
if ((c & 0xc0) != 0x80) {
|
|
++length;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
/** Safe addition to prevent integer overflow.
|
|
* Returns true if the addition is successful, false if it would overflow.
|
|
*/
|
|
template <typename T>
|
|
bool
|
|
SafeAdd(T a, T b, T &out) noexcept
|
|
{
|
|
static_assert(std::is_integral<T>::value, "SafeAdd requires integral types.");
|
|
if (std::is_unsigned<T>::value) {
|
|
using U = typename std::make_unsigned<T>::type;
|
|
const U ua = static_cast<U>(a);
|
|
const U ub = static_cast<U>(b);
|
|
const U maxv = std::numeric_limits<U>::max();
|
|
if (ua > maxv - ub) {
|
|
return false;
|
|
}
|
|
out = static_cast<T>(ua + ub);
|
|
return true;
|
|
}
|
|
else {
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
return !__builtin_add_overflow(a, b, &out);
|
|
#else
|
|
// Fallback bounds check
|
|
if (b > 0 && a > std::numeric_limits<T>::max() - b) {
|
|
return false;
|
|
}
|
|
if (b < 0 && a < std::numeric_limits<T>::min() - b) {
|
|
return false;
|
|
}
|
|
out = a + b;
|
|
return true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/** Safe multiplication to prevent integer overflow.
|
|
* Returns true if the multiplication is successful, false if it would overflow.
|
|
*/
|
|
template <typename T>
|
|
bool
|
|
SafeMultiply(T a, T b, T &out) noexcept
|
|
{
|
|
static_assert(std::is_integral<T>::value, "SafeMultiply requires integral types.");
|
|
|
|
if (a == 0 || b == 0) {
|
|
out = 0;
|
|
return true;
|
|
}
|
|
|
|
if (std::is_unsigned<T>::value) {
|
|
using U = typename std::make_unsigned<T>::type;
|
|
const U ua = static_cast<U>(a);
|
|
const U ub = static_cast<U>(b);
|
|
const U maxv = std::numeric_limits<U>::max();
|
|
if (ub > maxv / ua) {
|
|
return false;
|
|
}
|
|
out = static_cast<T>(ua * ub);
|
|
return true;
|
|
}
|
|
else {
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
return !__builtin_mul_overflow(a, b, &out);
|
|
#else
|
|
// Fallback bounds check
|
|
if (a == -1 && b == std::numeric_limits<T>::min()) {
|
|
return false;
|
|
}
|
|
if (b == -1 && a == std::numeric_limits<T>::min()) {
|
|
return false;
|
|
}
|
|
if ((a > 0 && b > 0 && a > std::numeric_limits<T>::max() / b) ||
|
|
(a > 0 && b < 0 && b < std::numeric_limits<T>::min() / a) ||
|
|
(a < 0 && b > 0 && a < std::numeric_limits<T>::min() / b) ||
|
|
(a < 0 && b < 0 && a < std::numeric_limits<T>::max() / b)) {
|
|
return false;
|
|
}
|
|
out = a * b;
|
|
return true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/** Safe subtraction to prevent integer underflow.
|
|
* Returns true if the subtraction is successful, false if it would underflow.
|
|
*/
|
|
template <typename T>
|
|
bool
|
|
SafeSub(T a, T b, T &out) noexcept
|
|
{
|
|
static_assert(std::is_integral<T>::value, "SafeSub requires integral types.");
|
|
if (std::is_unsigned<T>::value) {
|
|
if (a < b) {
|
|
return false;
|
|
}
|
|
out = a - b;
|
|
return true;
|
|
}
|
|
else {
|
|
#if defined(__clang__) || defined(__GNUC__)
|
|
return !__builtin_sub_overflow(a, b, &out);
|
|
#else
|
|
// Fallback bounds check
|
|
if (b > 0 && a < std::numeric_limits<T>::min() + b) {
|
|
return false;
|
|
}
|
|
if (b < 0 && a > std::numeric_limits<T>::max() + b) {
|
|
return false;
|
|
}
|
|
out = a - b;
|
|
return true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/** Safe negation to prevent integer overflow.
|
|
* Returns true if the negation is successful, false if it would overflow.
|
|
*/
|
|
// Unsigned overload
|
|
template <typename T>
|
|
typename std::enable_if<std::is_unsigned<T>::value, bool>::type
|
|
SafeNeg(T a, T &out) noexcept
|
|
{
|
|
static_assert(std::is_integral<T>::value, "SafeNeg requires integral types.");
|
|
if (a != 0) {
|
|
return false;
|
|
}
|
|
out = 0;
|
|
return true;
|
|
}
|
|
|
|
// Signed overload
|
|
template <typename T>
|
|
typename std::enable_if<std::is_signed<T>::value, bool>::type
|
|
SafeNeg(T a, T &out) noexcept
|
|
{
|
|
static_assert(std::is_integral<T>::value, "SafeNeg requires integral types.");
|
|
if (a == std::numeric_limits<T>::min()) {
|
|
return false;
|
|
}
|
|
out = -a;
|
|
return true;
|
|
}
|
|
|
|
/** (INTERNAL) Wrap a vector of words into a vector of lines
|
|
*
|
|
* Empty words are skipped. Word "\n" forces wrapping.
|
|
*
|
|
* \param begin The begin iterator
|
|
* \param end The end iterator
|
|
* \param width The width of the body
|
|
* \param firstlinewidth the width of the first line, defaults to the width of the body
|
|
* \param firstlineindent the indent of the first line, defaults to 0
|
|
* \return the vector of lines
|
|
*/
|
|
template <typename It>
|
|
inline std::vector<std::string>
|
|
Wrap(It begin, It end, const std::string::size_type width, std::string::size_type firstlinewidth = 0,
|
|
std::string::size_type firstlineindent = 0)
|
|
{
|
|
std::vector<std::string> output;
|
|
std::string line(firstlineindent, ' ');
|
|
bool empty = true;
|
|
|
|
if (firstlinewidth == 0) {
|
|
firstlinewidth = width;
|
|
}
|
|
|
|
auto currentwidth = firstlinewidth;
|
|
|
|
for (auto it = begin; it != end; ++it) {
|
|
if (it->empty()) {
|
|
continue;
|
|
}
|
|
|
|
if (*it == "\n") {
|
|
if (!empty) {
|
|
output.push_back(line);
|
|
line.clear();
|
|
empty = true;
|
|
currentwidth = width;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
auto itemsize = Glyphs(*it);
|
|
|
|
// Refactored to prevent integer overflow
|
|
bool needsWrap = false;
|
|
if (itemsize >= currentwidth) {
|
|
needsWrap = true;
|
|
}
|
|
else {
|
|
size_t remainingWidth = (currentwidth > itemsize) ? (currentwidth - itemsize) : 0;
|
|
size_t nextLength = 0;
|
|
if (!SafeAdd<std::string::size_type>(line.length(), static_cast<std::string::size_type>(1),
|
|
nextLength) ||
|
|
nextLength > remainingWidth) {
|
|
needsWrap = true;
|
|
}
|
|
}
|
|
|
|
if (needsWrap) {
|
|
if (!empty) {
|
|
output.push_back(line);
|
|
line.clear();
|
|
empty = true;
|
|
currentwidth = width;
|
|
}
|
|
}
|
|
|
|
if (itemsize > 0) {
|
|
if (!empty) {
|
|
line += ' ';
|
|
}
|
|
|
|
line += *it;
|
|
empty = false;
|
|
}
|
|
}
|
|
|
|
if (!empty) {
|
|
output.push_back(line);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
namespace detail {
|
|
template <typename T> std::string Join(const T &array, const std::string &delimiter)
|
|
{
|
|
std::string res;
|
|
|
|
// Safely compute reservation size to avoid unbounded reallocations
|
|
using size_type = std::string::size_type;
|
|
size_type total = 0;
|
|
size_type count = 0;
|
|
const size_type delim_size = static_cast<size_type>(delimiter.size());
|
|
bool can_reserve = true;
|
|
|
|
for (const auto &element : array) {
|
|
const size_type elem_size = static_cast<size_type>(element.size());
|
|
if (!SafeAdd<size_type>(total, elem_size, total)) {
|
|
can_reserve = false;
|
|
break;
|
|
}
|
|
++count;
|
|
}
|
|
|
|
if (can_reserve && count > 1) {
|
|
size_type delim_count = count - 1;
|
|
size_type delim_total = 0;
|
|
if (!SafeMultiply<size_type>(delim_count, delim_size, delim_total) ||
|
|
!SafeAdd<size_type>(total, delim_total, total)) {
|
|
can_reserve = false;
|
|
}
|
|
}
|
|
|
|
if (can_reserve && total > 0) {
|
|
try {
|
|
res.reserve(total);
|
|
}
|
|
catch (...) {
|
|
// Fall back to default allocation
|
|
}
|
|
}
|
|
|
|
bool first = true;
|
|
for (const auto &element : array) {
|
|
if (!first) {
|
|
res += delimiter;
|
|
}
|
|
res += element;
|
|
first = false;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/** (INTERNAL) Wrap a string into a vector of lines
|
|
*
|
|
* This is quick and hacky, but works well enough. You can specify a
|
|
* different width for the first line
|
|
*
|
|
* \param width The width of the body
|
|
* \param firstlinewid the width of the first line, defaults to the width of the body
|
|
* \return the vector of lines
|
|
*/
|
|
inline std::vector<std::string>
|
|
Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0)
|
|
{
|
|
// Preserve existing line breaks
|
|
const auto newlineloc = in.find('\n');
|
|
if (newlineloc != in.npos) {
|
|
auto first = Wrap(std::string(in, 0, newlineloc), width);
|
|
auto second = Wrap(std::string(in, newlineloc + 1), width);
|
|
first.insert(std::end(first), std::make_move_iterator(std::begin(second)),
|
|
std::make_move_iterator(std::end(second)));
|
|
return first;
|
|
}
|
|
|
|
std::istringstream stream(in);
|
|
std::string::size_type indent = 0;
|
|
|
|
for (auto c : in) {
|
|
if (!std::isspace(static_cast<unsigned char>(c))) {
|
|
break;
|
|
}
|
|
++indent;
|
|
}
|
|
|
|
return Wrap(std::istream_iterator<std::string>(stream), std::istream_iterator<std::string>(), width,
|
|
firstlinewidth, indent);
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Error class, for when ARGS_NOEXCEPT is defined
|
|
enum class Error {
|
|
None,
|
|
Usage,
|
|
Parse,
|
|
Validation,
|
|
Required,
|
|
Map,
|
|
Extra,
|
|
Help,
|
|
Subparser,
|
|
Completion,
|
|
};
|
|
#else
|
|
/** Base error class
|
|
*/
|
|
class Error : public std::runtime_error {
|
|
public:
|
|
Error(const std::string &problem) : std::runtime_error(problem)
|
|
{
|
|
}
|
|
virtual ~Error()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Errors that occur during usage
|
|
*/
|
|
class UsageError : public Error {
|
|
public:
|
|
UsageError(const std::string &problem) : Error(problem)
|
|
{
|
|
}
|
|
virtual ~UsageError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Errors that occur during regular parsing
|
|
*/
|
|
class ParseError : public Error {
|
|
public:
|
|
ParseError(const std::string &problem) : Error(problem)
|
|
{
|
|
}
|
|
virtual ~ParseError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Errors that are detected from group validation after parsing finishes
|
|
*/
|
|
class ValidationError : public Error {
|
|
public:
|
|
ValidationError(const std::string &problem) : Error(problem)
|
|
{
|
|
}
|
|
virtual ~ValidationError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Errors that when a required flag is omitted
|
|
*/
|
|
class RequiredError : public ValidationError {
|
|
public:
|
|
RequiredError(const std::string &problem) : ValidationError(problem)
|
|
{
|
|
}
|
|
virtual ~RequiredError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Errors in map lookups
|
|
*/
|
|
class MapError : public ParseError {
|
|
public:
|
|
MapError(const std::string &problem) : ParseError(problem)
|
|
{
|
|
}
|
|
virtual ~MapError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Error that occurs when a singular flag is specified multiple times
|
|
*/
|
|
class ExtraError : public ParseError {
|
|
public:
|
|
ExtraError(const std::string &problem) : ParseError(problem)
|
|
{
|
|
}
|
|
virtual ~ExtraError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** An exception that indicates that the user has requested help
|
|
*/
|
|
class Help : public Error {
|
|
public:
|
|
Help(const std::string &flag) : Error(flag)
|
|
{
|
|
}
|
|
virtual ~Help()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers.
|
|
*/
|
|
class SubparserError : public Error {
|
|
public:
|
|
SubparserError() : Error("")
|
|
{
|
|
}
|
|
virtual ~SubparserError()
|
|
{
|
|
}
|
|
};
|
|
|
|
/** An exception that contains autocompletion reply
|
|
*/
|
|
class Completion : public Error {
|
|
public:
|
|
Completion(const std::string &flag) : Error(flag)
|
|
{
|
|
}
|
|
virtual ~Completion()
|
|
{
|
|
}
|
|
};
|
|
#endif
|
|
|
|
/** A simple unified option type for unified initializer lists for the Matcher class.
|
|
*/
|
|
struct EitherFlag {
|
|
const bool isShort;
|
|
const char shortFlag;
|
|
const std::string longFlag;
|
|
EitherFlag(const std::string &flag) : isShort(false), shortFlag(), longFlag(flag)
|
|
{
|
|
}
|
|
EitherFlag(const char *flag) : isShort(false), shortFlag(), longFlag(flag)
|
|
{
|
|
}
|
|
EitherFlag(const char flag) : isShort(true), shortFlag(flag), longFlag()
|
|
{
|
|
}
|
|
|
|
/** Get just the long flags from an initializer list of EitherFlags
|
|
*/
|
|
static std::unordered_set<std::string> GetLong(std::initializer_list<EitherFlag> flags)
|
|
{
|
|
std::unordered_set<std::string> longFlags;
|
|
for (const EitherFlag &flag : flags) {
|
|
if (!flag.isShort) {
|
|
longFlags.insert(flag.longFlag);
|
|
}
|
|
}
|
|
return longFlags;
|
|
}
|
|
|
|
/** Get just the short flags from an initializer list of EitherFlags
|
|
*/
|
|
static std::unordered_set<char> GetShort(std::initializer_list<EitherFlag> flags)
|
|
{
|
|
std::unordered_set<char> shortFlags;
|
|
for (const EitherFlag &flag : flags) {
|
|
if (flag.isShort) {
|
|
shortFlags.insert(flag.shortFlag);
|
|
}
|
|
}
|
|
return shortFlags;
|
|
}
|
|
|
|
std::string str() const
|
|
{
|
|
return isShort ? std::string(1, shortFlag) : longFlag;
|
|
}
|
|
|
|
std::string str(const std::string &shortPrefix, const std::string &longPrefix) const
|
|
{
|
|
return isShort ? shortPrefix + std::string(1, shortFlag) : longPrefix + longFlag;
|
|
}
|
|
};
|
|
|
|
/** A class of "matchers", specifying short and flags that can possibly be
|
|
* matched.
|
|
*
|
|
* This is supposed to be constructed and then passed in, not used directly
|
|
* from user code.
|
|
*/
|
|
class Matcher {
|
|
private:
|
|
const std::unordered_set<char> shortFlags;
|
|
const std::unordered_set<std::string> longFlags;
|
|
|
|
public:
|
|
/** Specify short and long flags separately as iterators
|
|
*
|
|
* ex: `args::Matcher(shortFlags.begin(), shortFlags.end(), longFlags.begin(), longFlags.end())`
|
|
*/
|
|
template <typename ShortIt, typename LongIt>
|
|
Matcher(ShortIt shortFlagsStart, ShortIt shortFlagsEnd, LongIt longFlagsStart, LongIt longFlagsEnd)
|
|
: shortFlags(shortFlagsStart, shortFlagsEnd), longFlags(longFlagsStart, longFlagsEnd)
|
|
{
|
|
if (shortFlags.empty() && longFlags.empty()) {
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw UsageError("empty Matcher");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
Error GetError() const noexcept
|
|
{
|
|
return shortFlags.empty() && longFlags.empty() ? Error::Usage : Error::None;
|
|
}
|
|
#endif
|
|
|
|
/** Specify short and long flags separately as iterables
|
|
*
|
|
* ex: `args::Matcher(shortFlags, longFlags)`
|
|
*/
|
|
template <typename Short, typename Long>
|
|
Matcher(Short &&shortIn, Long &&longIn)
|
|
: Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn))
|
|
{
|
|
}
|
|
|
|
/** Specify a mixed single initializer-list of both short and long flags
|
|
*
|
|
* This is the fancy one. It takes a single initializer list of
|
|
* any number of any mixed kinds of flags. Chars are
|
|
* automatically interpreted as short flags, and strings are
|
|
* automatically interpreted as long flags:
|
|
*
|
|
* args::Matcher{'a'}
|
|
* args::Matcher{"foo"}
|
|
* args::Matcher{'h', "help"}
|
|
* args::Matcher{"foo", 'f', 'F', "FoO"}
|
|
*/
|
|
Matcher(std::initializer_list<EitherFlag> in) : Matcher(EitherFlag::GetShort(in), EitherFlag::GetLong(in))
|
|
{
|
|
}
|
|
|
|
Matcher(Matcher &&other) noexcept
|
|
: shortFlags(std::move(other.shortFlags)), longFlags(std::move(other.longFlags))
|
|
{
|
|
}
|
|
|
|
~Matcher()
|
|
{
|
|
}
|
|
|
|
/** (INTERNAL) Check if there is a match of a short flag
|
|
*/
|
|
bool Match(const char flag) const
|
|
{
|
|
return shortFlags.find(flag) != shortFlags.end();
|
|
}
|
|
|
|
/** (INTERNAL) Check if there is a match of a long flag
|
|
*/
|
|
bool Match(const std::string &flag) const
|
|
{
|
|
return longFlags.find(flag) != longFlags.end();
|
|
}
|
|
|
|
/** (INTERNAL) Check if there is a match of a flag
|
|
*/
|
|
bool Match(const EitherFlag &flag) const
|
|
{
|
|
return flag.isShort ? Match(flag.shortFlag) : Match(flag.longFlag);
|
|
}
|
|
|
|
/** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded
|
|
*/
|
|
std::vector<EitherFlag> GetFlagStrings() const
|
|
{
|
|
std::vector<EitherFlag> flagStrings;
|
|
flagStrings.reserve(shortFlags.size() + longFlags.size());
|
|
for (const char flag : shortFlags) {
|
|
flagStrings.emplace_back(flag);
|
|
}
|
|
for (const std::string &flag : longFlags) {
|
|
flagStrings.emplace_back(flag);
|
|
}
|
|
return flagStrings;
|
|
}
|
|
|
|
/** (INTERNAL) Get long flag if it exists or any short flag
|
|
*/
|
|
EitherFlag GetLongOrAny() const
|
|
{
|
|
if (!longFlags.empty()) {
|
|
return *longFlags.begin();
|
|
}
|
|
|
|
if (!shortFlags.empty()) {
|
|
return *shortFlags.begin();
|
|
}
|
|
|
|
// should be unreachable
|
|
return ' ';
|
|
}
|
|
|
|
/** (INTERNAL) Get short flag if it exists or any long flag
|
|
*/
|
|
EitherFlag GetShortOrAny() const
|
|
{
|
|
if (!shortFlags.empty()) {
|
|
return *shortFlags.begin();
|
|
}
|
|
|
|
if (!longFlags.empty()) {
|
|
return *longFlags.begin();
|
|
}
|
|
|
|
// should be unreachable
|
|
return ' ';
|
|
}
|
|
};
|
|
|
|
/** Attributes for flags.
|
|
*/
|
|
enum class Options {
|
|
/** Default options.
|
|
*/
|
|
None = 0x0,
|
|
|
|
/** Flag can't be passed multiple times.
|
|
*/
|
|
Single = 0x01,
|
|
|
|
/** Flag can't be omitted.
|
|
*/
|
|
Required = 0x02,
|
|
|
|
/** Flag is excluded from usage line.
|
|
*/
|
|
HiddenFromUsage = 0x04,
|
|
|
|
/** Flag is excluded from options help.
|
|
*/
|
|
HiddenFromDescription = 0x08,
|
|
|
|
/** Flag is global and can be used in any subcommand.
|
|
*/
|
|
Global = 0x10,
|
|
|
|
/** Flag stops a parser.
|
|
*/
|
|
KickOut = 0x20,
|
|
|
|
/** Flag is excluded from auto completion.
|
|
*/
|
|
HiddenFromCompletion = 0x40,
|
|
|
|
/** Flag is excluded from options help and usage line
|
|
*/
|
|
Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion,
|
|
};
|
|
|
|
inline Options
|
|
operator|(Options lhs, Options rhs)
|
|
{
|
|
return static_cast<Options>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
|
}
|
|
|
|
inline Options
|
|
operator&(Options lhs, Options rhs)
|
|
{
|
|
return static_cast<Options>(static_cast<int>(lhs) & static_cast<int>(rhs));
|
|
}
|
|
|
|
class FlagBase;
|
|
class PositionalBase;
|
|
class Command;
|
|
class ArgumentParser;
|
|
|
|
/** A simple structure of parameters for easy user-modifyable help menus
|
|
*/
|
|
struct HelpParams {
|
|
/** The width of the help menu
|
|
*/
|
|
unsigned int width = 80;
|
|
/** The indent of the program line
|
|
*/
|
|
unsigned int progindent = 2;
|
|
/** The indent of the program trailing lines for long parameters
|
|
*/
|
|
unsigned int progtailindent = 4;
|
|
/** The indent of the description and epilogs
|
|
*/
|
|
unsigned int descriptionindent = 4;
|
|
/** The indent of the flags
|
|
*/
|
|
unsigned int flagindent = 6;
|
|
/** The indent of the flag descriptions
|
|
*/
|
|
unsigned int helpindent = 40;
|
|
/** The additional indent each group adds
|
|
*/
|
|
unsigned int eachgroupindent = 2;
|
|
|
|
/** The minimum gutter between each flag and its help
|
|
*/
|
|
unsigned int gutter = 1;
|
|
|
|
/** Show the terminator when both options and positional parameters are present
|
|
*/
|
|
bool showTerminator = true;
|
|
|
|
/** Show the {OPTIONS} on the prog line when this is true
|
|
*/
|
|
bool showProglineOptions = true;
|
|
|
|
/** Show the positionals on the prog line when this is true
|
|
*/
|
|
bool showProglinePositionals = true;
|
|
|
|
/** The prefix for short flags
|
|
*/
|
|
std::string shortPrefix;
|
|
|
|
/** The prefix for long flags
|
|
*/
|
|
std::string longPrefix;
|
|
|
|
/** The separator for short flags
|
|
*/
|
|
std::string shortSeparator;
|
|
|
|
/** The separator for long flags
|
|
*/
|
|
std::string longSeparator;
|
|
|
|
/** The program name for help generation
|
|
*/
|
|
std::string programName;
|
|
|
|
/** Show command's flags
|
|
*/
|
|
bool showCommandChildren = false;
|
|
|
|
/** Show command's descriptions and epilog
|
|
*/
|
|
bool showCommandFullHelp = false;
|
|
|
|
/** The postfix for progline when showProglineOptions is true and command has any flags
|
|
*/
|
|
std::string proglineOptions = "{OPTIONS}";
|
|
|
|
/** The prefix for progline when command has any subcommands
|
|
*/
|
|
std::string proglineCommand = "COMMAND";
|
|
|
|
/** The prefix for progline value
|
|
*/
|
|
std::string proglineValueOpen = " <";
|
|
|
|
/** The postfix for progline value
|
|
*/
|
|
std::string proglineValueClose = ">";
|
|
|
|
/** The prefix for progline required argument
|
|
*/
|
|
std::string proglineRequiredOpen = "";
|
|
|
|
/** The postfix for progline required argument
|
|
*/
|
|
std::string proglineRequiredClose = "";
|
|
|
|
/** The prefix for progline non-required argument
|
|
*/
|
|
std::string proglineNonrequiredOpen = "[";
|
|
|
|
/** The postfix for progline non-required argument
|
|
*/
|
|
std::string proglineNonrequiredClose = "]";
|
|
|
|
/** Show flags in program line
|
|
*/
|
|
bool proglineShowFlags = false;
|
|
|
|
/** Use short flags in program lines when possible
|
|
*/
|
|
bool proglinePreferShortFlags = false;
|
|
|
|
/** Program line prefix
|
|
*/
|
|
std::string usageString;
|
|
|
|
/** String shown in help before flags descriptions
|
|
*/
|
|
std::string optionsString = "OPTIONS:";
|
|
|
|
/** Display value name after all the long and short flags
|
|
*/
|
|
bool useValueNameOnce = false;
|
|
|
|
/** Show value name
|
|
*/
|
|
bool showValueName = true;
|
|
|
|
/** Add newline before flag description
|
|
*/
|
|
bool addNewlineBeforeDescription = false;
|
|
|
|
/** The prefix for option value
|
|
*/
|
|
std::string valueOpen = "[";
|
|
|
|
/** The postfix for option value
|
|
*/
|
|
std::string valueClose = "]";
|
|
|
|
/** Add choices to argument description
|
|
*/
|
|
bool addChoices = false;
|
|
|
|
/** The prefix for choices
|
|
*/
|
|
std::string choiceString = "\nOne of: ";
|
|
|
|
/** Add default values to argument description
|
|
*/
|
|
bool addDefault = false;
|
|
|
|
/** The prefix for default values
|
|
*/
|
|
std::string defaultString = "\nDefault: ";
|
|
};
|
|
|
|
/** A number of arguments which can be consumed by an option.
|
|
*
|
|
* Represents a closed interval [min, max].
|
|
*/
|
|
struct Nargs {
|
|
const size_t min;
|
|
const size_t max;
|
|
|
|
Nargs(size_t min_, size_t max_) : min{min_}, max{max_}
|
|
{
|
|
#ifndef ARGS_NOEXCEPT
|
|
if (max < min) {
|
|
throw UsageError("Nargs: max < min");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Nargs(size_t num_) : min{num_}, max{num_}
|
|
{
|
|
}
|
|
|
|
friend bool operator==(const Nargs &lhs, const Nargs &rhs)
|
|
{
|
|
return lhs.min == rhs.min && lhs.max == rhs.max;
|
|
}
|
|
|
|
friend bool operator!=(const Nargs &lhs, const Nargs &rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
};
|
|
|
|
/** Base class for all match types
|
|
*/
|
|
class Base {
|
|
private:
|
|
Options options = {};
|
|
|
|
protected:
|
|
bool matched = false;
|
|
const std::string help;
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
mutable Error error = Error::None;
|
|
mutable std::string errorMsg;
|
|
#endif
|
|
|
|
public:
|
|
Base(const std::string &help_, Options options_ = {}) : options(options_), help(help_)
|
|
{
|
|
}
|
|
virtual ~Base()
|
|
{
|
|
}
|
|
|
|
Options GetOptions() const noexcept
|
|
{
|
|
return options;
|
|
}
|
|
|
|
bool IsRequired() const noexcept
|
|
{
|
|
return (GetOptions() & Options::Required) != Options::None;
|
|
}
|
|
|
|
virtual bool Matched() const noexcept
|
|
{
|
|
return matched;
|
|
}
|
|
|
|
virtual void Validate(const std::string &, const std::string &) const
|
|
{
|
|
}
|
|
|
|
operator bool() const noexcept
|
|
{
|
|
return Matched();
|
|
}
|
|
|
|
virtual std::vector<std::tuple<std::string, std::string, unsigned>>
|
|
GetDescription(const HelpParams &, const unsigned indentLevel) const
|
|
{
|
|
std::tuple<std::string, std::string, unsigned> description;
|
|
std::get<1>(description) = help;
|
|
std::get<2>(description) = indentLevel;
|
|
return {std::move(description)};
|
|
}
|
|
|
|
virtual std::vector<Command *> GetCommands()
|
|
{
|
|
return {};
|
|
}
|
|
|
|
virtual bool IsGroup() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
virtual PositionalBase *GetNextPositional()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
virtual std::vector<FlagBase *> GetAllFlags()
|
|
{
|
|
return {};
|
|
}
|
|
|
|
virtual bool HasFlag() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual bool HasPositional() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual bool HasCommand() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual std::vector<std::string> GetProgramLine(const HelpParams &) const
|
|
{
|
|
return {};
|
|
}
|
|
|
|
/// Sets a kick-out value for building subparsers
|
|
void KickOut(bool kickout_) noexcept
|
|
{
|
|
if (kickout_) {
|
|
options = options | Options::KickOut;
|
|
}
|
|
else {
|
|
options = static_cast<Options>(static_cast<int>(options) & ~static_cast<int>(Options::KickOut));
|
|
}
|
|
}
|
|
|
|
/// Gets the kick-out value for building subparsers
|
|
bool KickOut() const noexcept
|
|
{
|
|
return (options & Options::KickOut) != Options::None;
|
|
}
|
|
|
|
virtual void Reset() noexcept
|
|
{
|
|
matched = false;
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::None;
|
|
errorMsg.clear();
|
|
#endif
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual Error GetError() const
|
|
{
|
|
return error;
|
|
}
|
|
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual std::string GetErrorMsg() const
|
|
{
|
|
return errorMsg;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/** Base class for all match types that have a name
|
|
*/
|
|
class NamedBase : public Base {
|
|
protected:
|
|
const std::string name;
|
|
bool kickout = false;
|
|
std::string defaultString;
|
|
bool defaultStringManual = false;
|
|
std::vector<std::string> choicesStrings;
|
|
bool choicesStringManual = false;
|
|
|
|
virtual std::string GetDefaultString(const HelpParams &) const
|
|
{
|
|
return {};
|
|
}
|
|
|
|
virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const
|
|
{
|
|
return {};
|
|
}
|
|
|
|
virtual std::string GetNameString(const HelpParams &) const
|
|
{
|
|
return Name();
|
|
}
|
|
|
|
void AddDescriptionPostfix(std::string &dest, const bool isManual, const std::string &manual,
|
|
bool isGenerated, const std::string &generated, const std::string &str) const
|
|
{
|
|
if (isManual && !manual.empty()) {
|
|
dest += str;
|
|
dest += manual;
|
|
}
|
|
else if (!isManual && isGenerated && !generated.empty()) {
|
|
dest += str;
|
|
dest += generated;
|
|
}
|
|
}
|
|
|
|
public:
|
|
NamedBase(const std::string &name_, const std::string &help_, Options options_ = {})
|
|
: Base(help_, options_), name(name_)
|
|
{
|
|
}
|
|
virtual ~NamedBase()
|
|
{
|
|
}
|
|
|
|
/** Sets default value string that will be added to argument description.
|
|
* Use empty string to disable it for this argument.
|
|
*/
|
|
void HelpDefault(const std::string &str)
|
|
{
|
|
defaultStringManual = true;
|
|
defaultString = str;
|
|
}
|
|
|
|
/** Gets default value string that will be added to argument description.
|
|
*/
|
|
std::string HelpDefault(const HelpParams ¶ms) const
|
|
{
|
|
return defaultStringManual ? defaultString : GetDefaultString(params);
|
|
}
|
|
|
|
/** Sets choices strings that will be added to argument description.
|
|
* Use empty vector to disable it for this argument.
|
|
*/
|
|
void HelpChoices(const std::vector<std::string> &array)
|
|
{
|
|
choicesStringManual = true;
|
|
choicesStrings = array;
|
|
}
|
|
|
|
/** Gets choices strings that will be added to argument description.
|
|
*/
|
|
std::vector<std::string> HelpChoices(const HelpParams ¶ms) const
|
|
{
|
|
return choicesStringManual ? choicesStrings : GetChoicesStrings(params);
|
|
}
|
|
|
|
virtual std::vector<std::tuple<std::string, std::string, unsigned>>
|
|
GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override
|
|
{
|
|
std::tuple<std::string, std::string, unsigned> description;
|
|
std::get<0>(description) = GetNameString(params);
|
|
std::get<1>(description) = help;
|
|
std::get<2>(description) = indentLevel;
|
|
|
|
AddDescriptionPostfix(std::get<1>(description), choicesStringManual,
|
|
detail::Join(choicesStrings, ", "), params.addChoices,
|
|
detail::Join(GetChoicesStrings(params), ", "), params.choiceString);
|
|
AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault,
|
|
GetDefaultString(params), params.defaultString);
|
|
|
|
return {std::move(description)};
|
|
}
|
|
|
|
virtual std::string Name() const
|
|
{
|
|
return name;
|
|
}
|
|
};
|
|
|
|
namespace detail {
|
|
template <typename T> using vector = std::vector<T, std::allocator<T>>;
|
|
|
|
template <typename K, typename T>
|
|
using unordered_map =
|
|
std::unordered_map<K, T, std::hash<K>, std::equal_to<K>, std::allocator<std::pair<const K, T>>>;
|
|
|
|
template <typename S, typename T> class is_streamable {
|
|
template <typename SS, typename TT>
|
|
static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
|
|
|
|
template <typename, typename> static auto test(...) -> std::false_type;
|
|
|
|
public:
|
|
using type = decltype(test<S, T>(0));
|
|
};
|
|
|
|
template <typename T> using IsConvertableToString = typename is_streamable<std::ostringstream, T>::type;
|
|
|
|
template <typename T>
|
|
typename std::enable_if<IsConvertableToString<T>::value, std::string>::type ToString(const T &value)
|
|
{
|
|
std::ostringstream s;
|
|
s << value;
|
|
return s.str();
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<!IsConvertableToString<T>::value, std::string>::type ToString(const T &)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
template <typename T> std::vector<std::string> MapKeysToStrings(const T &map)
|
|
{
|
|
std::vector<std::string> res;
|
|
using K = typename std::decay<decltype(std::begin(map)->first)>::type;
|
|
if (IsConvertableToString<K>::value) {
|
|
for (const auto &p : map) {
|
|
res.push_back(detail::ToString(p.first));
|
|
}
|
|
|
|
std::sort(res.begin(), res.end());
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/** Base class for all flag options
|
|
*/
|
|
class FlagBase : public NamedBase {
|
|
protected:
|
|
const Matcher matcher;
|
|
|
|
virtual std::string GetNameString(const HelpParams ¶ms) const override
|
|
{
|
|
const std::string postfix =
|
|
!params.showValueName || NumberOfArguments() == 0 ? std::string() : Name();
|
|
std::string flags;
|
|
const auto flagStrings = matcher.GetFlagStrings();
|
|
const bool useValueNameOnce = flagStrings.size() == 1 ? false : params.useValueNameOnce;
|
|
for (auto it = flagStrings.begin(); it != flagStrings.end(); ++it) {
|
|
auto &flag = *it;
|
|
if (it != flagStrings.begin()) {
|
|
flags += ", ";
|
|
}
|
|
|
|
flags += flag.isShort ? params.shortPrefix : params.longPrefix;
|
|
flags += flag.str();
|
|
|
|
if (!postfix.empty() && (!useValueNameOnce || it + 1 == flagStrings.end())) {
|
|
flags += flag.isShort ? params.shortSeparator : params.longSeparator;
|
|
flags += params.valueOpen + postfix + params.valueClose;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
public:
|
|
FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const bool extraError_ = false)
|
|
: NamedBase(name_, help_, extraError_ ? Options::Single : Options()), matcher(std::move(matcher_))
|
|
{
|
|
}
|
|
|
|
FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_)
|
|
: NamedBase(name_, help_, options_), matcher(std::move(matcher_))
|
|
{
|
|
}
|
|
|
|
virtual ~FlagBase()
|
|
{
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &flag) override
|
|
{
|
|
if (matcher.Match(flag)) {
|
|
if ((GetOptions() & Options::Single) != Options::None && matched) {
|
|
std::ostringstream problem;
|
|
problem << "Flag '" << flag.str()
|
|
<< "' was passed multiple times, but is only allowed to be passed once";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Extra;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw ExtraError(problem.str());
|
|
#endif
|
|
}
|
|
matched = true;
|
|
return this;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual std::vector<FlagBase *> GetAllFlags() override
|
|
{
|
|
return {this};
|
|
}
|
|
|
|
const Matcher &GetMatcher() const
|
|
{
|
|
return matcher;
|
|
}
|
|
|
|
virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override
|
|
{
|
|
if (!Matched() && IsRequired()) {
|
|
std::ostringstream problem;
|
|
problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Required;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw RequiredError(problem.str());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
virtual std::vector<std::string> GetProgramLine(const HelpParams ¶ms) const override
|
|
{
|
|
if (!params.proglineShowFlags) {
|
|
return {};
|
|
}
|
|
|
|
const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name();
|
|
const EitherFlag flag =
|
|
params.proglinePreferShortFlags ? matcher.GetShortOrAny() : matcher.GetLongOrAny();
|
|
std::string res = flag.str(params.shortPrefix, params.longPrefix);
|
|
if (!postfix.empty()) {
|
|
res += params.proglineValueOpen + postfix + params.proglineValueClose;
|
|
}
|
|
|
|
return {IsRequired() ? params.proglineRequiredOpen + res + params.proglineRequiredClose
|
|
: params.proglineNonrequiredOpen + res + params.proglineNonrequiredClose};
|
|
}
|
|
|
|
virtual bool HasFlag() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual Error GetError() const override
|
|
{
|
|
const auto nargs = NumberOfArguments();
|
|
if (nargs.min > nargs.max) {
|
|
return Error::Usage;
|
|
}
|
|
|
|
const auto matcherError = matcher.GetError();
|
|
if (matcherError != Error::None) {
|
|
return matcherError;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
/** Defines how many values can be consumed by this option.
|
|
*
|
|
* \return closed interval [min, max]
|
|
*/
|
|
virtual Nargs NumberOfArguments() const noexcept = 0;
|
|
|
|
/** Parse values of this option.
|
|
*
|
|
* \param value Vector of values. It's size must be in NumberOfArguments() interval.
|
|
*/
|
|
virtual void ParseValue(const std::vector<std::string> &value) = 0;
|
|
};
|
|
|
|
/** Base class for value-accepting flag options
|
|
*/
|
|
class ValueFlagBase : public FlagBase {
|
|
public:
|
|
ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const bool extraError_ = false)
|
|
: FlagBase(name_, help_, std::move(matcher_), extraError_)
|
|
{
|
|
}
|
|
ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_)
|
|
: FlagBase(name_, help_, std::move(matcher_), options_)
|
|
{
|
|
}
|
|
virtual ~ValueFlagBase()
|
|
{
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
class CompletionFlag : public ValueFlagBase {
|
|
public:
|
|
std::vector<std::string> reply;
|
|
size_t cword = 0;
|
|
std::string syntax;
|
|
|
|
template <typename GroupClass>
|
|
CompletionFlag(GroupClass &group_, Matcher &&matcher_)
|
|
: ValueFlagBase("completion", "completion flag", std::move(matcher_), Options::Hidden)
|
|
{
|
|
group_.AddCompletion(*this);
|
|
}
|
|
|
|
virtual ~CompletionFlag()
|
|
{
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &value_) override
|
|
{
|
|
syntax = value_.at(0);
|
|
const std::string &raw = value_.at(1);
|
|
bool failed = false;
|
|
|
|
const auto firstNonSpace = std::find_if_not(
|
|
raw.begin(), raw.end(), [](char c) { return std::isspace(static_cast<unsigned char>(c)) != 0; });
|
|
|
|
// Reject explicit signs: cword must be a plain non-negative
|
|
// decimal index. istringstream would otherwise silently
|
|
// accept "+1".
|
|
if (firstNonSpace != raw.end() && (*firstNonSpace == '-' || *firstNonSpace == '+')) {
|
|
failed = true;
|
|
}
|
|
|
|
size_t parsed = 0;
|
|
if (!failed) {
|
|
std::istringstream ss(raw);
|
|
// Use the C locale so that the cword index parses
|
|
// consistently regardless of any std::locale::global call
|
|
// elsewhere in the process. A locale with a non-empty
|
|
// grouping facet would otherwise reject digit-only inputs
|
|
// like "12" when grouping rules expect separators.
|
|
ss.imbue(std::locale::classic());
|
|
ss >> parsed;
|
|
if (ss.fail()) {
|
|
failed = true;
|
|
}
|
|
else {
|
|
char extra;
|
|
if (ss >> extra) {
|
|
failed = true;
|
|
}
|
|
else if (!ss.eof()) {
|
|
failed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Parse;
|
|
errorMsg = "Argument 'completion' received invalid value type '" + raw + "'";
|
|
#else
|
|
std::ostringstream problem;
|
|
problem << "Argument 'completion' received invalid value type '" << raw << "'";
|
|
throw ParseError(problem.str());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
cword = parsed;
|
|
}
|
|
|
|
/** Get the completion reply
|
|
*/
|
|
std::string Get() noexcept
|
|
{
|
|
return detail::Join(reply, "\n");
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
ValueFlagBase::Reset();
|
|
cword = 0;
|
|
syntax.clear();
|
|
reply.clear();
|
|
}
|
|
};
|
|
|
|
/** Base class for positional options
|
|
*/
|
|
class PositionalBase : public NamedBase {
|
|
protected:
|
|
bool ready;
|
|
|
|
public:
|
|
PositionalBase(const std::string &name_, const std::string &help_, Options options_ = {})
|
|
: NamedBase(name_, help_, options_), ready(true)
|
|
{
|
|
}
|
|
virtual ~PositionalBase()
|
|
{
|
|
}
|
|
|
|
bool Ready()
|
|
{
|
|
return ready;
|
|
}
|
|
|
|
virtual void ParseValue(const std::string &value_) = 0;
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
matched = false;
|
|
ready = true;
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::None;
|
|
errorMsg.clear();
|
|
#endif
|
|
}
|
|
|
|
virtual PositionalBase *GetNextPositional() override
|
|
{
|
|
return Ready() ? this : nullptr;
|
|
}
|
|
|
|
virtual bool HasPositional() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual std::vector<std::string> GetProgramLine(const HelpParams ¶ms) const override
|
|
{
|
|
return {IsRequired() ? params.proglineRequiredOpen + Name() + params.proglineRequiredClose
|
|
: params.proglineNonrequiredOpen + Name() + params.proglineNonrequiredClose};
|
|
}
|
|
|
|
virtual void Validate(const std::string &, const std::string &) const override
|
|
{
|
|
if (IsRequired() && !Matched()) {
|
|
std::ostringstream problem;
|
|
problem << "Option '" << Name() << "' is required";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Required;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw RequiredError(problem.str());
|
|
#endif
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Class for all kinds of validating groups, including ArgumentParser
|
|
*/
|
|
class Group : public Base {
|
|
private:
|
|
std::vector<Base *> children;
|
|
std::function<bool(const Group &)> validator;
|
|
|
|
public:
|
|
/** Default validators
|
|
*/
|
|
struct Validators {
|
|
static bool Xor(const Group &group)
|
|
{
|
|
return group.MatchedChildren() == 1;
|
|
}
|
|
|
|
static bool AtLeastOne(const Group &group)
|
|
{
|
|
return group.MatchedChildren() >= 1;
|
|
}
|
|
|
|
static bool AtMostOne(const Group &group)
|
|
{
|
|
return group.MatchedChildren() <= 1;
|
|
}
|
|
|
|
static bool All(const Group &group)
|
|
{
|
|
return group.Children().size() == group.MatchedChildren();
|
|
}
|
|
|
|
static bool AllOrNone(const Group &group)
|
|
{
|
|
return (All(group) || None(group));
|
|
}
|
|
|
|
static bool AllChildGroups(const Group &group)
|
|
{
|
|
return std::none_of(
|
|
std::begin(group.Children()), std::end(group.Children()),
|
|
[](const Base *child) -> bool { return child->IsGroup() && !child->Matched(); });
|
|
}
|
|
|
|
static bool DontCare(const Group &)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static bool CareTooMuch(const Group &)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static bool None(const Group &group)
|
|
{
|
|
return group.MatchedChildren() == 0;
|
|
}
|
|
};
|
|
/// If help is empty, this group will not be printed in help output
|
|
Group(const std::string &help_ = std::string(),
|
|
const std::function<bool(const Group &)> &validator_ = Validators::DontCare, Options options_ = {})
|
|
: Base(help_, options_), validator(validator_)
|
|
{
|
|
}
|
|
/// If help is empty, this group will not be printed in help output
|
|
Group(Group &group_, const std::string &help_ = std::string(),
|
|
const std::function<bool(const Group &)> &validator_ = Validators::DontCare, Options options_ = {})
|
|
: Base(help_, options_), validator(validator_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
virtual ~Group()
|
|
{
|
|
}
|
|
|
|
/** Append a child to this Group.
|
|
*/
|
|
void Add(Base &child)
|
|
{
|
|
children.emplace_back(&child);
|
|
}
|
|
|
|
/** Get all this group's children
|
|
*/
|
|
const std::vector<Base *> &Children() const
|
|
{
|
|
return children;
|
|
}
|
|
|
|
/** Return the first FlagBase that matches flag, or nullptr
|
|
*
|
|
* \param flag The flag with prefixes stripped
|
|
* \return the first matching FlagBase pointer, or nullptr if there is no match
|
|
*/
|
|
virtual FlagBase *Match(const EitherFlag &flag) override
|
|
{
|
|
for (Base *child : Children()) {
|
|
if (FlagBase *match = child->Match(flag)) {
|
|
return match;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual std::vector<FlagBase *> GetAllFlags() override
|
|
{
|
|
std::vector<FlagBase *> res;
|
|
for (Base *child : Children()) {
|
|
auto childRes = child->GetAllFlags();
|
|
res.insert(res.end(), childRes.begin(), childRes.end());
|
|
}
|
|
return res;
|
|
}
|
|
|
|
virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override
|
|
{
|
|
for (Base *child : Children()) {
|
|
child->Validate(shortPrefix, longPrefix);
|
|
}
|
|
}
|
|
|
|
/** Get the next ready positional, or nullptr if there is none
|
|
*
|
|
* \return the first ready PositionalBase pointer, or nullptr if there is no match
|
|
*/
|
|
virtual PositionalBase *GetNextPositional() override
|
|
{
|
|
for (Base *child : Children()) {
|
|
if (auto next = child->GetNextPositional()) {
|
|
return next;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/** Get whether this has any FlagBase children
|
|
*
|
|
* \return Whether or not there are any FlagBase children
|
|
*/
|
|
virtual bool HasFlag() const override
|
|
{
|
|
return std::any_of(Children().begin(), Children().end(),
|
|
[](Base *child) { return child->HasFlag(); });
|
|
}
|
|
|
|
/** Get whether this has any PositionalBase children
|
|
*
|
|
* \return Whether or not there are any PositionalBase children
|
|
*/
|
|
virtual bool HasPositional() const override
|
|
{
|
|
return std::any_of(Children().begin(), Children().end(),
|
|
[](Base *child) { return child->HasPositional(); });
|
|
}
|
|
|
|
/** Get whether this has any Command children
|
|
*
|
|
* \return Whether or not there are any Command children
|
|
*/
|
|
virtual bool HasCommand() const override
|
|
{
|
|
return std::any_of(Children().begin(), Children().end(),
|
|
[](Base *child) { return child->HasCommand(); });
|
|
}
|
|
|
|
/** Count the number of matched children this group has
|
|
*/
|
|
std::vector<Base *>::size_type MatchedChildren() const
|
|
{
|
|
// Cast to avoid warnings from -Wsign-conversion
|
|
return static_cast<std::vector<Base *>::size_type>(
|
|
std::count_if(std::begin(Children()), std::end(Children()),
|
|
[](const Base *child) { return child->Matched(); }));
|
|
}
|
|
|
|
/** Whether or not this group matches validation
|
|
*/
|
|
virtual bool Matched() const noexcept override
|
|
{
|
|
return validator(*this);
|
|
}
|
|
|
|
/** Get validation
|
|
*/
|
|
bool Get() const
|
|
{
|
|
return Matched();
|
|
}
|
|
|
|
/** Get all the child descriptions for help generation
|
|
*/
|
|
virtual std::vector<std::tuple<std::string, std::string, unsigned>>
|
|
GetDescription(const HelpParams ¶ms, const unsigned int indent) const override
|
|
{
|
|
std::vector<std::tuple<std::string, std::string, unsigned int>> descriptions;
|
|
|
|
// Push that group description on the back if not empty
|
|
unsigned addindent = 0;
|
|
if (!help.empty()) {
|
|
descriptions.emplace_back(help, "", indent);
|
|
addindent = 1;
|
|
}
|
|
|
|
for (Base *child : Children()) {
|
|
if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) {
|
|
continue;
|
|
}
|
|
|
|
auto groupDescriptions = child->GetDescription(params, indent + addindent);
|
|
descriptions.insert(std::end(descriptions),
|
|
std::make_move_iterator(std::begin(groupDescriptions)),
|
|
std::make_move_iterator(std::end(groupDescriptions)));
|
|
}
|
|
return descriptions;
|
|
}
|
|
|
|
/** Get the names of positional parameters
|
|
*/
|
|
virtual std::vector<std::string> GetProgramLine(const HelpParams ¶ms) const override
|
|
{
|
|
std::vector<std::string> names;
|
|
for (Base *child : Children()) {
|
|
if ((child->GetOptions() & Options::HiddenFromUsage) != Options::None) {
|
|
continue;
|
|
}
|
|
|
|
auto groupNames = child->GetProgramLine(params);
|
|
names.insert(std::end(names), std::make_move_iterator(std::begin(groupNames)),
|
|
std::make_move_iterator(std::end(groupNames)));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
virtual std::vector<Command *> GetCommands() override
|
|
{
|
|
std::vector<Command *> res;
|
|
for (const auto &child : Children()) {
|
|
auto subparsers = child->GetCommands();
|
|
res.insert(std::end(res), std::begin(subparsers), std::end(subparsers));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
virtual bool IsGroup() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
Base::Reset();
|
|
|
|
for (auto &child : Children()) {
|
|
child->Reset();
|
|
}
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::None;
|
|
errorMsg.clear();
|
|
#endif
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual Error GetError() const override
|
|
{
|
|
if (error != Error::None) {
|
|
return error;
|
|
}
|
|
|
|
auto it = std::find_if(Children().begin(), Children().end(),
|
|
[](const Base *child) { return child->GetError() != Error::None; });
|
|
if (it == Children().end()) {
|
|
return Error::None;
|
|
}
|
|
else {
|
|
return (*it)->GetError();
|
|
}
|
|
}
|
|
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual std::string GetErrorMsg() const override
|
|
{
|
|
if (error != Error::None) {
|
|
return errorMsg;
|
|
}
|
|
|
|
auto it = std::find_if(Children().begin(), Children().end(),
|
|
[](const Base *child) { return child->GetError() != Error::None; });
|
|
if (it == Children().end()) {
|
|
return "";
|
|
}
|
|
else {
|
|
return (*it)->GetErrorMsg();
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/** Class for using global options in ArgumentParser.
|
|
*/
|
|
class GlobalOptions : public Group {
|
|
public:
|
|
GlobalOptions(Group &base, Base &options_) : Group(base, {}, Group::Validators::DontCare, Options::Global)
|
|
{
|
|
Add(options_);
|
|
}
|
|
};
|
|
|
|
/** Utility class for building subparsers with coroutines/callbacks.
|
|
*
|
|
* Brief example:
|
|
* \code
|
|
* Command command(argumentParser, "command", "my command", [](args::Subparser &s)
|
|
* {
|
|
* // your command flags/positionals
|
|
* s.Parse(); //required
|
|
* //your command code
|
|
* });
|
|
* \endcode
|
|
*
|
|
* For ARGS_NOEXCEPT mode don't forget to check `s.GetError()` after `s.Parse()`
|
|
* and return if it isn't equals to args::Error::None.
|
|
*
|
|
* \sa Command
|
|
*/
|
|
class Subparser : public Group {
|
|
private:
|
|
std::vector<std::string> args;
|
|
std::vector<std::string> kicked;
|
|
ArgumentParser *parser = nullptr;
|
|
const HelpParams &helpParams;
|
|
const Command &command;
|
|
bool isParsed = false;
|
|
|
|
public:
|
|
Subparser(std::vector<std::string> args_, ArgumentParser &parser_, const Command &command_,
|
|
const HelpParams &helpParams_)
|
|
: Group({}, Validators::AllChildGroups), args(std::move(args_)), parser(&parser_),
|
|
helpParams(helpParams_), command(command_)
|
|
{
|
|
}
|
|
|
|
Subparser(const Command &command_, const HelpParams &helpParams_)
|
|
: Group({}, Validators::AllChildGroups), helpParams(helpParams_), command(command_)
|
|
{
|
|
}
|
|
|
|
Subparser(const Subparser &) = delete;
|
|
Subparser(Subparser &&) = delete;
|
|
Subparser &operator=(const Subparser &) = delete;
|
|
Subparser &operator=(Subparser &&) = delete;
|
|
|
|
const Command &GetCommand()
|
|
{
|
|
return command;
|
|
}
|
|
|
|
/** (INTERNAL) Determines whether Parse was called or not.
|
|
*/
|
|
bool IsParsed() const
|
|
{
|
|
return isParsed;
|
|
}
|
|
|
|
/** Continue parsing arguments for new command.
|
|
*/
|
|
void Parse();
|
|
|
|
/** Returns a vector of kicked out arguments.
|
|
*
|
|
* \sa Base::KickOut
|
|
*/
|
|
const std::vector<std::string> &KickedOut() const noexcept
|
|
{
|
|
return kicked;
|
|
}
|
|
};
|
|
|
|
/** Main class for building subparsers.
|
|
*
|
|
* /sa Subparser
|
|
*/
|
|
class Command : public Group {
|
|
private:
|
|
friend class Subparser;
|
|
|
|
std::string name;
|
|
std::string help;
|
|
std::string description;
|
|
std::string epilog;
|
|
std::string proglinePostfix;
|
|
|
|
std::function<void(Subparser &)> parserCoroutine;
|
|
bool commandIsRequired = true;
|
|
Command *selectedCommand = nullptr;
|
|
|
|
mutable std::vector<std::tuple<std::string, std::string, unsigned>> subparserDescription;
|
|
mutable std::vector<std::string> subparserProgramLine;
|
|
mutable bool subparserHasFlag = false;
|
|
mutable bool subparserHasPositional = false;
|
|
mutable bool subparserHasCommand = false;
|
|
#ifdef ARGS_NOEXCEPT
|
|
mutable Error subparserError = Error::None;
|
|
#endif
|
|
mutable Subparser *subparser = nullptr;
|
|
|
|
protected:
|
|
class RaiiSubparser {
|
|
public:
|
|
RaiiSubparser(ArgumentParser &parser_, std::vector<std::string> args_);
|
|
RaiiSubparser(const Command &command_, const HelpParams ¶ms_);
|
|
|
|
~RaiiSubparser()
|
|
{
|
|
command.subparser = oldSubparser;
|
|
}
|
|
|
|
Subparser &Parser()
|
|
{
|
|
return parser;
|
|
}
|
|
|
|
private:
|
|
const Command &command;
|
|
Subparser parser;
|
|
Subparser *oldSubparser;
|
|
};
|
|
|
|
Command() = default;
|
|
|
|
std::function<void(Subparser &)> &GetCoroutine()
|
|
{
|
|
return selectedCommand != nullptr ? selectedCommand->GetCoroutine() : parserCoroutine;
|
|
}
|
|
|
|
Command &SelectedCommand()
|
|
{
|
|
Command *res = this;
|
|
while (res->selectedCommand != nullptr) {
|
|
res = res->selectedCommand;
|
|
}
|
|
|
|
return *res;
|
|
}
|
|
|
|
const Command &SelectedCommand() const
|
|
{
|
|
const Command *res = this;
|
|
while (res->selectedCommand != nullptr) {
|
|
res = res->selectedCommand;
|
|
}
|
|
|
|
return *res;
|
|
}
|
|
|
|
void UpdateSubparserHelp(const HelpParams ¶ms) const
|
|
{
|
|
if (parserCoroutine) {
|
|
RaiiSubparser coro(*this, params);
|
|
#ifndef ARGS_NOEXCEPT
|
|
try {
|
|
parserCoroutine(coro.Parser());
|
|
}
|
|
catch (args::SubparserError &) {
|
|
}
|
|
#else
|
|
parserCoroutine(coro.Parser());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public:
|
|
Command(Group &base_, std::string name_, std::string help_,
|
|
std::function<void(Subparser &)> coroutine_ = {})
|
|
: name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_))
|
|
{
|
|
base_.Add(*this);
|
|
}
|
|
|
|
/** The description that appears on the prog line after options
|
|
*/
|
|
const std::string &ProglinePostfix() const
|
|
{
|
|
return proglinePostfix;
|
|
}
|
|
|
|
/** The description that appears on the prog line after options
|
|
*/
|
|
void ProglinePostfix(const std::string &proglinePostfix_)
|
|
{
|
|
this->proglinePostfix = proglinePostfix_;
|
|
}
|
|
|
|
/** The description that appears above options
|
|
*/
|
|
const std::string &Description() const
|
|
{
|
|
return description;
|
|
}
|
|
/** The description that appears above options
|
|
*/
|
|
|
|
void Description(const std::string &description_)
|
|
{
|
|
this->description = description_;
|
|
}
|
|
|
|
/** The description that appears below options
|
|
*/
|
|
const std::string &Epilog() const
|
|
{
|
|
return epilog;
|
|
}
|
|
|
|
/** The description that appears below options
|
|
*/
|
|
void Epilog(const std::string &epilog_)
|
|
{
|
|
this->epilog = epilog_;
|
|
}
|
|
|
|
/** The name of command
|
|
*/
|
|
const std::string &Name() const
|
|
{
|
|
return name;
|
|
}
|
|
|
|
/** The description of command
|
|
*/
|
|
const std::string &Help() const
|
|
{
|
|
return help;
|
|
}
|
|
|
|
/** If value is true, parser will fail if no command was parsed.
|
|
*
|
|
* Default: true.
|
|
*/
|
|
void RequireCommand(bool value)
|
|
{
|
|
commandIsRequired = value;
|
|
}
|
|
|
|
virtual bool IsGroup() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual bool Matched() const noexcept override
|
|
{
|
|
return Base::Matched();
|
|
}
|
|
|
|
operator bool() const noexcept
|
|
{
|
|
return Matched();
|
|
}
|
|
|
|
void Match() noexcept
|
|
{
|
|
matched = true;
|
|
}
|
|
|
|
void SelectCommand(Command *c) noexcept
|
|
{
|
|
selectedCommand = c;
|
|
|
|
if (c != nullptr) {
|
|
c->Match();
|
|
}
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &flag) override
|
|
{
|
|
if (selectedCommand != nullptr) {
|
|
if (auto *res = selectedCommand->Match(flag)) {
|
|
return res;
|
|
}
|
|
|
|
for (auto *child : Children()) {
|
|
if ((child->GetOptions() & Options::Global) != Options::None) {
|
|
if (auto *res = child->Match(flag)) {
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (subparser != nullptr) {
|
|
return subparser->Match(flag);
|
|
}
|
|
|
|
return Matched() ? Group::Match(flag) : nullptr;
|
|
}
|
|
|
|
virtual std::vector<FlagBase *> GetAllFlags() override
|
|
{
|
|
std::vector<FlagBase *> res;
|
|
|
|
if (!Matched()) {
|
|
return res;
|
|
}
|
|
|
|
for (auto *child : Children()) {
|
|
if (selectedCommand == nullptr || (child->GetOptions() & Options::Global) != Options::None) {
|
|
auto childFlags = child->GetAllFlags();
|
|
res.insert(res.end(), childFlags.begin(), childFlags.end());
|
|
}
|
|
}
|
|
|
|
if (selectedCommand != nullptr) {
|
|
auto childFlags = selectedCommand->GetAllFlags();
|
|
res.insert(res.end(), childFlags.begin(), childFlags.end());
|
|
}
|
|
|
|
if (subparser != nullptr) {
|
|
auto childFlags = subparser->GetAllFlags();
|
|
res.insert(res.end(), childFlags.begin(), childFlags.end());
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
virtual PositionalBase *GetNextPositional() override
|
|
{
|
|
if (selectedCommand != nullptr) {
|
|
if (auto *res = selectedCommand->GetNextPositional()) {
|
|
return res;
|
|
}
|
|
|
|
for (auto *child : Children()) {
|
|
if ((child->GetOptions() & Options::Global) != Options::None) {
|
|
if (auto *res = child->GetNextPositional()) {
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (subparser != nullptr) {
|
|
return subparser->GetNextPositional();
|
|
}
|
|
|
|
return Matched() ? Group::GetNextPositional() : nullptr;
|
|
}
|
|
|
|
virtual bool HasFlag() const override
|
|
{
|
|
return subparserHasFlag || Group::HasFlag();
|
|
}
|
|
|
|
virtual bool HasPositional() const override
|
|
{
|
|
return subparserHasPositional || Group::HasPositional();
|
|
}
|
|
|
|
virtual bool HasCommand() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> GetCommandProgramLine(const HelpParams ¶ms) const
|
|
{
|
|
UpdateSubparserHelp(params);
|
|
|
|
std::vector<std::string> res;
|
|
|
|
if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions &&
|
|
!params.proglineShowFlags) {
|
|
res.push_back(params.proglineOptions);
|
|
}
|
|
|
|
auto group_res = Group::GetProgramLine(params);
|
|
std::move(std::move(group_res).begin(), std::move(group_res).end(), std::back_inserter(res));
|
|
|
|
res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end());
|
|
|
|
if (!params.proglineCommand.empty() && (Group::HasCommand() || subparserHasCommand)) {
|
|
res.insert(res.begin(),
|
|
commandIsRequired ? params.proglineCommand : "[" + params.proglineCommand + "]");
|
|
}
|
|
|
|
if (!Name().empty()) {
|
|
res.insert(res.begin(), Name());
|
|
}
|
|
|
|
if (!ProglinePostfix().empty()) {
|
|
std::string line;
|
|
for (auto c : ProglinePostfix()) {
|
|
if (std::isspace(static_cast<unsigned char>(c))) {
|
|
if (!line.empty()) {
|
|
res.push_back(line);
|
|
line.clear();
|
|
}
|
|
|
|
if (c == '\n') {
|
|
res.push_back("\n");
|
|
}
|
|
}
|
|
else {
|
|
line += c;
|
|
}
|
|
}
|
|
|
|
if (!line.empty()) {
|
|
res.push_back(line);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
virtual std::vector<std::string> GetProgramLine(const HelpParams ¶ms) const override
|
|
{
|
|
if (!Matched()) {
|
|
return {};
|
|
}
|
|
|
|
return GetCommandProgramLine(params);
|
|
}
|
|
|
|
virtual std::vector<Command *> GetCommands() override
|
|
{
|
|
if (selectedCommand != nullptr) {
|
|
return selectedCommand->GetCommands();
|
|
}
|
|
|
|
if (Matched()) {
|
|
return Group::GetCommands();
|
|
}
|
|
|
|
return {this};
|
|
}
|
|
|
|
virtual std::vector<std::tuple<std::string, std::string, unsigned>>
|
|
GetDescription(const HelpParams ¶ms, const unsigned int indent) const override
|
|
{
|
|
std::vector<std::tuple<std::string, std::string, unsigned>> descriptions;
|
|
unsigned addindent = 0;
|
|
|
|
UpdateSubparserHelp(params);
|
|
|
|
if (!Matched()) {
|
|
if (params.showCommandFullHelp) {
|
|
std::ostringstream s;
|
|
bool empty = true;
|
|
for (const auto &progline : GetCommandProgramLine(params)) {
|
|
if (!empty) {
|
|
s << ' ';
|
|
}
|
|
else {
|
|
empty = false;
|
|
}
|
|
|
|
s << progline;
|
|
}
|
|
|
|
descriptions.emplace_back(s.str(), "", indent);
|
|
}
|
|
else {
|
|
descriptions.emplace_back(Name(), help, indent);
|
|
}
|
|
|
|
if (!params.showCommandChildren && !params.showCommandFullHelp) {
|
|
return descriptions;
|
|
}
|
|
|
|
addindent = 1;
|
|
}
|
|
|
|
if (params.showCommandFullHelp && !Matched()) {
|
|
descriptions.emplace_back("", "", indent + addindent);
|
|
descriptions.emplace_back(Description().empty() ? Help() : Description(), "", indent + addindent);
|
|
descriptions.emplace_back("", "", indent + addindent);
|
|
}
|
|
|
|
for (Base *child : Children()) {
|
|
if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) {
|
|
continue;
|
|
}
|
|
|
|
auto groupDescriptions = child->GetDescription(params, indent + addindent);
|
|
descriptions.insert(std::end(descriptions),
|
|
std::make_move_iterator(std::begin(groupDescriptions)),
|
|
std::make_move_iterator(std::end(groupDescriptions)));
|
|
}
|
|
|
|
for (auto childDescription : subparserDescription) {
|
|
std::get<2>(childDescription) += indent + addindent;
|
|
descriptions.push_back(std::move(childDescription));
|
|
}
|
|
|
|
if (params.showCommandFullHelp && !Matched()) {
|
|
descriptions.emplace_back("", "", indent + addindent);
|
|
if (!Epilog().empty()) {
|
|
descriptions.emplace_back(Epilog(), "", indent + addindent);
|
|
descriptions.emplace_back("", "", indent + addindent);
|
|
}
|
|
}
|
|
|
|
return descriptions;
|
|
}
|
|
|
|
virtual void Validate(const std::string &shortprefix, const std::string &longprefix) const override
|
|
{
|
|
if (!Matched()) {
|
|
return;
|
|
}
|
|
|
|
auto onValidationError = [&] {
|
|
std::ostringstream problem;
|
|
problem << "Group validation failed somewhere!";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Validation;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw ValidationError(problem.str());
|
|
#endif
|
|
};
|
|
|
|
for (Base *child : Children()) {
|
|
if (child->IsGroup() && !child->Matched()) {
|
|
onValidationError();
|
|
}
|
|
|
|
child->Validate(shortprefix, longprefix);
|
|
}
|
|
|
|
if (subparser != nullptr) {
|
|
subparser->Validate(shortprefix, longprefix);
|
|
if (!subparser->Matched()) {
|
|
onValidationError();
|
|
}
|
|
}
|
|
|
|
if (selectedCommand == nullptr && commandIsRequired && (Group::HasCommand() || subparserHasCommand)) {
|
|
std::ostringstream problem;
|
|
problem << "Command is required";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Validation;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw ValidationError(problem.str());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
Group::Reset();
|
|
selectedCommand = nullptr;
|
|
subparserProgramLine.clear();
|
|
subparserDescription.clear();
|
|
subparserHasFlag = false;
|
|
subparserHasPositional = false;
|
|
subparserHasCommand = false;
|
|
#ifdef ARGS_NOEXCEPT
|
|
subparserError = Error::None;
|
|
#endif
|
|
}
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
/// Only for ARGS_NOEXCEPT
|
|
virtual Error GetError() const override
|
|
{
|
|
if (!Matched()) {
|
|
return Error::None;
|
|
}
|
|
|
|
if (error != Error::None) {
|
|
return error;
|
|
}
|
|
|
|
if (subparserError != Error::None) {
|
|
return subparserError;
|
|
}
|
|
|
|
return Group::GetError();
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/** The main user facing command line argument parser class
|
|
*/
|
|
class ArgumentParser : public Command {
|
|
friend class Subparser;
|
|
|
|
private:
|
|
std::string longprefix;
|
|
std::string shortprefix;
|
|
|
|
std::string longseparator;
|
|
|
|
std::string terminator;
|
|
|
|
bool allowJoinedShortValue = true;
|
|
bool allowJoinedLongValue = true;
|
|
bool allowSeparateShortValue = true;
|
|
bool allowSeparateLongValue = true;
|
|
|
|
bool readCompletion = false;
|
|
CompletionFlag *completion = nullptr;
|
|
|
|
protected:
|
|
enum class OptionType { LongFlag, ShortFlag, Positional };
|
|
|
|
OptionType ParseOption(const std::string &s, bool allowEmpty = false)
|
|
{
|
|
if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length())) {
|
|
return OptionType::LongFlag;
|
|
}
|
|
|
|
if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length())) {
|
|
return OptionType::ShortFlag;
|
|
}
|
|
|
|
return OptionType::Positional;
|
|
}
|
|
|
|
template <typename It> bool Complete(FlagBase &flag, It it, It end)
|
|
{
|
|
auto nextIt = it;
|
|
if (!readCompletion || (++nextIt != end)) {
|
|
return false;
|
|
}
|
|
|
|
const auto &chunk = *it;
|
|
for (auto &choice : flag.HelpChoices(helpParams)) {
|
|
AddCompletionReply(chunk, choice);
|
|
}
|
|
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw Completion(completion->Get());
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/** (INTERNAL) Parse flag's values
|
|
*
|
|
* \param arg The string to display in error message as a flag name
|
|
* \param[in, out] it The iterator to first value. It will point to the last value
|
|
* \param end The end iterator
|
|
* \param joinedArg Joined value (e.g. bar in --foo=bar)
|
|
* \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd)
|
|
* \param[out] values The vector to store parsed arg's values
|
|
*/
|
|
template <typename It>
|
|
std::string ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end,
|
|
const bool allowSeparate, const bool allowJoined, const bool hasJoined,
|
|
const std::string &joinedArg, const bool canDiscardJoined,
|
|
std::vector<std::string> &values)
|
|
{
|
|
values.clear();
|
|
|
|
Nargs nargs = flag.NumberOfArguments();
|
|
|
|
if (hasJoined && !allowJoined && nargs.min != 0) {
|
|
return "Flag '" + arg + "' was passed a joined argument, but these are disallowed";
|
|
}
|
|
|
|
if (hasJoined) {
|
|
if (!canDiscardJoined || nargs.max != 0) {
|
|
values.push_back(joinedArg);
|
|
}
|
|
}
|
|
else if (!allowSeparate) {
|
|
if (nargs.min != 0) {
|
|
return "Flag '" + arg + "' was passed a separate argument, but these are disallowed";
|
|
}
|
|
}
|
|
else {
|
|
auto valueIt = it;
|
|
++valueIt;
|
|
|
|
while (valueIt != end && values.size() < nargs.max &&
|
|
(values.size() < nargs.min || ParseOption(*valueIt) == OptionType::Positional)) {
|
|
if (Complete(flag, valueIt, end)) {
|
|
// Park `it` on the completion position rather than
|
|
// `end`. In ARGS_NOEXCEPT mode Complete returns
|
|
// true (no throw), so the caller's for-loop will
|
|
// run its ++it after we return; advancing an
|
|
// already-end iterator is undefined behavior and
|
|
// causes a subsequent out-of-bounds read of the
|
|
// arg vector. Since Complete only fires when
|
|
// ++nextIt == end, valueIt is the last element,
|
|
// and ++(it=valueIt) safely lands on end.
|
|
it = valueIt;
|
|
return "";
|
|
}
|
|
|
|
values.push_back(*valueIt);
|
|
++it;
|
|
++valueIt;
|
|
}
|
|
}
|
|
|
|
if (values.size() > nargs.max) {
|
|
return "Passed an argument into a non-argument flag: " + arg;
|
|
}
|
|
else if (values.size() < nargs.min) {
|
|
if (nargs.min == 1 && nargs.max == 1) {
|
|
return "Flag '" + arg + "' requires an argument but received none";
|
|
}
|
|
else if (nargs.min == 1) {
|
|
return "Flag '" + arg + "' requires at least one argument but received none";
|
|
}
|
|
else if (nargs.min != nargs.max) {
|
|
return "Flag '" + arg + "' requires at least " + std::to_string(nargs.min) +
|
|
" arguments but received " + std::to_string(values.size());
|
|
}
|
|
else {
|
|
return "Flag '" + arg + "' requires " + std::to_string(nargs.min) +
|
|
" arguments but received " + std::to_string(values.size());
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
template <typename It> bool ParseLong(It &it, It end)
|
|
{
|
|
const auto &chunk = *it;
|
|
const auto argchunk = chunk.substr(longprefix.size());
|
|
// Try to separate it, in case of a separator:
|
|
const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator);
|
|
// If the separator is in the argument, separate it.
|
|
const auto arg = (separator != argchunk.npos ? std::string(argchunk, 0, separator) : argchunk);
|
|
const auto joined =
|
|
(separator != argchunk.npos ? argchunk.substr(separator + longseparator.size()) : std::string());
|
|
|
|
if (auto flag = Match(arg)) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
// Match() may set the flag's error (e.g. Error::Extra when
|
|
// Options::Single is violated). In non-noexcept mode that
|
|
// path throws and parsing stops before the value is read;
|
|
// in noexcept mode we must mirror that and skip the value
|
|
// parsing so the previously-stored value is preserved.
|
|
if (flag->GetError() != Error::None) {
|
|
return false;
|
|
}
|
|
#endif
|
|
std::vector<std::string> values;
|
|
const std::string errorMessage =
|
|
ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue,
|
|
separator != argchunk.npos, joined, false, values);
|
|
if (!errorMessage.empty()) {
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if (!readCompletion) {
|
|
flag->ParseValue(values);
|
|
#ifdef ARGS_NOEXCEPT
|
|
// Non-noexcept ParseValue paths throw on Help, reader
|
|
// failure, or Map miss, which halts parsing. Mirror
|
|
// that here so a later parser-level error (e.g. an
|
|
// unknown flag) cannot shadow the flag's error in
|
|
// ArgumentParser::GetError().
|
|
if (flag->GetError() != Error::None) {
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (flag->KickOut()) {
|
|
++it;
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
const std::string errorMessage("Flag could not be matched: " + arg);
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename It> bool ParseShort(It &it, It end)
|
|
{
|
|
const auto &chunk = *it;
|
|
const auto argchunk = chunk.substr(shortprefix.size());
|
|
for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit) {
|
|
const auto arg = *argit;
|
|
|
|
if (auto flag = Match(arg)) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
// See ParseLong: if Match recorded an error
|
|
// (e.g. Options::Single violation), bail before the
|
|
// value is parsed so the prior value is preserved.
|
|
if (flag->GetError() != Error::None) {
|
|
return false;
|
|
}
|
|
#endif
|
|
const std::string value(argit + 1, std::end(argchunk));
|
|
std::vector<std::string> values;
|
|
const std::string errorMessage =
|
|
ParseArgsValues(*flag, std::string(1, arg), it, end, allowSeparateShortValue,
|
|
allowJoinedShortValue, !value.empty(), value, !value.empty(), values);
|
|
|
|
if (!errorMessage.empty()) {
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if (!readCompletion) {
|
|
flag->ParseValue(values);
|
|
#ifdef ARGS_NOEXCEPT
|
|
// See ParseLong: ensure a flag-level error from
|
|
// ParseValue (Help, Parse, Map) halts parsing so
|
|
// it cannot be shadowed by a later parser error.
|
|
if (flag->GetError() != Error::None) {
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (flag->KickOut()) {
|
|
++it;
|
|
return false;
|
|
}
|
|
|
|
if (!values.empty()) {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
const std::string errorMessage("Flag could not be matched: '" + std::string(1, arg) + "'");
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AddCompletionReply(const std::string &cur, const std::string &choice)
|
|
{
|
|
if (cur.empty() || choice.find(cur) == 0) {
|
|
if (completion->syntax == "bash" && ParseOption(choice) == OptionType::LongFlag &&
|
|
choice.find(longseparator) != std::string::npos) {
|
|
completion->reply.push_back(choice.substr(choice.find(longseparator) + longseparator.size()));
|
|
}
|
|
else {
|
|
completion->reply.push_back(choice);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename It> bool Complete(It it, It end)
|
|
{
|
|
auto nextIt = it;
|
|
if (!readCompletion || (++nextIt != end)) {
|
|
return false;
|
|
}
|
|
|
|
const auto &chunk = *it;
|
|
auto pos = GetNextPositional();
|
|
std::vector<Command *> commands = GetCommands();
|
|
const auto optionType = ParseOption(chunk, true);
|
|
|
|
if (!commands.empty() && (chunk.empty() || optionType == OptionType::Positional)) {
|
|
for (auto &cmd : commands) {
|
|
if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) {
|
|
AddCompletionReply(chunk, cmd->Name());
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
bool hasPositionalCompletion = true;
|
|
|
|
if (!commands.empty()) {
|
|
for (auto &cmd : commands) {
|
|
if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) {
|
|
AddCompletionReply(chunk, cmd->Name());
|
|
}
|
|
}
|
|
}
|
|
else if (pos) {
|
|
if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None) {
|
|
auto choices = pos->HelpChoices(helpParams);
|
|
hasPositionalCompletion = !choices.empty() || optionType != OptionType::Positional;
|
|
for (auto &choice : choices) {
|
|
AddCompletionReply(chunk, choice);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasPositionalCompletion) {
|
|
auto flags = GetAllFlags();
|
|
for (auto flag : flags) {
|
|
if ((flag->GetOptions() & Options::HiddenFromCompletion) != Options::None) {
|
|
continue;
|
|
}
|
|
|
|
auto &matcher = flag->GetMatcher();
|
|
if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) {
|
|
for (auto &flagName : matcher.GetFlagStrings()) {
|
|
if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix))) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (optionType == OptionType::LongFlag && allowJoinedLongValue) {
|
|
const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator);
|
|
// Only attempt joined-value completion when the
|
|
// separator lies at or past the long prefix, so
|
|
// there is a (possibly empty) flag name between
|
|
// them. With a custom longseparator that overlaps
|
|
// the prefix (e.g. LongSeparator("-") under the
|
|
// default "--" prefix), an attacker-controlled
|
|
// completion word like "--x" puts the separator
|
|
// inside the prefix, making `arg` shorter than
|
|
// longprefix. arg.substr(longprefix.size()) would
|
|
// then throw std::out_of_range, which escapes the
|
|
// parser as a non-args exception (bypassing the
|
|
// documented catch(args::Error) idiom) and is
|
|
// thrown even under ARGS_NOEXCEPT.
|
|
if (separator != chunk.npos && separator >= longprefix.size()) {
|
|
std::string arg(chunk, 0, separator);
|
|
if (auto flag = this->Match(arg.substr(longprefix.size()))) {
|
|
for (auto &choice : flag->HelpChoices(helpParams)) {
|
|
AddCompletionReply(chunk, arg + longseparator + choice);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (optionType == OptionType::ShortFlag && allowJoinedShortValue) {
|
|
if (chunk.size() > shortprefix.size() + 1) {
|
|
auto arg = chunk.at(shortprefix.size());
|
|
// TODO: support -abcVALUE where a and b take no value
|
|
if (auto flag = this->Match(arg)) {
|
|
for (auto &choice : flag->HelpChoices(helpParams)) {
|
|
AddCompletionReply(chunk, shortprefix + arg + choice);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw Completion(completion->Get());
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
template <typename It> It Parse(It begin, It end)
|
|
{
|
|
bool terminated = false;
|
|
std::vector<Command *> commands = GetCommands();
|
|
|
|
// Check all arg chunks
|
|
for (auto it = begin; it != end; ++it) {
|
|
if (Complete(it, end)) {
|
|
return end;
|
|
}
|
|
|
|
const auto &chunk = *it;
|
|
|
|
if (!terminated && chunk == terminator) {
|
|
terminated = true;
|
|
}
|
|
else if (!terminated && ParseOption(chunk) == OptionType::LongFlag) {
|
|
if (!ParseLong(it, end)) {
|
|
return it;
|
|
}
|
|
}
|
|
else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag) {
|
|
if (!ParseShort(it, end)) {
|
|
return it;
|
|
}
|
|
}
|
|
else if (!terminated && !commands.empty()) {
|
|
auto itCommand = std::find_if(commands.begin(), commands.end(),
|
|
[&chunk](Command *c) { return c->Name() == chunk; });
|
|
if (itCommand == commands.end()) {
|
|
const std::string errorMessage("Unknown command: " + chunk);
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return it;
|
|
#endif
|
|
}
|
|
|
|
SelectCommand(*itCommand);
|
|
|
|
if (const auto &coroutine = GetCoroutine()) {
|
|
++it;
|
|
RaiiSubparser coro(*this, std::vector<std::string>(it, end));
|
|
coroutine(coro.Parser());
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = GetError();
|
|
if (error != Error::None) {
|
|
return end;
|
|
}
|
|
|
|
if (!coro.Parser().IsParsed()) {
|
|
error = Error::Usage;
|
|
return end;
|
|
}
|
|
#else
|
|
if (!coro.Parser().IsParsed()) {
|
|
throw UsageError("Subparser::Parse was not called");
|
|
}
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
commands = GetCommands();
|
|
}
|
|
else {
|
|
auto pos = GetNextPositional();
|
|
if (pos) {
|
|
pos->ParseValue(chunk);
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (pos->GetError() != Error::None) {
|
|
return it;
|
|
}
|
|
#endif
|
|
|
|
if (pos->KickOut()) {
|
|
return ++it;
|
|
}
|
|
}
|
|
else {
|
|
const std::string errorMessage(
|
|
"Passed in argument, but no positional arguments were ready to receive it: " + chunk);
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw ParseError(errorMessage);
|
|
#else
|
|
error = Error::Parse;
|
|
errorMsg = errorMessage;
|
|
return it;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!readCompletion && completion != nullptr && completion->Matched()) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (completion->GetError() != Error::None) {
|
|
error = completion->GetError();
|
|
if (errorMsg.empty()) {
|
|
errorMsg = completion->GetErrorMsg();
|
|
}
|
|
return it;
|
|
}
|
|
|
|
error = Error::Completion;
|
|
#endif
|
|
readCompletion = true;
|
|
++it;
|
|
const auto argsLeft = static_cast<size_t>(std::distance(it, end));
|
|
if (completion->cword == 0 || argsLeft <= 1 || completion->cword >= argsLeft) {
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw Completion("");
|
|
#else
|
|
return end;
|
|
#endif
|
|
}
|
|
|
|
++it;
|
|
std::vector<std::string> curArgs;
|
|
curArgs.reserve(completion->cword);
|
|
auto curIt = it;
|
|
for (size_t idx = 0; idx < completion->cword && curIt != end; ++idx, ++curIt) {
|
|
curArgs.push_back(*curIt);
|
|
}
|
|
|
|
if (completion->syntax == "bash") {
|
|
// bash tokenizes --flag=value as --flag=value
|
|
// Security fix: Use size_t arithmetic throughout to avoid conversion issues
|
|
for (size_t idx = 0; idx < curArgs.size();) {
|
|
if (idx > 0 && curArgs[idx] == "=") {
|
|
size_t prev_idx = idx - 1; // Safe since we checked idx > 0
|
|
curArgs[prev_idx] += "=";
|
|
size_t next_idx = 0;
|
|
if (SafeAdd<size_t>(idx, static_cast<size_t>(1), next_idx) &&
|
|
next_idx < curArgs.size()) {
|
|
curArgs[prev_idx] += curArgs[next_idx];
|
|
// Erase the '=' token and the following value token.
|
|
size_t erase_end = 0;
|
|
if (SafeAdd<size_t>(next_idx, static_cast<size_t>(1), erase_end)) {
|
|
typedef std::vector<std::string>::difference_type diff_t;
|
|
curArgs.erase(curArgs.begin() + static_cast<diff_t>(idx),
|
|
curArgs.begin() + static_cast<diff_t>(erase_end));
|
|
}
|
|
}
|
|
else {
|
|
// Safe erase of single '=' token at the end
|
|
typedef std::vector<std::string>::difference_type diff_t;
|
|
curArgs.erase(curArgs.begin() + static_cast<diff_t>(idx));
|
|
}
|
|
// Do not increment idx - next element slides into current position
|
|
}
|
|
else {
|
|
++idx;
|
|
}
|
|
}
|
|
}
|
|
#ifndef ARGS_NOEXCEPT
|
|
try {
|
|
Parse(curArgs.begin(), curArgs.end());
|
|
throw Completion("");
|
|
}
|
|
catch (Completion &) {
|
|
throw;
|
|
}
|
|
catch (args::Error &) {
|
|
throw Completion("");
|
|
}
|
|
#else
|
|
// Discard the nested Parse's return value: it points
|
|
// into the local curArgs vector, which is destroyed
|
|
// when this function returns, leaving the caller with
|
|
// a dangling iterator that would be compared against
|
|
// the outer `end` in ParseCLI. Return the outer
|
|
// `end` instead so the iterator stays in the caller's
|
|
// container.
|
|
Parse(curArgs.begin(), curArgs.end());
|
|
error = Error::Completion;
|
|
errorMsg.clear();
|
|
return end;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
Validate(shortprefix, longprefix);
|
|
return end;
|
|
}
|
|
|
|
public:
|
|
HelpParams helpParams;
|
|
|
|
ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string())
|
|
{
|
|
Description(description_);
|
|
Epilog(epilog_);
|
|
LongPrefix("--");
|
|
ShortPrefix("-");
|
|
LongSeparator("=");
|
|
Terminator("--");
|
|
SetArgumentSeparations(true, true, true, true);
|
|
matched = true;
|
|
}
|
|
|
|
void AddCompletion(CompletionFlag &completionFlag)
|
|
{
|
|
completion = &completionFlag;
|
|
Add(completionFlag);
|
|
}
|
|
|
|
/** The program name for help generation
|
|
*/
|
|
const std::string &Prog() const
|
|
{
|
|
return helpParams.programName;
|
|
}
|
|
/** The program name for help generation
|
|
*/
|
|
void Prog(const std::string &prog_)
|
|
{
|
|
this->helpParams.programName = prog_;
|
|
}
|
|
|
|
/** The prefix for long flags
|
|
*/
|
|
const std::string &LongPrefix() const
|
|
{
|
|
return longprefix;
|
|
}
|
|
/** The prefix for long flags
|
|
*/
|
|
void LongPrefix(const std::string &longprefix_)
|
|
{
|
|
this->longprefix = longprefix_;
|
|
this->helpParams.longPrefix = longprefix_;
|
|
}
|
|
|
|
/** The prefix for short flags
|
|
*/
|
|
const std::string &ShortPrefix() const
|
|
{
|
|
return shortprefix;
|
|
}
|
|
/** The prefix for short flags
|
|
*/
|
|
void ShortPrefix(const std::string &shortprefix_)
|
|
{
|
|
this->shortprefix = shortprefix_;
|
|
this->helpParams.shortPrefix = shortprefix_;
|
|
}
|
|
|
|
/** The separator for long flags
|
|
*/
|
|
const std::string &LongSeparator() const
|
|
{
|
|
return longseparator;
|
|
}
|
|
/** The separator for long flags
|
|
*/
|
|
void LongSeparator(const std::string &longseparator_)
|
|
{
|
|
if (longseparator_.empty()) {
|
|
const std::string errorMessage("longseparator can not be set to empty");
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Usage;
|
|
errorMsg = errorMessage;
|
|
#else
|
|
throw UsageError(errorMessage);
|
|
#endif
|
|
}
|
|
else {
|
|
this->longseparator = longseparator_;
|
|
this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " ";
|
|
}
|
|
}
|
|
|
|
/** The terminator that forcibly separates flags from positionals
|
|
*/
|
|
const std::string &Terminator() const
|
|
{
|
|
return terminator;
|
|
}
|
|
/** The terminator that forcibly separates flags from positionals
|
|
*/
|
|
void Terminator(const std::string &terminator_)
|
|
{
|
|
this->terminator = terminator_;
|
|
}
|
|
|
|
/** Get the current argument separation parameters.
|
|
*
|
|
* See SetArgumentSeparations for details on what each one means.
|
|
*/
|
|
void GetArgumentSeparations(bool &allowJoinedShortValue_, bool &allowJoinedLongValue_,
|
|
bool &allowSeparateShortValue_, bool &allowSeparateLongValue_) const
|
|
{
|
|
allowJoinedShortValue_ = this->allowJoinedShortValue;
|
|
allowJoinedLongValue_ = this->allowJoinedLongValue;
|
|
allowSeparateShortValue_ = this->allowSeparateShortValue;
|
|
allowSeparateLongValue_ = this->allowSeparateLongValue;
|
|
}
|
|
|
|
/** Change allowed option separation.
|
|
*
|
|
* \param allowJoinedShortValue_ Allow a short flag that accepts an argument to be passed its argument
|
|
* immediately next to it (ie. in the same argv field)
|
|
* \param allowJoinedLongValue_ Allow a long flag that accepts an argument to be passed its argument
|
|
* separated by the longseparator (ie. in the same argv field)
|
|
* \param allowSeparateShortValue_ Allow a short flag that accepts an argument to be passed its argument
|
|
* separated by whitespace (ie. in the next argv field)
|
|
* \param allowSeparateLongValue_ Allow a long flag that accepts an argument to be passed its argument
|
|
* separated by whitespace (ie. in the next argv field)
|
|
*/
|
|
void SetArgumentSeparations(const bool allowJoinedShortValue_, const bool allowJoinedLongValue_,
|
|
const bool allowSeparateShortValue_, const bool allowSeparateLongValue_)
|
|
{
|
|
this->allowJoinedShortValue = allowJoinedShortValue_;
|
|
this->allowJoinedLongValue = allowJoinedLongValue_;
|
|
this->allowSeparateShortValue = allowSeparateShortValue_;
|
|
this->allowSeparateLongValue = allowSeparateLongValue_;
|
|
|
|
this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " ";
|
|
this->helpParams.shortSeparator = allowJoinedShortValue ? "" : " ";
|
|
}
|
|
|
|
/** Pass the help menu into an ostream
|
|
*/
|
|
void Help(std::ostream &help_) const
|
|
{
|
|
auto &command = SelectedCommand();
|
|
const auto &commandDescription =
|
|
command.Description().empty() ? command.Help() : command.Description();
|
|
const auto desc_indent = helpParams.descriptionindent;
|
|
const auto effective_desc_width =
|
|
(helpParams.width > desc_indent) ? helpParams.width - desc_indent : 0;
|
|
const auto description_text = Wrap(commandDescription, effective_desc_width);
|
|
const auto epilog_text = Wrap(command.Epilog(), effective_desc_width);
|
|
|
|
const bool hasoptions = command.HasFlag();
|
|
const bool hasarguments = command.HasPositional();
|
|
|
|
std::vector<std::string> prognameline;
|
|
prognameline.push_back(helpParams.usageString);
|
|
prognameline.push_back(Prog());
|
|
auto commandProgLine = command.GetProgramLine(helpParams);
|
|
prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end());
|
|
|
|
const auto prog_sum = helpParams.progindent + helpParams.progtailindent;
|
|
const auto effective_prog_width = (helpParams.width > prog_sum) ? helpParams.width - prog_sum : 0;
|
|
const auto effective_prog_first =
|
|
(helpParams.width > helpParams.progindent) ? helpParams.width - helpParams.progindent : 0;
|
|
const auto proglines =
|
|
Wrap(prognameline.begin(), prognameline.end(), effective_prog_width, effective_prog_first);
|
|
auto progit = std::begin(proglines);
|
|
if (progit != std::end(proglines)) {
|
|
help_ << std::string(helpParams.progindent, ' ') << *progit << '\n';
|
|
++progit;
|
|
}
|
|
for (; progit != std::end(proglines); ++progit) {
|
|
help_ << std::string(helpParams.progtailindent, ' ') << *progit << '\n';
|
|
}
|
|
|
|
help_ << '\n';
|
|
|
|
if (!description_text.empty()) {
|
|
for (const auto &line : description_text) {
|
|
help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n";
|
|
}
|
|
help_ << "\n";
|
|
}
|
|
|
|
bool lastDescriptionIsNewline = false;
|
|
|
|
if (!helpParams.optionsString.empty()) {
|
|
help_ << std::string(helpParams.progindent, ' ') << helpParams.optionsString << "\n\n";
|
|
}
|
|
|
|
for (const auto &desc : command.GetDescription(helpParams, 0)) {
|
|
lastDescriptionIsNewline = std::get<0>(desc).empty() && std::get<1>(desc).empty();
|
|
const auto groupindent = std::get<2>(desc) * helpParams.eachgroupindent;
|
|
const auto flag_sum = helpParams.flagindent + helpParams.helpindent + helpParams.gutter;
|
|
const auto effective_flag_width = (helpParams.width > flag_sum) ? helpParams.width - flag_sum : 0;
|
|
const auto flags = Wrap(std::get<0>(desc), effective_flag_width);
|
|
const auto info_sum = helpParams.helpindent + groupindent;
|
|
const auto effective_info_width = (helpParams.width > info_sum) ? helpParams.width - info_sum : 0;
|
|
const auto info = Wrap(std::get<1>(desc), effective_info_width);
|
|
|
|
std::string::size_type flagssize = 0;
|
|
for (auto flagsit = std::begin(flags); flagsit != std::end(flags); ++flagsit) {
|
|
if (flagsit != std::begin(flags)) {
|
|
help_ << '\n';
|
|
}
|
|
help_ << std::string(groupindent + helpParams.flagindent, ' ') << *flagsit;
|
|
flagssize = Glyphs(*flagsit);
|
|
}
|
|
|
|
auto infoit = std::begin(info);
|
|
// groupindent is on both sides of this inequality, and therefore can be removed
|
|
if ((helpParams.flagindent + flagssize + helpParams.gutter) > helpParams.helpindent ||
|
|
infoit == std::end(info) || helpParams.addNewlineBeforeDescription) {
|
|
help_ << '\n';
|
|
}
|
|
else {
|
|
// groupindent is on both sides of the minus sign, and therefore doesn't actually need to be
|
|
// in here
|
|
const auto indent_sum = helpParams.flagindent + flagssize;
|
|
const auto effective_space =
|
|
(helpParams.helpindent > indent_sum) ? helpParams.helpindent - indent_sum : 0;
|
|
help_ << std::string(effective_space, ' ') << *infoit << '\n';
|
|
++infoit;
|
|
}
|
|
for (; infoit != std::end(info); ++infoit) {
|
|
help_ << std::string(groupindent + helpParams.helpindent, ' ') << *infoit << '\n';
|
|
}
|
|
}
|
|
if (hasoptions && hasarguments && helpParams.showTerminator) {
|
|
lastDescriptionIsNewline = false;
|
|
const auto effective_term_width =
|
|
(helpParams.width > helpParams.flagindent) ? helpParams.width - helpParams.flagindent : 0;
|
|
for (const auto &item : Wrap(std::string("\"") + terminator +
|
|
"\" can be used to terminate flag options and force all "
|
|
"following arguments to be treated as positional options",
|
|
effective_term_width)) {
|
|
help_ << std::string(helpParams.flagindent, ' ') << item << '\n';
|
|
}
|
|
}
|
|
|
|
if (!lastDescriptionIsNewline) {
|
|
help_ << "\n";
|
|
}
|
|
|
|
for (const auto &line : epilog_text) {
|
|
help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n";
|
|
}
|
|
}
|
|
|
|
/** Generate a help menu as a string.
|
|
*
|
|
* \return the help text as a single string
|
|
*/
|
|
std::string Help() const
|
|
{
|
|
std::ostringstream help_;
|
|
Help(help_);
|
|
return help_.str();
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
Command::Reset();
|
|
matched = true;
|
|
readCompletion = false;
|
|
}
|
|
|
|
/** Parse all arguments.
|
|
*
|
|
* \param begin an iterator to the beginning of the argument list
|
|
* \param end an iterator to the past-the-end element of the argument list
|
|
* \return the iterator after the last parsed value. Only useful for kick-out
|
|
*/
|
|
template <typename It> It ParseArgs(It begin, It end)
|
|
{
|
|
// Reset all Matched statuses and errors
|
|
Reset();
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = GetError();
|
|
if (error != Error::None) {
|
|
return end;
|
|
}
|
|
#endif
|
|
return Parse(begin, end);
|
|
}
|
|
|
|
/** Parse all arguments.
|
|
*
|
|
* \param args an iterable of the arguments
|
|
* \return the iterator after the last parsed value. Only useful for kick-out
|
|
*/
|
|
template <typename T> auto ParseArgs(const T &args) -> decltype(std::begin(args))
|
|
{
|
|
return ParseArgs(std::begin(args), std::end(args));
|
|
}
|
|
|
|
/** Convenience function to parse the CLI from argc and argv
|
|
*
|
|
* Just assigns the program name and vectorizes arguments for passing into ParseArgs()
|
|
*
|
|
* \return whether or not all arguments were parsed. This works for detecting kick-out, but is generally
|
|
* useless as it can't do anything with it.
|
|
*/
|
|
bool ParseCLI(const int argc, const char *const *argv)
|
|
{
|
|
if (argc > 0 && argv != nullptr && argv[0] != nullptr && Prog().empty()) {
|
|
Prog(argv[0]);
|
|
}
|
|
|
|
std::vector<std::string> args;
|
|
if (argc > 1 && argv != nullptr) {
|
|
args.assign(argv + 1, argv + argc);
|
|
}
|
|
|
|
return ParseArgs(args) == std::end(args);
|
|
}
|
|
|
|
template <typename T> bool ParseCLI(const T &args)
|
|
{
|
|
return ParseArgs(args) == std::end(args);
|
|
}
|
|
};
|
|
|
|
inline Command::RaiiSubparser::RaiiSubparser(ArgumentParser &parser_, std::vector<std::string> args_)
|
|
: command(parser_.SelectedCommand()), parser(std::move(args_), parser_, command, parser_.helpParams),
|
|
oldSubparser(command.subparser)
|
|
{
|
|
command.subparser = &parser;
|
|
}
|
|
|
|
inline Command::RaiiSubparser::RaiiSubparser(const Command &command_, const HelpParams ¶ms_)
|
|
: command(command_), parser(command, params_), oldSubparser(command.subparser)
|
|
{
|
|
command.subparser = &parser;
|
|
}
|
|
|
|
inline void
|
|
Subparser::Parse()
|
|
{
|
|
isParsed = true;
|
|
Reset();
|
|
command.subparserDescription = GetDescription(helpParams, 0);
|
|
command.subparserHasFlag = HasFlag();
|
|
command.subparserHasPositional = HasPositional();
|
|
command.subparserHasCommand = HasCommand();
|
|
command.subparserProgramLine = GetProgramLine(helpParams);
|
|
if (parser == nullptr) {
|
|
#ifndef ARGS_NOEXCEPT
|
|
throw args::SubparserError();
|
|
#else
|
|
error = Error::Subparser;
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
auto it = parser->Parse(args.begin(), args.end());
|
|
command.Validate(parser->ShortPrefix(), parser->LongPrefix());
|
|
kicked.assign(it, args.end());
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
command.subparserError = GetError();
|
|
#endif
|
|
}
|
|
|
|
inline std::ostream &
|
|
operator<<(std::ostream &os, const ArgumentParser &parser)
|
|
{
|
|
parser.Help(os);
|
|
return os;
|
|
}
|
|
|
|
/** Boolean argument matcher
|
|
*/
|
|
class Flag : public FlagBase {
|
|
public:
|
|
Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Options options_)
|
|
: FlagBase(name_, help_, std::move(matcher_), options_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const bool extraError_ = false)
|
|
: Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None)
|
|
{
|
|
}
|
|
|
|
virtual ~Flag()
|
|
{
|
|
}
|
|
|
|
/** Get whether this was matched
|
|
*/
|
|
bool Get() const
|
|
{
|
|
return Matched();
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &) override
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Help flag class
|
|
*
|
|
* Works like a regular flag, but throws an instance of Help when it is matched
|
|
*/
|
|
class HelpFlag : public Flag {
|
|
public:
|
|
HelpFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Options options_ = {})
|
|
: Flag(group_, name_, help_, std::move(matcher_), options_)
|
|
{
|
|
}
|
|
|
|
virtual ~HelpFlag()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &)
|
|
{
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Help;
|
|
errorMsg = Name();
|
|
#else
|
|
throw Help(Name());
|
|
#endif
|
|
}
|
|
|
|
/** Get whether this was matched
|
|
*/
|
|
bool Get() const noexcept
|
|
{
|
|
return Matched();
|
|
}
|
|
};
|
|
|
|
/** A flag class that simply counts the number of times it's matched
|
|
*/
|
|
class CounterFlag : public Flag {
|
|
private:
|
|
const int startcount;
|
|
int count;
|
|
|
|
public:
|
|
CounterFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const int startcount_ = 0, Options options_ = {})
|
|
: Flag(group_, name_, help_, std::move(matcher_), options_), startcount(startcount_),
|
|
count(startcount_)
|
|
{
|
|
}
|
|
|
|
virtual ~CounterFlag()
|
|
{
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &arg) override
|
|
{
|
|
auto me = FlagBase::Match(arg);
|
|
if (me) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
// Suppress increment when FlagBase::Match recorded an
|
|
// error on this same call (e.g. Options::Single violated).
|
|
// In non-noexcept mode that path would have thrown before
|
|
// reaching here and the count would not have advanced.
|
|
if (GetError() != Error::None) {
|
|
return me;
|
|
}
|
|
#endif
|
|
++count;
|
|
}
|
|
return me;
|
|
}
|
|
|
|
/** Get the count
|
|
*/
|
|
int &Get() noexcept
|
|
{
|
|
return count;
|
|
}
|
|
|
|
int &operator*() noexcept
|
|
{
|
|
return count;
|
|
}
|
|
|
|
const int &operator*() const noexcept
|
|
{
|
|
return count;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
FlagBase::Reset();
|
|
count = startcount;
|
|
}
|
|
};
|
|
|
|
/** A flag class that calls a function when it's matched
|
|
*/
|
|
class ActionFlag : public FlagBase {
|
|
private:
|
|
std::function<void(const std::vector<std::string> &)> action;
|
|
Nargs nargs;
|
|
|
|
public:
|
|
ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Nargs nargs_, std::function<void(const std::vector<std::string> &)> action_,
|
|
Options options_ = {})
|
|
: FlagBase(name_, help_, std::move(matcher_), options_), action(std::move(action_)), nargs(nargs_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
std::function<void(const std::string &)> action_, Options options_ = {})
|
|
: FlagBase(name_, help_, std::move(matcher_), options_), nargs(1)
|
|
{
|
|
group_.Add(*this);
|
|
action = [action_](const std::vector<std::string> &a) { return action_(a.at(0)); };
|
|
}
|
|
|
|
ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
std::function<void()> action_, Options options_ = {})
|
|
: FlagBase(name_, help_, std::move(matcher_), options_), nargs(0)
|
|
{
|
|
group_.Add(*this);
|
|
action = [action_](const std::vector<std::string> &) { return action_(); };
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return nargs;
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &value) override
|
|
{
|
|
action(value);
|
|
}
|
|
};
|
|
|
|
/** A default Reader class for argument classes
|
|
*
|
|
* If destination type is assignable to std::string it uses an assignment to std::string.
|
|
* Otherwise ValueReader simply uses a std::istringstream to read into the destination type, and
|
|
* raises a ParseError if there are any characters left.
|
|
*/
|
|
struct ValueReader {
|
|
private:
|
|
template <typename T>
|
|
static typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value, bool>::type
|
|
HasUnsignedNegativeSign(const std::string &value)
|
|
{
|
|
const auto firstNonSpace = std::find_if_not(value.begin(), value.end(), [](char c) {
|
|
return std::isspace(static_cast<unsigned char>(c)) != 0;
|
|
});
|
|
|
|
return firstNonSpace != value.end() && *firstNonSpace == '-';
|
|
}
|
|
|
|
template <typename T>
|
|
static typename std::enable_if<!std::is_integral<T>::value || !std::is_unsigned<T>::value, bool>::type
|
|
HasUnsignedNegativeSign(const std::string &)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
template <typename T>
|
|
typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value &&
|
|
!std::is_same<T, char>::value && !std::is_same<T, signed char>::value &&
|
|
!std::is_same<T, unsigned char>::value,
|
|
bool>::type
|
|
ParseNumericValue(const std::string &value, T &destination)
|
|
{
|
|
if (HasUnsignedNegativeSign<T>(value)) {
|
|
return false;
|
|
}
|
|
|
|
const char *begin = value.c_str();
|
|
// The true end of the value, derived from its length rather than
|
|
// from the first NUL. strtoull/strtoll treat the buffer as a C
|
|
// string and stop at an embedded '\0', so checking `*end == '\0'`
|
|
// for "no trailing data" is defeated by a value like "12\0junk":
|
|
// end lands on the embedded NUL and the junk after it is silently
|
|
// accepted. Comparing against `stop` validates the whole string
|
|
// and matches the istringstream-based reader used for other types.
|
|
const char *const stop = begin + value.size();
|
|
|
|
// C++11-compatible: use strtoull/strtoll. Hardening retained from
|
|
// the original from_chars draft (errno save/restore, ERANGE check,
|
|
// narrowing range check, trailing-whitespace tolerance). No
|
|
// unconditional dependency on <charconv> / C++17.
|
|
const int saved_errno = errno;
|
|
errno = 0;
|
|
|
|
char *end = nullptr;
|
|
|
|
if (std::is_unsigned<T>::value) {
|
|
const unsigned long long parsed = std::strtoull(begin, &end, 0);
|
|
if (end == begin) {
|
|
errno = saved_errno;
|
|
return false;
|
|
}
|
|
while (end != stop && std::isspace(static_cast<unsigned char>(*end))) {
|
|
++end;
|
|
}
|
|
if (end != stop || errno == ERANGE ||
|
|
parsed > static_cast<unsigned long long>(std::numeric_limits<T>::max())) {
|
|
errno = saved_errno;
|
|
return false;
|
|
}
|
|
|
|
destination = static_cast<T>(parsed);
|
|
}
|
|
else {
|
|
const long long parsed = std::strtoll(begin, &end, 0);
|
|
if (end == begin) {
|
|
errno = saved_errno;
|
|
return false;
|
|
}
|
|
while (end != stop && std::isspace(static_cast<unsigned char>(*end))) {
|
|
++end;
|
|
}
|
|
if (end != stop || errno == ERANGE ||
|
|
parsed < static_cast<long long>(std::numeric_limits<T>::min()) ||
|
|
parsed > static_cast<long long>(std::numeric_limits<T>::max())) {
|
|
errno = saved_errno;
|
|
return false;
|
|
}
|
|
|
|
destination = static_cast<T>(parsed);
|
|
}
|
|
|
|
errno = saved_errno;
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<!std::is_integral<T>::value || std::is_same<T, bool>::value ||
|
|
std::is_same<T, char>::value || std::is_same<T, signed char>::value ||
|
|
std::is_same<T, unsigned char>::value,
|
|
bool>::type
|
|
ParseNumericValue(const std::string &value, T &destination)
|
|
{
|
|
std::istringstream ss(value);
|
|
// Pin parsing to the C locale so that the decimal separator and
|
|
// thousands grouping behavior do not silently depend on whatever
|
|
// std::locale::global was last set to elsewhere in the process.
|
|
// Without this, e.g. "3.14" parses as 3 (with ".14" trailing) in
|
|
// any locale whose numpunct facet treats ',' as the decimal point.
|
|
ss.imbue(std::locale::classic());
|
|
ss >> destination;
|
|
if (ss.fail()) {
|
|
return false;
|
|
}
|
|
|
|
// Check for trailing garbage by attempting to extract any remaining characters.
|
|
// Do not use 'ss >> std::ws' followed by peek(), as std::ws can set failbit
|
|
// on EOF, causing false rejection of valid input.
|
|
char extra = '\0';
|
|
ss >> std::ws >> extra;
|
|
// If extraction succeeded, there's trailing garbage (return false).
|
|
// If extraction failed due to EOF only (goodbit after ws extraction), it's valid (return true).
|
|
// If extraction failed for other reasons, it's invalid (return false).
|
|
if (ss.fail()) {
|
|
// Clear the failbit to check if EOF is the only issue
|
|
ss.clear(ss.rdstate() & ~std::ios::failbit);
|
|
return ss.eof();
|
|
}
|
|
// Extraction succeeded, meaning there's trailing garbage
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<!std::is_assignable<T, std::string>::value, bool>::type
|
|
operator()(const std::string &name, const std::string &value, T &destination)
|
|
{
|
|
const bool success = ParseNumericValue(value, destination);
|
|
if (!success) {
|
|
#ifdef ARGS_NOEXCEPT
|
|
(void)name;
|
|
return false;
|
|
#else
|
|
std::ostringstream problem;
|
|
problem << "Argument '" << name << "' received invalid value type '" << value << "'";
|
|
throw ParseError(problem.str());
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
typename std::enable_if<std::is_assignable<T, std::string>::value, bool>::type
|
|
operator()(const std::string &, const std::string &value, T &destination)
|
|
{
|
|
destination = value;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/** An argument-accepting flag class
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, typename Reader = ValueReader> class ValueFlag : public ValueFlagBase {
|
|
protected:
|
|
T value;
|
|
T defaultValue;
|
|
|
|
virtual std::string GetDefaultString(const HelpParams &) const override
|
|
{
|
|
return detail::ToString(defaultValue);
|
|
}
|
|
|
|
private:
|
|
Reader reader;
|
|
|
|
public:
|
|
ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const T &defaultValue_, Options options_)
|
|
: ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_),
|
|
defaultValue(defaultValue_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const T &defaultValue_ = T(), const bool extraError_ = false)
|
|
: ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_,
|
|
extraError_ ? Options::Single : Options::None)
|
|
{
|
|
}
|
|
|
|
ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Options options_)
|
|
: ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_)
|
|
{
|
|
}
|
|
|
|
virtual ~ValueFlag()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &values_) override
|
|
{
|
|
const std::string &value_ = values_.at(0);
|
|
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, this->value)) {
|
|
error = Error::Parse;
|
|
}
|
|
#else
|
|
reader(name, value_, this->value);
|
|
#endif
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
ValueFlagBase::Reset();
|
|
value = defaultValue;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &Get() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &operator*() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T &operator*() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T *operator->() noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T *operator->() const noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
/** Get the default value
|
|
*/
|
|
const T &GetDefault() noexcept
|
|
{
|
|
return defaultValue;
|
|
}
|
|
};
|
|
|
|
/** An optional argument-accepting flag class
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, typename Reader = ValueReader> class ImplicitValueFlag : public ValueFlag<T, Reader> {
|
|
protected:
|
|
T implicitValue;
|
|
|
|
public:
|
|
ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {})
|
|
: ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_),
|
|
implicitValue(implicitValue_)
|
|
{
|
|
}
|
|
|
|
ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const T &defaultValue_ = T(), Options options_ = {})
|
|
: ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_),
|
|
implicitValue(defaultValue_)
|
|
{
|
|
}
|
|
|
|
ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Options options_)
|
|
: ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue()
|
|
{
|
|
}
|
|
|
|
virtual ~ImplicitValueFlag()
|
|
{
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return {0, 1};
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &value_) override
|
|
{
|
|
if (value_.empty()) {
|
|
this->value = implicitValue;
|
|
}
|
|
else {
|
|
ValueFlag<T, Reader>::ParseValue(value_);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** A variadic arguments accepting flag class
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam List the list type that houses the values
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, template <typename...> class List = detail::vector, typename Reader = ValueReader>
|
|
class NargsValueFlag : public FlagBase {
|
|
protected:
|
|
List<T> values;
|
|
const List<T> defaultValues;
|
|
Nargs nargs;
|
|
Reader reader;
|
|
|
|
public:
|
|
typedef List<T> Container;
|
|
typedef T value_type;
|
|
typedef typename Container::allocator_type allocator_type;
|
|
typedef typename Container::pointer pointer;
|
|
typedef typename Container::const_pointer const_pointer;
|
|
typedef T &reference;
|
|
typedef const T &const_reference;
|
|
typedef typename Container::size_type size_type;
|
|
typedef typename Container::difference_type difference_type;
|
|
typedef typename Container::iterator iterator;
|
|
typedef typename Container::const_iterator const_iterator;
|
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
|
|
|
NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
Nargs nargs_, const List<T> &defaultValues_ = {}, Options options_ = {})
|
|
: FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_),
|
|
defaultValues(defaultValues_), nargs(nargs_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
virtual ~NargsValueFlag()
|
|
{
|
|
}
|
|
|
|
virtual Nargs NumberOfArguments() const noexcept override
|
|
{
|
|
return nargs;
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &values_) override
|
|
{
|
|
values.clear();
|
|
|
|
for (const std::string &value : values_) {
|
|
T v{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value, v)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value, v);
|
|
#endif
|
|
values.insert(std::end(values), v);
|
|
}
|
|
}
|
|
|
|
List<T> &Get() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
List<T> &operator*() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const List<T> &operator*() const noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
List<T> *operator->() noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const List<T> *operator->() const noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
iterator begin() noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator begin() const noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator cbegin() const noexcept
|
|
{
|
|
return values.cbegin();
|
|
}
|
|
|
|
iterator end() noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator end() const noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator cend() const noexcept
|
|
{
|
|
return values.cend();
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
FlagBase::Reset();
|
|
values = defaultValues;
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &arg) override
|
|
{
|
|
const bool wasMatched = Matched();
|
|
auto me = FlagBase::Match(arg);
|
|
if (me && !wasMatched) {
|
|
values.clear();
|
|
}
|
|
return me;
|
|
}
|
|
};
|
|
|
|
/** An argument-accepting flag class that pushes the found values into a list
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam List the list type that houses the values
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, template <typename...> class List = detail::vector, typename Reader = ValueReader>
|
|
class ValueFlagList : public ValueFlagBase {
|
|
private:
|
|
using Container = List<T>;
|
|
Container values;
|
|
const Container defaultValues;
|
|
Reader reader;
|
|
|
|
public:
|
|
typedef T value_type;
|
|
typedef typename Container::allocator_type allocator_type;
|
|
typedef typename Container::pointer pointer;
|
|
typedef typename Container::const_pointer const_pointer;
|
|
typedef T &reference;
|
|
typedef const T &const_reference;
|
|
typedef typename Container::size_type size_type;
|
|
typedef typename Container::difference_type difference_type;
|
|
typedef typename Container::iterator iterator;
|
|
typedef typename Container::const_iterator const_iterator;
|
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
|
|
|
ValueFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const Container &defaultValues_ = Container(), Options options_ = {})
|
|
: ValueFlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_),
|
|
defaultValues(defaultValues_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
virtual ~ValueFlagList()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &values_) override
|
|
{
|
|
const std::string &value_ = values_.at(0);
|
|
|
|
T v{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, v)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, v);
|
|
#endif
|
|
values.insert(std::end(values), v);
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container &Get() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &operator*() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container &operator*() const noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container *operator->() noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container *operator->() const noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
virtual std::string Name() const override
|
|
{
|
|
return name + std::string("...");
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
ValueFlagBase::Reset();
|
|
values = defaultValues;
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &arg) override
|
|
{
|
|
const bool wasMatched = Matched();
|
|
auto me = FlagBase::Match(arg);
|
|
if (me && !wasMatched) {
|
|
values.clear();
|
|
}
|
|
return me;
|
|
}
|
|
|
|
iterator begin() noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator begin() const noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator cbegin() const noexcept
|
|
{
|
|
return values.cbegin();
|
|
}
|
|
|
|
iterator end() noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator end() const noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator cend() const noexcept
|
|
{
|
|
return values.cend();
|
|
}
|
|
};
|
|
|
|
/** A mapping value flag class
|
|
*
|
|
* \tparam K the type to extract the argument as
|
|
* \tparam T the type to store the result as
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
* \tparam Map The Map type. Should operate like std::map or std::unordered_map
|
|
*/
|
|
template <typename K, typename T, typename Reader = ValueReader,
|
|
template <typename...> class Map = detail::unordered_map>
|
|
class MapFlag : public ValueFlagBase {
|
|
private:
|
|
const Map<K, T> map;
|
|
T value;
|
|
const T defaultValue;
|
|
Reader reader;
|
|
|
|
protected:
|
|
virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
|
|
{
|
|
return detail::MapKeysToStrings(map);
|
|
}
|
|
|
|
public:
|
|
MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const Map<K, T> &map_, const T &defaultValue_, Options options_)
|
|
: ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_),
|
|
defaultValue(defaultValue_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const Map<K, T> &map_, const T &defaultValue_ = T(), const bool extraError_ = false)
|
|
: MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_,
|
|
extraError_ ? Options::Single : Options::None)
|
|
{
|
|
}
|
|
|
|
MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const Map<K, T> &map_, Options options_)
|
|
: MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_)
|
|
{
|
|
}
|
|
|
|
virtual ~MapFlag()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &values_) override
|
|
{
|
|
const std::string &value_ = values_.at(0);
|
|
|
|
K key{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, key)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, key);
|
|
#endif
|
|
auto it = map.find(key);
|
|
if (it == std::end(map)) {
|
|
std::ostringstream problem;
|
|
problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Map;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw MapError(problem.str());
|
|
#endif
|
|
}
|
|
else {
|
|
this->value = it->second;
|
|
}
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &Get() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &operator*() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T &operator*() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T *operator->() noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T *operator->() const noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
ValueFlagBase::Reset();
|
|
value = defaultValue;
|
|
}
|
|
};
|
|
|
|
/** A mapping value flag list class
|
|
*
|
|
* \tparam K the type to extract the argument as
|
|
* \tparam T the type to store the result as
|
|
* \tparam List the list type that houses the values
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
* \tparam Map The Map type. Should operate like std::map or std::unordered_map
|
|
*/
|
|
template <typename K, typename T, template <typename...> class List = detail::vector,
|
|
typename Reader = ValueReader, template <typename...> class Map = detail::unordered_map>
|
|
class MapFlagList : public ValueFlagBase {
|
|
private:
|
|
using Container = List<T>;
|
|
const Map<K, T> map;
|
|
Container values;
|
|
const Container defaultValues;
|
|
Reader reader;
|
|
|
|
protected:
|
|
virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
|
|
{
|
|
return detail::MapKeysToStrings(map);
|
|
}
|
|
|
|
public:
|
|
typedef T value_type;
|
|
typedef typename Container::allocator_type allocator_type;
|
|
typedef typename Container::pointer pointer;
|
|
typedef typename Container::const_pointer const_pointer;
|
|
typedef T &reference;
|
|
typedef const T &const_reference;
|
|
typedef typename Container::size_type size_type;
|
|
typedef typename Container::difference_type difference_type;
|
|
typedef typename Container::iterator iterator;
|
|
typedef typename Container::const_iterator const_iterator;
|
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
|
|
|
MapFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_,
|
|
const Map<K, T> &map_, const Container &defaultValues_ = Container(), Options options_ = {})
|
|
: ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), values(defaultValues_),
|
|
defaultValues(defaultValues_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
virtual ~MapFlagList()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::vector<std::string> &values_) override
|
|
{
|
|
const std::string &value_ = values_.at(0);
|
|
|
|
K key{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, key)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, key);
|
|
#endif
|
|
auto it = map.find(key);
|
|
if (it == std::end(map)) {
|
|
std::ostringstream problem;
|
|
problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Map;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw MapError(problem.str());
|
|
#endif
|
|
}
|
|
else {
|
|
this->values.emplace_back(it->second);
|
|
}
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &Get() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &operator*() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container &operator*() const noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container *operator->() noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container *operator->() const noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
virtual std::string Name() const override
|
|
{
|
|
return name + std::string("...");
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
ValueFlagBase::Reset();
|
|
values = defaultValues;
|
|
}
|
|
|
|
virtual FlagBase *Match(const EitherFlag &arg) override
|
|
{
|
|
const bool wasMatched = Matched();
|
|
auto me = FlagBase::Match(arg);
|
|
if (me && !wasMatched) {
|
|
values.clear();
|
|
}
|
|
return me;
|
|
}
|
|
|
|
iterator begin() noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator begin() const noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator cbegin() const noexcept
|
|
{
|
|
return values.cbegin();
|
|
}
|
|
|
|
iterator end() noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator end() const noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator cend() const noexcept
|
|
{
|
|
return values.cend();
|
|
}
|
|
};
|
|
|
|
/** A positional argument class
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, typename Reader = ValueReader> class Positional : public PositionalBase {
|
|
private:
|
|
T value;
|
|
const T defaultValue;
|
|
Reader reader;
|
|
|
|
public:
|
|
Positional(Group &group_, const std::string &name_, const std::string &help_,
|
|
const T &defaultValue_ = T(), Options options_ = {})
|
|
: PositionalBase(name_, help_, options_), value(defaultValue_), defaultValue(defaultValue_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_)
|
|
: Positional(group_, name_, help_, T(), options_)
|
|
{
|
|
}
|
|
|
|
virtual ~Positional()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::string &value_) override
|
|
{
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, this->value)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, this->value);
|
|
#endif
|
|
ready = false;
|
|
matched = true;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &Get() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &operator*() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T &operator*() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T *operator->() noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T *operator->() const noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
PositionalBase::Reset();
|
|
value = defaultValue;
|
|
}
|
|
};
|
|
|
|
/** A positional argument class that pushes the found values into a list
|
|
*
|
|
* \tparam T the type to extract the argument as
|
|
* \tparam List the list type that houses the values
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
*/
|
|
template <typename T, template <typename...> class List = detail::vector, typename Reader = ValueReader>
|
|
class PositionalList : public PositionalBase {
|
|
private:
|
|
using Container = List<T>;
|
|
Container values;
|
|
const Container defaultValues;
|
|
Reader reader;
|
|
|
|
public:
|
|
typedef T value_type;
|
|
typedef typename Container::allocator_type allocator_type;
|
|
typedef typename Container::pointer pointer;
|
|
typedef typename Container::const_pointer const_pointer;
|
|
typedef T &reference;
|
|
typedef const T &const_reference;
|
|
typedef typename Container::size_type size_type;
|
|
typedef typename Container::difference_type difference_type;
|
|
typedef typename Container::iterator iterator;
|
|
typedef typename Container::const_iterator const_iterator;
|
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
|
|
|
PositionalList(Group &group_, const std::string &name_, const std::string &help_,
|
|
const Container &defaultValues_ = Container(), Options options_ = {})
|
|
: PositionalBase(name_, help_, options_), values(defaultValues_), defaultValues(defaultValues_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
PositionalList(Group &group_, const std::string &name_, const std::string &help_, Options options_)
|
|
: PositionalList(group_, name_, help_, {}, options_)
|
|
{
|
|
}
|
|
|
|
virtual ~PositionalList()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::string &value_) override
|
|
{
|
|
T v{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, v)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, v);
|
|
#endif
|
|
values.insert(std::end(values), v);
|
|
matched = true;
|
|
}
|
|
|
|
virtual std::string Name() const override
|
|
{
|
|
return name + std::string("...");
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container &Get() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &operator*() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container &operator*() const noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container *operator->() noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container *operator->() const noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
PositionalBase::Reset();
|
|
values = defaultValues;
|
|
}
|
|
|
|
virtual PositionalBase *GetNextPositional() override
|
|
{
|
|
const bool wasMatched = Matched();
|
|
auto me = PositionalBase::GetNextPositional();
|
|
if (me && !wasMatched) {
|
|
values.clear();
|
|
}
|
|
return me;
|
|
}
|
|
|
|
iterator begin() noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator begin() const noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator cbegin() const noexcept
|
|
{
|
|
return values.cbegin();
|
|
}
|
|
|
|
iterator end() noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator end() const noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator cend() const noexcept
|
|
{
|
|
return values.cend();
|
|
}
|
|
};
|
|
|
|
/** A positional argument mapping class
|
|
*
|
|
* \tparam K the type to extract the argument as
|
|
* \tparam T the type to store the result as
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
* \tparam Map The Map type. Should operate like std::map or std::unordered_map
|
|
*/
|
|
template <typename K, typename T, typename Reader = ValueReader,
|
|
template <typename...> class Map = detail::unordered_map>
|
|
class MapPositional : public PositionalBase {
|
|
private:
|
|
const Map<K, T> map;
|
|
T value;
|
|
const T defaultValue;
|
|
Reader reader;
|
|
|
|
protected:
|
|
virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
|
|
{
|
|
return detail::MapKeysToStrings(map);
|
|
}
|
|
|
|
public:
|
|
MapPositional(Group &group_, const std::string &name_, const std::string &help_, const Map<K, T> &map_,
|
|
const T &defaultValue_ = T(), Options options_ = {})
|
|
: PositionalBase(name_, help_, options_), map(map_), value(defaultValue_), defaultValue(defaultValue_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
virtual ~MapPositional()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::string &value_) override
|
|
{
|
|
K key{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, key)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, key);
|
|
#endif
|
|
auto it = map.find(key);
|
|
if (it == std::end(map)) {
|
|
std::ostringstream problem;
|
|
problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Map;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw MapError(problem.str());
|
|
#endif
|
|
}
|
|
else {
|
|
this->value = it->second;
|
|
ready = false;
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &Get() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T &operator*() noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T &operator*() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
T *operator->() noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
const T *operator->() const noexcept
|
|
{
|
|
return &value;
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
PositionalBase::Reset();
|
|
value = defaultValue;
|
|
}
|
|
};
|
|
|
|
/** A positional argument mapping list class
|
|
*
|
|
* \tparam K the type to extract the argument as
|
|
* \tparam T the type to store the result as
|
|
* \tparam List the list type that houses the values
|
|
* \tparam Reader The functor type used to read the argument, taking the name, value, and destination
|
|
* reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
|
|
* \tparam Map The Map type. Should operate like std::map or std::unordered_map
|
|
*/
|
|
template <typename K, typename T, template <typename...> class List = detail::vector,
|
|
typename Reader = ValueReader, template <typename...> class Map = detail::unordered_map>
|
|
class MapPositionalList : public PositionalBase {
|
|
private:
|
|
using Container = List<T>;
|
|
|
|
const Map<K, T> map;
|
|
Container values;
|
|
const Container defaultValues;
|
|
Reader reader;
|
|
|
|
protected:
|
|
virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
|
|
{
|
|
return detail::MapKeysToStrings(map);
|
|
}
|
|
|
|
public:
|
|
typedef T value_type;
|
|
typedef typename Container::allocator_type allocator_type;
|
|
typedef typename Container::pointer pointer;
|
|
typedef typename Container::const_pointer const_pointer;
|
|
typedef T &reference;
|
|
typedef const T &const_reference;
|
|
typedef typename Container::size_type size_type;
|
|
typedef typename Container::difference_type difference_type;
|
|
typedef typename Container::iterator iterator;
|
|
typedef typename Container::const_iterator const_iterator;
|
|
typedef std::reverse_iterator<iterator> reverse_iterator;
|
|
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
|
|
|
|
MapPositionalList(Group &group_, const std::string &name_, const std::string &help_,
|
|
const Map<K, T> &map_, const Container &defaultValues_ = Container(),
|
|
Options options_ = {})
|
|
: PositionalBase(name_, help_, options_), map(map_), values(defaultValues_),
|
|
defaultValues(defaultValues_)
|
|
{
|
|
group_.Add(*this);
|
|
}
|
|
|
|
virtual ~MapPositionalList()
|
|
{
|
|
}
|
|
|
|
virtual void ParseValue(const std::string &value_) override
|
|
{
|
|
K key{};
|
|
#ifdef ARGS_NOEXCEPT
|
|
if (!reader(name, value_, key)) {
|
|
error = Error::Parse;
|
|
return;
|
|
}
|
|
#else
|
|
reader(name, value_, key);
|
|
#endif
|
|
auto it = map.find(key);
|
|
if (it == std::end(map)) {
|
|
std::ostringstream problem;
|
|
problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
|
|
#ifdef ARGS_NOEXCEPT
|
|
error = Error::Map;
|
|
errorMsg = problem.str();
|
|
#else
|
|
throw MapError(problem.str());
|
|
#endif
|
|
}
|
|
else {
|
|
this->values.emplace_back(it->second);
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &Get() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the value
|
|
*/
|
|
Container &operator*() noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container &operator*() const noexcept
|
|
{
|
|
return values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
Container *operator->() noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
/** Get the values
|
|
*/
|
|
const Container *operator->() const noexcept
|
|
{
|
|
return &values;
|
|
}
|
|
|
|
virtual std::string Name() const override
|
|
{
|
|
return name + std::string("...");
|
|
}
|
|
|
|
virtual void Reset() noexcept override
|
|
{
|
|
PositionalBase::Reset();
|
|
values = defaultValues;
|
|
}
|
|
|
|
virtual PositionalBase *GetNextPositional() override
|
|
{
|
|
const bool wasMatched = Matched();
|
|
auto me = PositionalBase::GetNextPositional();
|
|
if (me && !wasMatched) {
|
|
values.clear();
|
|
}
|
|
return me;
|
|
}
|
|
|
|
iterator begin() noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator begin() const noexcept
|
|
{
|
|
return values.begin();
|
|
}
|
|
|
|
const_iterator cbegin() const noexcept
|
|
{
|
|
return values.cbegin();
|
|
}
|
|
|
|
iterator end() noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator end() const noexcept
|
|
{
|
|
return values.end();
|
|
}
|
|
|
|
const_iterator cend() const noexcept
|
|
{
|
|
return values.cend();
|
|
}
|
|
};
|
|
}
|
|
|
|
#pragma pop_macro("min")
|
|
#pragma pop_macro("max")
|
|
#endif
|