1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

Using more powerful policy system, to allow git-like -m options

This commit is contained in:
Henry Fredrick Schreiner 2017-11-28 13:27:41 -05:00 committed by Henry Schreiner
parent 29b36857fd
commit 954c93d585
4 changed files with 62 additions and 32 deletions

View File

@ -394,7 +394,8 @@ class App {
return opt; return opt;
} }
/// Bool version - defaults to allowing multiple passings, but can be forced to one if `take_last(false)` is used. /// Bool version - defaults to allowing multiple passings, but can be forced to one if
/// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>
Option *add_flag(std::string name, Option *add_flag(std::string name,
T &count, ///< A variable holding true if passed T &count, ///< A variable holding true if passed
@ -410,7 +411,7 @@ class App {
if(opt->get_positional()) if(opt->get_positional())
throw IncorrectConstruction("Flags cannot be positional"); throw IncorrectConstruction("Flags cannot be positional");
opt->set_custom_option("", 0); opt->set_custom_option("", 0);
opt->take_last(); opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
return opt; return opt;
} }

View File

@ -26,6 +26,8 @@ class App;
using Option_p = std::unique_ptr<Option>; using Option_p = std::unique_ptr<Option>;
enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join };
template <typename CRTP> class OptionBase { template <typename CRTP> class OptionBase {
friend App; friend App;
@ -39,14 +41,14 @@ template <typename CRTP> class OptionBase {
/// Ignore the case when matching (option, not value) /// Ignore the case when matching (option, not value)
bool ignore_case_{false}; bool ignore_case_{false};
/// Only take the last argument (requires `expected_ == 1`) /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
bool last_{false}; MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
template <typename T> void copy_to(T *other) const { template <typename T> void copy_to(T *other) const {
other->group(group_); other->group(group_);
other->required(required_); other->required(required_);
other->ignore_case(ignore_case_); other->ignore_case(ignore_case_);
other->take_last(last_); other->multi_option_policy(multi_option_policy_);
} }
public: public:
@ -79,8 +81,8 @@ template <typename CRTP> class OptionBase {
/// The status of ignore case /// The status of ignore case
bool get_ignore_case() const { return ignore_case_; } bool get_ignore_case() const { return ignore_case_; }
/// The status of the take last flag /// The status of the multi option policy
bool get_take_last() const { return last_; } MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
}; };
class OptionDefaults : public OptionBase<OptionDefaults> { class OptionDefaults : public OptionBase<OptionDefaults> {
@ -90,8 +92,8 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
// Methods here need a different implementation if they are Option vs. OptionDefault // Methods here need a different implementation if they are Option vs. OptionDefault
/// Take the last argument if given multiple times /// Take the last argument if given multiple times
OptionDefaults *take_last(bool value = true) { OptionDefaults *multi_option_policy(MultiOptionPolicy value) {
last_ = value; multi_option_policy_ = value;
return this; return this;
} }
@ -213,8 +215,9 @@ class Option : public OptionBase<Option> {
throw IncorrectConstruction("Cannot set 0 expected, use a flag instead"); throw IncorrectConstruction("Cannot set 0 expected, use a flag instead");
else if(!changeable_) else if(!changeable_)
throw IncorrectConstruction("You can only change the expected arguments for vectors"); throw IncorrectConstruction("You can only change the expected arguments for vectors");
else if(last_) else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw)
throw IncorrectConstruction("You can't change expected arguments after you've set take_last!"); throw IncorrectConstruction(
"You can't change expected arguments after you've changed the multi option policy!");
expected_ = value; expected_ = value;
return this; return this;
@ -304,10 +307,10 @@ class Option : public OptionBase<Option> {
} }
/// Take the last argument if given multiple times /// Take the last argument if given multiple times
Option *take_last(bool value = true) { Option *multi_option_policy(MultiOptionPolicy value) {
if(get_expected() != 0 && get_expected() != 1) if(get_expected() != 0 && get_expected() != 1)
throw IncorrectConstruction("take_last only works for flags and single value options!"); throw IncorrectConstruction("multi_option_policy only works for flags and single value options!");
last_ = value; multi_option_policy_ = value;
return this; return this;
} }
@ -315,15 +318,9 @@ class Option : public OptionBase<Option> {
/// @name Accessors /// @name Accessors
///@{ ///@{
/// True if this is a required option
bool get_required() const { return required_; }
/// The number of arguments the option expects /// The number of arguments the option expects
int get_expected() const { return expected_; } int get_expected() const { return expected_; }
/// The status of the take last flag
bool get_take_last() const { return last_; }
/// True if this has a default value /// True if this has a default value
int get_default() const { return default_; } int get_default() const { return default_; }
@ -336,9 +333,6 @@ class Option : public OptionBase<Option> {
/// True if option has description /// True if option has description
bool has_description() const { return description_.length() > 0; } bool has_description() const { return description_.length() > 0; }
/// Get the group of this option
const std::string &get_group() const { return group_; }
/// Get the description /// Get the description
const std::string &get_description() const { return description_; } const std::string &get_description() const { return description_; }
@ -443,10 +437,17 @@ class Option : public OptionBase<Option> {
} }
bool local_result; bool local_result;
// If take_last, only operate on the final item
if(last_) { // Operation depends on the policy setting
if(multi_option_policy_ == MultiOptionPolicy::TakeLast) {
results_t partial_result = {results_.back()}; results_t partial_result = {results_.back()};
local_result = !callback_(partial_result); local_result = !callback_(partial_result);
} else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) {
results_t partial_result = {results_.at(0)};
local_result = !callback_(partial_result);
} else if(multi_option_policy_ == MultiOptionPolicy::Join) {
results_t partial_result = {detail::join(results_, "\n")};
local_result = !callback_(partial_result);
} else { } else {
if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) || if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) ||
(expected_ < 0 && results_.size() < static_cast<size_t>(-expected_))) (expected_ < 0 && results_.size() < static_cast<size_t>(-expected_)))

View File

@ -169,7 +169,7 @@ TEST_F(TApp, BoolAndIntFlags) {
TEST_F(TApp, BoolOnlyFlag) { TEST_F(TApp, BoolOnlyFlag) {
bool bflag; bool bflag;
app.add_flag("-b", bflag)->take_last(false); app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
args = {"-b"}; args = {"-b"};
EXPECT_NO_THROW(run()); EXPECT_NO_THROW(run());
@ -221,7 +221,7 @@ TEST_F(TApp, DefaultOpts) {
TEST_F(TApp, TakeLastOpt) { TEST_F(TApp, TakeLastOpt) {
std::string str; std::string str;
app.add_option("--str", str)->take_last(); app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
args = {"--str=one", "--str=two"}; args = {"--str=one", "--str=two"};
@ -230,6 +230,30 @@ TEST_F(TApp, TakeLastOpt) {
EXPECT_EQ(str, "two"); EXPECT_EQ(str, "two");
} }
TEST_F(TApp, TakeFirstOpt) {
std::string str;
app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
args = {"--str=one", "--str=two"};
run();
EXPECT_EQ(str, "one");
}
TEST_F(TApp, JoinOpt) {
std::string str;
app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::Join);
args = {"--str=one", "--str=two"};
run();
EXPECT_EQ(str, "one\ntwo");
}
TEST_F(TApp, MissingValueNonRequiredOpt) { TEST_F(TApp, MissingValueNonRequiredOpt) {
int count; int count;
app.add_option("-c,--count", count); app.add_option("-c,--count", count);

View File

@ -152,14 +152,14 @@ TEST_F(TApp, IncorrectConstructionVectorAsFlag) {
TEST_F(TApp, IncorrectConstructionVectorTakeLast) { TEST_F(TApp, IncorrectConstructionVectorTakeLast) {
std::vector<int> vec; std::vector<int> vec;
auto cat = app.add_option("--vec", vec); auto cat = app.add_option("--vec", vec);
EXPECT_THROW(cat->take_last(), CLI::IncorrectConstruction); EXPECT_THROW(cat->multi_option_policy(CLI::MultiOptionPolicy::TakeLast), CLI::IncorrectConstruction);
} }
TEST_F(TApp, IncorrectConstructionTakeLastExpected) { TEST_F(TApp, IncorrectConstructionTakeLastExpected) {
std::vector<int> vec; std::vector<int> vec;
auto cat = app.add_option("--vec", vec); auto cat = app.add_option("--vec", vec);
cat->expected(1); cat->expected(1);
ASSERT_NO_THROW(cat->take_last()); ASSERT_NO_THROW(cat->multi_option_policy(CLI::MultiOptionPolicy::TakeLast));
EXPECT_THROW(cat->expected(2), CLI::IncorrectConstruction); EXPECT_THROW(cat->expected(2), CLI::IncorrectConstruction);
} }
@ -301,16 +301,20 @@ TEST_F(TApp, OptionFromDefaults) {
TEST_F(TApp, OptionFromDefaultsSubcommands) { TEST_F(TApp, OptionFromDefaultsSubcommands) {
// Initial defaults // Initial defaults
EXPECT_FALSE(app.option_defaults()->get_required()); EXPECT_FALSE(app.option_defaults()->get_required());
EXPECT_FALSE(app.option_defaults()->get_take_last()); EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
EXPECT_FALSE(app.option_defaults()->get_ignore_case()); EXPECT_FALSE(app.option_defaults()->get_ignore_case());
EXPECT_EQ(app.option_defaults()->get_group(), "Options"); EXPECT_EQ(app.option_defaults()->get_group(), "Options");
app.option_defaults()->required()->take_last()->ignore_case()->group("Something"); app.option_defaults()
->required()
->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)
->ignore_case()
->group("Something");
auto app2 = app.add_subcommand("app2"); auto app2 = app.add_subcommand("app2");
EXPECT_TRUE(app2->option_defaults()->get_required()); EXPECT_TRUE(app2->option_defaults()->get_required());
EXPECT_TRUE(app2->option_defaults()->get_take_last()); EXPECT_EQ(app2->option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::TakeLast);
EXPECT_TRUE(app2->option_defaults()->get_ignore_case()); EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
EXPECT_EQ(app2->option_defaults()->get_group(), "Something"); EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
} }