diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:01:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:01:36 +0000 |
commit | 62e4c68907d8d33709c2c1f92a161dff00b3d5f2 (patch) | |
tree | adbbaf3acf88ea08f6eeec4b75ee98ad3b07fbdc /src/third-party/CLI/Option.hpp | |
parent | Initial commit. (diff) | |
download | lnav-62e4c68907d8d33709c2c1f92a161dff00b3d5f2.tar.xz lnav-62e4c68907d8d33709c2c1f92a161dff00b3d5f2.zip |
Adding upstream version 0.11.2.upstream/0.11.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/third-party/CLI/Option.hpp')
-rw-r--r-- | src/third-party/CLI/Option.hpp | 1362 |
1 files changed, 1362 insertions, 0 deletions
diff --git a/src/third-party/CLI/Option.hpp b/src/third-party/CLI/Option.hpp new file mode 100644 index 0000000..b075bbc --- /dev/null +++ b/src/third-party/CLI/Option.hpp @@ -0,0 +1,1362 @@ +// Copyright (c) 2017-2022, 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 + +// [CLI11:public_includes:set] +#include <algorithm> +#include <functional> +#include <memory> +#include <set> +#include <string> +#include <tuple> +#include <utility> +#include <vector> +// [CLI11:public_includes:end] + +#include "Error.hpp" +#include "Macros.hpp" +#include "Split.hpp" +#include "StringTools.hpp" +#include "Validators.hpp" + +namespace CLI { +// [CLI11:option_hpp:verbatim] + +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 + Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter +}; + +/// 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) { + if(!detail::valid_alias_name_string(name)) { + throw IncorrectConstruction("Group names may not contain newlines or null characters"); + } + 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_{}; + + /// If given, replace the text that describes the option type and usage in the help text + std::string option_text_{}; + + /// 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 + ///@{ + + /// link back up to the parent App for fallthrough + 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 : char { + 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}; + /// flag indicating a separator needs to be injected after each argument call + bool inject_separator_{false}; + /// flag indicating that the option should trigger the validation and callback chain on each result when loaded + bool trigger_on_result_{false}; + /// flag indicating that the option should force the callback regardless if any results present + bool force_callback_{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 bool operator returns true if any arguments were passed or the option callback is forced + explicit operator bool() const { return !empty() || force_callback_; } + + /// 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 trigger_on_parse which specifies that the option callback should be triggered on every parse + Option *trigger_on_parse(bool value = true) { + trigger_on_result_ = value; + return this; + } + /// The status of trigger on parse + bool get_trigger_on_parse() const { return trigger_on_result_; } + + /// Set the value of force_callback + Option *force_callback(bool value = true) { + force_callback_ = value; + return this; + } + /// The status of force_callback + bool get_force_callback() const { return force_callback_; } + + /// 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_; } + + /// Return the inject_separator flag + int get_inject_separator() const { return inject_separator_; } + + /// 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_; } + /// Get a single name for the option, first of lname, pname, sname, envname + const std::string &get_single_name() const { + if(!lnames_.empty()) { + return lnames_[0]; + } + if(!pname_.empty()) { + return pname_; + } + if(!snames_.empty()) { + return snames_[0]; + } + return envname_; + } + /// 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; + } + + Option *option_text(std::string text) { + option_text_ = std::move(text); + return this; + } + + const std::string &get_option_text() const { return option_text_; } + + ///@} + /// @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(force_callback_ && results_.empty()) { + add_result(default_str_); + } + 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(const 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)); + if(!pname_.empty()) { + std::string local_pname = pname_; + std::string local_name = name; + if(ignore_underscore_) { + local_pname = detail::remove_underscore(local_pname); + local_name = detail::remove_underscore(local_name); + } + if(ignore_case_) { + local_pname = detail::to_lower(local_pname); + local_name = detail::to_lower(local_name); + } + if(local_name == local_pname) { + return true; + } + } + + if(!envname_.empty()) { + // this needs to be the original since envname_ shouldn't match on case insensitivity + return (name == envname_); + } + return false; + } + + /// 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) { + current_option_state_ = option_state::parsing; + for(auto &str : s) { + _add_result(std::move(str), results_); + } + return this; + } + + /// Get the current complete results set + const 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> 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; + } else { + inject_separator_ = true; + } + 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; + } + if(type_size_max_ >= detail::expected_max_vector_size) { + inject_separator_ = true; + } + return this; + } + + /// Set the value of the separator injection flag + void inject_separator(bool value = true) { inject_separator_ = value; } + + /// 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 trigger_on_result_ is set the callback already ran + if(run_callback_for_default_ && !trigger_on_result_) { + 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(detail::is_separator(result) && 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::Sum: + res.push_back(detail::sum_string_vector(original)); + 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; + } + } + // this check is to allow an empty vector in certain circumstances but not if expected is not zero. + // {} is the indicator for a an empty container + if(res.empty()) { + if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) { + res.push_back("{}"); + res.push_back("%%"); + } + } else if(res.size() == 1 && res[0] == "{}" && get_items_expected_min() > 0) { + res.push_back("%%"); + } + } + + // 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; + } +}; + +// [CLI11:option_hpp:end] +} // namespace CLI |