fboss/cli/fboss2/CLI11/Option.hpp (858 lines of code) (raw):

// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // // SPDX-License-Identifier: BSD-3-Clause #pragma once #include <algorithm> #include <functional> #include <memory> #include <set> #include <string> #include <tuple> #include <utility> #include <vector> #include "Error.hpp" #include "Macros.hpp" #include "Split.hpp" #include "StringTools.hpp" #include "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}; /// Control option to run the callback to set the default bool run_callback_for_default_{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_; } /// Set the value of run_callback_for_default which controls whether the callback function should be called to set /// the default This is controlled automatically but could be manipulated by the user. Option *run_callback_for_default(bool value = true) { run_callback_for_default_ = value; return this; } /// Get the current value of run_callback_for_default bool get_run_callback_for_default() const { return run_callback_for_default_; } /// 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 = static_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 = static_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 = static_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 = static_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, ///< Show the positional name bool all_options = false ///< 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 and validate the results and run the callback if appropriate to set the value into the /// bound value only available for types that can be converted to a string template <typename X> Option *default_val(const X &val) { std::string val_str = detail::to_string(val); auto old_option_state = current_option_state_; results_t old_results{std::move(results_)}; results_.clear(); try { add_result(val_str); if(run_callback_for_default_) { run_callback(); // run callback sets the state we need to reset it again current_option_state_ = option_state::parsing; } else { _validate_results(results_); current_option_state_ = old_option_state; } } catch(const CLI::Error &) { // this should be done results_ = std::move(old_results); current_option_state_ = old_option_state; throw; } results_ = std::move(old_results); default_str_ = std::move(val_str); 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