Skip to content

Commit

Permalink
Allow non standard option names like -option
Browse files Browse the repository at this point in the history
  • Loading branch information
phlptp committed Oct 19, 2024
1 parent e52bef1 commit 918b63e
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 10 deletions.
12 changes: 12 additions & 0 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ class App {
/// This is potentially useful as a modifier subcommand
bool silent_{false};

/// indicator that the subcommand should allow non-standard option arguments, such as -single_dash_flag
bool allow_non_standard_options_{false};

/// Counts the number of times this command/subcommand was parsed
std::uint32_t parsed_{0U};

Expand Down Expand Up @@ -392,6 +395,12 @@ class App {
return this;
}

/// allow non standard option names
App *allow_non_standard_option_names(bool allowed = true) {
allow_non_standard_options_ = allowed;
return this;
}

/// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
App *disabled_by_default(bool disable = true) {
if(disable) {
Expand Down Expand Up @@ -1146,6 +1155,9 @@ class App {
/// Get the status of silence
CLI11_NODISCARD bool get_silent() const { return silent_; }

/// Get the status of silence
CLI11_NODISCARD bool get_allow_non_standard_option_names() const { return allow_non_standard_options_; }

/// Get the status of disabled
CLI11_NODISCARD bool get_immediate_callback() const { return immediate_callback_; }

Expand Down
4 changes: 2 additions & 2 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,9 @@ class Option : public OptionBase<Option> {
///@}

/// Making an option by hand is not defined, it must be made by the App class
Option(std::string option_name, std::string option_description, callback_t callback, App *parent)
Option(std::string option_name, std::string option_description, callback_t callback, App *parent, bool allow_non_standard=false)
: description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name),allow_non_standard);
}

public:
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Split.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_v

/// Get a vector of short names, one of long names, and a single name
CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
get_names(const std::vector<std::string> &input);
get_names(const std::vector<std::string> &input,bool allow_non_standard=false);

} // namespace detail
// [CLI11:split_hpp:end]
Expand Down
56 changes: 53 additions & 3 deletions include/CLI/impl/App_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ CLI11_INLINE Option *App::add_option(std::string option_name,
std::string option_description,
bool defaulted,
std::function<std::string()> func) {
Option myopt{option_name, option_description, option_callback, this};
Option myopt{option_name, option_description, option_callback, this,allow_non_standard_options_};

if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) ==
std::end(options_)) {
Expand Down Expand Up @@ -191,9 +191,43 @@ CLI11_INLINE Option *App::add_option(std::string option_name,
}
}
}
if (allow_non_standard_options_ && !myopt.snames_.empty())
{
for (auto& sname : myopt.snames_)
{
if (sname.length() > 1)
{
std::string test_name;
test_name.push_back('-');
test_name.push_back(sname.front());
auto *op = get_option_no_throw(test_name);
if(op != nullptr) {
throw(OptionAlreadyAdded("added option interfers with existing short option: " + sname));
}
}
}
for (auto& opt : options_)
{
if (opt->snames_.empty()) {
continue;
}
for (auto osn : opt->snames_)
{
if (osn.size() > 1)
{
std::string test_name;
test_name.push_back(osn.front());
if (myopt.check_sname(test_name))
{
throw(OptionAlreadyAdded("added option interfers with existing non standard option: " + osn));
}
}
}
}
}
options_.emplace_back();
Option_p &option = options_.back();
option.reset(new Option(option_name, option_description, option_callback, this));
option.reset(new Option(option_name, option_description, option_callback, this,allow_non_standard_options_));

// Set the default string capture function
option->default_function(func);
Expand Down Expand Up @@ -1888,7 +1922,8 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
});

// Option not found
if(op_ptr == std::end(options_)) {
while(op_ptr == std::end(options_)) {
//using while so we can break
for(auto &subc : subcommands_) {
if(subc->name_.empty() && !subc->disabled_) {
if(subc->_parse_arg(args, current_type, local_processing_only)) {
Expand All @@ -1899,6 +1934,21 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
}
}
}
if (allow_non_standard_options_ && current_type == detail::Classifier::SHORT && current.size() > 2)
{
std::string narg_name;
std::string nvalue;
detail::split_long(std::string{'-'} + current, narg_name, nvalue);
op_ptr = std::find_if(std::begin(options_), std::end(options_), [narg_name](const Option_p &opt) {
return opt->check_sname(narg_name);
});
if (op_ptr != std::end(options_)) {
arg_name=narg_name;
value=nvalue;
rest.clear();
break;
}
}

// don't capture missing if this is a nameless subcommand and nameless subcommands can't fallthrough
if(parent_ != nullptr && name_.empty()) {
Expand Down
22 changes: 18 additions & 4 deletions include/CLI/impl/Split_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_v
}

CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
get_names(const std::vector<std::string> &input) {
get_names(const std::vector<std::string> &input,bool allow_non_standard) {

std::vector<std::string> short_names;
std::vector<std::string> long_names;
Expand All @@ -115,10 +115,24 @@ get_names(const std::vector<std::string> &input) {
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
if(name.length() == 2 && valid_first_char(name[1]))
short_names.emplace_back(1, name[1]);
else if(name.length() > 2)
throw BadNameString::MissingDash(name);
else
else if (name.length() > 2) {
if (allow_non_standard)
{
name = name.substr(1);
if (valid_name_string(name)) {
short_names.push_back(name);
}
else {
throw BadNameString::BadLongName(name);
}
}
else {
throw BadNameString::MissingDash(name);
}
}
else {
throw BadNameString::OneCharName(name);
}
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
name = name.substr(2);
if(valid_name_string(name))
Expand Down
43 changes: 43 additions & 0 deletions tests/AppTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2389,6 +2389,49 @@ TEST_CASE_METHOD(TApp, "OrderedModifyingTransforms", "[app]") {
CHECK(std::vector<std::string>({"one21", "two21"}) == val);
}

// non standard options
TEST_CASE_METHOD(TApp, "nonStandardOptions", "[app]") {
std::string string1;
CHECK_THROWS_AS( app.add_option("-single", string1),CLI::BadNameString);
app.allow_non_standard_option_names();
CHECK(app.get_allow_non_standard_option_names());
app.add_option("-single", string1);
args = {"-single", "string1"};

run();

CHECK(string1=="string1");

}

TEST_CASE_METHOD(TApp, "nonStandardOptions2", "[app]") {
std::vector<std::string> strings;
app.allow_non_standard_option_names();
app.add_option("-single,--single,-m", strings);
args = {"-single", "string1","--single","string2"};

run();

CHECK(strings == std::vector<std::string>{"string1", "string2"});

}

TEST_CASE_METHOD(TApp, "nonStandardOptionsIntersect", "[app]") {
std::vector<std::string> strings;
app.allow_non_standard_option_names();
app.add_option("-s,-t");
CHECK_THROWS_AS(app.add_option("-single,--single", strings),CLI::OptionAlreadyAdded);

}

TEST_CASE_METHOD(TApp, "nonStandardOptionsIntersect2", "[app]") {
std::vector<std::string> strings;
app.allow_non_standard_option_names();
app.add_option("-single,--single", strings);
CHECK_THROWS_AS( app.add_option("-s,-t"),CLI::OptionAlreadyAdded);

}

TEST_CASE_METHOD(TApp, "ThrowingTransform", "[app]") {
std::string val;
auto *m = app.add_option("-m,--mess", val);
Expand Down

0 comments on commit 918b63e

Please sign in to comment.