1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

Sets by reference (#114)

* Adding const & access to sets

* Adding set reference option

* One missing line in coverage
This commit is contained in:
Henry Schreiner 2018-05-02 16:06:20 +02:00 committed by GitHub
parent af2ed66d6e
commit 3917b1ab59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 219 additions and 19 deletions

View File

@ -1,6 +1,6 @@
### Version 1.6: Formatting
Added a new formatting system. You can now set the formatter on Apps.
Added a new formatting system. [#109] You can now set the formatter on Apps.
* Added `CLI::Formatter` and `formatter` slot for apps, inherited.
* Added `help_all` support (not added by default)
@ -18,11 +18,18 @@ Changes to the help system (most normal users will not notice this):
* `format_help` can now be chained
Other small changes:
Other changes:
* Testing (only) now uses submodules.
* Removed `requires` in favor of `needs` (deprecated in last version)
* Better CMake policy handling
* Using `add_set` will now capture L-values for sets, allowing further modification [#113]
* Testing (only) now uses submodules. [#111]
* Removed `requires` in favor of `needs` (deprecated in last version) [#112]
* Better CMake policy handling [#110]
[#109]: https://github.com/CLIUtils/CLI11/pull/109
[#110]: https://github.com/CLIUtils/CLI11/pull/110
[#111]: https://github.com/CLIUtils/CLI11/pull/111
[#112]: https://github.com/CLIUtils/CLI11/pull/112
[#113]: https://github.com/CLIUtils/CLI11/issues/113
### Version 1.5.3: Compiler compatibility
This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported.
@ -45,7 +52,7 @@ This patch release adds better access to the App progromatically, to assist with
[#102]: https://github.com/CLIUtils/CLI11/issues/102
[#104]: https://github.com/CLIUtils/CLI11/pull/104
[#105]: https://github.com/CLIUtils/CLI11/issues/105
[#105]: https://github.com/CLIUtils/CLI11/pull/105
[#106]: https://github.com/CLIUtils/CLI11/pull/106

View File

@ -163,7 +163,7 @@ app.add_set_ignore_case(... // String only
App* subcom = app.add_subcommand(name, discription);
```
An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options.
An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options; you can add an existing set if you need to modify the set later, or you can use an initializer list.
On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.

View File

@ -500,11 +500,11 @@ class App {
}
#endif
/// Add set of options (No default)
/// Add set of options (No default, temp refernce, such as an inline set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
std::set<T> options, ///< The set of possibilities
T &member, ///< The selected member of the set
const std::set<T> &&options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(name, ',').at(0);
@ -522,11 +522,33 @@ class App {
return opt;
}
/// Add set of options
/// Add set of options (No default, non-temp refernce, such as an existing set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
std::set<T> options, ///< The set of posibilities
T &member, ///< The selected member of the set
const std::set<T> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(name, fun, description, false);
std::string typeval = detail::type_name<T>();
typeval += " in {" + detail::join(options) + "}";
opt->set_custom_option(typeval);
return opt;
}
/// Add set of options (with default, R value, such as an inline set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
const std::set<T> &&options, ///< The set of posibilities
std::string description,
bool defaulted) {
@ -550,10 +572,38 @@ class App {
return opt;
}
/// Add set of options, string only, ignore case (no default)
/// Add set of options (with default, L value refernce, such as an existing set)
template <typename T>
Option *add_set(std::string name,
T &member, ///< The selected member of the set
const std::set<T> &options, ///< The set of posibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
bool retval = detail::lexical_cast(res[0], member);
if(!retval)
throw ConversionError(res[0], simple_name);
return std::find(std::begin(options), std::end(options), member) != std::end(options);
};
Option *opt = add_option(name, fun, description, defaulted);
std::string typeval = detail::type_name<T>();
typeval += " in {" + detail::join(options) + "}";
opt->set_custom_option(typeval);
if(defaulted) {
std::stringstream out;
out << member;
opt->set_default_str(out.str());
}
return opt;
}
/// Add set of options, string only, ignore case (no default, R value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of possibilities
std::string &member, ///< The selected member of the set
const std::set<std::string> &&options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(name, ',').at(0);
@ -578,10 +628,38 @@ class App {
return opt;
}
/// Add set of options, string only, ignore case
/// Add set of options, string only, ignore case (no default, L value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
std::set<std::string> options, ///< The set of posibilities
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of possibilities
std::string description = "") {
std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
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(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(name, fun, description, false);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->set_custom_option(typeval);
return opt;
}
/// Add set of options, string only, ignore case (default, R value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &&options, ///< The set of posibilities
std::string description,
bool defaulted) {
@ -609,6 +687,37 @@ class App {
return opt;
}
/// Add set of options, string only, ignore case (default, L value)
Option *add_set_ignore_case(std::string name,
std::string &member, ///< The selected member of the set
const std::set<std::string> &options, ///< The set of posibilities
std::string description,
bool defaulted) {
std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
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(member, simple_name);
else {
member = *iter;
return true;
}
};
Option *opt = add_option(name, fun, description, defaulted);
std::string typeval = detail::type_name<std::string>();
typeval += " in {" + detail::join(options) + "}";
opt->set_custom_option(typeval);
if(defaulted) {
opt->set_default_str(member);
}
return opt;
}
/// Add a complex number
template <typename T>
Option *add_complex(std::string name,

View File

@ -992,6 +992,21 @@ TEST_F(TApp, FailSet) {
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, FailLValueSet) {
int choice;
std::set<int> vals{1, 2, 3};
app.add_set("-q,--quick", choice, vals);
app.add_set("-s,--slow", choice, vals, "", true);
args = {"--quick=hello"};
EXPECT_THROW(run(), CLI::ConversionError);
app.reset();
args = {"--slow=hello"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, InSetIgnoreCase) {
std::string choice;
@ -1480,3 +1495,72 @@ TEST_F(TApp, CustomDoubleOption) {
EXPECT_EQ(custom_opt.first, 12);
EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
}
// #113
TEST_F(TApp, AddRemoveSetItems) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_set("--type1", type1, items);
app.add_set("--type2", type2, items, "", true);
args = {"--type1", "TYPE1", "--type2", "TYPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
app.reset();
args = {"--type1", "TYPE6", "--type2", "TYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
app.reset();
args = {"--type1", "TYPE1"};
EXPECT_THROW(run(), CLI::ConversionError);
app.reset();
args = {"--type2", "TYPE2"};
EXPECT_THROW(run(), CLI::ConversionError);
}
TEST_F(TApp, AddRemoveSetItemsNoCase) {
std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
std::string type1, type2;
app.add_set_ignore_case("--type1", type1, items);
app.add_set_ignore_case("--type2", type2, items, "", true);
args = {"--type1", "TYPe1", "--type2", "TyPE2"};
run();
EXPECT_EQ(type1, "TYPE1");
EXPECT_EQ(type2, "TYPE2");
items.insert("TYPE6");
items.insert("TYPE7");
items.erase("TYPE1");
items.erase("TYPE2");
app.reset();
args = {"--type1", "TyPE6", "--type2", "tYPE7"};
run();
EXPECT_EQ(type1, "TYPE6");
EXPECT_EQ(type2, "TYPE7");
app.reset();
args = {"--type1", "TYPe1"};
EXPECT_THROW(run(), CLI::ConversionError);
app.reset();
args = {"--type2", "TYpE2"};
EXPECT_THROW(run(), CLI::ConversionError);
}