mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
prefix_command tests and improvements (#1266)
adding a PrefixCommandMode option to the prefix_command to allow specification of a separator and catch other errors Addresses #1264 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1128,7 +1128,14 @@ option_groups. These are:
|
||||
- `.prefix_command()`: Like `allow_extras`, but stop processing immediately on
|
||||
the first unrecognized item. All subsequent arguments are placed in the
|
||||
remaining_arg list. It is ideal for allowing your app or subcommand to be a
|
||||
"prefix" to calling another app.
|
||||
"prefix" to calling another app. Can be called with a `bool` value to turn on
|
||||
or off
|
||||
- `.prefix_command(CLI::PrefixCommandMode)`: specify the prefix_command mode to
|
||||
use. `PrefixCommandMode::on` and `PrefixCommandMode::off` are the same as
|
||||
`prefix_command(true)` or `prefix_command(false)`. Calling with
|
||||
`PrefixCommandMode::separator_only` will only trigger prefix command mode by
|
||||
the use of the subcommand separator `--` other unrecognized arguments would be
|
||||
considered an error depending on whether `allow_extras` was set or not.
|
||||
- `.usage(message)`: Replace text to appear at the start of the help string
|
||||
after description.
|
||||
- `.usage(std::string())`: Set a callback to generate a string that will appear
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
CLI::App app("Prefix command app");
|
||||
app.prefix_command();
|
||||
app.prefix_command(CLI::PrefixCommandMode::On);
|
||||
|
||||
std::vector<int> vals;
|
||||
app.add_option("--vals,-v", vals)->expected(-1);
|
||||
|
||||
@@ -67,9 +67,13 @@ CLI11_INLINE std::string help(const App *app, const Error &e);
|
||||
} // namespace FailureMessage
|
||||
|
||||
/// enumeration of modes of how to deal with extras in config files
|
||||
|
||||
enum class config_extras_mode : std::uint8_t { error = 0, ignore, ignore_all, capture };
|
||||
|
||||
/// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other
|
||||
/// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other
|
||||
/// recognized options
|
||||
enum class PrefixCommandMode : std::uint8_t { Off = 0, SeparatorOnly = 1, On = 2 };
|
||||
|
||||
class App;
|
||||
|
||||
using App_p = std::shared_ptr<App>;
|
||||
@@ -119,7 +123,7 @@ class App {
|
||||
config_extras_mode allow_config_extras_{config_extras_mode::ignore};
|
||||
|
||||
/// If true, cease processing on an unrecognized option (implies allow_extras) INHERITABLE
|
||||
bool prefix_command_{false};
|
||||
PrefixCommandMode prefix_command_{PrefixCommandMode::Off};
|
||||
|
||||
/// If set to true the name was automatically generated from the command line vs a user set name
|
||||
bool has_automatic_name_{false};
|
||||
@@ -474,7 +478,14 @@ class App {
|
||||
/// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in
|
||||
/// remaining args
|
||||
App *prefix_command(bool is_prefix = true) {
|
||||
prefix_command_ = is_prefix;
|
||||
prefix_command_ = is_prefix ? PrefixCommandMode::On : PrefixCommandMode::Off;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in
|
||||
/// remaining args
|
||||
App *prefix_command(PrefixCommandMode mode) {
|
||||
prefix_command_ = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1162,7 +1173,10 @@ class App {
|
||||
CLI11_NODISCARD std::size_t get_require_option_max() const { return require_option_max_; }
|
||||
|
||||
/// Get the prefix command status
|
||||
CLI11_NODISCARD bool get_prefix_command() const { return prefix_command_; }
|
||||
CLI11_NODISCARD bool get_prefix_command() const { return static_cast<bool>(prefix_command_); }
|
||||
|
||||
/// Get the prefix command status
|
||||
CLI11_NODISCARD PrefixCommandMode get_prefix_command_mode() const { return prefix_command_; }
|
||||
|
||||
/// Get the status of allow extras
|
||||
CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_; }
|
||||
@@ -1319,10 +1333,6 @@ class App {
|
||||
/// Throw an error if anything is left over and should not be.
|
||||
void _process_extras();
|
||||
|
||||
/// Throw an error if anything is left over and should not be.
|
||||
/// Modifies the args to fill in the missing items before throwing.
|
||||
void _process_extras(std::vector<std::string> &args);
|
||||
|
||||
/// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
|
||||
void increment_parsed();
|
||||
|
||||
|
||||
@@ -1072,7 +1072,7 @@ CLI11_INLINE void App::_configure() {
|
||||
}
|
||||
if(app->name_.empty()) {
|
||||
app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop
|
||||
app->prefix_command_ = false;
|
||||
app->prefix_command_ = PrefixCommandMode::Off;
|
||||
}
|
||||
// make sure the parent is set to be this object in preparation for parse
|
||||
app->parent_ = this;
|
||||
@@ -1461,34 +1461,26 @@ CLI11_INLINE void App::_process() {
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::_process_extras() {
|
||||
if(!(allow_extras_ || prefix_command_)) {
|
||||
if(!allow_extras_ && prefix_command_ == PrefixCommandMode::Off) {
|
||||
std::size_t num_left_over = remaining_size();
|
||||
if(num_left_over > 0) {
|
||||
throw ExtrasError(name_, remaining(false));
|
||||
}
|
||||
}
|
||||
|
||||
if(!allow_extras_ && prefix_command_ == PrefixCommandMode::SeparatorOnly) {
|
||||
std::size_t num_left_over = remaining_size();
|
||||
if(num_left_over > 0) {
|
||||
if(remaining(false).front() != "--") {
|
||||
throw ExtrasError(name_, remaining(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
for(App_p &sub : subcommands_) {
|
||||
if(sub->count() > 0)
|
||||
sub->_process_extras();
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::_process_extras(std::vector<std::string> &args) {
|
||||
if(!(allow_extras_ || prefix_command_)) {
|
||||
std::size_t num_left_over = remaining_size();
|
||||
if(num_left_over > 0) {
|
||||
args = remaining(false);
|
||||
throw ExtrasError(name_, args);
|
||||
}
|
||||
}
|
||||
|
||||
for(App_p &sub : subcommands_) {
|
||||
if(sub->count() > 0)
|
||||
sub->_process_extras(args);
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::increment_parsed() {
|
||||
++parsed_;
|
||||
for(App_p &sub : subcommands_) {
|
||||
@@ -1512,8 +1504,7 @@ CLI11_INLINE void App::_parse(std::vector<std::string> &args) {
|
||||
_process();
|
||||
|
||||
// Throw error if any items are left over (depending on settings)
|
||||
_process_extras(args);
|
||||
|
||||
_process_extras();
|
||||
// Convert missing (pairs) to extras (string only) ready for processing in another app
|
||||
args = remaining_for_passthrough(false);
|
||||
} else if(parse_complete_callback_) {
|
||||
@@ -1738,7 +1729,13 @@ CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &posit
|
||||
case detail::Classifier::POSITIONAL_MARK:
|
||||
args.pop_back();
|
||||
positional_only = true;
|
||||
if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
|
||||
if(get_prefix_command()) {
|
||||
_move_to_missing(classifier, "--");
|
||||
while(!args.empty()) {
|
||||
missing_.emplace_back(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
} else if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
|
||||
retval = false;
|
||||
} else {
|
||||
_move_to_missing(classifier, "--");
|
||||
@@ -1922,9 +1919,9 @@ CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool ha
|
||||
/// We are out of other options this goes to missing
|
||||
_move_to_missing(detail::Classifier::NONE, positional);
|
||||
args.pop_back();
|
||||
if(prefix_command_) {
|
||||
if(get_prefix_command()) {
|
||||
while(!args.empty()) {
|
||||
_move_to_missing(detail::Classifier::NONE, args.back());
|
||||
missing_.emplace_back(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
}
|
||||
@@ -2151,6 +2148,12 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
|
||||
// Otherwise, add to missing
|
||||
args.pop_back();
|
||||
_move_to_missing(current_type, current);
|
||||
if(get_prefix_command_mode() == PrefixCommandMode::On) {
|
||||
while(!args.empty()) {
|
||||
missing_.emplace_back(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2340,11 +2343,11 @@ CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(c
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) {
|
||||
if(allow_extras_ || subcommands_.empty()) {
|
||||
if(allow_extras_ || subcommands_.empty() || get_prefix_command()) {
|
||||
missing_.emplace_back(val_type, val);
|
||||
return;
|
||||
}
|
||||
// allow extra arguments to be places in an option group if it is allowed there
|
||||
// allow extra arguments to be placed in an option group if it is allowed there
|
||||
for(auto &subc : subcommands_) {
|
||||
if(subc->name_.empty() && subc->allow_extras_) {
|
||||
subc->missing_.emplace_back(val_type, val);
|
||||
|
||||
@@ -2427,6 +2427,57 @@ TEST_CASE_METHOD(TApp, "AllowExtrasCascade", "[app]") {
|
||||
CHECK(45 == v1);
|
||||
CHECK(27 == v2);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {
|
||||
int v1{0};
|
||||
int v2{0};
|
||||
app.add_option("-f", v1);
|
||||
app.add_option("-x", v2);
|
||||
app.prefix_command();
|
||||
args = {"-x", "45", "-f", "27"};
|
||||
run();
|
||||
auto rem = app.remaining();
|
||||
CHECK(rem.empty());
|
||||
|
||||
args = {"-x", "45", "-f", "27", "--test", "23"};
|
||||
|
||||
run();
|
||||
rem = app.remaining();
|
||||
CHECK(rem.size() == 2U);
|
||||
|
||||
args = {"-x", "45", "-f", "27", "--", "--test", "23"};
|
||||
|
||||
run();
|
||||
rem = app.remaining();
|
||||
CHECK(rem.size() == 3U);
|
||||
|
||||
args = {"-x", "45", "--test4", "-f", "27", "--test", "23"};
|
||||
|
||||
run();
|
||||
rem = app.remaining();
|
||||
CHECK(rem.size() == 5U);
|
||||
|
||||
app.prefix_command(CLI::PrefixCommandMode::SeparatorOnly);
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
|
||||
args = {"-x", "45", "positional", "-f", "27", "--test", "23"};
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
|
||||
args = {"-x", "45", "-f", "27", "--", "--test", "23"};
|
||||
|
||||
run();
|
||||
rem = app.remaining();
|
||||
CHECK(rem.size() == 3U);
|
||||
|
||||
args = {"-x", "45", "--test4", "-f", "27", "--", "--test", "23"};
|
||||
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
app.allow_extras(true);
|
||||
run();
|
||||
rem = app.remaining();
|
||||
CHECK(rem.size() == 4U);
|
||||
}
|
||||
|
||||
// makes sure the error throws on the rValue version of the parse
|
||||
TEST_CASE_METHOD(TApp, "ExtrasErrorRvalueParse", "[app]") {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user