Commit a83109002c00ef51f0cc99aefbf1d9ef935a92a3

Authored by Philip Top
Committed by Henry Schreiner
1 parent c3d8d4a2

Add ignore underscore (#185)

* add ignore_underscore test cases and options to app

* add ignore_underscore for add_sets and some more tests for the sets and subcommands

* add some documentation lines and some failing tests

* update readme with ignore_underscore option

* remove failing tests from known issue

* remove empty line for code coverage
README.md
@@ -162,7 +162,7 @@ try { @@ -162,7 +162,7 @@ try {
162 } 162 }
163 ``` 163 ```
164 164
165 -The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere. 165 +The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.
166 166
167 </p></details> 167 </p></details>
168 </br> 168 </br>
@@ -197,6 +197,10 @@ app.add_set(option_name, @@ -197,6 +197,10 @@ app.add_set(option_name,
197 197
198 app.add_set_ignore_case(... // String only 198 app.add_set_ignore_case(... // String only
199 199
  200 +app.add_set_ignore_underscore(... // String only
  201 +
  202 +app.add_set_ignore_case_underscore(... // String only
  203 +
200 App* subcom = app.add_subcommand(name, description); 204 App* subcom = app.add_subcommand(name, description);
201 ``` 205 ```
202 206
@@ -212,7 +216,7 @@ On a compiler that supports C++17&#39;s `__has_include`, you can also use `std::opti @@ -212,7 +216,7 @@ On a compiler that supports C++17&#39;s `__has_include`, you can also use `std::opti
212 - `"this"` Can only be passed positionally 216 - `"this"` Can only be passed positionally
213 - `"-a,-b,-c"` No limit to the number of non-positional option names 217 - `"-a,-b,-c"` No limit to the number of non-positional option names
214 218
215 -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. 219 +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.
216 220
217 #### Option options 221 #### Option options
218 222
@@ -227,6 +231,7 @@ Before parsing, you can set the following options: @@ -227,6 +231,7 @@ Before parsing, you can set the following options:
227 - `->envname(name)`: Gets the value from the environment if present and not passed on the command line. 231 - `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
228 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). 232 - `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
229 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). 233 - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
  234 +- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with optionone. This does not apply to short form options since they only have one character
230 - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last). 235 - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which always default to take last).
231 - `->check(CLI::ExistingFile)`: Requires that the file exists if given. 236 - `->check(CLI::ExistingFile)`: Requires that the file exists if given.
232 - `->check(CLI::ExistingDirectory)`: Requires that the directory exists. 237 - `->check(CLI::ExistingDirectory)`: Requires that the directory exists.
@@ -279,6 +284,8 @@ Multiple subcommands are allowed, to allow [`Click`][click] like series of comma @@ -279,6 +284,8 @@ Multiple subcommands are allowed, to allow [`Click`][click] like series of comma
279 There are several options that are supported on the main app and subcommands. These are: 284 There are several options that are supported on the main app and subcommands. These are:
280 285
281 - `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. 286 - `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
  287 +- `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
  288 +
282 - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through. 289 - `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent command. Subcommands always are allowed to fall through.
283 - `.require_subcommand()`: Require 1 or more subcommands. 290 - `.require_subcommand()`: Require 1 or more subcommands.
284 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more. 291 - `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default 0 or more.
@@ -340,7 +347,7 @@ arguments, use `.config_to_str(default_also=false, prefix=&quot;&quot;, write_description= @@ -340,7 +347,7 @@ arguments, use `.config_to_str(default_also=false, prefix=&quot;&quot;, write_description=
340 347
341 Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. 348 Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `fallthrough`, `group`, `footer`, and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
342 349
343 -Options have defaults for `group`, `required`, `multi_option_policy`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example: 350 +Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_underscore`, and `ignore_case`. To set these defaults, you should set the `option_defaults()` object, for example:
344 351
345 ```cpp 352 ```cpp
346 app.option_defaults()->required(); 353 app.option_defaults()->required();
@@ -351,7 +358,7 @@ The default settings for options are inherited to subcommands, as well. @@ -351,7 +358,7 @@ The default settings for options are inherited to subcommands, as well.
351 358
352 ### Formatting 359 ### Formatting
353 360
354 -The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature. 361 +The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
355 CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do 362 CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
356 that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`. 363 that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`.
357 364
@@ -387,7 +394,7 @@ app.add_option(&quot;--fancy-count&quot;, [](std::vector&lt;std::string&gt; val){ @@ -387,7 +394,7 @@ app.add_option(&quot;--fancy-count&quot;, [](std::vector&lt;std::string&gt; val){
387 394
388 ### Utilities 395 ### Utilities
389 396
390 -There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output. 397 +There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output.
391 398
392 ```cpp 399 ```cpp
393 { 400 {
include/CLI/App.hpp
@@ -139,6 +139,9 @@ class App { @@ -139,6 +139,9 @@ class App {
139 /// If true, the program name is not case sensitive INHERITABLE 139 /// If true, the program name is not case sensitive INHERITABLE
140 bool ignore_case_{false}; 140 bool ignore_case_{false};
141 141
  142 + /// If true, the program should ignore underscores INHERITABLE
  143 + bool ignore_underscore_{false};
  144 +
142 /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE 145 /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE
143 bool fallthrough_{false}; 146 bool fallthrough_{false};
144 147
@@ -195,6 +198,7 @@ class App { @@ -195,6 +198,7 @@ class App {
195 allow_config_extras_ = parent_->allow_config_extras_; 198 allow_config_extras_ = parent_->allow_config_extras_;
196 prefix_command_ = parent_->prefix_command_; 199 prefix_command_ = parent_->prefix_command_;
197 ignore_case_ = parent_->ignore_case_; 200 ignore_case_ = parent_->ignore_case_;
  201 + ignore_underscore_ = parent_->ignore_underscore_;
198 fallthrough_ = parent_->fallthrough_; 202 fallthrough_ = parent_->fallthrough_;
199 group_ = parent_->group_; 203 group_ = parent_->group_;
200 footer_ = parent_->footer_; 204 footer_ = parent_->footer_;
@@ -265,6 +269,18 @@ class App { @@ -265,6 +269,18 @@ class App {
265 return this; 269 return this;
266 } 270 }
267 271
  272 + /// Ignore underscore. Subcommand inherit value.
  273 + App *ignore_underscore(bool value = true) {
  274 + ignore_underscore_ = value;
  275 + if(parent_ != nullptr) {
  276 + for(const auto &subc : parent_->subcommands_) {
  277 + if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_)))
  278 + throw OptionAlreadyAdded(subc->name_);
  279 + }
  280 + }
  281 + return this;
  282 + }
  283 +
268 /// Set the help formatter 284 /// Set the help formatter
269 App *formatter(std::shared_ptr<FormatterBase> fmt) { 285 App *formatter(std::shared_ptr<FormatterBase> fmt) {
270 formatter_ = fmt; 286 formatter_ = fmt;
@@ -510,7 +526,7 @@ class App { @@ -510,7 +526,7 @@ class App {
510 } 526 }
511 #endif 527 #endif
512 528
513 - /// Add set of options (No default, temp refernce, such as an inline set) 529 + /// Add set of options (No default, temp reference, such as an inline set)
514 template <typename T> 530 template <typename T>
515 Option *add_set(std::string name, 531 Option *add_set(std::string name,
516 T &member, ///< The selected member of the set 532 T &member, ///< The selected member of the set
@@ -532,7 +548,7 @@ class App { @@ -532,7 +548,7 @@ class App {
532 return opt; 548 return opt;
533 } 549 }
534 550
535 - /// Add set of options (No default, non-temp refernce, such as an existing set) 551 + /// Add set of options (No default, non-temp reference, such as an existing set)
536 template <typename T> 552 template <typename T>
537 Option *add_set(std::string name, 553 Option *add_set(std::string name,
538 T &member, ///< The selected member of the set 554 T &member, ///< The selected member of the set
@@ -558,7 +574,7 @@ class App { @@ -558,7 +574,7 @@ class App {
558 template <typename T> 574 template <typename T>
559 Option *add_set(std::string name, 575 Option *add_set(std::string name,
560 T &member, ///< The selected member of the set 576 T &member, ///< The selected member of the set
561 - const std::set<T> &&options, ///< The set of posibilities 577 + const std::set<T> &&options, ///< The set of possibilities
562 std::string description, 578 std::string description,
563 bool defaulted) { 579 bool defaulted) {
564 580
@@ -582,11 +598,11 @@ class App { @@ -582,11 +598,11 @@ class App {
582 return opt; 598 return opt;
583 } 599 }
584 600
585 - /// Add set of options (with default, L value refernce, such as an existing set) 601 + /// Add set of options (with default, L value reference, such as an existing set)
586 template <typename T> 602 template <typename T>
587 Option *add_set(std::string name, 603 Option *add_set(std::string name,
588 T &member, ///< The selected member of the set 604 T &member, ///< The selected member of the set
589 - const std::set<T> &options, ///< The set of posibilities 605 + const std::set<T> &options, ///< The set of possibilities
590 std::string description, 606 std::string description,
591 bool defaulted) { 607 bool defaulted) {
592 608
@@ -668,7 +684,7 @@ class App { @@ -668,7 +684,7 @@ class App {
668 /// Add set of options, string only, ignore case (default, R value) 684 /// Add set of options, string only, ignore case (default, R value)
669 Option *add_set_ignore_case(std::string name, 685 Option *add_set_ignore_case(std::string name,
670 std::string &member, ///< The selected member of the set 686 std::string &member, ///< The selected member of the set
671 - const std::set<std::string> &&options, ///< The set of posibilities 687 + const std::set<std::string> &&options, ///< The set of possibilities
672 std::string description, 688 std::string description,
673 bool defaulted) { 689 bool defaulted) {
674 690
@@ -699,7 +715,7 @@ class App { @@ -699,7 +715,7 @@ class App {
699 /// Add set of options, string only, ignore case (default, L value) 715 /// Add set of options, string only, ignore case (default, L value)
700 Option *add_set_ignore_case(std::string name, 716 Option *add_set_ignore_case(std::string name,
701 std::string &member, ///< The selected member of the set 717 std::string &member, ///< The selected member of the set
702 - const std::set<std::string> &options, ///< The set of posibilities 718 + const std::set<std::string> &options, ///< The set of possibilities
703 std::string description, 719 std::string description,
704 bool defaulted) { 720 bool defaulted) {
705 721
@@ -727,6 +743,242 @@ class App { @@ -727,6 +743,242 @@ class App {
727 return opt; 743 return opt;
728 } 744 }
729 745
  746 + /// Add set of options, string only, ignore underscore (no default, R value)
  747 + Option *add_set_ignore_underscore(std::string name,
  748 + std::string &member, ///< The selected member of the set
  749 + const std::set<std::string> &&options, ///< The set of possibilities
  750 + std::string description = "") {
  751 +
  752 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  753 + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
  754 + member = detail::remove_underscore(res[0]);
  755 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  756 + return detail::remove_underscore(val) == member;
  757 + });
  758 + if(iter == std::end(options))
  759 + throw ConversionError(member, simple_name);
  760 + else {
  761 + member = *iter;
  762 + return true;
  763 + }
  764 + };
  765 +
  766 + Option *opt = add_option(name, fun, description, false);
  767 + std::string typeval = detail::type_name<std::string>();
  768 + typeval += " in {" + detail::join(options) + "}";
  769 + opt->type_name(typeval);
  770 +
  771 + return opt;
  772 + }
  773 +
  774 + /// Add set of options, string only, ignore underscore (no default, L value)
  775 + Option *add_set_ignore_underscore(std::string name,
  776 + std::string &member, ///< The selected member of the set
  777 + const std::set<std::string> &options, ///< The set of possibilities
  778 + std::string description = "") {
  779 +
  780 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  781 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  782 + member = detail::remove_underscore(res[0]);
  783 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  784 + return detail::remove_underscore(val) == member;
  785 + });
  786 + if(iter == std::end(options))
  787 + throw ConversionError(member, simple_name);
  788 + else {
  789 + member = *iter;
  790 + return true;
  791 + }
  792 + };
  793 +
  794 + Option *opt = add_option(name, fun, description, false);
  795 + opt->type_name_fn([&options]() {
  796 + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
  797 + });
  798 +
  799 + return opt;
  800 + }
  801 +
  802 + /// Add set of options, string only, ignore underscore (default, R value)
  803 + Option *add_set_ignore_underscore(std::string name,
  804 + std::string &member, ///< The selected member of the set
  805 + const std::set<std::string> &&options, ///< The set of possibilities
  806 + std::string description,
  807 + bool defaulted) {
  808 +
  809 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  810 + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
  811 + member = detail::remove_underscore(res[0]);
  812 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  813 + return detail::remove_underscore(val) == member;
  814 + });
  815 + if(iter == std::end(options))
  816 + throw ConversionError(member, simple_name);
  817 + else {
  818 + member = *iter;
  819 + return true;
  820 + }
  821 + };
  822 +
  823 + Option *opt = add_option(name, fun, description, defaulted);
  824 + std::string typeval = detail::type_name<std::string>();
  825 + typeval += " in {" + detail::join(options) + "}";
  826 + opt->type_name(typeval);
  827 + if(defaulted) {
  828 + opt->default_str(member);
  829 + }
  830 + return opt;
  831 + }
  832 +
  833 + /// Add set of options, string only, ignore underscore (default, L value)
  834 + Option *add_set_ignore_underscore(std::string name,
  835 + std::string &member, ///< The selected member of the set
  836 + const std::set<std::string> &options, ///< The set of possibilities
  837 + std::string description,
  838 + bool defaulted) {
  839 +
  840 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  841 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  842 + member = detail::remove_underscore(res[0]);
  843 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  844 + return detail::remove_underscore(val) == member;
  845 + });
  846 + if(iter == std::end(options))
  847 + throw ConversionError(member, simple_name);
  848 + else {
  849 + member = *iter;
  850 + return true;
  851 + }
  852 + };
  853 +
  854 + Option *opt = add_option(name, fun, description, defaulted);
  855 + opt->type_name_fn([&options]() {
  856 + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
  857 + });
  858 + if(defaulted) {
  859 + opt->default_str(member);
  860 + }
  861 + return opt;
  862 + }
  863 +
  864 + /// Add set of options, string only, ignore underscore and case(no default, R value)
  865 + Option *add_set_ignore_case_underscore(std::string name,
  866 + std::string &member, ///< The selected member of the set
  867 + const std::set<std::string> &&options, ///< The set of possibilities
  868 + std::string description = "") {
  869 +
  870 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  871 + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
  872 + member = detail::to_lower(detail::remove_underscore(res[0]));
  873 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  874 + return detail::to_lower(detail::remove_underscore(val)) == member;
  875 + });
  876 + if(iter == std::end(options))
  877 + throw ConversionError(member, simple_name);
  878 + else {
  879 + member = *iter;
  880 + return true;
  881 + }
  882 + };
  883 +
  884 + Option *opt = add_option(name, fun, description, false);
  885 + std::string typeval = detail::type_name<std::string>();
  886 + typeval += " in {" + detail::join(options) + "}";
  887 + opt->type_name(typeval);
  888 +
  889 + return opt;
  890 + }
  891 +
  892 + /// Add set of options, string only, ignore underscore and case(no default, L value)
  893 + Option *add_set_ignore_case_underscore(std::string name,
  894 + std::string &member, ///< The selected member of the set
  895 + const std::set<std::string> &options, ///< The set of possibilities
  896 + std::string description = "") {
  897 +
  898 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  899 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  900 + member = detail::to_lower(detail::remove_underscore(res[0]));
  901 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  902 + return detail::to_lower(detail::remove_underscore(val)) == member;
  903 + });
  904 + if(iter == std::end(options))
  905 + throw ConversionError(member, simple_name);
  906 + else {
  907 + member = *iter;
  908 + return true;
  909 + }
  910 + };
  911 +
  912 + Option *opt = add_option(name, fun, description, false);
  913 + opt->type_name_fn([&options]() {
  914 + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
  915 + });
  916 +
  917 + return opt;
  918 + }
  919 +
  920 + /// Add set of options, string only, ignore underscore and case (default, R value)
  921 + Option *add_set_ignore_case_underscore(std::string name,
  922 + std::string &member, ///< The selected member of the set
  923 + const std::set<std::string> &&options, ///< The set of possibilities
  924 + std::string description,
  925 + bool defaulted) {
  926 +
  927 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  928 + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) {
  929 + member = detail::to_lower(detail::remove_underscore(res[0]));
  930 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  931 + return detail::to_lower(detail::remove_underscore(val)) == member;
  932 + });
  933 + if(iter == std::end(options))
  934 + throw ConversionError(member, simple_name);
  935 + else {
  936 + member = *iter;
  937 + return true;
  938 + }
  939 + };
  940 +
  941 + Option *opt = add_option(name, fun, description, defaulted);
  942 + std::string typeval = detail::type_name<std::string>();
  943 + typeval += " in {" + detail::join(options) + "}";
  944 + opt->type_name(typeval);
  945 + if(defaulted) {
  946 + opt->default_str(member);
  947 + }
  948 + return opt;
  949 + }
  950 +
  951 + /// Add set of options, string only, ignore underscore and case (default, L value)
  952 + Option *add_set_ignore_case_underscore(std::string name,
  953 + std::string &member, ///< The selected member of the set
  954 + const std::set<std::string> &options, ///< The set of possibilities
  955 + std::string description,
  956 + bool defaulted) {
  957 +
  958 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  959 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  960 + member = detail::to_lower(detail::remove_underscore(res[0]));
  961 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  962 + return detail::to_lower(detail::remove_underscore(val)) == member;
  963 + });
  964 + if(iter == std::end(options))
  965 + throw ConversionError(member, simple_name);
  966 + else {
  967 + member = *iter;
  968 + return true;
  969 + }
  970 + };
  971 +
  972 + Option *opt = add_option(name, fun, description, defaulted);
  973 + opt->type_name_fn([&options]() {
  974 + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}";
  975 + });
  976 + if(defaulted) {
  977 + opt->default_str(member);
  978 + }
  979 + return opt;
  980 + }
  981 +
730 /// Add a complex number 982 /// Add a complex number
731 template <typename T> 983 template <typename T>
732 Option *add_complex(std::string name, 984 Option *add_complex(std::string name,
@@ -1122,6 +1374,9 @@ class App { @@ -1122,6 +1374,9 @@ class App {
1122 /// Check the status of ignore_case 1374 /// Check the status of ignore_case
1123 bool get_ignore_case() const { return ignore_case_; } 1375 bool get_ignore_case() const { return ignore_case_; }
1124 1376
  1377 + /// Check the status of ignore_underscore
  1378 + bool get_ignore_underscore() const { return ignore_underscore_; }
  1379 +
1125 /// Check the status of fallthrough 1380 /// Check the status of fallthrough
1126 bool get_fallthrough() const { return fallthrough_; } 1381 bool get_fallthrough() const { return fallthrough_; }
1127 1382
@@ -1170,9 +1425,13 @@ class App { @@ -1170,9 +1425,13 @@ class App {
1170 /// Get the name of the current app 1425 /// Get the name of the current app
1171 std::string get_name() const { return name_; } 1426 std::string get_name() const { return name_; }
1172 1427
1173 - /// Check the name, case insensitive if set 1428 + /// Check the name, case insensitive and underscore insensitive if set
1174 bool check_name(std::string name_to_check) const { 1429 bool check_name(std::string name_to_check) const {
1175 std::string local_name = name_; 1430 std::string local_name = name_;
  1431 + if(ignore_underscore_) {
  1432 + local_name = detail::remove_underscore(name_);
  1433 + name_to_check = detail::remove_underscore(name_to_check);
  1434 + }
1176 if(ignore_case_) { 1435 if(ignore_case_) {
1177 local_name = detail::to_lower(name_); 1436 local_name = detail::to_lower(name_);
1178 name_to_check = detail::to_lower(name_to_check); 1437 name_to_check = detail::to_lower(name_to_check);
include/CLI/Option.hpp
@@ -45,6 +45,9 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -45,6 +45,9 @@ template &lt;typename CRTP&gt; class OptionBase {
45 /// Ignore the case when matching (option, not value) 45 /// Ignore the case when matching (option, not value)
46 bool ignore_case_{false}; 46 bool ignore_case_{false};
47 47
  48 + /// Ignore underscores when matching (option, not value)
  49 + bool ignore_underscore_{false};
  50 +
48 /// Allow this option to be given in a configuration file 51 /// Allow this option to be given in a configuration file
49 bool configurable_{true}; 52 bool configurable_{true};
50 53
@@ -56,6 +59,7 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -56,6 +59,7 @@ template &lt;typename CRTP&gt; class OptionBase {
56 other->group(group_); 59 other->group(group_);
57 other->required(required_); 60 other->required(required_);
58 other->ignore_case(ignore_case_); 61 other->ignore_case(ignore_case_);
  62 + other->ignore_underscore(ignore_underscore_);
59 other->configurable(configurable_); 63 other->configurable(configurable_);
60 other->multi_option_policy(multi_option_policy_); 64 other->multi_option_policy(multi_option_policy_);
61 } 65 }
@@ -90,6 +94,9 @@ template &lt;typename CRTP&gt; class OptionBase { @@ -90,6 +94,9 @@ template &lt;typename CRTP&gt; class OptionBase {
90 /// The status of ignore case 94 /// The status of ignore case
91 bool get_ignore_case() const { return ignore_case_; } 95 bool get_ignore_case() const { return ignore_case_; }
92 96
  97 + /// The status of ignore_underscore
  98 + bool get_ignore_underscore() const { return ignore_underscore_; }
  99 +
93 /// The status of configurable 100 /// The status of configurable
94 bool get_configurable() const { return configurable_; } 101 bool get_configurable() const { return configurable_; }
95 102
@@ -145,6 +152,12 @@ class OptionDefaults : public OptionBase&lt;OptionDefaults&gt; { @@ -145,6 +152,12 @@ class OptionDefaults : public OptionBase&lt;OptionDefaults&gt; {
145 ignore_case_ = value; 152 ignore_case_ = value;
146 return this; 153 return this;
147 } 154 }
  155 +
  156 + /// Ignore underscores in the option name
  157 + OptionDefaults *ignore_underscore(bool value = true) {
  158 + ignore_underscore_ = value;
  159 + return this;
  160 + }
148 }; 161 };
149 162
150 class Option : public OptionBase<Option> { 163 class Option : public OptionBase<Option> {
@@ -411,6 +424,20 @@ class Option : public OptionBase&lt;Option&gt; { @@ -411,6 +424,20 @@ class Option : public OptionBase&lt;Option&gt; {
411 return this; 424 return this;
412 } 425 }
413 426
  427 + /// Ignore underscores in the option names
  428 + ///
  429 + /// The template hides the fact that we don't have the definition of App yet.
  430 + /// You are never expected to add an argument to the template here.
  431 + template <typename T = App> Option *ignore_underscore(bool value = true) {
  432 + ignore_underscore_ = value;
  433 + auto *parent = dynamic_cast<T *>(parent_);
  434 + for(const Option_p &opt : parent->options_)
  435 + if(opt.get() != this && *opt == *this)
  436 + throw OptionAlreadyAdded(opt->get_name(true, true));
  437 +
  438 + return this;
  439 + }
  440 +
414 /// Take the last argument if given multiple times (or another policy) 441 /// Take the last argument if given multiple times (or another policy)
415 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { 442 Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
416 443
@@ -602,7 +629,7 @@ class Option : public OptionBase&lt;Option&gt; { @@ -602,7 +629,7 @@ class Option : public OptionBase&lt;Option&gt; {
602 for(const std::string &lname : lnames_) 629 for(const std::string &lname : lnames_)
603 if(other.check_lname(lname)) 630 if(other.check_lname(lname))
604 return true; 631 return true;
605 - // We need to do the inverse, just in case we are ignore_case 632 + // We need to do the inverse, just in case we are ignore_case or ignore underscore
606 for(const std::string &sname : other.snames_) 633 for(const std::string &sname : other.snames_)
607 if(check_sname(sname)) 634 if(check_sname(sname))
608 return true; 635 return true;
@@ -625,13 +652,17 @@ class Option : public OptionBase&lt;Option&gt; { @@ -625,13 +652,17 @@ class Option : public OptionBase&lt;Option&gt; {
625 local_pname = detail::to_lower(local_pname); 652 local_pname = detail::to_lower(local_pname);
626 name = detail::to_lower(name); 653 name = detail::to_lower(name);
627 } 654 }
  655 + if(ignore_underscore_) {
  656 + local_pname = detail::remove_underscore(local_pname);
  657 + name = detail::remove_underscore(name);
  658 + }
628 return name == local_pname; 659 return name == local_pname;
629 } 660 }
630 } 661 }
631 662
632 /// Requires "-" to be removed from string 663 /// Requires "-" to be removed from string
633 bool check_sname(std::string name) const { 664 bool check_sname(std::string name) const {
634 - if(ignore_case_) { 665 + if(ignore_case_) { // there can be no extra underscores in check_sname
635 name = detail::to_lower(name); 666 name = detail::to_lower(name);
636 return std::find_if(std::begin(snames_), std::end(snames_), [&name](std::string local_sname) { 667 return std::find_if(std::begin(snames_), std::end(snames_), [&name](std::string local_sname) {
637 return detail::to_lower(local_sname) == name; 668 return detail::to_lower(local_sname) == name;
@@ -643,9 +674,22 @@ class Option : public OptionBase&lt;Option&gt; { @@ -643,9 +674,22 @@ class Option : public OptionBase&lt;Option&gt; {
643 /// Requires "--" to be removed from string 674 /// Requires "--" to be removed from string
644 bool check_lname(std::string name) const { 675 bool check_lname(std::string name) const {
645 if(ignore_case_) { 676 if(ignore_case_) {
646 - name = detail::to_lower(name); 677 + if(ignore_underscore_) {
  678 + name = detail::to_lower(detail::remove_underscore(name));
  679 + return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
  680 + return detail::to_lower(detail::remove_underscore(local_sname)) == name;
  681 + }) != std::end(lnames_);
  682 + } else {
  683 + name = detail::to_lower(name);
  684 + return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
  685 + return detail::to_lower(local_sname) == name;
  686 + }) != std::end(lnames_);
  687 + }
  688 +
  689 + } else if(ignore_underscore_) {
  690 + name = detail::remove_underscore(name);
647 return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) { 691 return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) {
648 - return detail::to_lower(local_sname) == name; 692 + return detail::remove_underscore(local_sname) == name;
649 }) != std::end(lnames_); 693 }) != std::end(lnames_);
650 } else 694 } else
651 return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_); 695 return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_);
include/CLI/StringTools.hpp
@@ -142,6 +142,12 @@ inline std::string to_lower(std::string str) { @@ -142,6 +142,12 @@ inline std::string to_lower(std::string str) {
142 return str; 142 return str;
143 } 143 }
144 144
  145 +/// remove underscores from a string
  146 +inline std::string remove_underscore(std::string str) {
  147 + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
  148 + return str;
  149 +}
  150 +
145 /// Split a string '"one two" "three"' into 'one two', 'three' 151 /// Split a string '"one two" "three"' into 'one two', 'three'
146 inline std::vector<std::string> split_up(std::string str) { 152 inline std::vector<std::string> split_up(std::string str) {
147 153
tests/AppTest.cpp
@@ -994,6 +994,115 @@ TEST_F(TApp, InSetIgnoreCase) { @@ -994,6 +994,115 @@ TEST_F(TApp, InSetIgnoreCase) {
994 EXPECT_THROW(run(), CLI::ArgumentMismatch); 994 EXPECT_THROW(run(), CLI::ArgumentMismatch);
995 } 995 }
996 996
  997 +/*
  998 +TEST_F(TApp, InSetIgnoreCaseLValue) {
  999 +
  1000 + std::set<std::string> options{"one", "Two", "THREE"};
  1001 + std::string choice;
  1002 + app.add_set_ignore_case("-q,--quick", choice, options);
  1003 +
  1004 + args = {"--quick", "One"};
  1005 + run();
  1006 + EXPECT_EQ("one", choice);
  1007 +
  1008 + args = {"--quick", "two"};
  1009 + run();
  1010 + EXPECT_EQ("Two", choice); // Keeps caps from set
  1011 +
  1012 + args = {"--quick", "ThrEE"};
  1013 + run();
  1014 + EXPECT_EQ("THREE", choice); // Keeps caps from set
  1015 +
  1016 + options.clear();
  1017 + args = {"--quick", "ThrEE"};
  1018 + run();
  1019 + EXPECT_EQ("THREE", choice); // this will now fail since options was cleared
  1020 +
  1021 + args = {"--quick", "four"};
  1022 + EXPECT_THROW(run(), CLI::ConversionError);
  1023 +
  1024 + args = {"--quick=one", "--quick=two"};
  1025 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1026 +}
  1027 +
  1028 +TEST_F(TApp, InSetIgnoreCasePointer) {
  1029 +
  1030 + std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
  1031 + std::string choice;
  1032 + app.add_set_ignore_case("-q,--quick", choice, *options);
  1033 +
  1034 + args = {"--quick", "One"};
  1035 + run();
  1036 + EXPECT_EQ("one", choice);
  1037 +
  1038 + args = {"--quick", "two"};
  1039 + run();
  1040 + EXPECT_EQ("Two", choice); // Keeps caps from set
  1041 +
  1042 + args = {"--quick", "ThrEE"};
  1043 + run();
  1044 + EXPECT_EQ("THREE", choice); // Keeps caps from set
  1045 +
  1046 + delete options;
  1047 + args = {"--quick", "ThrEE"};
  1048 + run();
  1049 + EXPECT_EQ("THREE", choice); // this could cause a seg fault
  1050 +
  1051 + args = {"--quick", "four"};
  1052 + EXPECT_THROW(run(), CLI::ConversionError);
  1053 +
  1054 + args = {"--quick=one", "--quick=two"};
  1055 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1056 +}
  1057 +*/
  1058 +TEST_F(TApp, InSetIgnoreUnderscore) {
  1059 +
  1060 + std::string choice;
  1061 + app.add_set_ignore_underscore("-q,--quick", choice, {"option_one", "option_two", "optionthree"});
  1062 +
  1063 + args = {"--quick", "option_one"};
  1064 + run();
  1065 + EXPECT_EQ("option_one", choice);
  1066 +
  1067 + args = {"--quick", "optiontwo"};
  1068 + run();
  1069 + EXPECT_EQ("option_two", choice); // Keeps underscore from set
  1070 +
  1071 + args = {"--quick", "_option_thr_ee"};
  1072 + run();
  1073 + EXPECT_EQ("optionthree", choice); // no underscore
  1074 +
  1075 + args = {"--quick", "Option4"};
  1076 + EXPECT_THROW(run(), CLI::ConversionError);
  1077 +
  1078 + args = {"--quick=option_one", "--quick=option_two"};
  1079 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1080 +}
  1081 +
  1082 +TEST_F(TApp, InSetIgnoreCaseUnderscore) {
  1083 +
  1084 + std::string choice;
  1085 + app.add_set_ignore_case_underscore("-q,--quick", choice, {"Option_One", "option_two", "OptionThree"});
  1086 +
  1087 + args = {"--quick", "option_one"};
  1088 + run();
  1089 + EXPECT_EQ("Option_One", choice);
  1090 +
  1091 + args = {"--quick", "OptionTwo"};
  1092 + run();
  1093 + EXPECT_EQ("option_two", choice); // Keeps underscore and case from set
  1094 +
  1095 + args = {"--quick", "_OPTION_thr_ee"};
  1096 + run();
  1097 + EXPECT_EQ("OptionThree", choice); // no underscore
  1098 +
  1099 + args = {"--quick", "Option4"};
  1100 + EXPECT_THROW(run(), CLI::ConversionError);
  1101 +
  1102 + args = {"--quick=option_one", "--quick=option_two"};
  1103 + EXPECT_THROW(run(), CLI::ArgumentMismatch);
  1104 +}
  1105 +
997 TEST_F(TApp, VectorFixedString) { 1106 TEST_F(TApp, VectorFixedString) {
998 std::vector<std::string> strvec; 1107 std::vector<std::string> strvec;
999 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 1108 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
tests/CreationTest.cpp
@@ -92,6 +92,17 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseFirst) { @@ -92,6 +92,17 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseFirst) {
92 EXPECT_THROW(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded); 92 EXPECT_THROW(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
93 } 93 }
94 94
  95 +TEST_F(TApp, MultipleSubcomMatchingWithUnderscore) {
  96 + app.add_subcommand("first_option")->ignore_underscore();
  97 + EXPECT_THROW(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
  98 +}
  99 +
  100 +TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreFirst) {
  101 + app.ignore_underscore();
  102 + app.add_subcommand("first_option");
  103 + EXPECT_THROW(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
  104 +}
  105 +
95 TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace) { 106 TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace) {
96 app.add_subcommand("first"); 107 app.add_subcommand("first");
97 auto first = app.add_subcommand("fIrst"); 108 auto first = app.add_subcommand("fIrst");
@@ -106,6 +117,20 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace2) { @@ -106,6 +117,20 @@ TEST_F(TApp, MultipleSubcomMatchingWithCaseInplace2) {
106 EXPECT_THROW(first->ignore_case(), CLI::OptionAlreadyAdded); 117 EXPECT_THROW(first->ignore_case(), CLI::OptionAlreadyAdded);
107 } 118 }
108 119
  120 +TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreInplace) {
  121 + app.add_subcommand("first_option");
  122 + auto first = app.add_subcommand("firstoption");
  123 +
  124 + EXPECT_THROW(first->ignore_underscore(), CLI::OptionAlreadyAdded);
  125 +}
  126 +
  127 +TEST_F(TApp, MultipleSubcomMatchingWithUnderscoreInplace2) {
  128 + auto first = app.add_subcommand("firstoption");
  129 + app.add_subcommand("first_option");
  130 +
  131 + EXPECT_THROW(first->ignore_underscore(), CLI::OptionAlreadyAdded);
  132 +}
  133 +
109 TEST_F(TApp, MultipleSubcomNoMatchingInplace2) { 134 TEST_F(TApp, MultipleSubcomNoMatchingInplace2) {
110 auto first = app.add_subcommand("first"); 135 auto first = app.add_subcommand("first");
111 auto second = app.add_subcommand("second"); 136 auto second = app.add_subcommand("second");
@@ -114,6 +139,14 @@ TEST_F(TApp, MultipleSubcomNoMatchingInplace2) { @@ -114,6 +139,14 @@ TEST_F(TApp, MultipleSubcomNoMatchingInplace2) {
114 EXPECT_NO_THROW(second->ignore_case()); 139 EXPECT_NO_THROW(second->ignore_case());
115 } 140 }
116 141
  142 +TEST_F(TApp, MultipleSubcomNoMatchingInplaceUnderscore2) {
  143 + auto first = app.add_subcommand("first_option");
  144 + auto second = app.add_subcommand("second_option");
  145 +
  146 + EXPECT_NO_THROW(first->ignore_underscore());
  147 + EXPECT_NO_THROW(second->ignore_underscore());
  148 +}
  149 +
117 TEST_F(TApp, IncorrectConstructionFlagPositional1) { EXPECT_THROW(app.add_flag("cat"), CLI::IncorrectConstruction); } 150 TEST_F(TApp, IncorrectConstructionFlagPositional1) { EXPECT_THROW(app.add_flag("cat"), CLI::IncorrectConstruction); }
118 151
119 TEST_F(TApp, IncorrectConstructionFlagPositional2) { 152 TEST_F(TApp, IncorrectConstructionFlagPositional2) {
@@ -262,6 +295,68 @@ TEST_F(TApp, CheckNameNoCase) { @@ -262,6 +295,68 @@ TEST_F(TApp, CheckNameNoCase) {
262 EXPECT_TRUE(pos2->check_name("pos2")); 295 EXPECT_TRUE(pos2->check_name("pos2"));
263 } 296 }
264 297
  298 +TEST_F(TApp, CheckNameNoUnderscore) {
  299 + auto long1 = app.add_flag("--longoption1")->ignore_underscore();
  300 + auto long2 = app.add_flag("--long_option2")->ignore_underscore();
  301 +
  302 + int x, y;
  303 + auto pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
  304 + auto pos2 = app.add_option("posoption2", y)->ignore_underscore();
  305 +
  306 + EXPECT_TRUE(long1->check_name("--long_option1"));
  307 + EXPECT_TRUE(long1->check_name("--longoption_1"));
  308 + EXPECT_TRUE(long1->check_name("--longoption1"));
  309 + EXPECT_TRUE(long1->check_name("--long__opt_ion__1"));
  310 + EXPECT_TRUE(long1->check_name("--__l_o_n_g_o_p_t_i_o_n_1"));
  311 +
  312 + EXPECT_TRUE(long2->check_name("--long_option2"));
  313 + EXPECT_TRUE(long2->check_name("--longoption2"));
  314 + EXPECT_TRUE(long2->check_name("--longoption_2"));
  315 + EXPECT_TRUE(long2->check_name("--long__opt_ion__2"));
  316 + EXPECT_TRUE(long2->check_name("--__l_o_n_go_p_t_i_o_n_2__"));
  317 +
  318 + EXPECT_TRUE(pos1->check_name("pos_option1"));
  319 + EXPECT_TRUE(pos1->check_name("pos_option_1"));
  320 + EXPECT_TRUE(pos1->check_name("pos_o_p_t_i_on_1"));
  321 + EXPECT_TRUE(pos1->check_name("posoption1"));
  322 +
  323 + EXPECT_TRUE(pos2->check_name("pos_option2"));
  324 + EXPECT_TRUE(pos2->check_name("pos_option_2"));
  325 + EXPECT_TRUE(pos2->check_name("pos_o_p_t_i_on_2"));
  326 + EXPECT_TRUE(pos2->check_name("posoption2"));
  327 +}
  328 +
  329 +TEST_F(TApp, CheckNameNoCaseNoUnderscore) {
  330 + auto long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
  331 + auto long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
  332 +
  333 + int x, y;
  334 + auto pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
  335 + auto pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
  336 +
  337 + EXPECT_TRUE(long1->check_name("--Long_Option1"));
  338 + EXPECT_TRUE(long1->check_name("--lONgoption_1"));
  339 + EXPECT_TRUE(long1->check_name("--LongOption1"));
  340 + EXPECT_TRUE(long1->check_name("--long__Opt_ion__1"));
  341 + EXPECT_TRUE(long1->check_name("--__l_o_N_g_o_P_t_i_O_n_1"));
  342 +
  343 + EXPECT_TRUE(long2->check_name("--long_Option2"));
  344 + EXPECT_TRUE(long2->check_name("--LongOption2"));
  345 + EXPECT_TRUE(long2->check_name("--longOPTION_2"));
  346 + EXPECT_TRUE(long2->check_name("--long__OPT_ion__2"));
  347 + EXPECT_TRUE(long2->check_name("--__l_o_n_GO_p_t_i_o_n_2__"));
  348 +
  349 + EXPECT_TRUE(pos1->check_name("POS_Option1"));
  350 + EXPECT_TRUE(pos1->check_name("pos_option_1"));
  351 + EXPECT_TRUE(pos1->check_name("pos_o_p_t_i_on_1"));
  352 + EXPECT_TRUE(pos1->check_name("posoption1"));
  353 +
  354 + EXPECT_TRUE(pos2->check_name("pos_option2"));
  355 + EXPECT_TRUE(pos2->check_name("pos_OPTION_2"));
  356 + EXPECT_TRUE(pos2->check_name("poS_o_p_T_I_on_2"));
  357 + EXPECT_TRUE(pos2->check_name("PosOption2"));
  358 +}
  359 +
265 TEST_F(TApp, PreSpaces) { 360 TEST_F(TApp, PreSpaces) {
266 int x; 361 int x;
267 auto myapp = app.add_option(" -a, --long, other", x); 362 auto myapp = app.add_option(" -a, --long, other", x);
@@ -301,6 +396,12 @@ TEST_F(TApp, OptionFromDefaults) { @@ -301,6 +396,12 @@ TEST_F(TApp, OptionFromDefaults) {
301 auto opt3 = app.add_option("--simple3", x); 396 auto opt3 = app.add_option("--simple3", x);
302 EXPECT_TRUE(opt3->get_required()); 397 EXPECT_TRUE(opt3->get_required());
303 EXPECT_TRUE(opt3->get_ignore_case()); 398 EXPECT_TRUE(opt3->get_ignore_case());
  399 +
  400 + app.option_defaults()->required()->ignore_underscore();
  401 +
  402 + auto opt4 = app.add_option("--simple4", x);
  403 + EXPECT_TRUE(opt4->get_required());
  404 + EXPECT_TRUE(opt4->get_ignore_underscore());
304 } 405 }
305 406
306 TEST_F(TApp, OptionFromDefaultsSubcommands) { 407 TEST_F(TApp, OptionFromDefaultsSubcommands) {
@@ -308,6 +409,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -308,6 +409,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
308 EXPECT_FALSE(app.option_defaults()->get_required()); 409 EXPECT_FALSE(app.option_defaults()->get_required());
309 EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw); 410 EXPECT_EQ(app.option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::Throw);
310 EXPECT_FALSE(app.option_defaults()->get_ignore_case()); 411 EXPECT_FALSE(app.option_defaults()->get_ignore_case());
  412 + EXPECT_FALSE(app.option_defaults()->get_ignore_underscore());
311 EXPECT_TRUE(app.option_defaults()->get_configurable()); 413 EXPECT_TRUE(app.option_defaults()->get_configurable());
312 EXPECT_EQ(app.option_defaults()->get_group(), "Options"); 414 EXPECT_EQ(app.option_defaults()->get_group(), "Options");
313 415
@@ -315,6 +417,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -315,6 +417,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
315 ->required() 417 ->required()
316 ->multi_option_policy(CLI::MultiOptionPolicy::TakeLast) 418 ->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)
317 ->ignore_case() 419 ->ignore_case()
  420 + ->ignore_underscore()
318 ->configurable(false) 421 ->configurable(false)
319 ->group("Something"); 422 ->group("Something");
320 423
@@ -323,6 +426,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) { @@ -323,6 +426,7 @@ TEST_F(TApp, OptionFromDefaultsSubcommands) {
323 EXPECT_TRUE(app2->option_defaults()->get_required()); 426 EXPECT_TRUE(app2->option_defaults()->get_required());
324 EXPECT_EQ(app2->option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::TakeLast); 427 EXPECT_EQ(app2->option_defaults()->get_multi_option_policy(), CLI::MultiOptionPolicy::TakeLast);
325 EXPECT_TRUE(app2->option_defaults()->get_ignore_case()); 428 EXPECT_TRUE(app2->option_defaults()->get_ignore_case());
  429 + EXPECT_TRUE(app2->option_defaults()->get_ignore_underscore());
326 EXPECT_FALSE(app2->option_defaults()->get_configurable()); 430 EXPECT_FALSE(app2->option_defaults()->get_configurable());
327 EXPECT_EQ(app2->option_defaults()->get_group(), "Something"); 431 EXPECT_EQ(app2->option_defaults()->get_group(), "Something");
328 } 432 }
@@ -352,6 +456,7 @@ TEST_F(TApp, SubcommandDefaults) { @@ -352,6 +456,7 @@ TEST_F(TApp, SubcommandDefaults) {
352 EXPECT_FALSE(app.get_allow_extras()); 456 EXPECT_FALSE(app.get_allow_extras());
353 EXPECT_FALSE(app.get_prefix_command()); 457 EXPECT_FALSE(app.get_prefix_command());
354 EXPECT_FALSE(app.get_ignore_case()); 458 EXPECT_FALSE(app.get_ignore_case());
  459 + EXPECT_FALSE(app.get_ignore_underscore());
355 EXPECT_FALSE(app.get_fallthrough()); 460 EXPECT_FALSE(app.get_fallthrough());
356 EXPECT_EQ(app.get_footer(), ""); 461 EXPECT_EQ(app.get_footer(), "");
357 EXPECT_EQ(app.get_group(), "Subcommands"); 462 EXPECT_EQ(app.get_group(), "Subcommands");
@@ -361,6 +466,7 @@ TEST_F(TApp, SubcommandDefaults) { @@ -361,6 +466,7 @@ TEST_F(TApp, SubcommandDefaults) {
361 app.allow_extras(); 466 app.allow_extras();
362 app.prefix_command(); 467 app.prefix_command();
363 app.ignore_case(); 468 app.ignore_case();
  469 + app.ignore_underscore();
364 app.fallthrough(); 470 app.fallthrough();
365 app.footer("footy"); 471 app.footer("footy");
366 app.group("Stuff"); 472 app.group("Stuff");
@@ -372,6 +478,7 @@ TEST_F(TApp, SubcommandDefaults) { @@ -372,6 +478,7 @@ TEST_F(TApp, SubcommandDefaults) {
372 EXPECT_TRUE(app2->get_allow_extras()); 478 EXPECT_TRUE(app2->get_allow_extras());
373 EXPECT_TRUE(app2->get_prefix_command()); 479 EXPECT_TRUE(app2->get_prefix_command());
374 EXPECT_TRUE(app2->get_ignore_case()); 480 EXPECT_TRUE(app2->get_ignore_case());
  481 + EXPECT_TRUE(app2->get_ignore_underscore());
375 EXPECT_TRUE(app2->get_fallthrough()); 482 EXPECT_TRUE(app2->get_fallthrough());
376 EXPECT_EQ(app2->get_footer(), "footy"); 483 EXPECT_EQ(app2->get_footer(), "footy");
377 EXPECT_EQ(app2->get_group(), "Stuff"); 484 EXPECT_EQ(app2->get_group(), "Stuff");
tests/SubcommandTest.cpp
@@ -530,6 +530,43 @@ TEST_F(TApp, SubcomInheritCaseCheck) { @@ -530,6 +530,43 @@ TEST_F(TApp, SubcomInheritCaseCheck) {
530 EXPECT_EQ(sub2, app.get_subcommands().at(0)); 530 EXPECT_EQ(sub2, app.get_subcommands().at(0));
531 } 531 }
532 532
  533 +TEST_F(SubcommandProgram, UnderscoreCheck) {
  534 + args = {"start_"};
  535 + EXPECT_THROW(run(), CLI::ExtrasError);
  536 +
  537 + args = {"start"};
  538 + run();
  539 +
  540 + start->ignore_underscore();
  541 + run();
  542 +
  543 + args = {"_start_"};
  544 + run();
  545 +}
  546 +
  547 +TEST_F(TApp, SubcomInheritUnderscoreCheck) {
  548 + app.ignore_underscore();
  549 + auto sub1 = app.add_subcommand("sub_option1");
  550 + auto sub2 = app.add_subcommand("sub_option2");
  551 +
  552 + run();
  553 + EXPECT_EQ((size_t)0, app.get_subcommands().size());
  554 + EXPECT_EQ((size_t)2, app.get_subcommands({}).size());
  555 + EXPECT_EQ((size_t)1, app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub_option1"; }).size());
  556 +
  557 + args = {"suboption1"};
  558 + run();
  559 + EXPECT_EQ(sub1, app.get_subcommands().at(0));
  560 + EXPECT_EQ((size_t)1, app.get_subcommands().size());
  561 +
  562 + app.clear();
  563 + EXPECT_EQ((size_t)0, app.get_subcommands().size());
  564 +
  565 + args = {"_suboption2"};
  566 + run();
  567 + EXPECT_EQ(sub2, app.get_subcommands().at(0));
  568 +}
  569 +
533 TEST_F(SubcommandProgram, HelpOrder) { 570 TEST_F(SubcommandProgram, HelpOrder) {
534 571
535 args = {"-h"}; 572 args = {"-h"};