Skip to content

Commit

Permalink
allow callbacks for option_groups, and allow some other characters as…
Browse files Browse the repository at this point in the history
… flags
  • Loading branch information
phlptp committed Mar 6, 2019
1 parent 2b99577 commit 9d421e0
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 13 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ Option_group *app.add_option_group(name,description); // 🚧
-app.add_mutable_set_ignore_case_underscore(... // 🆕 String only
```

An option name must start with a alphabetic character, underscore, or a number🚧. For long options, after the first character '.', and '-' are also valid. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.
An option name must start with a alphabetic character, underscore, a number🚧, or one 🚧 of '$', '?', '#', '@', '%'. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option`.

The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function.

Expand Down Expand Up @@ -511,7 +511,7 @@ There are several options that are supported on the main app and subcommands and
#### Callbacks
A subcommand has two optional callbacks that are executed at different stages of processing. The `preparse_callback` 🚧 is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
The second callback is executed after parsing. Depending on the status of the `immediate_callback` flag 🚧. This is either immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the immediate_callback is set then the callback can be executed multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback.
The second callback is executed after parsing. Depending on the status of the `immediate_callback` flag 🚧. This is either immediately after the parsing of the subcommand. Or if the flag is false, once after parsing of all arguments. If the immediate_callback is set then the callback can be executed multiple times. If the main app or subcommand has a config file, no data from the config file will be reflected in immediate_callback. `immediate_callback()` has no effect on the main app. For option_groups `immediate_callback` causes the callback to be run prior to other option groups and options in the main app, effectively giving the options in the group priority.
For example say an application was set up like
```cpp
Expand All @@ -534,8 +534,8 @@ program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub
* c1 will be called when the `sub2` command is encountered
* pc2 will be called with value of 6 after the sub2 command is encountered.
* c1 will be called again after the second sub2 command is encountered
* ac will be called after completing the parse
* c2 will be called once after processing all arguments
* ac will be called after completing the parse and all lower level callbacks have been executed

A subcommand is considered terminated when one of the following conditions are met.
1. There are no more arguments to process
Expand Down
33 changes: 28 additions & 5 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1778,15 +1778,26 @@ class App {
app->_configure();
}
}
/// Internal function to run (App) callback, top down
/// Internal function to run (App) callback, bottom up
void run_callback() {
pre_callback();
if(callback_ && (parsed_ > 0))
callback_();
// run the callbacks for the received subcommands
for(App *subc : get_subcommands()) {
if((subc->get_name().empty()) || (!subc->immediate_callback_))
if(!subc->immediate_callback_)
subc->run_callback();
}
// now run callbacks for option_groups
for(auto &subc : subcommands_) {
if((!subc->immediate_callback_) && (subc->name_.empty()) && (subc->count_all() > 0)) {
subc->run_callback();
}
}
// finally run the main callback
if(callback_ && (parsed_ > 0)) {
if((!name_.empty()) || (count_all() > 0)) {
callback_();
}
}
}

/// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.
Expand Down Expand Up @@ -1879,15 +1890,27 @@ class App {

/// Process callbacks. Runs on *all* subcommands.
void _process_callbacks() {

for(App_p &sub : subcommands_) {
// process the priority option_groups first
if((sub->get_name().empty()) && (sub->immediate_callback_)) {
if(sub->count_all() > 0) {
sub->_process_callbacks();
sub->run_callback();
}
}
}

for(const Option_p &opt : options_) {
if(opt->count() > 0 && !opt->get_callback_run()) {
opt->run_callback();
}
}

for(App_p &sub : subcommands_) {
if((sub->get_name().empty()) || (!sub->immediate_callback_))
if(!sub->immediate_callback_) {
sub->_process_callbacks();
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ class Option : public OptionBase<Option> {
// Run the validators (can change the string)
if(!validators_.empty()) {
for(std::string &result : results_)
for(const std::function<std::string(std::string &)> &vali : validators_) {
for(const auto &vali : validators_) {
std::string err_msg;

try {
Expand Down
8 changes: 4 additions & 4 deletions include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin
}

/// Verify the first character of an option
template <typename T> bool valid_first_char(T c) { return std::isalnum(c, std::locale()) || c == '_'; }
template <typename T> bool valid_first_char(T c) {
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '$' || c == '%' || c == '#' || c == '@';
}

/// Verify following characters of an option
template <typename T> bool valid_later_char(T c) {
return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-';
}
template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; }

/// Verify an option name
inline bool valid_name_string(const std::string &str) {
Expand Down
11 changes: 11 additions & 0 deletions tests/HelpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,17 @@ TEST(THelp, OnlyOneHelp) {
EXPECT_THROW(app.parse(input), CLI::ExtrasError);
}

TEST(THelp, MultiHelp) {
CLI::App app{"My prog"};

// It is not supported to have more than one help flag, last one wins
app.set_help_flag("--help,-h,-?", "No short name allowed");
app.allow_windows_style_options();

std::vector<std::string> input{"/?"};
EXPECT_THROW(app.parse(input), CLI::CallForHelp);
}

TEST(THelp, OnlyOneAllHelp) {
CLI::App app{"My prog"};

Expand Down
24 changes: 24 additions & 0 deletions tests/OptionGroupTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,30 @@ TEST_F(ManyGroups, SameSubcommand) {
EXPECT_EQ(subs[1], sub2);
EXPECT_EQ(subs[2], sub3);
}
TEST_F(ManyGroups, CallbackOrder) {
// only 1 group can be used
remove_required();
std::vector<int> callback_order;
g1->callback([&callback_order]() { callback_order.push_back(1); });
g2->callback([&callback_order]() { callback_order.push_back(2); });
main->callback([&callback_order]() { callback_order.push_back(3); });

args = {"--name2", "test"};
run();
EXPECT_EQ(callback_order, std::vector<int>({2, 3}));

callback_order.clear();
args = {"--name1", "t2", "--name2", "test"};
g2->immediate_callback();
run();
EXPECT_EQ(callback_order, std::vector<int>({2, 1, 3}));
callback_order.clear();

args = {"--name2", "test", "--name1", "t2"};
g2->immediate_callback(false);
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 2, 3}));
}

struct ManyGroupsPreTrigger : public ManyGroups {
size_t triggerMain, trigger1{87u}, trigger2{34u}, trigger3{27u};
Expand Down
16 changes: 16 additions & 0 deletions tests/SubcommandTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,22 @@ TEST_F(SubcommandProgram, CallbackOrder) {
EXPECT_EQ(callback_order, std::vector<int>({2, 1}));
}

TEST_F(SubcommandProgram, CallbackOrderImmediate) {
std::vector<int> callback_order;
start->callback([&callback_order]() { callback_order.push_back(1); })->immediate_callback();
stop->callback([&callback_order]() { callback_order.push_back(2); });

args = {"start", "stop", "start"};
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 1, 2}));

callback_order.clear();

args = {"stop", "start", "stop", "start"};
run();
EXPECT_EQ(callback_order, std::vector<int>({1, 1, 2}));
}

struct ManySubcommands : public TApp {

CLI::App *sub1;
Expand Down

0 comments on commit 9d421e0

Please sign in to comment.