Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inherit defaults for Options and Apps #48

Merged
merged 10 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .ci/check_style.sh → .ci/check_tidy.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
#!/usr/bin/env sh
set -evx

clang-format --version

git ls-files -- '*.cpp' '*.hpp' | xargs clang-format -i -style=file

git diff --exit-code --color

mkdir build-tidy || true
cd build-tidy
CXX_FLAGS="-Werror -Wall -Wextra -pedantic -std=c++11" cmake .. -DCLANG_TIDY_FIX=ON
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ script:
- |
if [ -n "$CHECK_STYLE" ]
then
.ci/check_style.sh
scripts/check_style.sh
.ci/check_tidy.sh
else
.ci/travis.sh
fi
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## Version 1.3 (in progress)

* Reworked the way defaults are set and inherited; explicit control given to user with `->option_defaults()` [#48](https://github.com/CLIUtils/CLI11/pull/48)
* Hidden options now are based on an empty group name, instead of special "hidden" keyword [#48](https://github.com/CLIUtils/CLI11/pull/48)
* `parse` no longer returns (so `CLI11_PARSE` is always usable) [#37](https://github.com/CLIUtils/CLI11/pull/37)
* Added `remaining()` and `remaining_size()` [#37](https://github.com/CLIUtils/CLI11/pull/37)
* `allow_extras` and `prefix_command` are now valid on subcommands [#37](https://github.com/CLIUtils/CLI11/pull/37)
Expand All @@ -8,6 +11,7 @@
* Help flags are easier to customize [#43](https://github.com/CLIUtils/CLI11/pull/43)
* Subcommand now support groups [#46](https://github.com/CLIUtils/CLI11/pull/46)
* `CLI::RuntimeError` added, for easy exit with error codes [#45](https://github.com/CLIUtils/CLI11/pull/45)
* The clang-format script is now no longer "hidden" [#48](https://github.com/CLIUtils/CLI11/pull/48)


## Version 1.2
Expand Down
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ In general, make sure the addition is well thought out and does not increase the

* Once you make the PR, tests will run to make sure your code works on all supported platforms
* The test coverage is also measured, and that should remain 100%
* Formatting should be done with clang-format, otherwise the format check will not pass. However, it is trivial to apply this to your PR, so don't worry about this check

* Formatting should be done with clang-format, otherwise the format check will not pass. However, it is trivial to apply this to your PR, so don't worry about this check. If you do have clang-format, just run `scripts/check_style.sh`
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
* `->requires(opt)`: This option requires another option to also be present, opt is an `Option` pointer.
* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer.
* `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print.
* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
* `->take_last()`: Only take the last option/flag given on the command line, automatically true for bool flags
* `->check(CLI::ExistingFile)`: Requires that the file exists if given.
Expand Down Expand Up @@ -211,6 +211,7 @@ There are several options that are supported on the main app and subcommands. Th
* `.allow_extras()`: Do not throw an error if extra arguments are left over
* `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognised item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
* `.set_footer(message)`: Set text to appear at the bottom of the help string.
* `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.

> Note: if you have a fixed number of required positional options, that will match before subcommand names.

Expand Down Expand Up @@ -242,10 +243,22 @@ sub.subcommand = true
Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". To print a configuration file from the passed
arguments, use `.config_to_str(default_also=false)`, where `default_also` will also show any defaulted arguments.

## Inheriting defaults

Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, and `footer`. The help flag existence, name, and description are inherited, as well.

Options have defaults for `group`, `required`, `take_last`, and `ignore_case`. To set these defaults, you should set the `option_defauts()` object, for example:

```cpp
app.option_defauts()->required();
// All future options will be required
```

The default settings for options are inherited to subcommands, as well.

## Subclassing

The App class was designed allow toolkits to subclass it, to provide default options and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, but provides an option to disable it in the constructor (and in `add_subcommand`), and can removed/replaced using `.set_help_flag(name, help_string)`. You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
but before run behavior, while
still giving the user freedom to `set_callback` on the main app.

Expand Down
2 changes: 1 addition & 1 deletion examples/subcom_in_files/subcommand_a.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
void setup_subcommand_a(CLI::App &app) {
// Create the option and subcommand objects.
auto opt = std::make_shared<SubcommandAOptions>();
auto sub = app.add_subcommand("subcommand_a", "performs subcommand a", true);
auto sub = app.add_subcommand("subcommand_a", "performs subcommand a");

// Add options to sub, binding them to opt.
sub->add_option("-f,--file", opt->file, "File name")->required();
Expand Down
84 changes: 57 additions & 27 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ class App {
/// Description of the current program/subcommand
std::string description_;

/// Footer to put after all options in the help output
/// Footer to put after all options in the help output INHERITABLE
std::string footer_;

/// If true, allow extra arguments (ie, don't throw an error).
/// If true, allow extra arguments (ie, don't throw an error). INHERITABLE
bool allow_extras_{false};

/// If true, return immediately on an unrecognised option (implies allow_extras)
/// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE
bool prefix_command_{false};

/// This is a function that runs when complete. Great for subcommands. Can throw.
Expand All @@ -79,10 +79,13 @@ class App {
/// @name Options
///@{

/// The default values for options, customizable and changeable INHERITABLE
OptionDefaults option_defaults_;

/// The list of options, stored locally
std::vector<Option_p> options_;

/// A pointer to the help flag if there is one
/// A pointer to the help flag if there is one INHERITABLE
Option *help_ptr_{nullptr};

///@}
Expand All @@ -106,10 +109,10 @@ class App {
/// Storage for subcommand list
std::vector<App_p> subcommands_;

/// If true, the program name is not case sensitive
/// If true, the program name is not case sensitive INHERITABLE
bool ignore_case_{false};

/// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand.
/// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE
bool fallthrough_{false};

/// A pointer to the parent if this is a subcommand
Expand All @@ -121,7 +124,7 @@ class App {
/// -1 for 1 or more, 0 for not required, # for exact number required
int require_subcommand_ = 0;

/// The group membership
/// The group membership INHERITABLE
std::string group_{"Subcommands"};

///@}
Expand All @@ -140,25 +143,43 @@ class App {
///@}

/// Special private constructor for subcommand
App(std::string description_, bool help, detail::enabler) : description_(std::move(description_)) {

if(help)
set_help_flag("-h,--help", "Print this help message and exit");
App(std::string description_, App *parent) : description_(std::move(description_)), parent_(parent) {
// Inherit if not from a nullptr
if(parent_ != nullptr) {
if(parent_->help_ptr_ != nullptr)
set_help_flag(parent_->help_ptr_->get_name(), parent_->help_ptr_->get_description());

/// OptionDefaults
option_defaults_ = parent_->option_defaults_;

// INHERITABLE
allow_extras_ = parent_->allow_extras_;
prefix_command_ = parent_->prefix_command_;
ignore_case_ = parent_->ignore_case_;
fallthrough_ = parent_->fallthrough_;
group_ = parent_->group_;
footer_ = parent_->footer_;
}
}

public:
/// @name Basic
///@{

/// Create a new program. Pass in the same arguments as main(), along with a help string.
App(std::string description_ = "", bool help = true) : App(description_, help, detail::dummy) {}
App(std::string description_ = "") : App(description_, nullptr) {
set_help_flag("-h,--help", "Print this help message and exit");
}

/// Set footer.
App *set_footer(std::string footer) {
footer_ = footer;
return this;
}

/// Get footer.
std::string get_footer() const { return footer_; }

/// Set a callback for the end of parsing.
///
/// Due to a bug in c++11,
Expand All @@ -176,12 +197,17 @@ class App {
return this;
}

/// Get the status of allow extras
bool get_allow_extras() const { return allow_extras_; }

/// Do not parse anything after the first unrecognised option and return
App *prefix_command(bool allow = true) {
prefix_command_ = allow;
return this;
}

bool get_prefix_command() const { return prefix_command_; }

/// Ignore case. Subcommand inherit value.
App *ignore_case(bool value = true) {
ignore_case_ = value;
Expand All @@ -194,6 +220,8 @@ class App {
return this;
}

bool get_ignore_case() const { return ignore_case_; }

/// Check to see if this subcommand was parsed, true only if received on command line.
bool parsed() const { return parsed_; }

Expand All @@ -215,6 +243,9 @@ class App {
return this;
}

/// Check the status of fallthrough
bool get_fallthrough() const { return fallthrough_; }

/// Changes the group membership
App *group(std::string name) {
group_ = name;
Expand All @@ -224,6 +255,9 @@ class App {
/// Get the group of this subcommand
const std::string &get_group() const { return group_; }

/// Get the OptionDefault object, to set option defaults
OptionDefaults *option_defaults() { return &option_defaults_; }

///@}
/// @name Adding options
///@{
Expand Down Expand Up @@ -251,6 +285,7 @@ class App {
options_.emplace_back();
Option_p &option = options_.back();
option.reset(new Option(name, description, callback, defaulted, this));
option_defaults_.copy_to(option.get());
return option.get();
} else
throw OptionAlreadyAdded(myopt.get_name());
Expand Down Expand Up @@ -615,15 +650,10 @@ class App {
/// @name Subcommmands
///@{

/// Add a subcommand. Like the constructor, you can override the help message addition by setting help=false
App *add_subcommand(std::string name, std::string description = "", bool help = true) {
subcommands_.emplace_back(new App(description, help, detail::dummy));
/// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
App *add_subcommand(std::string name, std::string description = "") {
subcommands_.emplace_back(new App(description, this));
subcommands_.back()->name_ = name;
subcommands_.back()->allow_extras_ = allow_extras_;
subcommands_.back()->prefix_command_ = prefix_command_;
subcommands_.back()->parent_ = this;
subcommands_.back()->ignore_case_ = ignore_case_;
subcommands_.back()->fallthrough_ = fallthrough_;
for(const auto &subc : subcommands_)
if(subc.get() != subcommands_.back().get())
if(subc->check_name(subcommands_.back()->name_) || subcommands_.back()->check_name(subc->name_))
Expand Down Expand Up @@ -820,7 +850,7 @@ class App {
for(const Option_p &opt : options_)
if(opt->get_positional()) {
// A hidden positional should still show up in the usage statement
// if(detail::to_lower(opt->get_group()) == "hidden")
// if(detail::to_lower(opt->get_group()).empty())
// continue;
out << " " << opt->help_positional();
if(opt->_has_help_positional())
Expand All @@ -840,8 +870,8 @@ class App {
if(pos) {
out << std::endl << "Positionals:" << std::endl;
for(const Option_p &opt : options_) {
if(detail::to_lower(opt->get_group()) == "hidden")
continue;
if(detail::to_lower(opt->get_group()).empty())
continue; // Hidden
if(opt->_has_help_positional())
detail::format_help(out, opt->help_pname(), opt->get_description(), wid);
}
Expand All @@ -850,8 +880,8 @@ class App {
// Options
if(npos) {
for(const std::string &group : groups) {
if(detail::to_lower(group) == "hidden")
continue;
if(detail::to_lower(group).empty())
continue; // Hidden
out << std::endl << group << ":" << std::endl;
for(const Option_p &opt : options_) {
if(opt->nonpositional() && opt->get_group() == group)
Expand All @@ -865,8 +895,8 @@ class App {
std::set<std::string> subcmd_groups_seen;
for(const App_p &com : subcommands_) {
const std::string &group_key = detail::to_lower(com->get_group());
if(group_key == "hidden" || subcmd_groups_seen.count(group_key) != 0)
continue;
if(group_key.empty() || subcmd_groups_seen.count(group_key) != 0)
continue; // Hidden or not in a group

subcmd_groups_seen.insert(group_key);
out << std::endl << com->get_group() << ":" << std::endl;
Expand Down
Loading