1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-01 05:03:52 +00:00

Adding ability to “leave out” options (optional). Rename RequierdError to ExtrasError

This commit is contained in:
Henry Fredrick Schreiner 2017-02-20 11:18:51 -05:00
parent 537aa3aa5d
commit fd313fcadd
4 changed files with 85 additions and 37 deletions

View File

@ -25,7 +25,9 @@
namespace CLI { namespace CLI {
namespace detail {
enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND};
}
class App; class App;
@ -42,8 +44,11 @@ protected:
std::string name; std::string name;
std::string prog_description; std::string prog_description;
std::vector<Option_p> options; std::vector<Option_p> options;
std::vector<std::string> missing_options;
std::deque<std::string> positionals; /// Pair of classifer, string for missing options. (extra detail is removed on returning from parse)
std::vector<std::pair<detail::Classifer, std::string>> missing;
bool no_extras {true};
std::vector<App_p> subcommands; std::vector<App_p> subcommands;
bool parsed {false}; bool parsed {false};
App* subcommand {nullptr}; App* subcommand {nullptr};
@ -115,6 +120,7 @@ public:
App* add_subcommand(std::string name_, std::string description="", bool help=true) { App* add_subcommand(std::string name_, std::string description="", bool help=true) {
subcommands.emplace_back(new App(description, help)); subcommands.emplace_back(new App(description, help));
subcommands.back()->name = name_; subcommands.back()->name = name_;
subcommands.back()->allow_extras();
return subcommands.back().get(); return subcommands.back().get();
} }
@ -337,19 +343,26 @@ public:
/// Parses the command line - throws errors /// Parses the command line - throws errors
/// This must be called after the options are in but before the rest of the program. /// This must be called after the options are in but before the rest of the program.
void parse(int argc, char **argv) { std::vector<std::string> parse(int argc, char **argv) {
progname = argv[0]; progname = argv[0];
std::vector<std::string> args; std::vector<std::string> args;
for(int i=argc-1; i>0; i--) for(int i=argc-1; i>0; i--)
args.push_back(argv[i]); args.push_back(argv[i]);
parse(args); return parse(args);
} }
/// The real work is done here. Expects a reversed vector /// The real work is done here. Expects a reversed vector.
void parse(std::vector<std::string> &args) { /// Changes the vector to the remaining options.
std::vector<std::string>& parse(std::vector<std::string> &args) {
return _parse(args); return _parse(args);
} }
/// Remove the error when extras are left over on the command line.
void allow_extras (bool allow=true) {
no_extras = !allow;
}
/// Print a nice error message and return the exit code /// Print a nice error message and return the exit code
int exit(const Error& e) const { int exit(const Error& e) const {
@ -478,25 +491,25 @@ protected:
} }
/// Selects a Classifer enum based on the type of the current argument /// Selects a Classifer enum based on the type of the current argument
Classifer _recognize(std::string current) const { detail::Classifer _recognize(std::string current) const {
std::string dummy1, dummy2; std::string dummy1, dummy2;
if(current == "--") if(current == "--")
return Classifer::POSITIONAL_MARK; return detail::Classifer::POSITIONAL_MARK;
for(const App_p &com : subcommands) { for(const App_p &com : subcommands) {
if(com->name == current) if(com->name == current)
return Classifer::SUBCOMMAND; return detail::Classifer::SUBCOMMAND;
} }
if(detail::split_long(current, dummy1, dummy2)) if(detail::split_long(current, dummy1, dummy2))
return Classifer::LONG; return detail::Classifer::LONG;
if(detail::split_short(current, dummy1, dummy2)) if(detail::split_short(current, dummy1, dummy2))
return Classifer::SHORT; return detail::Classifer::SHORT;
return Classifer::NONE; return detail::Classifer::NONE;
} }
/// Internal parse function /// Internal parse function
void _parse(std::vector<std::string> &args) { std::vector<std::string>& _parse(std::vector<std::string> &args) {
parsed = true; parsed = true;
bool positional_only = false; bool positional_only = false;
@ -504,23 +517,24 @@ protected:
while(args.size()>0) { while(args.size()>0) {
Classifer classifer = positional_only ? Classifer::NONE : _recognize(args.back()); detail::Classifer classifer = positional_only ? detail::Classifer::NONE : _recognize(args.back());
switch(classifer) { switch(classifer) {
case Classifer::POSITIONAL_MARK: case detail::Classifer::POSITIONAL_MARK:
missing.emplace_back(classifer, args.back());
args.pop_back(); args.pop_back();
positional_only = true; positional_only = true;
break; break;
case Classifer::SUBCOMMAND: case detail::Classifer::SUBCOMMAND:
_parse_subcommand(args); _parse_subcommand(args);
break; break;
case Classifer::LONG: case detail::Classifer::LONG:
_parse_long(args); _parse_long(args);
break; break;
case Classifer::SHORT: case detail::Classifer::SHORT:
_parse_short(args); _parse_short(args);
break; break;
case Classifer::NONE: case detail::Classifer::NONE:
positionals.push_back(args.back()); missing.emplace_back(classifer, args.back());
args.pop_back(); args.pop_back();
} }
} }
@ -531,11 +545,34 @@ protected:
// Collect positionals // Collect positionals
// Loop over all positionals
for(int i=0; i<missing.size(); i++) {
// Skip non-positionals (speedup)
if(missing.at(i).first != detail::Classifer::NONE)
continue;
// Loop over all options
for(const Option_p& opt : options) { for(const Option_p& opt : options) {
while (opt->get_positional() && opt->count() < opt->get_expected() && positionals.size() > 0) {
// Eat options, one by one, until done
while ( opt->get_positional()
&& opt->count() < opt->get_expected()
&& i < missing.size()
) {
// Skip options, only eat positionals
if(missing.at(i).first != detail::Classifer::NONE) {
i++;
continue;
}
opt->get_new(); opt->get_new();
opt->add_result(0, positionals.front()); opt->add_result(0, missing.at(i).second);
positionals.pop_front(); missing.erase(missing.begin() + i); // Remove option that was eaten
// Don't need to remove 1 from i since this while loop keeps reading i
}
} }
} }
@ -591,11 +628,22 @@ protected:
if(required_subcommand && subcommand == nullptr) if(required_subcommand && subcommand == nullptr)
throw RequiredError("Subcommand required"); throw RequiredError("Subcommand required");
if(positionals.size()>0) // Convert missing (pairs) to extras (string only)
throw PositionalError("[" + detail::join(positionals) + "]"); args.resize(missing.size());
std::transform(std::begin(missing), std::end(missing), std::begin(args),
[](const std::pair<detail::Classifer, std::string>& val){return val.second;});
std::reverse(std::begin(args), std::end(args));
size_t num_left_over = std::count_if(std::begin(missing), std::end(missing),
[](std::pair<detail::Classifer, std::string>& val){return val.first != detail::Classifer::POSITIONAL_MARK;});
if(num_left_over>0 && no_extras)
throw ExtrasError("[" + detail::join(args, " ") + "]");
pre_callback(); pre_callback();
run_callback(); run_callback();
return args;
} }
@ -624,7 +672,7 @@ protected:
auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &opt){return opt->check_sname(name_);}); auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &opt){return opt->check_sname(name_);});
if(op_ptr == std::end(options)) { if(op_ptr == std::end(options)) {
missing_options.push_back("-" + name_); missing.emplace_back(detail::Classifer::SHORT, "-" + name_);
return; return;
} }
@ -645,7 +693,7 @@ protected:
if(num == -1) { if(num == -1) {
while(args.size()>0 && _recognize(args.back()) == Classifer::NONE) { while(args.size()>0 && _recognize(args.back()) == detail::Classifer::NONE) {
op->add_result(vnum, args.back()); op->add_result(vnum, args.back());
args.pop_back(); args.pop_back();
} }
@ -675,7 +723,7 @@ protected:
auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &v){return v->check_lname(name_);}); auto op_ptr = std::find_if(std::begin(options), std::end(options), [name_](const Option_p &v){return v->check_lname(name_);});
if(op_ptr == std::end(options)) { if(op_ptr == std::end(options)) {
missing_options.push_back("--" + name_); missing.emplace_back(detail::Classifer::LONG, "--" + name_);
return; return;
} }
@ -699,7 +747,7 @@ protected:
} }
if(num == -1) { if(num == -1) {
while(args.size() > 0 && _recognize(args.back()) == Classifer::NONE) { while(args.size() > 0 && _recognize(args.back()) == detail::Classifer::NONE) {
op->add_result(vnum, args.back()); op->add_result(vnum, args.back());
args.pop_back(); args.pop_back();
} }

View File

@ -95,9 +95,9 @@ struct ExcludesError : public ParseError {
ExcludesError(std::string name, std::string subname) : ParseError("ExcludesError", name + " excludes " + subname, 14) {} ExcludesError(std::string name, std::string subname) : ParseError("ExcludesError", name + " excludes " + subname, 14) {}
}; };
/// Thrown when too many positionals are found /// Thrown when too many positionals or options are found
struct PositionalError : public ParseError { struct ExtrasError : public ParseError {
PositionalError(std::string name) : ParseError("PositionalError", name, 6) {} ExtrasError(std::string name) : ParseError("ExtrasError", name, 6) {}
}; };
/// This is just a safety check to verify selection and parsing match /// This is just a safety check to verify selection and parsing match

View File

@ -18,7 +18,7 @@ TEST(Basic, Empty) {
{ {
CLI::App app; CLI::App app;
input_t spare = {"spare"}; input_t spare = {"spare"};
EXPECT_THROW(app.parse(spare), CLI::PositionalError); EXPECT_THROW(app.parse(spare), CLI::ExtrasError);
} }
{ {
CLI::App app; CLI::App app;

View File

@ -89,13 +89,13 @@ TEST_F(SubcommandProgram, Working) {
TEST_F(SubcommandProgram, Spare) { TEST_F(SubcommandProgram, Spare) {
args = {"extra", "-d", "start", "-ffilename"}; args = {"extra", "-d", "start", "-ffilename"};
EXPECT_THROW(run(), CLI::PositionalError); EXPECT_THROW(run(), CLI::ExtrasError);
} }
TEST_F(SubcommandProgram, SpareSub) { TEST_F(SubcommandProgram, SpareSub) {
args = {"-d", "start", "spare", "-ffilename"}; args = {"-d", "start", "spare", "-ffilename"};
EXPECT_THROW(run(), CLI::PositionalError); EXPECT_THROW(run(), CLI::ExtrasError);
} }