mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-01-16 07:08:01 +00:00
Compare commits
4 Commits
31be35b241
...
32e6c3f347
Author | SHA1 | Date | |
---|---|---|---|
|
32e6c3f347 | ||
|
822f3d6700 | ||
|
a86f7fbd5e | ||
|
f0461525bb |
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
# Official actions have moving tags like v1
|
||||
# that are used, so they don't need updates here
|
||||
- dependency-name: "actions/checkout"
|
||||
- dependency-name: "actions/setup-python"
|
||||
- dependency-name: "actions/cache"
|
||||
- dependency-name: "actions/upload-artifact"
|
||||
- dependency-name: "actions/download-artifact"
|
||||
- dependency-name: "actions/labeler"
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Add wget
|
||||
run: apt-get update && apt-get install -y wget
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.4
|
||||
uses: jwlawson/actions-setup-cmake@v1.5
|
||||
- name: Configure
|
||||
run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON
|
||||
- name: Build
|
||||
|
@ -1,11 +1,12 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.10b0
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.1.0
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: mixed-line-ending
|
||||
@ -14,6 +15,7 @@ repos:
|
||||
- id: check-case-conflict
|
||||
- id: check-symlinks
|
||||
- id: check-yaml
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: docker-clang-format
|
||||
|
@ -560,6 +560,7 @@ There are several options that are supported on the main app and subcommands and
|
||||
- `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
|
||||
- `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
|
||||
- `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others.
|
||||
- `.silent()`: 🚧 Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers
|
||||
- `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
|
||||
- `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
|
||||
- `.needs(option_or_subcommand)`: 🆕 If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
|
||||
|
@ -112,3 +112,18 @@ Here, `--shared_flag` was set on the main app, and on the command line it "falls
|
||||
|
||||
This is a special mode that allows "prefix" commands, where the parsing completely stops when it gets to an unknown option. Further unknown options are ignored, even if they could match. Git is the traditional example for prefix commands; if you run git with an unknown subcommand, like "`git thing`", it then calls another command called "`git-thing`" with the remaining options intact.
|
||||
|
||||
### Silent subcommands
|
||||
|
||||
Subcommands can be modified by using the `silent` option. This will prevent the subcommand from showing up in the get_subcommands list. This can be used to make subcommands into modifiers. For example, a help subcommand might look like
|
||||
|
||||
```c++
|
||||
auto sub1 = app.add_subcommand("help")->silent();
|
||||
sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
|
||||
```
|
||||
|
||||
This would allow calling help such as:
|
||||
|
||||
```bash
|
||||
./app help
|
||||
./app help sub1
|
||||
```
|
||||
|
@ -218,11 +218,12 @@ class App {
|
||||
/// If set to true positional options are validated before assigning INHERITABLE
|
||||
bool validate_positionals_{false};
|
||||
|
||||
/// A pointer to the parent if this is a subcommand
|
||||
App *parent_{nullptr};
|
||||
/// indicator that the subcommand is silent and won't show up in subcommands list
|
||||
/// This is potentially useful as a modifier subcommand
|
||||
bool silent_{false};
|
||||
|
||||
/// Counts the number of times this command/subcommand was parsed
|
||||
std::size_t parsed_{0};
|
||||
std::uint32_t parsed_{0U};
|
||||
|
||||
/// Minimum required subcommands (not inheritable!)
|
||||
std::size_t require_subcommand_min_{0};
|
||||
@ -236,6 +237,9 @@ class App {
|
||||
/// Max number of options allowed. 0 is unlimited (not inheritable)
|
||||
std::size_t require_option_max_{0};
|
||||
|
||||
/// A pointer to the parent if this is a subcommand
|
||||
App *parent_{nullptr};
|
||||
|
||||
/// The group membership INHERITABLE
|
||||
std::string group_{"Subcommands"};
|
||||
|
||||
@ -396,6 +400,12 @@ class App {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// silence the subcommand from showing up in the processed list
|
||||
App *silent(bool silence = true) {
|
||||
silent_ = silence;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
|
||||
App *disabled_by_default(bool disable = true) {
|
||||
if(disable) {
|
||||
@ -1767,6 +1777,9 @@ class App {
|
||||
/// Get the status of disabled
|
||||
bool get_disabled() const { return disabled_; }
|
||||
|
||||
/// Get the status of silence
|
||||
bool get_silent() const { return silent_; }
|
||||
|
||||
/// Get the status of disabled
|
||||
bool get_immediate_callback() const { return immediate_callback_; }
|
||||
|
||||
@ -1821,7 +1834,21 @@ class App {
|
||||
}
|
||||
|
||||
/// Get a display name for an app
|
||||
std::string get_display_name() const { return (!name_.empty()) ? name_ : "[Option Group: " + get_group() + "]"; }
|
||||
std::string get_display_name(bool with_aliases = false) const {
|
||||
if(name_.empty()) {
|
||||
return std::string("[Option Group: ") + get_group() + "]";
|
||||
}
|
||||
if(aliases_.empty() || !with_aliases || aliases_.empty()) {
|
||||
return name_;
|
||||
}
|
||||
std::string dispname = name_;
|
||||
for(const auto &lalias : aliases_) {
|
||||
dispname.push_back(',');
|
||||
dispname.push_back(' ');
|
||||
dispname.append(lalias);
|
||||
}
|
||||
return dispname;
|
||||
}
|
||||
|
||||
/// Check the name, case insensitive and underscore insensitive if set
|
||||
bool check_name(std::string name_to_check) const {
|
||||
@ -2673,12 +2700,16 @@ class App {
|
||||
auto com = _find_subcommand(args.back(), true, true);
|
||||
if(com != nullptr) {
|
||||
args.pop_back();
|
||||
parsed_subcommands_.push_back(com);
|
||||
if(!com->silent_) {
|
||||
parsed_subcommands_.push_back(com);
|
||||
}
|
||||
com->_parse(args);
|
||||
auto parent_app = com->parent_;
|
||||
while(parent_app != this) {
|
||||
parent_app->_trigger_pre_parse(args.size());
|
||||
parent_app->parsed_subcommands_.push_back(com);
|
||||
if(!com->silent_) {
|
||||
parent_app->parsed_subcommands_.push_back(com);
|
||||
}
|
||||
parent_app = parent_app->parent_;
|
||||
}
|
||||
return true;
|
||||
|
@ -205,15 +205,18 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod
|
||||
|
||||
inline std::string Formatter::make_subcommand(const App *sub) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
|
||||
detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_expanded(const App *sub) const {
|
||||
std::stringstream out;
|
||||
out << sub->get_display_name() << "\n";
|
||||
out << sub->get_display_name(true) << "\n";
|
||||
|
||||
out << make_description(sub);
|
||||
if(sub->get_name().empty() && !sub->get_aliases().empty()) {
|
||||
detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
|
||||
}
|
||||
out << make_positionals(sub);
|
||||
out << make_groups(sub, AppFormatMode::Sub);
|
||||
out << make_subcommands(sub, AppFormatMode::Sub);
|
||||
|
@ -159,7 +159,7 @@ inline std::string trim_copy(const std::string &str, const std::string &filter)
|
||||
return trim(s, filter);
|
||||
}
|
||||
/// Print a two part "help" string
|
||||
inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) {
|
||||
inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(static_cast<int>(wid)) << std::left << name;
|
||||
if(!description.empty()) {
|
||||
@ -176,6 +176,24 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Print subcommand aliases
|
||||
inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
|
||||
if(!aliases.empty()) {
|
||||
out << std::setw(static_cast<int>(wid)) << " aliases: ";
|
||||
bool front = true;
|
||||
for(const auto &alias : aliases) {
|
||||
if(!front) {
|
||||
out << ", ";
|
||||
} else {
|
||||
front = false;
|
||||
}
|
||||
out << alias;
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Verify the first character of an option
|
||||
template <typename T> bool valid_first_char(T c) {
|
||||
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';
|
||||
|
@ -564,6 +564,26 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) {
|
||||
EXPECT_EQ(1u, app.count("-A"));
|
||||
}
|
||||
|
||||
TEST_F(TApp, SingleArgVector) {
|
||||
|
||||
std::vector<std::string> channels;
|
||||
std::vector<std::string> iargs;
|
||||
std::string path;
|
||||
app.add_option("-c", channels)->type_size(1)->allow_extra_args(false);
|
||||
app.add_option("args", iargs);
|
||||
app.add_option("-p", path);
|
||||
|
||||
app.parse("-c t1 -c t2 -c t3 a1 a2 a3 a4 -p happy");
|
||||
EXPECT_EQ(3u, channels.size());
|
||||
EXPECT_EQ(4u, iargs.size());
|
||||
EXPECT_EQ(path, "happy");
|
||||
|
||||
app.parse("-c t1 a1 -c t2 -c t3 a2 a3 a4 -p happy");
|
||||
EXPECT_EQ(3u, channels.size());
|
||||
EXPECT_EQ(4u, iargs.size());
|
||||
EXPECT_EQ(path, "happy");
|
||||
}
|
||||
|
||||
TEST_F(TApp, FlagLikeOption) {
|
||||
bool val{false};
|
||||
auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
|
||||
|
@ -467,6 +467,36 @@ TEST(THelp, Subcom) {
|
||||
EXPECT_THAT(help, HasSubstr("Usage: ./myprogram sub2"));
|
||||
}
|
||||
|
||||
TEST(THelp, Subcom_alias) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
auto sub1 = app.add_subcommand("sub1", "Subcommand1 description test");
|
||||
sub1->alias("sub_alias1");
|
||||
sub1->alias("sub_alias2");
|
||||
|
||||
app.add_subcommand("sub2", "Subcommand2 description test");
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("Usage: [OPTIONS] [SUBCOMMAND]"));
|
||||
EXPECT_THAT(help, HasSubstr("sub_alias1"));
|
||||
EXPECT_THAT(help, HasSubstr("sub_alias2"));
|
||||
}
|
||||
|
||||
TEST(THelp, Subcom_alias_group) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
auto sub1 = app.add_subcommand("", "Subcommand1 description test");
|
||||
sub1->alias("sub_alias1");
|
||||
sub1->alias("sub_alias2");
|
||||
|
||||
app.add_subcommand("sub2", "Subcommand2 description test");
|
||||
|
||||
std::string help = app.help();
|
||||
EXPECT_THAT(help, HasSubstr("Usage: [OPTIONS] [SUBCOMMAND]"));
|
||||
EXPECT_THAT(help, HasSubstr("sub_alias1"));
|
||||
EXPECT_THAT(help, HasSubstr("sub_alias2"));
|
||||
}
|
||||
|
||||
TEST(THelp, MasterName) {
|
||||
CLI::App app{"My prog", "MyRealName"};
|
||||
|
||||
|
@ -117,6 +117,19 @@ TEST_F(TApp, StdOptionalComplexDirect) {
|
||||
EXPECT_EQ(*opt, val2);
|
||||
}
|
||||
|
||||
TEST_F(TApp, StdOptionalUint) {
|
||||
std::optional<std::uint64_t> opt;
|
||||
app.add_option("-i,--int", opt);
|
||||
run();
|
||||
EXPECT_FALSE(opt);
|
||||
|
||||
args = {"-i", "15"};
|
||||
run();
|
||||
EXPECT_EQ(*opt, 15U);
|
||||
static_assert(CLI::detail::classify_object<std::optional<std::uint64_t>>::value ==
|
||||
CLI::detail::object_category::wrapper_value);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default : 4244)
|
||||
#endif
|
||||
|
@ -1466,6 +1466,21 @@ TEST_F(ManySubcommands, SubcommandTriggeredOn) {
|
||||
EXPECT_THROW(run(), CLI::ExtrasError);
|
||||
}
|
||||
|
||||
TEST_F(ManySubcommands, SubcommandSilence) {
|
||||
|
||||
sub1->silent();
|
||||
args = {"sub1", "sub2"};
|
||||
EXPECT_NO_THROW(run());
|
||||
|
||||
auto subs = app.get_subcommands();
|
||||
EXPECT_EQ(subs.size(), 1U);
|
||||
sub1->silent(false);
|
||||
EXPECT_FALSE(sub1->get_silent());
|
||||
run();
|
||||
subs = app.get_subcommands();
|
||||
EXPECT_EQ(subs.size(), 2U);
|
||||
}
|
||||
|
||||
TEST_F(TApp, UnnamedSub) {
|
||||
double val{0.0};
|
||||
auto sub = app.add_subcommand("", "empty name");
|
||||
@ -1622,6 +1637,23 @@ TEST_F(TApp, OptionGroupAlias) {
|
||||
EXPECT_EQ(val, -3);
|
||||
}
|
||||
|
||||
TEST_F(TApp, subcommand_help) {
|
||||
auto sub1 = app.add_subcommand("help")->silent();
|
||||
bool flag{false};
|
||||
app.add_flag("--one", flag, "FLAGGER");
|
||||
sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
|
||||
bool called{false};
|
||||
args = {"help"};
|
||||
try {
|
||||
run();
|
||||
} catch(const CLI::CallForHelp &) {
|
||||
called = true;
|
||||
}
|
||||
auto helpstr = app.help();
|
||||
EXPECT_THAT(helpstr, HasSubstr("FLAGGER"));
|
||||
EXPECT_TRUE(called);
|
||||
}
|
||||
|
||||
TEST_F(TApp, AliasErrors) {
|
||||
auto sub1 = app.add_subcommand("sub1");
|
||||
auto sub2 = app.add_subcommand("sub2");
|
||||
|
Loading…
Reference in New Issue
Block a user