Skip to content

Commit

Permalink
remove template for operator[] and adjust some tests
Browse files Browse the repository at this point in the history
add some comments in readME about performance

move operator[] to return const Option *

Apply suggestions from code review

Co-Authored-By: phlptp <top1@llnl.gov>

update readme and add some IniTests and fix a bug from the tests

add_flag_callback

add a few tests to capture the different paths

fix incorrectly updated CMAKE file, and add some subcommand test for option finding

add disable_flag_override and work out some kinks in the find option functions

add some more tests and fix a few bugs in as<> function for options

Allow general flag types and default values, add shortcut notation for retrieving values
  • Loading branch information
phlptp committed Feb 22, 2019
1 parent 1a1cde9 commit 700db24
Show file tree
Hide file tree
Showing 15 changed files with 739 additions and 259 deletions.
66 changes: 46 additions & 20 deletions README.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION
"--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}")

add_cli_exe(digit_args digit_args.cpp)
add_test(NAME digit_args COMMAND digit_args -h)
set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION
"-3{3}")

add_cli_exe(modhelp modhelp.cpp)
add_test(NAME modhelp COMMAND modhelp -a test -h)
set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION
Expand Down
15 changes: 15 additions & 0 deletions examples/digit_args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <CLI/CLI.hpp>
#include <iostream>

int main(int argc, char **argv) {
CLI::App app;

int val;
// add a set of flags with default values associate with them
app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val, "compression level");

CLI11_PARSE(app, argc, argv);

std::cout << "value = " << val << std::endl;
return 0;
}
278 changes: 179 additions & 99 deletions include/CLI/App.hpp

Large diffs are not rendered by default.

10 changes: 3 additions & 7 deletions include/CLI/ConfigFwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,10 @@ class Config {
/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;

/// Convert a flag to a bool representation
/// Get a flag value
virtual std::string to_flag(const ConfigItem &item) const {
if(item.inputs.size() == 1) {
try {
return detail::to_flag_value(item.inputs.at(0));
} catch(const std::invalid_argument &) {
throw ConversionError::TrueFalse(item.fullname());
}
return item.inputs.at(0);
}
throw ConversionError::TooManyInputsFlag(item.fullname());
}
Expand All @@ -90,7 +86,7 @@ class Config {
return from_config(input);
}

/// virtual destructor
/// Virtual destructor
virtual ~Config() = default;
};

Expand Down
3 changes: 3 additions & 0 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ class ArgumentMismatch : public ParseError {
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
}
static ArgumentMismatch FlagOverride(std::string name) {
return ArgumentMismatch(name + " was given a disallowed flag override");
}
};

/// Thrown when a requires option is missing
Expand Down
150 changes: 139 additions & 11 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class App;

using Option_p = std::unique_ptr<Option>;

enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join };
enum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join };

/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
/// to share parts of the class; an OptionDefaults can copy to an Option.
Expand All @@ -50,6 +50,8 @@ template <typename CRTP> class OptionBase {

/// Allow this option to be given in a configuration file
bool configurable_{true};
/// Disable overriding flag values with '=value'
bool disable_flag_override_{false};

/// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too)
MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
Expand All @@ -61,6 +63,7 @@ template <typename CRTP> class OptionBase {
other->ignore_case(ignore_case_);
other->ignore_underscore(ignore_underscore_);
other->configurable(configurable_);
other->disable_flag_override(disable_flag_override_);
other->multi_option_policy(multi_option_policy_);
}

Expand Down Expand Up @@ -100,6 +103,9 @@ template <typename CRTP> class OptionBase {
/// The status of configurable
bool get_configurable() const { return configurable_; }

/// The status of configurable
bool get_disable_flag_override() const { return disable_flag_override_; }

/// The status of the multi option policy
MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }

Expand Down Expand Up @@ -158,6 +164,12 @@ class OptionDefaults : public OptionBase<OptionDefaults> {
ignore_underscore_ = value;
return this;
}

/// Ignore underscores in the option name
OptionDefaults *disable_flag_override(bool value = true) {
disable_flag_override_ = value;
return this;
}
};

class Option : public OptionBase<Option> {
Expand All @@ -173,8 +185,11 @@ class Option : public OptionBase<Option> {
/// A list of the long names (`--a`) without the leading dashes
std::vector<std::string> lnames_;

/// A list of the negation names, should be duplicates of what is in snames or lnames but trigger a false response
/// on a flag
/// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
/// what is in snames or lnames but will trigger a particular response on a flag
std::vector<std::pair<std::string, std::string>> default_flag_values_;

/// a list of flag names with specified default values;
std::vector<std::string> fnames_;

/// A positional name
Expand Down Expand Up @@ -251,7 +266,7 @@ class Option : public OptionBase<Option> {
bool defaulted,
App *parent)
: description_(std::move(option_description)), default_(defaulted), parent_(parent),
callback_(callback ? std::move(callback) : [](results_t) { return true; }) {
callback_(std::move(callback)) {
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
}

Expand Down Expand Up @@ -471,6 +486,11 @@ class Option : public OptionBase<Option> {
return this;
}

/// disable flag overrides
Option *disable_flag_override(bool value = true) {
disable_flag_override_ = value;
return this;
}
///@}
/// @name Accessors
///@{
Expand Down Expand Up @@ -499,7 +519,7 @@ class Option : public OptionBase<Option> {
/// Get the short names
const std::vector<std::string> get_snames() const { return snames_; }

/// get the negative flag names
/// get the flag names with specified default values
const std::vector<std::string> get_fnames() const { return fnames_; }

/// The number of times the option expects to be included
Expand Down Expand Up @@ -570,14 +590,14 @@ class Option : public OptionBase<Option> {
for(const std::string &sname : snames_) {
name_list.push_back("-" + sname);
if(check_fname(sname)) {
name_list.back() += "{false}";
name_list.back() += "{" + get_flag_value(sname, "") + "}";
}
}

for(const std::string &lname : lnames_) {
name_list.push_back("--" + lname);
if(check_fname(lname)) {
name_list.back() += "{false}";
name_list.back() += "{" + get_flag_value(lname, "") + "}";
}
}
} else {
Expand Down Expand Up @@ -635,7 +655,9 @@ class Option : public OptionBase<Option> {
throw ValidationError(get_name(), err_msg);
}
}

if(!(callback_)) {
return;
}
bool local_result;

// Num items expected or length of vector, always at least 1
Expand Down Expand Up @@ -716,19 +738,57 @@ class Option : public OptionBase<Option> {
}

/// Requires "-" to be removed from string
bool check_sname(std::string name) const { return detail::check_is_member(name, snames_, ignore_case_); }
bool check_sname(std::string name) const { return (detail::find_member(name, snames_, ignore_case_) >= 0); }

/// Requires "--" to be removed from string
bool check_lname(std::string name) const {
return detail::check_is_member(name, lnames_, ignore_case_, ignore_underscore_);
return (detail::find_member(name, lnames_, ignore_case_, ignore_underscore_) >= 0);
}

/// Requires "--" to be removed from string
bool check_fname(std::string name) const {
if(fnames_.empty()) {
return false;
}
return detail::check_is_member(name, fnames_, ignore_case_, ignore_underscore_);
return (detail::find_member(name, fnames_, ignore_case_, ignore_underscore_) >= 0);
}

std::string get_flag_value(std::string name, std::string input_value) const {
static const std::string trueString{"true"};
static const std::string falseString{"false"};
static const std::string emptyString{"{}"};
// check for disable flag override_
if(disable_flag_override_) {
if(!((input_value.empty()) || (input_value == emptyString))) {
auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
if(default_ind >= 0) {
if(default_flag_values_[default_ind].second != input_value) {
throw(ArgumentMismatch::FlagOverride(name));
}
} else {
if(input_value != trueString) {
throw(ArgumentMismatch::FlagOverride(name));
}
}
}
}
auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
if((input_value.empty()) || (input_value == emptyString)) {
return (ind < 0) ? trueString : default_flag_values_[ind].second;
}
if(ind < 0) {
return input_value;
}
if(default_flag_values_[ind].second == falseString) {
try {
auto val = detail::to_flag_value(input_value);
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
} catch(const std::invalid_argument &) {
return input_value;
}
} else {
return input_value;
}
}

/// Puts a result at the end
Expand Down Expand Up @@ -760,6 +820,74 @@ class Option : public OptionBase<Option> {
/// Get a copy of the results
std::vector<std::string> results() const { return results_; }

/// get the results as a particular type
template <typename T,
enable_if_t<!is_vector<T>::value && !std::is_const<T>::value, detail::enabler> = detail::dummy>
void results(T &output) const {
bool retval;
if(results_.empty()) {
retval = detail::lexical_cast(defaultval_, output);
} else if(results_.size() == 1) {
retval = detail::lexical_cast(results_[0], output);
} else {
switch(multi_option_policy_) {
case MultiOptionPolicy::TakeFirst:
retval = detail::lexical_cast(results_.front(), output);
break;
case MultiOptionPolicy::TakeLast:
default:
retval = detail::lexical_cast(results_.back(), output);
break;
case MultiOptionPolicy::Throw:
throw ConversionError(get_name(), results_);
case MultiOptionPolicy::Join:
retval = detail::lexical_cast(detail::join(results_), output);
break;
}
}
if(!retval) {
throw ConversionError(get_name(), results_);
}
}
/// get the results as a vector of a particular type
template <typename T> void results(std::vector<T> &output, char delim = '\0') const {
output.clear();
bool retval = true;

for(const auto &elem : results_) {
if(delim != '\0') {
for(const auto &var : CLI::detail::split(elem, delim)) {
if(!var.empty()) {
output.emplace_back();
retval &= detail::lexical_cast(var, output.back());
}
}
} else {
output.emplace_back();
retval &= detail::lexical_cast(elem, output.back());
}
}

if(!retval) {
throw ConversionError(get_name(), results_);
}
}

/// return the results as a particular type
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> T as() const {
T output;
results(output);
return output;
}

/// get the results as a vector of a particular type
template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
T as(char delim = '\0') const {
T output;
results(output, delim);
return output;
}

/// See if the callback has been run already
bool get_callback_run() const { return callback_run_; }

Expand Down
37 changes: 22 additions & 15 deletions include/CLI/Split.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ inline bool split_long(const std::string &current, std::string &name, std::strin
}

// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
inline bool split_windows(const std::string &current, std::string &name, std::string &value) {
inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
auto loc = current.find_first_of(':');
if(loc != std::string::npos) {
Expand All @@ -67,22 +67,29 @@ inline std::vector<std::string> split_names(std::string current) {
return output;
}

/// extract negation arguments basically everything after a '|' and before the next comma
inline std::vector<std::string> get_false_flags(const std::string &str) {
std::vector<std::string> output = split_names(str);
output.erase(std::remove_if(output.begin(),
output.end(),
[](const std::string &name) {
return ((name.empty()) ||
((name.find("{false}") == std::string::npos) && (name[0] != '!')));
}),
output.end());
for(auto &flag : output) {
auto false_loc = flag.find("{false}");
if(false_loc != std::string::npos) {
flag.erase(false_loc, std::string::npos);
/// extract default flag values either {def} or starting with a !
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
std::vector<std::string> flags = split_names(str);
flags.erase(std::remove_if(flags.begin(),
flags.end(),
[](const std::string &name) {
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
(name.back() == '}')) ||
(name[0] == '!'))));
}),
flags.end());
std::vector<std::pair<std::string, std::string>> output;
output.reserve(flags.size());
for(auto &flag : flags) {
auto def_start = flag.find_first_of('{');
std::string defval = "false";
if((def_start != std::string::npos) && (flag.back() == '}')) {
defval = flag.substr(def_start + 1);
defval.pop_back();
flag.erase(def_start, std::string::npos);
}
flag.erase(0, flag.find_first_not_of("-!"));
output.emplace_back(flag, defval);
}
return output;
}
Expand Down
Loading

0 comments on commit 700db24

Please sign in to comment.