mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 20:23:55 +00:00
remove restrictions on tuple size, and add some additional tests and modified documentation fix some issues with the negative number check add some test for indexed validation on tuple allow specific validators for specific elements in a type with multiple values, or to just apply to the last given argument
1039 lines
35 KiB
C++
1039 lines
35 KiB
C++
#pragma once
|
|
|
|
// Distributed under the 3-Clause BSD License. See accompanying
|
|
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "CLI/Error.hpp"
|
|
#include "CLI/Macros.hpp"
|
|
#include "CLI/Split.hpp"
|
|
#include "CLI/StringTools.hpp"
|
|
#include "CLI/Validators.hpp"
|
|
|
|
namespace CLI {
|
|
|
|
using results_t = std::vector<std::string>;
|
|
using callback_t = std::function<bool(results_t)>;
|
|
|
|
class Option;
|
|
class App;
|
|
|
|
using Option_p = std::unique_ptr<Option>;
|
|
|
|
enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join };
|
|
|
|
/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
|
|
/// to share parts of the class; an OptionDefaults can copy to an Option.
|
|
template <typename CRTP> class OptionBase {
|
|
friend App;
|
|
|
|
protected:
|
|
/// The group membership
|
|
std::string group_ = std::string("Options");
|
|
|
|
/// True if this is a required option
|
|
bool required_{false};
|
|
|
|
/// Ignore the case when matching (option, not value)
|
|
bool ignore_case_{false};
|
|
|
|
/// Ignore underscores when matching (option, not value)
|
|
bool ignore_underscore_{false};
|
|
|
|
/// Allow this option to be given in a configuration file
|
|
bool configurable_{true};
|
|
|
|
/// Disable overriding flag values with '=value'
|
|
bool disable_flag_override_{false};
|
|
|
|
/// Specify a delimiter character for vector arguments
|
|
char delimiter_{'\0'};
|
|
|
|
/// Automatically capture default value
|
|
bool always_capture_default_{false};
|
|
|
|
/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
|
|
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
|
|
|
|
/// Copy the contents to another similar class (one based on OptionBase)
|
|
template <typename T> void copy_to(T *other) const {
|
|
other->group(group_);
|
|
other->required(required_);
|
|
other->ignore_case(ignore_case_);
|
|
other->ignore_underscore(ignore_underscore_);
|
|
other->configurable(configurable_);
|
|
other->disable_flag_override(disable_flag_override_);
|
|
other->delimiter(delimiter_);
|
|
other->always_capture_default(always_capture_default_);
|
|
other->multi_option_policy(multi_option_policy_);
|
|
}
|
|
|
|
public:
|
|
// setters
|
|
|
|
/// Changes the group membership
|
|
CRTP *group(std::string name) {
|
|
group_ = name;
|
|
return static_cast<CRTP *>(this);
|
|
}
|
|
|
|
/// Set the option as required
|
|
CRTP *required(bool value = true) {
|
|
required_ = value;
|
|
return static_cast<CRTP *>(this);
|
|
}
|
|
|
|
/// Support Plumbum term
|
|
CRTP *mandatory(bool value = true) { return required(value); }
|
|
|
|
CRTP *always_capture_default(bool value = true) {
|
|
always_capture_default_ = value;
|
|
return static_cast<CRTP *>(this);
|
|
}
|
|
|
|
// Getters
|
|
|
|
/// Get the group of this option
|
|
const std::string &get_group() const { return group_; }
|
|
|
|
/// True if this is a required option
|
|
bool get_required() const { return required_; }
|
|
|
|
/// The status of ignore case
|
|
bool get_ignore_case() const { return ignore_case_; }
|
|
|
|
/// The status of ignore_underscore
|
|
bool get_ignore_underscore() const { return ignore_underscore_; }
|
|
|
|
/// The status of configurable
|
|
bool get_configurable() const { return configurable_; }
|
|
|
|
/// The status of configurable
|
|
bool get_disable_flag_override() const { return disable_flag_override_; }
|
|
|
|
/// Get the current delimeter char
|
|
char get_delimiter() const { return delimiter_; }
|
|
|
|
/// Return true if this will automatically capture the default value for help printing
|
|
bool get_always_capture_default() const { return always_capture_default_; }
|
|
|
|
/// The status of the multi option policy
|
|
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
|
|
|
|
// Shortcuts for multi option policy
|
|
|
|
/// Set the multi option policy to take last
|
|
CRTP *take_last() {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->multi_option_policy(MultiOptionPolicy::TakeLast);
|
|
return self;
|
|
}
|
|
|
|
/// Set the multi option policy to take last
|
|
CRTP *take_first() {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->multi_option_policy(MultiOptionPolicy::TakeFirst);
|
|
return self;
|
|
}
|
|
|
|
/// Set the multi option policy to take last
|
|
CRTP *join() {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->multi_option_policy(MultiOptionPolicy::Join);
|
|
return self;
|
|
}
|
|
|
|
/// Allow in a configuration file
|
|
CRTP *configurable(bool value = true) {
|
|
configurable_ = value;
|
|
return static_cast<CRTP *>(this);
|
|
}
|
|
|
|
/// Allow in a configuration file
|
|
CRTP *delimiter(char value = '\0') {
|
|
delimiter_ = value;
|
|
return static_cast<CRTP *>(this);
|
|
}
|
|
};
|
|
|
|
/// This is a version of OptionBase that only supports setting values,
|
|
/// for defaults. It is stored as the default option in an App.
|
|
class OptionDefaults : public OptionBase<OptionDefaults> {
|
|
public:
|
|
OptionDefaults() = default;
|
|
|
|
// Methods here need a different implementation if they are Option vs. OptionDefault
|
|
|
|
/// Take the last argument if given multiple times
|
|
OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
|
|
multi_option_policy_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// Ignore the case of the option name
|
|
OptionDefaults *ignore_case(bool value = true) {
|
|
ignore_case_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// Ignore underscores in the option name
|
|
OptionDefaults *ignore_underscore(bool value = true) {
|
|
ignore_underscore_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// Disable overriding flag values with an '=<value>' segment
|
|
OptionDefaults *disable_flag_override(bool value = true) {
|
|
disable_flag_override_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// set a delimiter character to split up single arguments to treat as multiple inputs
|
|
OptionDefaults *delimiter(char value = '\0') {
|
|
delimiter_ = value;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
class Option : public OptionBase<Option> {
|
|
friend App;
|
|
|
|
protected:
|
|
/// @name Names
|
|
///@{
|
|
|
|
/// A list of the short names (`-a`) without the leading dashes
|
|
std::vector<std::string> snames_;
|
|
|
|
/// A list of the long names (`--a`) without the leading dashes
|
|
std::vector<std::string> lnames_;
|
|
|
|
/// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
|
|
/// what is in snames or lnames but will trigger a particular response on a flag
|
|
std::vector<std::pair<std::string, std::string>> default_flag_values_;
|
|
|
|
/// a list of flag names with specified default values;
|
|
std::vector<std::string> fnames_;
|
|
|
|
/// A positional name
|
|
std::string pname_;
|
|
|
|
/// If given, check the environment for this option
|
|
std::string envname_;
|
|
|
|
///@}
|
|
/// @name Help
|
|
///@{
|
|
|
|
/// The description for help strings
|
|
std::string description_;
|
|
|
|
/// A human readable default value, either manually set, captured, or captured by default
|
|
std::string default_str_;
|
|
|
|
/// A human readable type value, set when App creates this
|
|
///
|
|
/// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
|
|
std::function<std::string()> type_name_{[]() { return std::string(); }};
|
|
|
|
/// Run this function to capture a default (ignore if empty)
|
|
std::function<std::string()> default_function_;
|
|
|
|
///@}
|
|
/// @name Configuration
|
|
///@{
|
|
|
|
/// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option,
|
|
/// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean
|
|
/// vector of pairs.
|
|
int type_size_{1};
|
|
|
|
/// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values.
|
|
int expected_{1};
|
|
|
|
/// A list of validators to run on each value parsed
|
|
std::vector<Validator> validators_;
|
|
|
|
/// A list of options that are required with this option
|
|
std::set<Option *> needs_;
|
|
|
|
/// A list of options that are excluded with this option
|
|
std::set<Option *> excludes_;
|
|
|
|
///@}
|
|
/// @name Other
|
|
///@{
|
|
|
|
/// Remember the parent app
|
|
App *parent_;
|
|
|
|
/// Options store a callback to do all the work
|
|
callback_t callback_;
|
|
|
|
///@}
|
|
/// @name Parsing results
|
|
///@{
|
|
|
|
/// Results of parsing
|
|
results_t results_;
|
|
|
|
/// Whether the callback has run (needed for INI parsing)
|
|
bool callback_run_{false};
|
|
|
|
///@}
|
|
|
|
/// Making an option by hand is not defined, it must be made by the App class
|
|
Option(std::string option_name,
|
|
std::string option_description,
|
|
std::function<bool(results_t)> callback,
|
|
App *parent)
|
|
: description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
|
|
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
|
|
}
|
|
|
|
public:
|
|
/// @name Basic
|
|
///@{
|
|
|
|
/// Count the total number of times an option was passed
|
|
size_t count() const { return results_.size(); }
|
|
|
|
/// True if the option was not passed
|
|
bool empty() const { return results_.empty(); }
|
|
|
|
/// This class is true if option is passed.
|
|
operator bool() const { return !empty(); }
|
|
|
|
/// Clear the parsed results (mostly for testing)
|
|
void clear() { results_.clear(); }
|
|
|
|
///@}
|
|
/// @name Setting options
|
|
///@{
|
|
|
|
/// Set the number of expected arguments (Flags don't use this)
|
|
Option *expected(int value) {
|
|
|
|
// Break if this is a flag
|
|
if(type_size_ == 0)
|
|
throw IncorrectConstruction::SetFlag(get_name(true, true));
|
|
|
|
// Setting 0 is not allowed
|
|
if(value == 0)
|
|
throw IncorrectConstruction::Set0Opt(get_name());
|
|
|
|
// No change is okay, quit now
|
|
if(expected_ == value)
|
|
return this;
|
|
|
|
// Type must be a vector
|
|
if(type_size_ >= 0)
|
|
throw IncorrectConstruction::ChangeNotVector(get_name());
|
|
|
|
// TODO: Can support multioption for non-1 values (except for join)
|
|
if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)
|
|
throw IncorrectConstruction::AfterMultiOpt(get_name());
|
|
|
|
expected_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// Adds a Validator with a built in type name
|
|
Option *check(Validator validator, std::string validator_name = "") {
|
|
validator.non_modifying();
|
|
validators_.push_back(std::move(validator));
|
|
if(!validator_name.empty())
|
|
validators_.front().name(validator_name);
|
|
return this;
|
|
}
|
|
|
|
/// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
|
|
Option *check(std::function<std::string(const std::string &)> validator,
|
|
std::string validator_description = "",
|
|
std::string validator_name = "") {
|
|
validators_.emplace_back(validator, std::move(validator_description), std::move(validator_name));
|
|
validators_.back().non_modifying();
|
|
return this;
|
|
}
|
|
|
|
/// Adds a transforming validator with a built in type name
|
|
Option *transform(Validator validator, std::string validator_name = "") {
|
|
validators_.insert(validators_.begin(), std::move(validator));
|
|
if(!validator_name.empty())
|
|
validators_.front().name(validator_name);
|
|
return this;
|
|
}
|
|
|
|
/// Adds a validator-like function that can change result
|
|
Option *transform(std::function<std::string(std::string)> func,
|
|
std::string transform_description = "",
|
|
std::string transform_name = "") {
|
|
validators_.insert(validators_.begin(),
|
|
Validator(
|
|
[func](std::string &val) {
|
|
val = func(val);
|
|
return std::string{};
|
|
},
|
|
std::move(transform_description),
|
|
std::move(transform_name)));
|
|
|
|
return this;
|
|
}
|
|
|
|
/// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
|
|
Option *each(std::function<void(std::string)> func) {
|
|
validators_.emplace_back(
|
|
[func](std::string &inout) {
|
|
func(inout);
|
|
return std::string{};
|
|
},
|
|
std::string{});
|
|
return this;
|
|
}
|
|
/// Get a named Validator
|
|
Validator *get_validator(const std::string &validator_name = "") {
|
|
for(auto &validator : validators_) {
|
|
if(validator_name == validator.get_name()) {
|
|
return &validator;
|
|
}
|
|
}
|
|
if((validator_name.empty()) && (!validators_.empty())) {
|
|
return &(validators_.front());
|
|
}
|
|
throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found");
|
|
}
|
|
|
|
/// Get a Validator by index NOTE: this may not be the order of definition
|
|
Validator *get_validator(int index) {
|
|
if(index >= 0 && index < static_cast<int>(validators_.size())) {
|
|
return &(validators_[index]);
|
|
}
|
|
throw OptionNotFound("Validator index is not valid");
|
|
}
|
|
|
|
/// Sets required options
|
|
Option *needs(Option *opt) {
|
|
auto tup = needs_.insert(opt);
|
|
if(!tup.second)
|
|
throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());
|
|
return this;
|
|
}
|
|
|
|
/// Can find a string if needed
|
|
template <typename T = App> Option *needs(std::string opt_name) {
|
|
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
|
|
if(opt.get() != this && opt->check_name(opt_name))
|
|
return needs(opt.get());
|
|
throw IncorrectConstruction::MissingOption(opt_name);
|
|
}
|
|
|
|
/// Any number supported, any mix of string and Opt
|
|
template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) {
|
|
needs(opt);
|
|
return needs(opt1, args...);
|
|
}
|
|
|
|
/// Remove needs link from an option. Returns true if the option really was in the needs list.
|
|
bool remove_needs(Option *opt) {
|
|
auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
|
|
|
|
if(iterator == std::end(needs_)) {
|
|
return false;
|
|
}
|
|
needs_.erase(iterator);
|
|
return true;
|
|
}
|
|
|
|
/// Sets excluded options
|
|
Option *excludes(Option *opt) {
|
|
excludes_.insert(opt);
|
|
|
|
// Help text should be symmetric - excluding a should exclude b
|
|
opt->excludes_.insert(this);
|
|
|
|
// Ignoring the insert return value, excluding twice is now allowed.
|
|
// (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
|
|
|
|
return this;
|
|
}
|
|
|
|
/// Can find a string if needed
|
|
template <typename T = App> Option *excludes(std::string opt_name) {
|
|
for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_)
|
|
if(opt.get() != this && opt->check_name(opt_name))
|
|
return excludes(opt.get());
|
|
throw IncorrectConstruction::MissingOption(opt_name);
|
|
}
|
|
|
|
/// Any number supported, any mix of string and Opt
|
|
template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) {
|
|
excludes(opt);
|
|
return excludes(opt1, args...);
|
|
}
|
|
|
|
/// Remove needs link from an option. Returns true if the option really was in the needs list.
|
|
bool remove_excludes(Option *opt) {
|
|
auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
|
|
|
|
if(iterator == std::end(excludes_)) {
|
|
return false;
|
|
}
|
|
excludes_.erase(iterator);
|
|
return true;
|
|
}
|
|
|
|
/// Sets environment variable to read if no option given
|
|
Option *envname(std::string name) {
|
|
envname_ = name;
|
|
return this;
|
|
}
|
|
|
|
/// Ignore case
|
|
///
|
|
/// The template hides the fact that we don't have the definition of App yet.
|
|
/// You are never expected to add an argument to the template here.
|
|
template <typename T = App> Option *ignore_case(bool value = true) {
|
|
ignore_case_ = value;
|
|
auto *parent = dynamic_cast<T *>(parent_);
|
|
|
|
for(const Option_p &opt : parent->options_)
|
|
if(opt.get() != this && *opt == *this)
|
|
throw OptionAlreadyAdded(opt->get_name(true, true));
|
|
|
|
return this;
|
|
}
|
|
|
|
/// Ignore underscores in the option names
|
|
///
|
|
/// The template hides the fact that we don't have the definition of App yet.
|
|
/// You are never expected to add an argument to the template here.
|
|
template <typename T = App> Option *ignore_underscore(bool value = true) {
|
|
ignore_underscore_ = value;
|
|
auto *parent = dynamic_cast<T *>(parent_);
|
|
for(const Option_p &opt : parent->options_)
|
|
if(opt.get() != this && *opt == *this)
|
|
throw OptionAlreadyAdded(opt->get_name(true, true));
|
|
|
|
return this;
|
|
}
|
|
|
|
/// Take the last argument if given multiple times (or another policy)
|
|
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
|
|
|
|
if(get_items_expected() < 0)
|
|
throw IncorrectConstruction::MultiOptionPolicy(get_name());
|
|
multi_option_policy_ = value;
|
|
return this;
|
|
}
|
|
|
|
/// Disable flag overrides values, e.g. --flag=<value> is not allowed
|
|
Option *disable_flag_override(bool value = true) {
|
|
disable_flag_override_ = value;
|
|
return this;
|
|
}
|
|
///@}
|
|
/// @name Accessors
|
|
///@{
|
|
|
|
/// The number of arguments the option expects
|
|
int get_type_size() const { return type_size_; }
|
|
|
|
/// The environment variable associated to this value
|
|
std::string get_envname() const { return envname_; }
|
|
|
|
/// The set of options needed
|
|
std::set<Option *> get_needs() const { return needs_; }
|
|
|
|
/// The set of options excluded
|
|
std::set<Option *> get_excludes() const { return excludes_; }
|
|
|
|
/// The default value (for help printing) DEPRECATED Use get_default_str() instead
|
|
CLI11_DEPRECATED("Use get_default_str() instead")
|
|
std::string get_defaultval() const { return default_str_; }
|
|
|
|
/// The default value (for help printing)
|
|
std::string get_default_str() const { return default_str_; }
|
|
|
|
/// Get the callback function
|
|
callback_t get_callback() const { return callback_; }
|
|
|
|
/// Get the long names
|
|
const std::vector<std::string> get_lnames() const { return lnames_; }
|
|
|
|
/// Get the short names
|
|
const std::vector<std::string> get_snames() const { return snames_; }
|
|
|
|
/// Get the flag names with specified default values
|
|
const std::vector<std::string> get_fnames() const { return fnames_; }
|
|
|
|
/// The number of times the option expects to be included
|
|
int get_expected() const { return expected_; }
|
|
|
|
/// \brief The total number of expected values (including the type)
|
|
/// This is positive if exactly this number is expected, and negative for at least N values
|
|
///
|
|
/// v = fabs(size_type*expected)
|
|
/// !MultiOptionPolicy::Throw
|
|
/// | Expected < 0 | Expected == 0 | Expected > 0
|
|
/// Size < 0 | -v | 0 | -v
|
|
/// Size == 0 | 0 | 0 | 0
|
|
/// Size > 0 | -v | 0 | -v // Expected must be 1
|
|
///
|
|
/// MultiOptionPolicy::Throw
|
|
/// | Expected < 0 | Expected == 0 | Expected > 0
|
|
/// Size < 0 | -v | 0 | v
|
|
/// Size == 0 | 0 | 0 | 0
|
|
/// Size > 0 | v | 0 | v // Expected must be 1
|
|
///
|
|
int get_items_expected() const {
|
|
return std::abs(type_size_ * expected_) *
|
|
((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1));
|
|
}
|
|
|
|
/// True if the argument can be given directly
|
|
bool get_positional() const { return pname_.length() > 0; }
|
|
|
|
/// True if option has at least one non-positional name
|
|
bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
|
|
|
|
/// True if option has description
|
|
bool has_description() const { return description_.length() > 0; }
|
|
|
|
/// Get the description
|
|
const std::string &get_description() const { return description_; }
|
|
|
|
/// Set the description
|
|
Option *description(std::string option_description) {
|
|
description_ = std::move(option_description);
|
|
return this;
|
|
}
|
|
|
|
///@}
|
|
/// @name Help tools
|
|
///@{
|
|
|
|
/// \brief Gets a comma separated list of names.
|
|
/// Will include / prefer the positional name if positional is true.
|
|
/// If all_options is false, pick just the most descriptive name to show.
|
|
/// Use `get_name(true)` to get the positional name (replaces `get_pname`)
|
|
std::string get_name(bool positional = false, //<[input] Show the positional name
|
|
bool all_options = false //<[input] Show every option
|
|
) const {
|
|
|
|
if(all_options) {
|
|
|
|
std::vector<std::string> name_list;
|
|
|
|
/// The all list will never include a positional unless asked or that's the only name.
|
|
if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
|
|
name_list.push_back(pname_);
|
|
}
|
|
if((get_items_expected() == 0) && (!fnames_.empty())) {
|
|
for(const std::string &sname : snames_) {
|
|
name_list.push_back("-" + sname);
|
|
if(check_fname(sname)) {
|
|
name_list.back() += "{" + get_flag_value(sname, "") + "}";
|
|
}
|
|
}
|
|
|
|
for(const std::string &lname : lnames_) {
|
|
name_list.push_back("--" + lname);
|
|
if(check_fname(lname)) {
|
|
name_list.back() += "{" + get_flag_value(lname, "") + "}";
|
|
}
|
|
}
|
|
} else {
|
|
for(const std::string &sname : snames_)
|
|
name_list.push_back("-" + sname);
|
|
|
|
for(const std::string &lname : lnames_)
|
|
name_list.push_back("--" + lname);
|
|
}
|
|
|
|
return detail::join(name_list);
|
|
}
|
|
|
|
// This returns the positional name no matter what
|
|
if(positional)
|
|
return pname_;
|
|
|
|
// Prefer long name
|
|
if(!lnames_.empty())
|
|
return std::string("--") + lnames_[0];
|
|
|
|
// Or short name if no long name
|
|
if(!snames_.empty())
|
|
return std::string("-") + snames_[0];
|
|
|
|
// If positional is the only name, it's okay to use that
|
|
return pname_;
|
|
}
|
|
|
|
///@}
|
|
/// @name Parser tools
|
|
///@{
|
|
|
|
/// Process the callback
|
|
void run_callback() {
|
|
|
|
callback_run_ = true;
|
|
|
|
// Run the validators (can change the string)
|
|
if(!validators_.empty()) {
|
|
int index = 0;
|
|
// this is not available until multi_option_policy with type_size_>0 is enabled and functional
|
|
// if(type_size_ > 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
|
// index = type_size_ - static_cast<int>(results_.size());
|
|
//}
|
|
if(type_size_ < 0 && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { // for vector operations
|
|
index = expected_ - static_cast<int>(results_.size());
|
|
}
|
|
for(std::string &result : results_) {
|
|
auto err_msg = _validate(result, index);
|
|
++index;
|
|
if(!err_msg.empty())
|
|
throw ValidationError(get_name(), err_msg);
|
|
}
|
|
}
|
|
if(!(callback_)) {
|
|
return;
|
|
}
|
|
bool local_result;
|
|
|
|
// Num items expected or length of vector, always at least 1
|
|
// Only valid for a trimming policy
|
|
int trim_size =
|
|
std::min<int>(std::max<int>(std::abs(get_items_expected()), 1), static_cast<int>(results_.size()));
|
|
|
|
// Operation depends on the policy setting
|
|
if(multi_option_policy_ == MultiOptionPolicy::TakeLast) {
|
|
// Allow multi-option sizes (including 0)
|
|
results_t partial_result{results_.end() - trim_size, results_.end()};
|
|
local_result = !callback_(partial_result);
|
|
|
|
} else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) {
|
|
results_t partial_result{results_.begin(), results_.begin() + trim_size};
|
|
local_result = !callback_(partial_result);
|
|
|
|
} else if(multi_option_policy_ == MultiOptionPolicy::Join) {
|
|
results_t partial_result = {detail::join(results_, "\n")};
|
|
local_result = !callback_(partial_result);
|
|
|
|
} else {
|
|
// Exact number required
|
|
if(get_items_expected() > 0) {
|
|
if(results_.size() != static_cast<size_t>(get_items_expected()))
|
|
throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());
|
|
// Variable length list
|
|
} else if(get_items_expected() < 0) {
|
|
// Require that this be a multiple of expected size and at least as many as expected
|
|
if(results_.size() < static_cast<size_t>(-get_items_expected()) ||
|
|
results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0u)
|
|
throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());
|
|
}
|
|
local_result = !callback_(results_);
|
|
}
|
|
|
|
if(local_result)
|
|
throw ConversionError(get_name(), results_);
|
|
}
|
|
|
|
/// If options share any of the same names, they are equal (not counting positional)
|
|
bool operator==(const Option &other) const {
|
|
for(const std::string &sname : snames_)
|
|
if(other.check_sname(sname))
|
|
return true;
|
|
for(const std::string &lname : lnames_)
|
|
if(other.check_lname(lname))
|
|
return true;
|
|
|
|
if(ignore_case_ ||
|
|
ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
|
|
for(const std::string &sname : other.snames_)
|
|
if(check_sname(sname))
|
|
return true;
|
|
for(const std::string &lname : other.lnames_)
|
|
if(check_lname(lname))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Check a name. Requires "-" or "--" for short / long, supports positional name
|
|
bool check_name(std::string name) const {
|
|
|
|
if(name.length() > 2 && name[0] == '-' && name[1] == '-')
|
|
return check_lname(name.substr(2));
|
|
if(name.length() > 1 && name.front() == '-')
|
|
return check_sname(name.substr(1));
|
|
|
|
std::string local_pname = pname_;
|
|
if(ignore_underscore_) {
|
|
local_pname = detail::remove_underscore(local_pname);
|
|
name = detail::remove_underscore(name);
|
|
}
|
|
if(ignore_case_) {
|
|
local_pname = detail::to_lower(local_pname);
|
|
name = detail::to_lower(name);
|
|
}
|
|
return name == local_pname;
|
|
}
|
|
|
|
/// Requires "-" to be removed from string
|
|
bool check_sname(std::string name) const { return (detail::find_member(name, snames_, ignore_case_) >= 0); }
|
|
|
|
/// Requires "--" to be removed from string
|
|
bool check_lname(std::string name) const {
|
|
return (detail::find_member(name, lnames_, ignore_case_, ignore_underscore_) >= 0);
|
|
}
|
|
|
|
/// Requires "--" to be removed from string
|
|
bool check_fname(std::string name) const {
|
|
if(fnames_.empty()) {
|
|
return false;
|
|
}
|
|
return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0);
|
|
}
|
|
|
|
/// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not disabled
|
|
std::string get_flag_value(std::string name, std::string input_value) const {
|
|
static const std::string trueString{"true"};
|
|
static const std::string falseString{"false"};
|
|
static const std::string emptyString{"{}"};
|
|
// check for disable flag override_
|
|
if(disable_flag_override_) {
|
|
if(!((input_value.empty()) || (input_value == emptyString))) {
|
|
auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
|
|
if(default_ind >= 0) {
|
|
// We can static cast this to size_t because it is more than 0 in this block
|
|
if(default_flag_values_[static_cast<size_t>(default_ind)].second != input_value) {
|
|
throw(ArgumentMismatch::FlagOverride(name));
|
|
}
|
|
} else {
|
|
if(input_value != trueString) {
|
|
throw(ArgumentMismatch::FlagOverride(name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
|
|
if((input_value.empty()) || (input_value == emptyString)) {
|
|
return (ind < 0) ? trueString : default_flag_values_[static_cast<size_t>(ind)].second;
|
|
}
|
|
if(ind < 0) {
|
|
return input_value;
|
|
}
|
|
if(default_flag_values_[static_cast<size_t>(ind)].second == falseString) {
|
|
try {
|
|
auto val = detail::to_flag_value(input_value);
|
|
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
|
|
} catch(const std::invalid_argument &) {
|
|
return input_value;
|
|
}
|
|
} else {
|
|
return input_value;
|
|
}
|
|
}
|
|
|
|
/// Puts a result at the end
|
|
Option *add_result(std::string s) {
|
|
_add_result(std::move(s));
|
|
callback_run_ = false;
|
|
return this;
|
|
}
|
|
|
|
/// Puts a result at the end and get a count of the number of arguments actually added
|
|
Option *add_result(std::string s, int &results_added) {
|
|
results_added = _add_result(std::move(s));
|
|
callback_run_ = false;
|
|
return this;
|
|
}
|
|
|
|
/// Puts a result at the end
|
|
Option *add_result(std::vector<std::string> s) {
|
|
for(auto &str : s) {
|
|
_add_result(std::move(str));
|
|
}
|
|
callback_run_ = false;
|
|
return this;
|
|
}
|
|
|
|
/// Get a copy of the results
|
|
std::vector<std::string> results() const { return results_; }
|
|
|
|
/// Get the results as a specified type
|
|
template <typename T,
|
|
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
|
|
void results(T &output) const {
|
|
bool retval;
|
|
if(results_.empty()) {
|
|
retval = detail::lexical_cast(default_str_, output);
|
|
} else if(results_.size() == 1) {
|
|
retval = detail::lexical_cast(results_[0], output);
|
|
} else {
|
|
switch(multi_option_policy_) {
|
|
case MultiOptionPolicy::TakeFirst:
|
|
retval = detail::lexical_cast(results_.front(), output);
|
|
break;
|
|
case MultiOptionPolicy::TakeLast:
|
|
default:
|
|
retval = detail::lexical_cast(results_.back(), output);
|
|
break;
|
|
case MultiOptionPolicy::Throw:
|
|
throw ConversionError(get_name(), results_);
|
|
case MultiOptionPolicy::Join:
|
|
retval = detail::lexical_cast(detail::join(results_), output);
|
|
break;
|
|
}
|
|
}
|
|
if(!retval) {
|
|
throw ConversionError(get_name(), results_);
|
|
}
|
|
}
|
|
/// Get the results as a vector of the specified type
|
|
template <typename T> void results(std::vector<T> &output) const {
|
|
output.clear();
|
|
bool retval = true;
|
|
|
|
for(const auto &elem : results_) {
|
|
output.emplace_back();
|
|
retval &= detail::lexical_cast(elem, output.back());
|
|
}
|
|
|
|
if(!retval) {
|
|
throw ConversionError(get_name(), results_);
|
|
}
|
|
}
|
|
|
|
/// Return the results as the specified type
|
|
template <typename T> T as() const {
|
|
T output;
|
|
results(output);
|
|
return output;
|
|
}
|
|
|
|
/// See if the callback has been run already
|
|
bool get_callback_run() const { return callback_run_; }
|
|
|
|
///@}
|
|
/// @name Custom options
|
|
///@{
|
|
|
|
/// Set the type function to run when displayed on this option
|
|
Option *type_name_fn(std::function<std::string()> typefun) {
|
|
type_name_ = typefun;
|
|
return this;
|
|
}
|
|
|
|
/// Set a custom option typestring
|
|
Option *type_name(std::string typeval) {
|
|
type_name_fn([typeval]() { return typeval; });
|
|
return this;
|
|
}
|
|
|
|
/// Set a custom option size
|
|
Option *type_size(int option_type_size) {
|
|
type_size_ = option_type_size;
|
|
if(type_size_ == 0)
|
|
required_ = false;
|
|
if(option_type_size < 0)
|
|
expected_ = -1;
|
|
return this;
|
|
}
|
|
|
|
/// Set a capture function for the default. Mostly used by App.
|
|
Option *default_function(const std::function<std::string()> &func) {
|
|
default_function_ = func;
|
|
return this;
|
|
}
|
|
|
|
/// Capture the default value from the original value (if it can be captured)
|
|
Option *capture_default_str() {
|
|
if(default_function_) {
|
|
default_str_ = default_function_();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// Set the default value string representation (does not change the contained value)
|
|
Option *default_str(std::string val) {
|
|
default_str_ = val;
|
|
return this;
|
|
}
|
|
|
|
/// Set the default value string representation and evaluate into the bound value
|
|
Option *default_val(std::string val) {
|
|
default_str(val);
|
|
auto old_results = results_;
|
|
results_ = {val};
|
|
run_callback();
|
|
results_ = std::move(old_results);
|
|
return this;
|
|
}
|
|
|
|
/// Get the full typename for this option
|
|
std::string get_type_name() const {
|
|
std::string full_type_name = type_name_();
|
|
if(!validators_.empty()) {
|
|
for(auto &validator : validators_) {
|
|
std::string vtype = validator.get_description();
|
|
if(!vtype.empty()) {
|
|
full_type_name += ":" + vtype;
|
|
}
|
|
}
|
|
}
|
|
return full_type_name;
|
|
}
|
|
|
|
private:
|
|
// Run a result through the validators
|
|
std::string _validate(std::string &result, int index) {
|
|
std::string err_msg;
|
|
for(const auto &vali : validators_) {
|
|
auto v = vali.get_application_index();
|
|
if(v == -1 || v == index) {
|
|
try {
|
|
err_msg = vali(result);
|
|
} catch(const ValidationError &err) {
|
|
err_msg = err.what();
|
|
}
|
|
if(!err_msg.empty())
|
|
break;
|
|
}
|
|
}
|
|
return err_msg;
|
|
}
|
|
|
|
/// Add a single result to the result set, taking into account delimiters
|
|
int _add_result(std::string &&result) {
|
|
int result_count = 0;
|
|
if(delimiter_ == '\0') {
|
|
results_.push_back(std::move(result));
|
|
++result_count;
|
|
} else {
|
|
if((result.find_first_of(delimiter_) != std::string::npos)) {
|
|
for(const auto &var : CLI::detail::split(result, delimiter_)) {
|
|
if(!var.empty()) {
|
|
results_.push_back(var);
|
|
++result_count;
|
|
}
|
|
}
|
|
} else {
|
|
results_.push_back(std::move(result));
|
|
++result_count;
|
|
}
|
|
}
|
|
return result_count;
|
|
}
|
|
}; // namespace CLI
|
|
|
|
} // namespace CLI
|