Commit d46c2c5727c645b132a1dd109481abbe54280509

Authored by Henry Fredrick Schreiner
Committed by Henry Schreiner
1 parent 0d9a33d4

Adding Config class

CHANGELOG.md
1 ## Version 1.6: Formatting help 1 ## Version 1.6: Formatting help
2 2
3 -Added a new formatting system [#109]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating all formatting code. 3 +Added a new formatting system [#109]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code.
4 4
5 * Added `CLI::Formatter` and `formatter` slot for apps, inherited. 5 * Added `CLI::Formatter` and `formatter` slot for apps, inherited.
  6 +* `FormatterBase` is the minimum required
  7 +* `FormatterLambda` provides for the easy addition of an arbitrary function
6 * Added `help_all` support (not added by default) 8 * Added `help_all` support (not added by default)
7 -* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering  
8 -* Added `get_groups()` to get groups  
9 -* Added getters for the missing parts of options (help no longer uses any private parts)  
10 9
11 Changes to the help system (most normal users will not notice this): 10 Changes to the help system (most normal users will not notice this):
12 11
@@ -17,6 +16,17 @@ Changes to the help system (most normal users will not notice this): @@ -17,6 +16,17 @@ Changes to the help system (most normal users will not notice this):
17 * Protected function `_has_help_positional` removed 16 * Protected function `_has_help_positional` removed
18 * `format_help` can now be chained 17 * `format_help` can now be chained
19 18
  19 +
  20 +New for Config file reading and writing [#121]:
  21 +
  22 +* Overridable, bidirectional Config
  23 +* ConfigINI provided and used by default
  24 +* Renamed ini to config in many places
  25 +* Has `config_formatter()` and `get_config_formatter()`
  26 +* Dropped prefix argument from `config_to_str`
  27 +* Added `ConfigItem`
  28 +
  29 +
20 Validators are now much more powerful [#118], all built in validators upgraded to the new form: 30 Validators are now much more powerful [#118], all built in validators upgraded to the new form:
21 31
22 * A subclass of `CLI::Validator` is now also accepted. 32 * A subclass of `CLI::Validator` is now also accepted.
@@ -26,6 +36,9 @@ Validators are now much more powerful [#118], all built in validators upgraded t @@ -26,6 +36,9 @@ Validators are now much more powerful [#118], all built in validators upgraded t
26 36
27 Other changes: 37 Other changes:
28 38
  39 +* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering
  40 +* Added `get_groups()` to get groups
  41 +* Added getters for the missing parts of options (help no longer uses any private parts)
29 * Better support for manual options with `get_option`, `set_results`, and `empty` [#119] 42 * Better support for manual options with `get_option`, `set_results`, and `empty` [#119]
30 * `lname` and `sname` have getters, added `const get_parent` [#120] 43 * `lname` and `sname` have getters, added `const get_parent` [#120]
31 * Using `add_set` will now capture L-values for sets, allowing further modification [#113] 44 * Using `add_set` will now capture L-values for sets, allowing further modification [#113]
@@ -35,6 +48,7 @@ Other changes: @@ -35,6 +48,7 @@ Other changes:
35 * Removed `requires` in favor of `needs` (deprecated in last version) [#112] 48 * Removed `requires` in favor of `needs` (deprecated in last version) [#112]
36 * Better CMake policy handling [#110] 49 * Better CMake policy handling [#110]
37 * Includes are properly sorted [#120] 50 * Includes are properly sorted [#120]
  51 +* Help flags now use new `short_circuit` property to simplify parsing [#121]
38 52
39 [#109]: https://github.com/CLIUtils/CLI11/pull/109 53 [#109]: https://github.com/CLIUtils/CLI11/pull/109
40 [#110]: https://github.com/CLIUtils/CLI11/pull/110 54 [#110]: https://github.com/CLIUtils/CLI11/pull/110
@@ -45,6 +59,7 @@ Other changes: @@ -45,6 +59,7 @@ Other changes:
45 [#118]: https://github.com/CLIUtils/CLI11/pull/118 59 [#118]: https://github.com/CLIUtils/CLI11/pull/118
46 [#119]: https://github.com/CLIUtils/CLI11/pull/119 60 [#119]: https://github.com/CLIUtils/CLI11/pull/119
47 [#120]: https://github.com/CLIUtils/CLI11/pull/120 61 [#120]: https://github.com/CLIUtils/CLI11/pull/120
  62 +[#121]: https://github.com/CLIUtils/CLI11/pull/121
48 63
49 ### Version 1.5.3: Compiler compatibility 64 ### Version 1.5.3: Compiler compatibility
50 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. 65 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.
README.md
@@ -192,7 +192,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t @@ -192,7 +192,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
192 * `->check(CLI::NonexistentPath)`: Requires that the path does not exist. 192 * `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
193 * `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. 193 * `->check(CLI::Range(min,max))`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
194 * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options. 194 * `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
195 -* `->configurable(false)`: Disable this option from being in an ini configuration file. 195 +* `->configurable(false)`: Disable this option from being in a configuration file.
196 196
197 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort). 197 These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. Validate can also be a subclass of `CLI::Validator`, in which case it can also set the type name and can be combined with `&` and `|` (all built-in validators are this sort).
198 198
@@ -272,7 +272,7 @@ app.set_config(option_name=&quot;&quot;, @@ -272,7 +272,7 @@ app.set_config(option_name=&quot;&quot;,
272 required=false) 272 required=false)
273 ``` 273 ```
274 274
275 -If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format. An example of a file: 275 +If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format by default (other formats can be added by an adept user). An example of a file:
276 276
277 ```ini 277 ```ini
278 ; Commments are supported, using a ; 278 ; Commments are supported, using a ;
examples/formatter.cpp
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 class MyFormatter : public CLI::Formatter { 3 class MyFormatter : public CLI::Formatter {
4 public: 4 public:
5 - using Formatter::Formatter; 5 + MyFormatter() : Formatter() {}
6 std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; } 6 std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; }
7 }; 7 };
8 8
include/CLI/App.hpp
@@ -17,9 +17,9 @@ @@ -17,9 +17,9 @@
17 #include <vector> 17 #include <vector>
18 18
19 // CLI Library includes 19 // CLI Library includes
  20 +#include "CLI/ConfigFwd.hpp"
20 #include "CLI/Error.hpp" 21 #include "CLI/Error.hpp"
21 #include "CLI/FormatterFwd.hpp" 22 #include "CLI/FormatterFwd.hpp"
22 -#include "CLI/Ini.hpp"  
23 #include "CLI/Macros.hpp" 23 #include "CLI/Macros.hpp"
24 #include "CLI/Option.hpp" 24 #include "CLI/Option.hpp"
25 #include "CLI/Split.hpp" 25 #include "CLI/Split.hpp"
@@ -75,7 +75,7 @@ class App { @@ -75,7 +75,7 @@ class App {
75 bool allow_extras_{false}; 75 bool allow_extras_{false};
76 76
77 /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE 77 /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
78 - bool allow_ini_extras_{false}; 78 + bool allow_config_extras_{false};
79 79
80 /// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE 80 /// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE
81 bool prefix_command_{false}; 81 bool prefix_command_{false};
@@ -170,6 +170,9 @@ class App { @@ -170,6 +170,9 @@ class App {
170 /// Pointer to the config option 170 /// Pointer to the config option
171 Option *config_ptr_{nullptr}; 171 Option *config_ptr_{nullptr};
172 172
  173 + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
  174 + std::shared_ptr<Config> config_formatter_{new ConfigINI()};
  175 +
173 ///@} 176 ///@}
174 177
175 /// Special private constructor for subcommand 178 /// Special private constructor for subcommand
@@ -189,13 +192,14 @@ class App { @@ -189,13 +192,14 @@ class App {
189 // INHERITABLE 192 // INHERITABLE
190 failure_message_ = parent_->failure_message_; 193 failure_message_ = parent_->failure_message_;
191 allow_extras_ = parent_->allow_extras_; 194 allow_extras_ = parent_->allow_extras_;
192 - allow_ini_extras_ = parent_->allow_ini_extras_; 195 + allow_config_extras_ = parent_->allow_config_extras_;
193 prefix_command_ = parent_->prefix_command_; 196 prefix_command_ = parent_->prefix_command_;
194 ignore_case_ = parent_->ignore_case_; 197 ignore_case_ = parent_->ignore_case_;
195 fallthrough_ = parent_->fallthrough_; 198 fallthrough_ = parent_->fallthrough_;
196 group_ = parent_->group_; 199 group_ = parent_->group_;
197 footer_ = parent_->footer_; 200 footer_ = parent_->footer_;
198 formatter_ = parent_->formatter_; 201 formatter_ = parent_->formatter_;
  202 + config_formatter_ = parent_->config_formatter_;
199 require_subcommand_max_ = parent_->require_subcommand_max_; 203 require_subcommand_max_ = parent_->require_subcommand_max_;
200 } 204 }
201 } 205 }
@@ -237,9 +241,9 @@ class App { @@ -237,9 +241,9 @@ class App {
237 241
238 /// Remove the error when extras are left over on the command line. 242 /// Remove the error when extras are left over on the command line.
239 /// Will also call App::allow_extras(). 243 /// Will also call App::allow_extras().
240 - App *allow_ini_extras(bool allow = true) { 244 + App *allow_config_extras(bool allow = true) {
241 allow_extras(allow); 245 allow_extras(allow);
242 - allow_ini_extras_ = allow; 246 + allow_config_extras_ = allow;
243 return this; 247 return this;
244 } 248 }
245 249
@@ -268,11 +272,17 @@ class App { @@ -268,11 +272,17 @@ class App {
268 } 272 }
269 273
270 /// Set the help formatter 274 /// Set the help formatter
271 - App *formatter(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) { 275 + App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
272 formatter_ = std::make_shared<FormatterLambda>(fmt); 276 formatter_ = std::make_shared<FormatterLambda>(fmt);
273 return this; 277 return this;
274 } 278 }
275 279
  280 + /// Set the config formatter
  281 + App *config_formatter(std::shared_ptr<Config> fmt) {
  282 + config_formatter_ = fmt;
  283 + return this;
  284 + }
  285 +
276 /// Check to see if this subcommand was parsed, true only if received on command line. 286 /// Check to see if this subcommand was parsed, true only if received on command line.
277 bool parsed() const { return parsed_; } 287 bool parsed() const { return parsed_; }
278 288
@@ -1015,53 +1025,8 @@ class App { @@ -1015,53 +1025,8 @@ class App {
1015 1025
1016 /// Produce a string that could be read in as a config of the current values of the App. Set default_also to include 1026 /// Produce a string that could be read in as a config of the current values of the App. Set default_also to include
1017 /// default arguments. Prefix will add a string to the beginning of each option. 1027 /// default arguments. Prefix will add a string to the beginning of each option.
1018 - std::string  
1019 - config_to_str(bool default_also = false, std::string prefix = "", bool write_description = false) const {  
1020 - std::stringstream out;  
1021 - for(const Option_p &opt : options_) {  
1022 -  
1023 - // Only process option with a long-name and configurable  
1024 - if(!opt->get_lnames().empty() && opt->get_configurable()) {  
1025 - std::string name = prefix + opt->get_lnames()[0];  
1026 - std::string value;  
1027 -  
1028 - // Non-flags  
1029 - if(opt->get_type_size() != 0) {  
1030 -  
1031 - // If the option was found on command line  
1032 - if(opt->count() > 0)  
1033 - value = detail::inijoin(opt->results());  
1034 -  
1035 - // If the option has a default and is requested by optional argument  
1036 - else if(default_also && !opt->get_defaultval().empty())  
1037 - value = opt->get_defaultval();  
1038 - // Flag, one passed  
1039 - } else if(opt->count() == 1) {  
1040 - value = "true";  
1041 -  
1042 - // Flag, multiple passed  
1043 - } else if(opt->count() > 1) {  
1044 - value = std::to_string(opt->count());  
1045 -  
1046 - // Flag, not present  
1047 - } else if(opt->count() == 0 && default_also) {  
1048 - value = "false";  
1049 - }  
1050 -  
1051 - if(!value.empty()) {  
1052 - if(write_description && opt->has_description()) {  
1053 - if(static_cast<int>(out.tellp()) != 0) {  
1054 - out << std::endl;  
1055 - }  
1056 - out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl;  
1057 - }  
1058 - out << name << "=" << value << std::endl;  
1059 - }  
1060 - }  
1061 - }  
1062 - for(const App_p &subcom : subcommands_)  
1063 - out << subcom->config_to_str(default_also, prefix + subcom->name_ + ".");  
1064 - return out.str(); 1028 + std::string config_to_str(bool default_also = false, bool write_description = false) const {
  1029 + return config_formatter_->to_config(this, default_also, write_description, "");
1065 } 1030 }
1066 1031
1067 /// Makes a help message, using the currently configured formatter 1032 /// Makes a help message, using the currently configured formatter
@@ -1087,6 +1052,9 @@ class App { @@ -1087,6 +1052,9 @@ class App {
1087 /// Access the formatter 1052 /// Access the formatter
1088 std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; } 1053 std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
1089 1054
  1055 + /// Access the config formatter
  1056 + std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
  1057 +
1090 /// Get the app or subcommand description 1058 /// Get the app or subcommand description
1091 std::string get_description() const { return description_; } 1059 std::string get_description() const { return description_; }
1092 1060
@@ -1117,6 +1085,16 @@ class App { @@ -1117,6 +1085,16 @@ class App {
1117 throw OptionNotFound(name); 1085 throw OptionNotFound(name);
1118 } 1086 }
1119 1087
  1088 + /// Get an option by name (non-const version)
  1089 + Option *get_option(std::string name) {
  1090 + for(Option_p &opt : options_) {
  1091 + if(opt->check_name(name)) {
  1092 + return opt.get();
  1093 + }
  1094 + }
  1095 + throw OptionNotFound(name);
  1096 + }
  1097 +
1120 /// Check the status of ignore_case 1098 /// Check the status of ignore_case
1121 bool get_ignore_case() const { return ignore_case_; } 1099 bool get_ignore_case() const { return ignore_case_; }
1122 1100
@@ -1142,7 +1120,7 @@ class App { @@ -1142,7 +1120,7 @@ class App {
1142 bool get_allow_extras() const { return allow_extras_; } 1120 bool get_allow_extras() const { return allow_extras_; }
1143 1121
1144 /// Get the status of allow extras 1122 /// Get the status of allow extras
1145 - bool get_allow_ini_extras() const { return allow_ini_extras_; } 1123 + bool get_allow_config_extras() const { return allow_config_extras_; }
1146 1124
1147 /// Get a pointer to the help flag. 1125 /// Get a pointer to the help flag.
1148 Option *get_help_ptr() { return help_ptr_; } 1126 Option *get_help_ptr() { return help_ptr_; }
@@ -1156,15 +1134,15 @@ class App { @@ -1156,15 +1134,15 @@ class App {
1156 /// Get a pointer to the config option. 1134 /// Get a pointer to the config option.
1157 Option *get_config_ptr() { return config_ptr_; } 1135 Option *get_config_ptr() { return config_ptr_; }
1158 1136
  1137 + /// Get a pointer to the config option. (const)
  1138 + const Option *get_config_ptr() const { return config_ptr_; }
  1139 +
1159 /// Get the parent of this subcommand (or nullptr if master app) 1140 /// Get the parent of this subcommand (or nullptr if master app)
1160 App *get_parent() { return parent_; } 1141 App *get_parent() { return parent_; }
1161 1142
1162 /// Get the parent of this subcommand (or nullptr if master app) (const version) 1143 /// Get the parent of this subcommand (or nullptr if master app) (const version)
1163 const App *get_parent() const { return parent_; } 1144 const App *get_parent() const { return parent_; }
1164 1145
1165 - /// Get a pointer to the config option. (const)  
1166 - const Option *get_config_ptr() const { return config_ptr_; }  
1167 -  
1168 /// Get the name of the current app 1146 /// Get the name of the current app
1169 std::string get_name() const { return name_; } 1147 std::string get_name() const { return name_; }
1170 1148
@@ -1304,12 +1282,8 @@ class App { @@ -1304,12 +1282,8 @@ class App {
1304 } 1282 }
1305 if(!config_name_.empty()) { 1283 if(!config_name_.empty()) {
1306 try { 1284 try {
1307 - std::vector<detail::ini_ret_t> values = detail::parse_ini(config_name_);  
1308 - while(!values.empty()) {  
1309 - if(!_parse_ini(values)) {  
1310 - throw INIError::Extras(values.back().fullname);  
1311 - }  
1312 - } 1285 + std::vector<ConfigItem> values = config_formatter_->from_file(config_name_);
  1286 + _parse_config(values);
1313 } catch(const FileError &) { 1287 } catch(const FileError &) {
1314 if(config_required_) 1288 if(config_required_)
1315 throw; 1289 throw;
@@ -1387,70 +1361,54 @@ class App { @@ -1387,70 +1361,54 @@ class App {
1387 } 1361 }
1388 } 1362 }
1389 1363
1390 - /// Parse one ini param, return false if not found in any subcommand, remove if it is 1364 + /// Parse one config param, return false if not found in any subcommand, remove if it is
1391 /// 1365 ///
1392 /// If this has more than one dot.separated.name, go into the subcommand matching it 1366 /// If this has more than one dot.separated.name, go into the subcommand matching it
1393 /// Returns true if it managed to find the option, if false you'll need to remove the arg manually. 1367 /// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
1394 - bool _parse_ini(std::vector<detail::ini_ret_t> &args) {  
1395 - detail::ini_ret_t &current = args.back();  
1396 - std::string parent = current.parent(); // respects current.level  
1397 - std::string name = current.name();  
1398 -  
1399 - // If a parent is listed, go to a subcommand  
1400 - if(!parent.empty()) {  
1401 - current.level++;  
1402 - for(const App_p &com : subcommands_)  
1403 - if(com->check_name(parent))  
1404 - return com->_parse_ini(args);  
1405 - return false; 1368 + void _parse_config(std::vector<ConfigItem> &args) {
  1369 + for(ConfigItem item : args) {
  1370 + if(!_parse_single_config(item) && !allow_config_extras_)
  1371 + throw ConfigError::Extras(item.fullname());
1406 } 1372 }
  1373 + }
1407 1374
1408 - auto op_ptr = std::find_if(  
1409 - std::begin(options_), std::end(options_), [name](const Option_p &v) { return v->check_lname(name); }); 1375 + /// Fill in a single config option
  1376 + bool _parse_single_config(const ConfigItem &item, size_t level = 0) {
  1377 + if(level < item.parents.size()) {
  1378 + App *subcom;
  1379 + try {
  1380 + std::cout << item.parents.at(level) << std::endl;
  1381 + subcom = get_subcommand(item.parents.at(level));
  1382 + } catch(const OptionNotFound &) {
  1383 + return false;
  1384 + }
  1385 + return subcom->_parse_single_config(item, level + 1);
  1386 + }
1410 1387
1411 - if(op_ptr == std::end(options_)) {  
1412 - if(allow_ini_extras_) { 1388 + Option *op;
  1389 + try {
  1390 + op = get_option("--" + item.name);
  1391 + } catch(const OptionNotFound &) {
  1392 + // If the option was not present
  1393 + if(get_allow_config_extras())
1413 // Should we worry about classifying the extras properly? 1394 // Should we worry about classifying the extras properly?
1414 - missing_.emplace_back(detail::Classifer::NONE, current.fullname);  
1415 - args.pop_back();  
1416 - return true;  
1417 - } 1395 + missing_.emplace_back(detail::Classifer::NONE, item.fullname());
1418 return false; 1396 return false;
1419 } 1397 }
1420 1398
1421 - // Let's not go crazy with pointer syntax  
1422 - Option_p &op = *op_ptr;  
1423 -  
1424 if(!op->get_configurable()) 1399 if(!op->get_configurable())
1425 - throw INIError::NotConfigurable(current.fullname); 1400 + throw ConfigError::NotConfigurable(item.fullname());
1426 1401
1427 if(op->empty()) { 1402 if(op->empty()) {
1428 // Flag parsing 1403 // Flag parsing
1429 if(op->get_type_size() == 0) { 1404 if(op->get_type_size() == 0) {
1430 - if(current.inputs.size() == 1) {  
1431 - std::string val = current.inputs.at(0);  
1432 - val = detail::to_lower(val);  
1433 - if(val == "true" || val == "on" || val == "yes")  
1434 - op->set_results({""});  
1435 - else if(val == "false" || val == "off" || val == "no")  
1436 - ;  
1437 - else  
1438 - try {  
1439 - size_t ui = std::stoul(val);  
1440 - for(size_t i = 0; i < ui; i++)  
1441 - op->add_result("");  
1442 - } catch(const std::invalid_argument &) {  
1443 - throw ConversionError::TrueFalse(current.fullname);  
1444 - }  
1445 - } else  
1446 - throw ConversionError::TooManyInputsFlag(current.fullname); 1405 + op->set_results(config_formatter_->to_flag(item));
1447 } else { 1406 } else {
1448 - op->set_results(current.inputs); 1407 + op->set_results(item.inputs);
1449 op->run_callback(); 1408 op->run_callback();
1450 } 1409 }
1451 } 1410 }
1452 1411
1453 - args.pop_back();  
1454 return true; 1412 return true;
1455 } 1413 }
1456 1414
include/CLI/CLI.hpp
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 20
21 #include "CLI/Split.hpp" 21 #include "CLI/Split.hpp"
22 22
23 -#include "CLI/Ini.hpp" 23 +#include "CLI/ConfigFwd.hpp"
24 24
25 #include "CLI/Validators.hpp" 25 #include "CLI/Validators.hpp"
26 26
@@ -30,4 +30,6 @@ @@ -30,4 +30,6 @@
30 30
31 #include "CLI/App.hpp" 31 #include "CLI/App.hpp"
32 32
  33 +#include "CLI/Config.hpp"
  34 +
33 #include "CLI/Formatter.hpp" 35 #include "CLI/Formatter.hpp"
include/CLI/Config.hpp 0 โ†’ 100644
  1 +#pragma once
  2 +
  3 +// Distributed under the 3-Clause BSD License. See accompanying
  4 +// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
  5 +
  6 +#include <algorithm>
  7 +#include <fstream>
  8 +#include <iostream>
  9 +#include <string>
  10 +
  11 +#include "CLI/App.hpp"
  12 +#include "CLI/ConfigFwd.hpp"
  13 +#include "CLI/StringTools.hpp"
  14 +
  15 +namespace CLI {
  16 +
  17 +inline std::string
  18 +ConfigINI::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
  19 + std::stringstream out;
  20 + for(const Option *opt : app->get_options({})) {
  21 +
  22 + // Only process option with a long-name and configurable
  23 + if(!opt->get_lnames().empty() && opt->get_configurable()) {
  24 + std::string name = prefix + opt->get_lnames()[0];
  25 + std::string value;
  26 +
  27 + // Non-flags
  28 + if(opt->get_type_size() != 0) {
  29 +
  30 + // If the option was found on command line
  31 + if(opt->count() > 0)
  32 + value = detail::inijoin(opt->results());
  33 +
  34 + // If the option has a default and is requested by optional argument
  35 + else if(default_also && !opt->get_defaultval().empty())
  36 + value = opt->get_defaultval();
  37 + // Flag, one passed
  38 + } else if(opt->count() == 1) {
  39 + value = "true";
  40 +
  41 + // Flag, multiple passed
  42 + } else if(opt->count() > 1) {
  43 + value = std::to_string(opt->count());
  44 +
  45 + // Flag, not present
  46 + } else if(opt->count() == 0 && default_also) {
  47 + value = "false";
  48 + }
  49 +
  50 + if(!value.empty()) {
  51 + if(write_description && opt->has_description()) {
  52 + if(static_cast<int>(out.tellp()) != 0) {
  53 + out << std::endl;
  54 + }
  55 + out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl;
  56 + }
  57 + out << name << "=" << value << std::endl;
  58 + }
  59 + }
  60 + }
  61 +
  62 + for(const App *subcom : app->get_subcommands({}))
  63 + out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
  64 +
  65 + return out.str();
  66 +}
  67 +
  68 +} // namespace CLI
include/CLI/Ini.hpp renamed to include/CLI/ConfigFwd.hpp
@@ -11,6 +11,9 @@ @@ -11,6 +11,9 @@
11 #include "CLI/StringTools.hpp" 11 #include "CLI/StringTools.hpp"
12 12
13 namespace CLI { 13 namespace CLI {
  14 +
  15 +class App;
  16 +
14 namespace detail { 17 namespace detail {
15 18
16 inline std::string inijoin(std::vector<std::string> args) { 19 inline std::string inijoin(std::vector<std::string> args) {
@@ -32,84 +35,122 @@ inline std::string inijoin(std::vector&lt;std::string&gt; args) { @@ -32,84 +35,122 @@ inline std::string inijoin(std::vector&lt;std::string&gt; args) {
32 return s.str(); 35 return s.str();
33 } 36 }
34 37
35 -struct ini_ret_t {  
36 - /// This is the full name with dots  
37 - std::string fullname; 38 +} // namespace detail
  39 +
  40 +struct ConfigItem {
  41 + /// This is the list of parents
  42 + std::vector<std::string> parents;
  43 +
  44 + /// This is the name
  45 + std::string name;
38 46
39 /// Listing of inputs 47 /// Listing of inputs
40 std::vector<std::string> inputs; 48 std::vector<std::string> inputs;
41 49
42 - /// Current parent level  
43 - size_t level = 0;  
44 -  
45 - /// Return parent or empty string, based on level  
46 - ///  
47 - /// Level 0, a.b.c would return a  
48 - /// Level 1, a.b.c could return b  
49 - std::string parent() const {  
50 - std::vector<std::string> plist = detail::split(fullname, '.');  
51 - if(plist.size() > (level + 1))  
52 - return plist[level];  
53 - else  
54 - return ""; 50 + /// The list of parents and name joined by "."
  51 + std::string fullname() const {
  52 + std::vector<std::string> tmp = parents;
  53 + tmp.emplace_back(name);
  54 + return detail::join(tmp, ".");
55 } 55 }
  56 +};
56 57
57 - /// Return name  
58 - std::string name() const {  
59 - std::vector<std::string> plist = detail::split(fullname, '.');  
60 - return plist.at(plist.size() - 1); 58 +/// This class provides a converter for configuration files.
  59 +class Config {
  60 + protected:
  61 + std::vector<ConfigItem> items;
  62 +
  63 + public:
  64 + /// Convert an app into a configuration
  65 + virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
  66 +
  67 + /// Convert a configuration into an app
  68 + virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
  69 +
  70 + /// Convert a flag to a bool
  71 + virtual std::vector<std::string> to_flag(const ConfigItem &) const = 0;
  72 +
  73 + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
  74 + std::vector<ConfigItem> from_file(const std::string &name) {
  75 + std::ifstream input{name};
  76 + if(!input.good())
  77 + throw FileError::Missing(name);
  78 +
  79 + return from_config(input);
61 } 80 }
62 }; 81 };
63 82
64 -/// Internal parsing function  
65 -inline std::vector<ini_ret_t> parse_ini(std::istream &input) {  
66 - std::string name, line;  
67 - std::string section = "default";  
68 -  
69 - std::vector<ini_ret_t> output;  
70 -  
71 - while(getline(input, line)) {  
72 - std::vector<std::string> items;  
73 -  
74 - detail::trim(line);  
75 - size_t len = line.length();  
76 - if(len > 1 && line[0] == '[' && line[len - 1] == ']') {  
77 - section = line.substr(1, len - 2);  
78 - } else if(len > 0 && line[0] != ';') {  
79 - output.emplace_back();  
80 - ini_ret_t &out = output.back();  
81 -  
82 - // Find = in string, split and recombine  
83 - auto pos = line.find("=");  
84 - if(pos != std::string::npos) {  
85 - name = detail::trim_copy(line.substr(0, pos));  
86 - std::string item = detail::trim_copy(line.substr(pos + 1));  
87 - items = detail::split_up(item);  
88 - } else {  
89 - name = detail::trim_copy(line);  
90 - items = {"ON"};  
91 - } 83 +/// This converter works with INI files
  84 +class ConfigINI : public Config {
  85 + public:
  86 + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override;
92 87
93 - if(detail::to_lower(section) == "default")  
94 - out.fullname = name;  
95 - else  
96 - out.fullname = section + "." + name; 88 + std::vector<std::string> to_flag(const ConfigItem &item) const override {
  89 + if(item.inputs.size() == 1) {
  90 + std::string val = item.inputs.at(0);
  91 + val = detail::to_lower(val);
97 92
98 - out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); 93 + if(val == "true" || val == "on" || val == "yes") {
  94 + return std::vector<std::string>(1);
  95 + } else if(val == "false" || val == "off" || val == "no") {
  96 + return std::vector<std::string>();
  97 + } else {
  98 + try {
  99 + size_t ui = std::stoul(val);
  100 + return std::vector<std::string>(ui);
  101 + } catch(const std::invalid_argument &) {
  102 + throw ConversionError::TrueFalse(item.fullname());
  103 + }
  104 + }
  105 + } else {
  106 + throw ConversionError::TooManyInputsFlag(item.fullname());
99 } 107 }
100 } 108 }
101 - return output;  
102 -}  
103 -  
104 -/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure  
105 -inline std::vector<ini_ret_t> parse_ini(const std::string &name) {  
106 -  
107 - std::ifstream input{name};  
108 - if(!input.good())  
109 - throw FileError::Missing(name);  
110 109
111 - return parse_ini(input);  
112 -} 110 + std::vector<ConfigItem> from_config(std::istream &input) const override {
  111 + std::string line;
  112 + std::string section = "default";
  113 +
  114 + std::vector<ConfigItem> output;
  115 +
  116 + while(getline(input, line)) {
  117 + std::vector<std::string> items;
  118 +
  119 + detail::trim(line);
  120 + size_t len = line.length();
  121 + if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
  122 + section = line.substr(1, len - 2);
  123 + } else if(len > 0 && line[0] != ';') {
  124 + output.emplace_back();
  125 + ConfigItem &out = output.back();
  126 +
  127 + // Find = in string, split and recombine
  128 + auto pos = line.find('=');
  129 + if(pos != std::string::npos) {
  130 + out.name = detail::trim_copy(line.substr(0, pos));
  131 + std::string item = detail::trim_copy(line.substr(pos + 1));
  132 + items = detail::split_up(item);
  133 + } else {
  134 + out.name = detail::trim_copy(line);
  135 + items = {"ON"};
  136 + }
  137 +
  138 + if(detail::to_lower(section) != "default") {
  139 + out.parents = {section};
  140 + }
  141 +
  142 + if(out.name.find('.') != std::string::npos) {
  143 + std::vector<std::string> plist = detail::split(out.name, '.');
  144 + out.name = plist.back();
  145 + plist.pop_back();
  146 + out.parents.insert(out.parents.end(), plist.begin(), plist.end());
  147 + }
  148 +
  149 + out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
  150 + }
  151 + }
  152 + return output;
  153 + }
  154 +};
113 155
114 -} // namespace detail  
115 } // namespace CLI 156 } // namespace CLI
include/CLI/Error.hpp
@@ -42,7 +42,7 @@ enum class ExitCodes { @@ -42,7 +42,7 @@ enum class ExitCodes {
42 RequiresError, 42 RequiresError,
43 ExcludesError, 43 ExcludesError,
44 ExtrasError, 44 ExtrasError,
45 - INIError, 45 + ConfigError,
46 InvalidError, 46 InvalidError,
47 HorribleError, 47 HorribleError,
48 OptionNotFound, 48 OptionNotFound,
@@ -257,12 +257,12 @@ class ExtrasError : public ParseError { @@ -257,12 +257,12 @@ class ExtrasError : public ParseError {
257 }; 257 };
258 258
259 /// Thrown when extra values are found in an INI file 259 /// Thrown when extra values are found in an INI file
260 -class INIError : public ParseError {  
261 - CLI11_ERROR_DEF(ParseError, INIError)  
262 - CLI11_ERROR_SIMPLE(INIError)  
263 - static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); }  
264 - static INIError NotConfigurable(std::string item) {  
265 - return INIError(item + ": This option is not allowed in a configuration file"); 260 +class ConfigError : public ParseError {
  261 + CLI11_ERROR_DEF(ParseError, ConfigError)
  262 + CLI11_ERROR_SIMPLE(ConfigError)
  263 + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
  264 + static ConfigError NotConfigurable(std::string item) {
  265 + return ConfigError(item + ": This option is not allowed in a configuration file");
266 } 266 }
267 }; 267 };
268 268
include/CLI/FormatterFwd.hpp
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 5
6 #include <map> 6 #include <map>
7 #include <string> 7 #include <string>
  8 +#include <utility>
8 9
9 #include "CLI/StringTools.hpp" 10 #include "CLI/StringTools.hpp"
10 11
@@ -88,7 +89,7 @@ class FormatterLambda final : public FormatterBase { @@ -88,7 +89,7 @@ class FormatterLambda final : public FormatterBase {
88 funct_t lambda_; 89 funct_t lambda_;
89 90
90 public: 91 public:
91 - explicit FormatterLambda(funct_t funct) : lambda_(funct) {} 92 + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
92 93
93 /// This will simply call the lambda function 94 /// This will simply call the lambda function
94 std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { 95 std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
include/CLI/Option.hpp
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 #include "CLI/Macros.hpp" 16 #include "CLI/Macros.hpp"
17 #include "CLI/Split.hpp" 17 #include "CLI/Split.hpp"
18 #include "CLI/StringTools.hpp" 18 #include "CLI/StringTools.hpp"
  19 +#include "CLI/Validators.hpp"
19 20
20 namespace CLI { 21 namespace CLI {
21 22
tests/FormatterTest.cpp
@@ -33,7 +33,7 @@ TEST(Formatter, Nothing) { @@ -33,7 +33,7 @@ TEST(Formatter, Nothing) {
33 TEST(Formatter, NothingLambda) { 33 TEST(Formatter, NothingLambda) {
34 CLI::App app{"My prog"}; 34 CLI::App app{"My prog"};
35 35
36 - app.formatter( 36 + app.formatter_fn(
37 [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); }); 37 [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
38 38
39 std::string help = app.help(); 39 std::string help = app.help();
tests/HelpTest.cpp
@@ -519,7 +519,7 @@ TEST_F(CapturedHelp, CallForAllHelpOutput) { @@ -519,7 +519,7 @@ TEST_F(CapturedHelp, CallForAllHelpOutput) {
519 " --three \n"); 519 " --three \n");
520 } 520 }
521 TEST_F(CapturedHelp, NewFormattedHelp) { 521 TEST_F(CapturedHelp, NewFormattedHelp) {
522 - app.formatter([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; }); 522 + app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
523 EXPECT_EQ(run(CLI::CallForHelp()), 0); 523 EXPECT_EQ(run(CLI::CallForHelp()), 0);
524 EXPECT_EQ(out.str(), "New Help"); 524 EXPECT_EQ(out.str(), "New Help");
525 EXPECT_EQ(err.str(), ""); 525 EXPECT_EQ(err.str(), "");
tests/IniTest.cpp
@@ -15,13 +15,13 @@ TEST(StringBased, First) { @@ -15,13 +15,13 @@ TEST(StringBased, First) {
15 15
16 ofile.seekg(0, std::ios::beg); 16 ofile.seekg(0, std::ios::beg);
17 17
18 - std::vector<CLI::detail::ini_ret_t> output = CLI::detail::parse_ini(ofile); 18 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
19 19
20 EXPECT_EQ((size_t)2, output.size()); 20 EXPECT_EQ((size_t)2, output.size());
21 - EXPECT_EQ("one", output.at(0).name()); 21 + EXPECT_EQ("one", output.at(0).name);
22 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 22 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
23 EXPECT_EQ("three", output.at(0).inputs.at(0)); 23 EXPECT_EQ("three", output.at(0).inputs.at(0));
24 - EXPECT_EQ("two", output.at(1).name()); 24 + EXPECT_EQ("two", output.at(1).name);
25 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 25 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
26 EXPECT_EQ("four", output.at(1).inputs.at(0)); 26 EXPECT_EQ("four", output.at(1).inputs.at(0));
27 } 27 }
@@ -36,13 +36,13 @@ TEST(StringBased, FirstWithComments) { @@ -36,13 +36,13 @@ TEST(StringBased, FirstWithComments) {
36 36
37 ofile.seekg(0, std::ios::beg); 37 ofile.seekg(0, std::ios::beg);
38 38
39 - auto output = CLI::detail::parse_ini(ofile); 39 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
40 40
41 EXPECT_EQ((size_t)2, output.size()); 41 EXPECT_EQ((size_t)2, output.size());
42 - EXPECT_EQ("one", output.at(0).name()); 42 + EXPECT_EQ("one", output.at(0).name);
43 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 43 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
44 EXPECT_EQ("three", output.at(0).inputs.at(0)); 44 EXPECT_EQ("three", output.at(0).inputs.at(0));
45 - EXPECT_EQ("two", output.at(1).name()); 45 + EXPECT_EQ("two", output.at(1).name);
46 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 46 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
47 EXPECT_EQ("four", output.at(1).inputs.at(0)); 47 EXPECT_EQ("four", output.at(1).inputs.at(0));
48 } 48 }
@@ -56,16 +56,16 @@ TEST(StringBased, Quotes) { @@ -56,16 +56,16 @@ TEST(StringBased, Quotes) {
56 56
57 ofile.seekg(0, std::ios::beg); 57 ofile.seekg(0, std::ios::beg);
58 58
59 - auto output = CLI::detail::parse_ini(ofile); 59 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
60 60
61 EXPECT_EQ((size_t)3, output.size()); 61 EXPECT_EQ((size_t)3, output.size());
62 - EXPECT_EQ("one", output.at(0).name()); 62 + EXPECT_EQ("one", output.at(0).name);
63 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 63 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
64 EXPECT_EQ("three", output.at(0).inputs.at(0)); 64 EXPECT_EQ("three", output.at(0).inputs.at(0));
65 - EXPECT_EQ("two", output.at(1).name()); 65 + EXPECT_EQ("two", output.at(1).name);
66 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 66 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
67 EXPECT_EQ("four", output.at(1).inputs.at(0)); 67 EXPECT_EQ("four", output.at(1).inputs.at(0));
68 - EXPECT_EQ("five", output.at(2).name()); 68 + EXPECT_EQ("five", output.at(2).name);
69 EXPECT_EQ((size_t)1, output.at(2).inputs.size()); 69 EXPECT_EQ((size_t)1, output.at(2).inputs.size());
70 EXPECT_EQ("six and seven", output.at(2).inputs.at(0)); 70 EXPECT_EQ("six and seven", output.at(2).inputs.at(0));
71 } 71 }
@@ -79,16 +79,16 @@ TEST(StringBased, Vector) { @@ -79,16 +79,16 @@ TEST(StringBased, Vector) {
79 79
80 ofile.seekg(0, std::ios::beg); 80 ofile.seekg(0, std::ios::beg);
81 81
82 - auto output = CLI::detail::parse_ini(ofile); 82 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
83 83
84 EXPECT_EQ((size_t)3, output.size()); 84 EXPECT_EQ((size_t)3, output.size());
85 - EXPECT_EQ("one", output.at(0).name()); 85 + EXPECT_EQ("one", output.at(0).name);
86 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 86 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
87 EXPECT_EQ("three", output.at(0).inputs.at(0)); 87 EXPECT_EQ("three", output.at(0).inputs.at(0));
88 - EXPECT_EQ("two", output.at(1).name()); 88 + EXPECT_EQ("two", output.at(1).name);
89 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 89 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
90 EXPECT_EQ("four", output.at(1).inputs.at(0)); 90 EXPECT_EQ("four", output.at(1).inputs.at(0));
91 - EXPECT_EQ("five", output.at(2).name()); 91 + EXPECT_EQ("five", output.at(2).name);
92 EXPECT_EQ((size_t)3, output.at(2).inputs.size()); 92 EXPECT_EQ((size_t)3, output.at(2).inputs.size());
93 EXPECT_EQ("six", output.at(2).inputs.at(0)); 93 EXPECT_EQ("six", output.at(2).inputs.at(0));
94 EXPECT_EQ("and", output.at(2).inputs.at(1)); 94 EXPECT_EQ("and", output.at(2).inputs.at(1));
@@ -103,13 +103,13 @@ TEST(StringBased, Spaces) { @@ -103,13 +103,13 @@ TEST(StringBased, Spaces) {
103 103
104 ofile.seekg(0, std::ios::beg); 104 ofile.seekg(0, std::ios::beg);
105 105
106 - auto output = CLI::detail::parse_ini(ofile); 106 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
107 107
108 EXPECT_EQ((size_t)2, output.size()); 108 EXPECT_EQ((size_t)2, output.size());
109 - EXPECT_EQ("one", output.at(0).name()); 109 + EXPECT_EQ("one", output.at(0).name);
110 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 110 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
111 EXPECT_EQ("three", output.at(0).inputs.at(0)); 111 EXPECT_EQ("three", output.at(0).inputs.at(0));
112 - EXPECT_EQ("two", output.at(1).name()); 112 + EXPECT_EQ("two", output.at(1).name);
113 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 113 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
114 EXPECT_EQ("four", output.at(1).inputs.at(0)); 114 EXPECT_EQ("four", output.at(1).inputs.at(0));
115 } 115 }
@@ -123,16 +123,17 @@ TEST(StringBased, Sections) { @@ -123,16 +123,17 @@ TEST(StringBased, Sections) {
123 123
124 ofile.seekg(0, std::ios::beg); 124 ofile.seekg(0, std::ios::beg);
125 125
126 - auto output = CLI::detail::parse_ini(ofile); 126 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
127 127
128 EXPECT_EQ((size_t)2, output.size()); 128 EXPECT_EQ((size_t)2, output.size());
129 - EXPECT_EQ("one", output.at(0).name()); 129 + EXPECT_EQ("one", output.at(0).name);
130 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 130 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
131 EXPECT_EQ("three", output.at(0).inputs.at(0)); 131 EXPECT_EQ("three", output.at(0).inputs.at(0));
132 - EXPECT_EQ("two", output.at(1).name());  
133 - EXPECT_EQ("second", output.at(1).parent()); 132 + EXPECT_EQ("two", output.at(1).name);
  133 + EXPECT_EQ("second", output.at(1).parents.at(0));
134 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 134 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
135 EXPECT_EQ("four", output.at(1).inputs.at(0)); 135 EXPECT_EQ("four", output.at(1).inputs.at(0));
  136 + EXPECT_EQ("second.two", output.at(1).fullname());
136 } 137 }
137 138
138 TEST(StringBased, SpacesSections) { 139 TEST(StringBased, SpacesSections) {
@@ -146,14 +147,15 @@ TEST(StringBased, SpacesSections) { @@ -146,14 +147,15 @@ TEST(StringBased, SpacesSections) {
146 147
147 ofile.seekg(0, std::ios::beg); 148 ofile.seekg(0, std::ios::beg);
148 149
149 - auto output = CLI::detail::parse_ini(ofile); 150 + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
150 151
151 EXPECT_EQ((size_t)2, output.size()); 152 EXPECT_EQ((size_t)2, output.size());
152 - EXPECT_EQ("one", output.at(0).name()); 153 + EXPECT_EQ("one", output.at(0).name);
153 EXPECT_EQ((size_t)1, output.at(0).inputs.size()); 154 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
154 EXPECT_EQ("three", output.at(0).inputs.at(0)); 155 EXPECT_EQ("three", output.at(0).inputs.at(0));
155 - EXPECT_EQ("two", output.at(1).name());  
156 - EXPECT_EQ("second", output.at(1).parent()); 156 + EXPECT_EQ("two", output.at(1).name);
  157 + EXPECT_EQ((size_t)1, output.at(1).parents.size());
  158 + EXPECT_EQ("second", output.at(1).parents.at(0));
157 EXPECT_EQ((size_t)1, output.at(1).inputs.size()); 159 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
158 EXPECT_EQ("four", output.at(1).inputs.at(0)); 160 EXPECT_EQ("four", output.at(1).inputs.at(0));
159 } 161 }
@@ -199,7 +201,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) { @@ -199,7 +201,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
199 TempFile tmpini{"TestIniTmp.ini"}; 201 TempFile tmpini{"TestIniTmp.ini"};
200 202
201 app.set_config("--config", tmpini); 203 app.set_config("--config", tmpini);
202 - app.allow_ini_extras(true); 204 + app.allow_config_extras(true);
203 205
204 { 206 {
205 std::ofstream out{tmpini}; 207 std::ofstream out{tmpini};
@@ -209,7 +211,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) { @@ -209,7 +211,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
209 211
210 int two = 0; 212 int two = 0;
211 app.add_option("--two", two); 213 app.add_option("--two", two);
212 - EXPECT_NO_THROW(run()); 214 + run();
213 EXPECT_EQ(99, two); 215 EXPECT_EQ(99, two);
214 } 216 }
215 217
@@ -217,7 +219,7 @@ TEST_F(TApp, IniGetRemainingOption) { @@ -217,7 +219,7 @@ TEST_F(TApp, IniGetRemainingOption) {
217 TempFile tmpini{"TestIniTmp.ini"}; 219 TempFile tmpini{"TestIniTmp.ini"};
218 220
219 app.set_config("--config", tmpini); 221 app.set_config("--config", tmpini);
220 - app.allow_ini_extras(true); 222 + app.allow_config_extras(true);
221 223
222 std::string ExtraOption = "three"; 224 std::string ExtraOption = "three";
223 std::string ExtraOptionValue = "3"; 225 std::string ExtraOptionValue = "3";
@@ -238,7 +240,7 @@ TEST_F(TApp, IniGetNoRemaining) { @@ -238,7 +240,7 @@ TEST_F(TApp, IniGetNoRemaining) {
238 TempFile tmpini{"TestIniTmp.ini"}; 240 TempFile tmpini{"TestIniTmp.ini"};
239 241
240 app.set_config("--config", tmpini); 242 app.set_config("--config", tmpini);
241 - app.allow_ini_extras(true); 243 + app.allow_config_extras(true);
242 244
243 { 245 {
244 std::ofstream out{tmpini}; 246 std::ofstream out{tmpini};
@@ -413,7 +415,7 @@ TEST_F(TApp, IniLayered) { @@ -413,7 +415,7 @@ TEST_F(TApp, IniLayered) {
413 auto subsubcom = subcom->add_subcommand("subsubcom"); 415 auto subsubcom = subcom->add_subcommand("subsubcom");
414 subsubcom->add_option("--val", three); 416 subsubcom->add_option("--val", three);
415 417
416 - ASSERT_NO_THROW(run()); 418 + run();
417 419
418 EXPECT_EQ(1, one); 420 EXPECT_EQ(1, one);
419 EXPECT_EQ(2, two); 421 EXPECT_EQ(2, two);
@@ -432,7 +434,7 @@ TEST_F(TApp, IniFailure) { @@ -432,7 +434,7 @@ TEST_F(TApp, IniFailure) {
432 out << "val=1" << std::endl; 434 out << "val=1" << std::endl;
433 } 435 }
434 436
435 - EXPECT_THROW(run(), CLI::INIError); 437 + EXPECT_THROW(run(), CLI::ConfigError);
436 } 438 }
437 439
438 TEST_F(TApp, IniConfigurable) { 440 TEST_F(TApp, IniConfigurable) {
@@ -467,7 +469,7 @@ TEST_F(TApp, IniNotConfigurable) { @@ -467,7 +469,7 @@ TEST_F(TApp, IniNotConfigurable) {
467 out << "val=1" << std::endl; 469 out << "val=1" << std::endl;
468 } 470 }
469 471
470 - EXPECT_THROW(run(), CLI::INIError); 472 + EXPECT_THROW(run(), CLI::ConfigError);
471 } 473 }
472 474
473 TEST_F(TApp, IniSubFailure) { 475 TEST_F(TApp, IniSubFailure) {
@@ -483,7 +485,7 @@ TEST_F(TApp, IniSubFailure) { @@ -483,7 +485,7 @@ TEST_F(TApp, IniSubFailure) {
483 out << "val=1" << std::endl; 485 out << "val=1" << std::endl;
484 } 486 }
485 487
486 - EXPECT_THROW(run(), CLI::INIError); 488 + EXPECT_THROW(run(), CLI::ConfigError);
487 } 489 }
488 490
489 TEST_F(TApp, IniNoSubFailure) { 491 TEST_F(TApp, IniNoSubFailure) {
@@ -498,7 +500,7 @@ TEST_F(TApp, IniNoSubFailure) { @@ -498,7 +500,7 @@ TEST_F(TApp, IniNoSubFailure) {
498 out << "val=1" << std::endl; 500 out << "val=1" << std::endl;
499 } 501 }
500 502
501 - EXPECT_THROW(run(), CLI::INIError); 503 + EXPECT_THROW(run(), CLI::ConfigError);
502 } 504 }
503 505
504 TEST_F(TApp, IniFlagConvertFailure) { 506 TEST_F(TApp, IniFlagConvertFailure) {
@@ -638,7 +640,7 @@ TEST_F(TApp, IniOutputShortSingleDescription) { @@ -638,7 +640,7 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
638 640
639 run(); 641 run();
640 642
641 - std::string str = app.config_to_str(true, "", true); 643 + std::string str = app.config_to_str(true, true);
642 EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n")); 644 EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n"));
643 } 645 }
644 646
@@ -652,7 +654,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) { @@ -652,7 +654,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
652 654
653 run(); 655 run();
654 656
655 - std::string str = app.config_to_str(true, "", true); 657 + std::string str = app.config_to_str(true, true);
656 EXPECT_EQ(str, "; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n"); 658 EXPECT_EQ(str, "; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n");
657 } 659 }
658 660
@@ -663,7 +665,7 @@ TEST_F(TApp, IniOutputMultiLineDescription) { @@ -663,7 +665,7 @@ TEST_F(TApp, IniOutputMultiLineDescription) {
663 665
664 run(); 666 run();
665 667
666 - std::string str = app.config_to_str(true, "", true); 668 + std::string str = app.config_to_str(true, true);
667 EXPECT_THAT(str, HasSubstr("; Some short description.\n")); 669 EXPECT_THAT(str, HasSubstr("; Some short description.\n"));
668 EXPECT_THAT(str, HasSubstr("; That has lines.\n")); 670 EXPECT_THAT(str, HasSubstr("; That has lines.\n"));
669 EXPECT_THAT(str, HasSubstr(flag + "=false\n")); 671 EXPECT_THAT(str, HasSubstr(flag + "=false\n"));