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

Error work #56

Merged
merged 10 commits into from
Nov 26, 2017
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
* Unlimited options no longer prioritize over remaining/unlimited positionals [#51](https://github.com/CLIUtils/CLI11/pull/51)
* Added `->transform` which modifies the string parsed [#54](https://github.com/CLIUtils/CLI11/pull/54)
* Changed of API in validators to `void(std::string &)` (const for users), throwing providing nicer errors [#54](https://github.com/CLIUtils/CLI11/pull/54)
* Added `CLI::ArgumentMismatch` [#56](https://github.com/CLIUtils/CLI11/pull/56) and fixed missing failure if one arg expected [#55](https://github.com/CLIUtils/CLI11/issues/55)
* Support for minimum unlimited expected arguments [#56](https://github.com/CLIUtils/CLI11/pull/56)
* Single internal arg parse function [#56](https://github.com/CLIUtils/CLI11/pull/56)

## Version 1.2

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ On a C++14 compiler, you can pass a callback function directly to `.add_flag`, w
The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup. Before parsing, you can set the following options:

* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args.
* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`.
* `->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.
Expand Down
190 changes: 52 additions & 138 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,6 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&variable, simple_name](CLI::results_t res) {
if(res.size() != 1)
throw ConversionError("Only one " + simple_name + " allowed");
return detail::lexical_cast(res[0], variable);
};

Expand All @@ -293,8 +291,6 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&variable, simple_name](CLI::results_t res) {
if(res.size() != 1)
throw ConversionError("Only one " + simple_name + " allowed");
return detail::lexical_cast(res[0], variable);
};

Expand Down Expand Up @@ -325,7 +321,7 @@ class App {
};

Option *opt = add_option(name, fun, description, false);
opt->set_custom_option(detail::type_name<T>(), -1, true);
opt->set_custom_option(detail::type_name<T>(), -1);
return opt;
}

Expand All @@ -347,7 +343,7 @@ class App {
};

Option *opt = add_option(name, fun, description, defaulted);
opt->set_custom_option(detail::type_name<T>(), -1, true);
opt->set_custom_option(detail::type_name<T>(), -1);
if(defaulted)
opt->set_default_str("[" + detail::join(variable) + "]");
return opt;
Expand Down Expand Up @@ -454,12 +450,9 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
if(res.size() != 1) {
throw ConversionError("Only one " + simple_name + " allowed");
}
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError("The value " + res[0] + "is not an allowed value for " + simple_name);
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};

Expand All @@ -480,12 +473,9 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
if(res.size() != 1) {
throw ConversionError("Only one " + simple_name + " allowed");
}
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError("The value " + res[0] + "is not an allowed value for " + simple_name);
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};

Expand All @@ -509,15 +499,12 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
if(res.size() != 1) {
throw ConversionError("Only one " + simple_name + " allowed");
}
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError("The value " + member + "is not an allowed value for " + simple_name);
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
Expand All @@ -541,15 +528,12 @@ class App {

std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
if(res.size() != 1) {
throw ConversionError("Only one " + simple_name + " allowed");
}
member = detail::to_lower(res[0]);
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
return detail::to_lower(val) == member;
});
if(iter == std::end(options))
throw ConversionError("The value " + member + "is not an allowed value for " + simple_name);
throw ConversionError(member, simple_name);
else {
member = *iter;
return true;
Expand Down Expand Up @@ -639,15 +623,15 @@ class App {
for(const App_p &subcomptr : subcommands_)
if(subcomptr.get() == subcom)
return subcom;
throw CLI::OptionNotFound(subcom->get_name());
throw OptionNotFound(subcom->get_name());
}

/// Check to see if a subcommand is part of this command (text version)
App *get_subcommand(std::string subcom) const {
for(const App_p &subcomptr : subcommands_)
if(subcomptr->check_name(subcom))
return subcomptr.get();
throw CLI::OptionNotFound(subcom);
throw OptionNotFound(subcom);
}

/// Changes the group membership
Expand Down Expand Up @@ -1046,7 +1030,7 @@ class App {
return opt->get_expected() == -1 && opt->get_positional();
});
if(count > 1)
throw InvalidError(name_ + ": Too many positional arguments with unlimited expected args");
throw InvalidError(name_);
for(const App_p &app : subcommands_)
app->_validate();
}
Expand Down Expand Up @@ -1162,15 +1146,14 @@ class App {
for(const Option_p &opt : options_) {
// Required or partially filled
if(opt->get_required() || opt->count() != 0) {
// Make sure enough -N arguments parsed (+N is already handled in parsing function)
if(opt->get_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_expected()))
throw ArgumentMismatch(opt->single_name() + ": At least " + std::to_string(-opt->get_expected()) +
" required");

// Required but empty
if(opt->get_required() && opt->count() == 0)
throw RequiredError(opt->help_name() + " is required");

// Partially filled
if(opt->get_expected() > 0 && static_cast<int>(opt->count()) < opt->get_expected())
throw RequiredError(opt->help_name() + " requires " + std::to_string(opt->get_expected()) +
" arguments");
throw RequiredError(opt->single_name() + " is required");
}
// Requires
for(const Option *opt_req : opt->requires_)
Expand All @@ -1194,9 +1177,7 @@ class App {
if(num_left_over > 0) {
args = remaining(false);
std::reverse(std::begin(args), std::end(args));
throw ExtrasError((args.size() > 1 ? "The following argument was not expected: "
: "The following arguments were not expected: ") +
detail::rjoin(args, " "));
throw ExtrasError(args);
}
}
}
Expand Down Expand Up @@ -1274,11 +1255,11 @@ class App {
break;
case detail::Classifer::LONG:
// If already parsed a subcommand, don't accept options_
_parse_long(args);
_parse_arg(args, true);
break;
case detail::Classifer::SHORT:
// If already parsed a subcommand, don't accept options_
_parse_short(args);
_parse_arg(args, false);
break;
case detail::Classifer::NONE:
// Probably a positional or something for a parent (sub)command
Expand Down Expand Up @@ -1350,27 +1331,38 @@ class App {
throw HorribleError("Subcommand " + args.back() + " missing");
}

/// Parse a short argument, must be at the top of the list
void _parse_short(std::vector<std::string> &args) {
/// Parse a short (false) or long (true) argument, must be at the top of the list
void _parse_arg(std::vector<std::string> &args, bool second_dash) {

detail::Classifer current_type = second_dash ? detail::Classifer::LONG : detail::Classifer::SHORT;

std::string current = args.back();

std::string name;
std::string value;
std::string rest;
if(!detail::split_short(current, name, rest))
throw HorribleError("Short parsed but missing! You should not see this");

auto op_ptr = std::find_if(
std::begin(options_), std::end(options_), [name](const Option_p &opt) { return opt->check_sname(name); });
if(second_dash) {
if(!detail::split_long(current, name, value))
throw HorribleError("Long parsed but missing (you should not see this):" + args.back());
} else {
if(!detail::split_short(current, name, rest))
throw HorribleError("Short parsed but missing! You should not see this");
}

auto op_ptr = std::find_if(std::begin(options_), std::end(options_), [name, second_dash](const Option_p &opt) {
return second_dash ? opt->check_lname(name) : opt->check_sname(name);
});

// Option not found
if(op_ptr == std::end(options_)) {
// If a subcommand, try the master command
if(parent_ != nullptr && fallthrough_)
return parent_->_parse_short(args);
return parent_->_parse_arg(args, second_dash);
// Otherwise, add to missing
else {
args.pop_back();
missing_.emplace_back(detail::Classifer::SHORT, current);
missing_.emplace_back(current_type, current);
return;
}
}
Expand All @@ -1382,7 +1374,12 @@ class App {

int num = op->get_expected();

if(num == 0) {
if(!value.empty()) {
if(num != -1)
num--;
op->add_result(value);
parse_order_.push_back(op.get());
} else if(num == 0) {
op->add_result("");
parse_order_.push_back(op.get());
} else if(!rest.empty()) {
Expand All @@ -1394,10 +1391,10 @@ class App {
}

// Unlimited vector parser
if(num == -1) {
bool already_ate_one = false; // Make sure we always eat one
if(num < 0) {
int collected = 0; // Make sure we always eat the minimum
while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
if(already_ate_one) {
if(collected >= -num) {
// We could break here for allow extras, but we don't

// If any positionals remain, don't keep eating
Expand All @@ -1413,8 +1410,9 @@ class App {
op->add_result(args.back());
parse_order_.push_back(op.get());
args.pop_back();
already_ate_one = true;
collected++;
}

} else {
while(num > 0 && !args.empty()) {
num--;
Expand All @@ -1425,8 +1423,8 @@ class App {
}

if(num > 0) {
throw RequiredError(op->single_name() + ": " + std::to_string(num) + " required " +
op->get_type_name() + " missing");
throw ArgumentMismatch(op->single_name() + ": " + std::to_string(num) + " required " +
op->get_type_name() + " missing");
}
}

Expand All @@ -1435,83 +1433,6 @@ class App {
args.push_back(rest);
}
}

/// Parse a long argument, must be at the top of the list
void _parse_long(std::vector<std::string> &args) {
std::string current = args.back();

std::string name;
std::string value;
if(!detail::split_long(current, name, value))
throw HorribleError("Long parsed but missing (you should not see this):" + args.back());

auto op_ptr = std::find_if(
std::begin(options_), std::end(options_), [name](const Option_p &v) { return v->check_lname(name); });

// Option not found
if(op_ptr == std::end(options_)) {
// If a subcommand, try the master command
if(parent_ != nullptr && fallthrough_)
return parent_->_parse_long(args);
// Otherwise, add to missing
else {
args.pop_back();
missing_.emplace_back(detail::Classifer::LONG, current);
return;
}
}

args.pop_back();

// Get a reference to the pointer to make syntax bearable
Option_p &op = *op_ptr;

int num = op->get_expected();

if(!value.empty()) {
if(num != -1)
num--;
op->add_result(value);
parse_order_.push_back(op.get());
} else if(num == 0) {
op->add_result("");
parse_order_.push_back(op.get());
} else if(num == -1) {
// Unlimited vector parser
bool already_ate_one = false; // Make sure we always eat one
while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) {
if(already_ate_one) {
// We could break here for allow extras, but we don't

// If any positionals remain, don't keep eating
if(_count_remaining_positionals() > 0)
break;

// If there are any unlimited positionals, those also take priority
if(std::any_of(std::begin(options_), std::end(options_), [](const Option_p &opt) {
return opt->get_positional() && opt->get_expected() < 0;
}))
break;
}
op->add_result(args.back());
parse_order_.push_back(op.get());
args.pop_back();
already_ate_one = true;
}
} else {
while(num > 0 && !args.empty()) {
num--;
op->add_result(args.back());
parse_order_.push_back(op.get());
args.pop_back();
}
if(num > 0) {
throw RequiredError(op->single_name() + ": " + std::to_string(num) + " required " +
op->get_type_name() + " missing");
}
}
return;
}
};

namespace FailureMessage {
Expand All @@ -1537,16 +1458,9 @@ struct AppFriend {

/// Wrap _parse_short, perfectly forward arguments and return
template <typename... Args>
static auto parse_short(App *app, Args &&... args) ->
typename std::result_of<decltype (&App::_parse_short)(App, Args...)>::type {
return app->_parse_short(std::forward<Args>(args)...);
}

/// Wrap _parse_long, perfectly forward arguments and return
template <typename... Args>
static auto parse_long(App *app, Args &&... args) ->
typename std::result_of<decltype (&App::_parse_long)(App, Args...)>::type {
return app->_parse_long(std::forward<Args>(args)...);
static auto parse_arg(App *app, Args &&... args) ->
typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type {
return app->_parse_arg(std::forward<Args>(args)...);
}

/// Wrap _parse_subcommand, perfectly forward arguments and return
Expand Down
Loading