1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

Adding requires, excludes, and getenv (untested)

This commit is contained in:
Henry Fredrick Schreiner 2017-02-12 14:04:36 -05:00
parent 00acc84b52
commit f4bf6d7226
5 changed files with 83 additions and 2 deletions

View File

@ -103,8 +103,11 @@ Adding a configuration option is special. If it is present, it will be read alon
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 direcly 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
* `->required()`: The program will quit if this option is not present. This is `manditory` in Plumbum, but required options seems to be a more standard term.
* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args
* `->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
* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`.
* `->check(CLI::ExistingFile)`: Requires that the file exists if given
* `->check(CLI::ExistingDirectory)`: Requires that the directory exists

View File

@ -55,6 +55,7 @@ protected:
std::string ini_file;
bool ini_required {false};
Option* ini_setting {nullptr};
public:
@ -379,12 +380,23 @@ public:
opt->add_result(0, positionals.front());
positionals.pop_front();
}
if (first_parse && opt->count() == 0 && opt->_envname != "") {
// Will not interact very well with ini files
char *ename = std::getenv(opt->_envname.c_str());
if(ename != nullptr) {
opt->get_new();
opt->add_result(0, std::string(ename));
}
}
if (opt->count() > 0) {
if(!opt->run_callback())
throw ConversionError(opt->get_name() + "=" + detail::join(opt->flatten_results()));
}
}
// Process an INI file
if (first_parse && ini_setting != nullptr && ini_file != "") {
try {
std::vector<std::string> values = detail::parse_ini(ini_file);
@ -398,9 +410,19 @@ public:
}
}
// Verify required options
for(const Option_p& opt : options) {
// Required
if (opt->get_required() && opt->count() < opt->get_expected())
throw RequiredError(opt->get_name());
// Requires
for (const Option* opt_req : opt->_requires)
if (opt_req->count() == 0)
throw RequiresError(opt->get_name(), opt_req->get_name());
// Excludes
for (const Option* opt_ex : opt->_excludes)
if (opt_ex->count() != 0)
throw ExcludesError(opt->get_name(), opt_ex->get_name());
}
if(positionals.size()>0)

View File

@ -75,6 +75,16 @@ struct RequiredError : public ParseError {
RequiredError(std::string name) : ParseError("RequiredError", name, 5) {}
};
/// Thrown when a requires option is missing
struct RequiresError : public ParseError {
RequiresError(std::string name, std::string subname) : ParseError("RequiresError", name + " requires " + subname, 13) {}
};
/// Thrown when a exludes option is present
struct ExcludesError : public ParseError {
ExcludesError(std::string name, std::string subname) : ParseError("ExcludesError", name + " excludes " + subname, 14) {}
};
/// Thrown when too many positionals are found
struct PositionalError : public ParseError {
PositionalError(std::string name) : ParseError("PositionalError", name, 6) {}

View File

@ -8,6 +8,7 @@
#include <vector>
#include <tuple>
#include <algorithm>
#include <set>
#include "CLI/Error.hpp"
#include "CLI/StringTools.hpp"
@ -42,6 +43,10 @@ protected:
bool allow_vector {false};
std::vector<std::function<bool(std::string)>> _validators;
std::set<Option*> _requires;
std::set<Option*> _excludes;
std::string _envname;
// Results
results_t results;
@ -69,6 +74,7 @@ public:
return this;
}
/// True if this is a required option
bool get_required() const {
return _required;
}
@ -115,11 +121,13 @@ public:
return this;
}
/// Changes the group membership
Option* group(std::string name) {
_group = name;
return this;
}
/// Get the group of this option
const std::string& get_group() const {
return _group;
}
@ -129,6 +137,42 @@ public:
return description;
}
/// Sets required options
Option* requires(Option* opt) {
auto tup = _requires.insert(opt);
if(!tup.second)
throw OptionAlreadyAdded(get_name() + " requires " + opt->get_name());
return this;
}
/// Any number supported
template<typename... ARG>
Option* requires(Option* opt, Option* opt1, ARG... args) {
requires(opt);
return requires(opt1, args...);
}
/// Sets excluded options
Option* excludes(Option* opt) {
auto tup = _excludes.insert(opt);
if(!tup.second)
throw OptionAlreadyAdded(get_name() + " excludes " + opt->get_name());
return this;
}
/// Any number supported
template<typename... ARG>
Option* excludes(Option* opt, Option* opt1, ARG... args) {
excludes(opt);
return excludes(opt1, args...);
}
/// Sets environment variable to read if no option given
Option* envname(std::string name) {
_envname = name;
return this;
}
/// The name and any extras needed for positionals
std::string help_positional() const {
std::string out = pname;
@ -144,6 +188,8 @@ public:
std::string get_pname() const {
return pname;
}
/// Process the callback
bool run_callback() const {
if(_validators.size()>0) {

View File

@ -537,4 +537,4 @@ TEST_F(SubcommandProgram, SpareSub) {
}
// TODO: Check help output and formatting
// TODO: add tests for requires, excludes, envname