mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
Refactor some of the configuration file handling code. Make it easier to get the actual file that was processed, and allow extras in the config file to be ignored (default now), captured or errored. fix std::error reference and formatting add test for required but no default and fix a shadow warning on 'required' from gcc 4.8 Test correctness of config write-read loop fix config generation for flag definitions make the config output conform with toml continue work on the config file interpretation and construction get all the ini tests working again with the cleaned up features. update formatting rename IniTest to ConfigFileTest to better reflect actual tests and add a few more test of the configTOML disambiguate enable/disable by default to an enumeration, and to make room for a configurable option to allow subcommands to be triggered by a config file. add a ConfigBase class to generally reflect a broader class of configuration files formats of similar nature to INI files add configurable to app and allow it to trigger subcommands add test of ini formatting add section support to the config files so sections can be opened and closed and the callbacks triggered as appropriate. add handling of option groups to the config file output add subcommand and option group configuration to config file output subsubcom test on config files fix a few sign comparison warnings and formatting start working on the book edits for configuration and a few more tests more test to check for subcommand close in config files more tests for coverage generalize section opening and closing add more tests and some fixes for different configurations yet more tests of different situations related to configuration files test more paths for configuration file sections remove some unused code and fix some codacy warnings update readme with updates from configuration files more book edits and README formatting remove extra space Apply suggestions from code review Co-Authored-By: Henry Schreiner <HenrySchreinerIII@gmail.com> fix some comments and documentation fix spacing Rename size_t -> std::size_t Fix compiler warnings with -Wsign-conversion Fix new warnings with -Wsign-conversion in PR
1247 lines
44 KiB
C++
1247 lines
44 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>;
|
|
/// callback function definition
|
|
using callback_t = std::function<bool(const results_t &)>;
|
|
|
|
class Option;
|
|
class App;
|
|
|
|
using Option_p = std::unique_ptr<Option>;
|
|
/// Enumeration of the multiOption Policy selection
|
|
enum class MultiOptionPolicy : char {
|
|
Throw, //!< Throw an error if any extra arguments were given
|
|
TakeLast, //!< take only the last Expected number of arguments
|
|
TakeFirst, //!< take only the first Expected number of arguments
|
|
Join, //!< merge all the arguments together into a single string via the delimiter character default('\n')
|
|
TakeAll //!< just get all the passed argument regardless
|
|
};
|
|
|
|
/// 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 handling multiple arguments beyond the expected Max
|
|
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(const 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 delimiter 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 all arguments
|
|
CRTP *take_all() {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->multi_option_policy(MultiOptionPolicy::TakeAll);
|
|
return self;
|
|
}
|
|
|
|
/// Set the multi option policy to join
|
|
CRTP *join() {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->multi_option_policy(MultiOptionPolicy::Join);
|
|
return self;
|
|
}
|
|
|
|
/// Set the multi option policy to join with a specific delimiter
|
|
CRTP *join(char delim) {
|
|
auto self = static_cast<CRTP *>(this);
|
|
self->delimiter_ = delim;
|
|
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 (`--long`) 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. max is the nominal type size, min is the minimum number of
|
|
/// strings
|
|
int type_size_max_{1};
|
|
/// The minimum number of arguments an option should be expecting
|
|
int type_size_min_{1};
|
|
|
|
/// The minimum number of expected values
|
|
int expected_min_{1};
|
|
/// The maximum number of expected values
|
|
int expected_max_{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_{nullptr};
|
|
|
|
/// Options store a callback to do all the work
|
|
callback_t callback_{};
|
|
|
|
///@}
|
|
/// @name Parsing results
|
|
///@{
|
|
|
|
/// complete Results of parsing
|
|
results_t results_{};
|
|
/// results after reduction
|
|
results_t proc_results_{};
|
|
/// enumeration for the option state machine
|
|
enum class option_state {
|
|
parsing = 0, //!< The option is currently collecting parsed results
|
|
validated = 2, //!< the results have been validated
|
|
reduced = 4, //!< a subset of results has been generated
|
|
callback_run = 6, //!< the callback has been executed
|
|
};
|
|
/// Whether the callback has run (needed for INI parsing)
|
|
option_state current_option_state_{option_state::parsing};
|
|
/// Specify that extra args beyond type_size_max should be allowed
|
|
bool allow_extra_args_{false};
|
|
/// Specify that the option should act like a flag vs regular option
|
|
bool flag_like_{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, callback_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
|
|
///@{
|
|
|
|
Option(const Option &) = delete;
|
|
Option &operator=(const Option &) = delete;
|
|
|
|
/// Count the total number of times an option was passed
|
|
std::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.
|
|
explicit operator bool() const { return !empty(); }
|
|
|
|
/// Clear the parsed results (mostly for testing)
|
|
void clear() {
|
|
results_.clear();
|
|
current_option_state_ = option_state::parsing;
|
|
}
|
|
|
|
///@}
|
|
/// @name Setting options
|
|
///@{
|
|
|
|
/// Set the number of expected arguments
|
|
Option *expected(int value) {
|
|
if(value < 0) {
|
|
expected_min_ = -value;
|
|
if(expected_max_ < expected_min_) {
|
|
expected_max_ = expected_min_;
|
|
}
|
|
allow_extra_args_ = true;
|
|
flag_like_ = false;
|
|
} else if(value == detail::expected_max_vector_size) {
|
|
expected_min_ = 1;
|
|
expected_max_ = detail::expected_max_vector_size;
|
|
allow_extra_args_ = true;
|
|
flag_like_ = false;
|
|
} else {
|
|
expected_min_ = value;
|
|
expected_max_ = value;
|
|
flag_like_ = (expected_min_ == 0);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// Set the range of expected arguments
|
|
Option *expected(int value_min, int value_max) {
|
|
if(value_min < 0) {
|
|
value_min = -value_min;
|
|
}
|
|
|
|
if(value_max < 0) {
|
|
value_max = detail::expected_max_vector_size;
|
|
}
|
|
if(value_max < value_min) {
|
|
expected_min_ = value_max;
|
|
expected_max_ = value_min;
|
|
} else {
|
|
expected_max_ = value_max;
|
|
expected_min_ = value_min;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
/// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
|
|
/// with each instance
|
|
Option *allow_extra_args(bool value = true) {
|
|
allow_extra_args_ = value;
|
|
return this;
|
|
}
|
|
/// Get the current value of allow extra args
|
|
bool get_allow_extra_args() const { return allow_extra_args_; }
|
|
|
|
/// Adds a Validator with a built in type name
|
|
Option *check(Validator validator, const std::string &validator_name = "") {
|
|
validator.non_modifying();
|
|
validators_.push_back(std::move(validator));
|
|
if(!validator_name.empty())
|
|
validators_.back().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, const 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(const 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(const 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) {
|
|
// This is an signed int so that it is not equivalent to a pointer.
|
|
if(index >= 0 && index < static_cast<int>(validators_.size())) {
|
|
return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
|
|
}
|
|
throw OptionNotFound("Validator index is not valid");
|
|
}
|
|
|
|
/// Sets required options
|
|
Option *needs(Option *opt) {
|
|
if(opt != this) {
|
|
needs_.insert(opt);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// Can find a string if needed
|
|
template <typename T = App> Option *needs(std::string opt_name) {
|
|
auto opt = dynamic_cast<T *>(parent_)->get_option_no_throw(opt_name);
|
|
if(opt == nullptr) {
|
|
throw IncorrectConstruction::MissingOption(opt_name);
|
|
}
|
|
return needs(opt);
|
|
}
|
|
|
|
/// 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) {
|
|
if(opt == this) {
|
|
throw(IncorrectConstruction("and option cannot exclude itself"));
|
|
}
|
|
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) {
|
|
auto opt = dynamic_cast<T *>(parent_)->get_option_no_throw(opt_name);
|
|
if(opt == nullptr) {
|
|
throw IncorrectConstruction::MissingOption(opt_name);
|
|
}
|
|
return excludes(opt);
|
|
}
|
|
|
|
/// 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_ = std::move(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) {
|
|
if(!ignore_case_ && value) {
|
|
ignore_case_ = value;
|
|
auto *parent = dynamic_cast<T *>(parent_);
|
|
for(const Option_p &opt : parent->options_) {
|
|
if(opt.get() == this) {
|
|
continue;
|
|
}
|
|
auto &omatch = opt->matching_name(*this);
|
|
if(!omatch.empty()) {
|
|
ignore_case_ = false;
|
|
throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
|
|
}
|
|
}
|
|
} else {
|
|
ignore_case_ = value;
|
|
}
|
|
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) {
|
|
|
|
if(!ignore_underscore_ && value) {
|
|
ignore_underscore_ = value;
|
|
auto *parent = dynamic_cast<T *>(parent_);
|
|
for(const Option_p &opt : parent->options_) {
|
|
if(opt.get() == this) {
|
|
continue;
|
|
}
|
|
auto &omatch = opt->matching_name(*this);
|
|
if(!omatch.empty()) {
|
|
ignore_underscore_ = false;
|
|
throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
|
|
}
|
|
}
|
|
} else {
|
|
ignore_underscore_ = value;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// Take the last argument if given multiple times (or another policy)
|
|
Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
|
|
if(value != multi_option_policy_) {
|
|
if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
|
|
expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility
|
|
// with the previous behavior of expected_ with vectors
|
|
expected_max_ = expected_min_;
|
|
}
|
|
multi_option_policy_ = value;
|
|
current_option_state_ = option_state::parsing;
|
|
}
|
|
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_min_; }
|
|
|
|
/// The minimum number of arguments the option expects
|
|
int get_type_size_min() const { return type_size_min_; }
|
|
/// The maximum number of arguments the option expects
|
|
int get_type_size_max() const { return type_size_max_; }
|
|
|
|
/// 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)
|
|
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_min_; }
|
|
|
|
/// The number of times the option expects to be included
|
|
int get_expected_min() const { return expected_min_; }
|
|
/// The max number of times the option expects to be included
|
|
int get_expected_max() const { return expected_max_; }
|
|
|
|
/// The total min number of expected string values to be used
|
|
int get_items_expected_min() const { return type_size_min_ * expected_min_; }
|
|
|
|
/// Get the maximum number of items expected to be returned and used for the callback
|
|
int get_items_expected_max() const {
|
|
int t = type_size_max_;
|
|
return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
|
|
}
|
|
/// The total min number of expected string values to be used
|
|
int get_items_expected() const { return get_items_expected_min(); }
|
|
|
|
/// 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(get_group().empty())
|
|
return {}; // Hidden
|
|
|
|
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(2, '-') + lnames_[0];
|
|
|
|
// Or short name if no long name
|
|
if(!snames_.empty())
|
|
return std::string(1, '-') + 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() {
|
|
|
|
if(current_option_state_ == option_state::parsing) {
|
|
_validate_results(results_);
|
|
current_option_state_ = option_state::validated;
|
|
}
|
|
|
|
if(current_option_state_ < option_state::reduced) {
|
|
_reduce_results(proc_results_, results_);
|
|
current_option_state_ = option_state::reduced;
|
|
}
|
|
if(current_option_state_ >= option_state::reduced) {
|
|
current_option_state_ = option_state::callback_run;
|
|
if(!(callback_)) {
|
|
return;
|
|
}
|
|
const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
|
|
bool local_result = callback_(send_results);
|
|
|
|
if(!local_result)
|
|
throw ConversionError(get_name(), results_);
|
|
}
|
|
}
|
|
|
|
/// If options share any of the same names, find it
|
|
const std::string &matching_name(const Option &other) const {
|
|
static const std::string estring;
|
|
for(const std::string &sname : snames_)
|
|
if(other.check_sname(sname))
|
|
return sname;
|
|
for(const std::string &lname : lnames_)
|
|
if(other.check_lname(lname))
|
|
return lname;
|
|
|
|
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 sname;
|
|
for(const std::string &lname : other.lnames_)
|
|
if(check_lname(lname))
|
|
return lname;
|
|
}
|
|
return estring;
|
|
}
|
|
/// If options share any of the same names, they are equal (not counting positional)
|
|
bool operator==(const Option &other) const { return !matching_name(other).empty(); }
|
|
|
|
/// 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(std::move(name), snames_, ignore_case_) >= 0);
|
|
}
|
|
|
|
/// Requires "--" to be removed from string
|
|
bool check_lname(std::string name) const {
|
|
return (detail::find_member(std::move(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(std::move(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(const 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 std::size_t because it is more than 0 in this block
|
|
if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
|
|
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)) {
|
|
if(flag_like_) {
|
|
return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
|
|
} else {
|
|
return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
|
|
}
|
|
}
|
|
if(ind < 0) {
|
|
return input_value;
|
|
}
|
|
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
|
|
try {
|
|
auto val = detail::to_flag_value(input_value);
|
|
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
|
|
} catch(const std::invalid_argument &) {
|
|
return input_value;
|
|
}
|
|
} else {
|
|
return input_value;
|
|
}
|
|
}
|
|
|
|
/// Puts a result at the end
|
|
Option *add_result(std::string s) {
|
|
_add_result(std::move(s), results_);
|
|
current_option_state_ = option_state::parsing;
|
|
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), results_);
|
|
current_option_state_ = option_state::parsing;
|
|
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), results_);
|
|
}
|
|
current_option_state_ = option_state::parsing;
|
|
return this;
|
|
}
|
|
|
|
/// Get a copy of the results
|
|
results_t results() const { return results_; }
|
|
|
|
/// Get a copy of the results
|
|
results_t reduced_results() const {
|
|
results_t res = proc_results_.empty() ? results_ : proc_results_;
|
|
if(current_option_state_ < option_state::reduced) {
|
|
if(current_option_state_ == option_state::parsing) {
|
|
res = results_;
|
|
_validate_results(res);
|
|
}
|
|
if(!res.empty()) {
|
|
results_t extra;
|
|
_reduce_results(extra, res);
|
|
if(!extra.empty()) {
|
|
res = std::move(extra);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// Get the results as a specified type
|
|
template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy>
|
|
void results(T &output) const {
|
|
bool retval;
|
|
if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
|
|
const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
|
|
retval = detail::lexical_conversion<T, T>(res, output);
|
|
} else {
|
|
results_t res;
|
|
if(results_.empty()) {
|
|
if(!default_str_.empty()) {
|
|
//_add_results takes an rvalue only
|
|
_add_result(std::string(default_str_), res);
|
|
_validate_results(res);
|
|
results_t extra;
|
|
_reduce_results(extra, res);
|
|
if(!extra.empty()) {
|
|
res = std::move(extra);
|
|
}
|
|
} else {
|
|
res.emplace_back();
|
|
}
|
|
} else {
|
|
res = reduced_results();
|
|
}
|
|
retval = detail::lexical_conversion<T, T>(res, output);
|
|
}
|
|
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 (current_option_state_ == option_state::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_ = std::move(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) {
|
|
if(option_type_size < 0) {
|
|
// this section is included for backwards compatibility
|
|
type_size_max_ = -option_type_size;
|
|
type_size_min_ = -option_type_size;
|
|
expected_max_ = detail::expected_max_vector_size;
|
|
} else {
|
|
type_size_max_ = option_type_size;
|
|
if(type_size_max_ < detail::expected_max_vector_size) {
|
|
type_size_min_ = option_type_size;
|
|
}
|
|
if(type_size_max_ == 0)
|
|
required_ = false;
|
|
}
|
|
return this;
|
|
}
|
|
/// Set a custom option type size range
|
|
Option *type_size(int option_type_size_min, int option_type_size_max) {
|
|
if(option_type_size_min < 0 || option_type_size_max < 0) {
|
|
// this section is included for backwards compatibility
|
|
expected_max_ = detail::expected_max_vector_size;
|
|
option_type_size_min = (std::abs)(option_type_size_min);
|
|
option_type_size_max = (std::abs)(option_type_size_max);
|
|
}
|
|
|
|
if(option_type_size_min > option_type_size_max) {
|
|
type_size_max_ = option_type_size_min;
|
|
type_size_min_ = option_type_size_max;
|
|
} else {
|
|
type_size_min_ = option_type_size_min;
|
|
type_size_max_ = option_type_size_max;
|
|
}
|
|
if(type_size_max_ == 0) {
|
|
required_ = false;
|
|
}
|
|
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_ = std::move(val);
|
|
return this;
|
|
}
|
|
|
|
/// Set the default value string representation and evaluate into the bound value
|
|
Option *default_val(const std::string &val) {
|
|
default_str(val);
|
|
auto old_results = results_;
|
|
results_.clear();
|
|
add_result(val);
|
|
run_callback();
|
|
results_ = std::move(old_results);
|
|
current_option_state_ = option_state::parsing;
|
|
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 the results through the Validators
|
|
void _validate_results(results_t &res) const {
|
|
// Run the Validators (can change the string)
|
|
if(!validators_.empty()) {
|
|
if(type_size_max_ > 1) { // in this context index refers to the index in the type
|
|
int index = 0;
|
|
if(get_items_expected_max() < static_cast<int>(res.size()) &&
|
|
multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
|
// create a negative index for the earliest ones
|
|
index = get_items_expected_max() - static_cast<int>(res.size());
|
|
}
|
|
|
|
for(std::string &result : res) {
|
|
if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) {
|
|
index = 0; // reset index for variable size chunks
|
|
continue;
|
|
}
|
|
auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
|
|
if(!err_msg.empty())
|
|
throw ValidationError(get_name(), err_msg);
|
|
++index;
|
|
}
|
|
} else {
|
|
int index = 0;
|
|
if(expected_max_ < static_cast<int>(res.size()) &&
|
|
multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
|
|
// create a negative index for the earliest ones
|
|
index = expected_max_ - static_cast<int>(res.size());
|
|
}
|
|
for(std::string &result : res) {
|
|
auto err_msg = _validate(result, index);
|
|
++index;
|
|
if(!err_msg.empty())
|
|
throw ValidationError(get_name(), err_msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** reduce the results in accordance with the MultiOptionPolicy
|
|
@param[out] res results are assigned to res if there if they are different
|
|
*/
|
|
void _reduce_results(results_t &res, const results_t &original) const {
|
|
|
|
// max num items expected or length of vector, always at least 1
|
|
// Only valid for a trimming policy
|
|
|
|
res.clear();
|
|
// Operation depends on the policy setting
|
|
switch(multi_option_policy_) {
|
|
case MultiOptionPolicy::TakeAll:
|
|
break;
|
|
case MultiOptionPolicy::TakeLast: {
|
|
// Allow multi-option sizes (including 0)
|
|
std::size_t trim_size = std::min<std::size_t>(
|
|
static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
|
|
if(original.size() != trim_size) {
|
|
res.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
|
|
}
|
|
} break;
|
|
case MultiOptionPolicy::TakeFirst: {
|
|
std::size_t trim_size = std::min<std::size_t>(
|
|
static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
|
|
if(original.size() != trim_size) {
|
|
res.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
|
|
}
|
|
} break;
|
|
case MultiOptionPolicy::Join:
|
|
if(results_.size() > 1) {
|
|
res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
|
|
}
|
|
break;
|
|
case MultiOptionPolicy::Throw:
|
|
default: {
|
|
auto num_min = static_cast<std::size_t>(get_items_expected_min());
|
|
auto num_max = static_cast<std::size_t>(get_items_expected_max());
|
|
if(num_min == 0) {
|
|
num_min = 1;
|
|
}
|
|
if(num_max == 0) {
|
|
num_max = 1;
|
|
}
|
|
if(original.size() < num_min) {
|
|
throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
|
|
}
|
|
if(original.size() > num_max) {
|
|
throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run a result through the Validators
|
|
std::string _validate(std::string &result, int index) const {
|
|
std::string err_msg;
|
|
if(result.empty() && expected_min_ == 0) {
|
|
// an empty with nothing expected is allowed
|
|
return 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, std::vector<std::string> &res) const {
|
|
int result_count = 0;
|
|
if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
|
|
result.back() == ']') { // this is now a vector string likely from the default or user entry
|
|
result.pop_back();
|
|
|
|
for(auto &var : CLI::detail::split(result.substr(1), ',')) {
|
|
if(!var.empty()) {
|
|
result_count += _add_result(std::move(var), res);
|
|
}
|
|
}
|
|
return result_count;
|
|
}
|
|
if(delimiter_ == '\0') {
|
|
res.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()) {
|
|
res.push_back(var);
|
|
++result_count;
|
|
}
|
|
}
|
|
} else {
|
|
res.push_back(std::move(result));
|
|
++result_count;
|
|
}
|
|
}
|
|
return result_count;
|
|
}
|
|
}; // namespace CLI
|
|
|
|
} // namespace CLI
|