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:
parent
537aa3aa5d
commit
fd313fcadd
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user