Commit d46c2c5727c645b132a1dd109481abbe54280509

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

Adding Config class

CHANGELOG.md
1 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 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 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 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 16 * Protected function `_has_help_positional` removed
18 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 30 Validators are now much more powerful [#118], all built in validators upgraded to the new form:
21 31  
22 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 36  
27 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 42 * Better support for manual options with `get_option`, `set_results`, and `empty` [#119]
30 43 * `lname` and `sname` have getters, added `const get_parent` [#120]
31 44 * Using `add_set` will now capture L-values for sets, allowing further modification [#113]
... ... @@ -35,6 +48,7 @@ Other changes:
35 48 * Removed `requires` in favor of `needs` (deprecated in last version) [#112]
36 49 * Better CMake policy handling [#110]
37 50 * Includes are properly sorted [#120]
  51 +* Help flags now use new `short_circuit` property to simplify parsing [#121]
38 52  
39 53 [#109]: https://github.com/CLIUtils/CLI11/pull/109
40 54 [#110]: https://github.com/CLIUtils/CLI11/pull/110
... ... @@ -45,6 +59,7 @@ Other changes:
45 59 [#118]: https://github.com/CLIUtils/CLI11/pull/118
46 60 [#119]: https://github.com/CLIUtils/CLI11/pull/119
47 61 [#120]: https://github.com/CLIUtils/CLI11/pull/120
  62 +[#121]: https://github.com/CLIUtils/CLI11/pull/121
48 63  
49 64 ### Version 1.5.3: Compiler compatibility
50 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 192 * `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
193 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 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 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 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 277 ```ini
278 278 ; Commments are supported, using a ;
... ...
examples/formatter.cpp
... ... @@ -2,7 +2,7 @@
2 2  
3 3 class MyFormatter : public CLI::Formatter {
4 4 public:
5   - using Formatter::Formatter;
  5 + MyFormatter() : Formatter() {}
6 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 17 #include <vector>
18 18  
19 19 // CLI Library includes
  20 +#include "CLI/ConfigFwd.hpp"
20 21 #include "CLI/Error.hpp"
21 22 #include "CLI/FormatterFwd.hpp"
22   -#include "CLI/Ini.hpp"
23 23 #include "CLI/Macros.hpp"
24 24 #include "CLI/Option.hpp"
25 25 #include "CLI/Split.hpp"
... ... @@ -75,7 +75,7 @@ class App {
75 75 bool allow_extras_{false};
76 76  
77 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 80 /// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE
81 81 bool prefix_command_{false};
... ... @@ -170,6 +170,9 @@ class App {
170 170 /// Pointer to the config option
171 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 178 /// Special private constructor for subcommand
... ... @@ -189,13 +192,14 @@ class App {
189 192 // INHERITABLE
190 193 failure_message_ = parent_->failure_message_;
191 194 allow_extras_ = parent_->allow_extras_;
192   - allow_ini_extras_ = parent_->allow_ini_extras_;
  195 + allow_config_extras_ = parent_->allow_config_extras_;
193 196 prefix_command_ = parent_->prefix_command_;
194 197 ignore_case_ = parent_->ignore_case_;
195 198 fallthrough_ = parent_->fallthrough_;
196 199 group_ = parent_->group_;
197 200 footer_ = parent_->footer_;
198 201 formatter_ = parent_->formatter_;
  202 + config_formatter_ = parent_->config_formatter_;
199 203 require_subcommand_max_ = parent_->require_subcommand_max_;
200 204 }
201 205 }
... ... @@ -237,9 +241,9 @@ class App {
237 241  
238 242 /// Remove the error when extras are left over on the command line.
239 243 /// Will also call App::allow_extras().
240   - App *allow_ini_extras(bool allow = true) {
  244 + App *allow_config_extras(bool allow = true) {
241 245 allow_extras(allow);
242   - allow_ini_extras_ = allow;
  246 + allow_config_extras_ = allow;
243 247 return this;
244 248 }
245 249  
... ... @@ -268,11 +272,17 @@ class App {
268 272 }
269 273  
270 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 276 formatter_ = std::make_shared<FormatterLambda>(fmt);
273 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 286 /// Check to see if this subcommand was parsed, true only if received on command line.
277 287 bool parsed() const { return parsed_; }
278 288  
... ... @@ -1015,53 +1025,8 @@ class App {
1015 1025  
1016 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 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 1032 /// Makes a help message, using the currently configured formatter
... ... @@ -1087,6 +1052,9 @@ class App {
1087 1052 /// Access the formatter
1088 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 1058 /// Get the app or subcommand description
1091 1059 std::string get_description() const { return description_; }
1092 1060  
... ... @@ -1117,6 +1085,16 @@ class App {
1117 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 1098 /// Check the status of ignore_case
1121 1099 bool get_ignore_case() const { return ignore_case_; }
1122 1100  
... ... @@ -1142,7 +1120,7 @@ class App {
1142 1120 bool get_allow_extras() const { return allow_extras_; }
1143 1121  
1144 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 1125 /// Get a pointer to the help flag.
1148 1126 Option *get_help_ptr() { return help_ptr_; }
... ... @@ -1156,15 +1134,15 @@ class App {
1156 1134 /// Get a pointer to the config option.
1157 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 1140 /// Get the parent of this subcommand (or nullptr if master app)
1160 1141 App *get_parent() { return parent_; }
1161 1142  
1162 1143 /// Get the parent of this subcommand (or nullptr if master app) (const version)
1163 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 1146 /// Get the name of the current app
1169 1147 std::string get_name() const { return name_; }
1170 1148  
... ... @@ -1304,12 +1282,8 @@ class App {
1304 1282 }
1305 1283 if(!config_name_.empty()) {
1306 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 1287 } catch(const FileError &) {
1314 1288 if(config_required_)
1315 1289 throw;
... ... @@ -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 1366 /// If this has more than one dot.separated.name, go into the subcommand matching it
1393 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 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 1396 return false;
1419 1397 }
1420 1398  
1421   - // Let's not go crazy with pointer syntax
1422   - Option_p &op = *op_ptr;
1423   -
1424 1399 if(!op->get_configurable())
1425   - throw INIError::NotConfigurable(current.fullname);
  1400 + throw ConfigError::NotConfigurable(item.fullname());
1426 1401  
1427 1402 if(op->empty()) {
1428 1403 // Flag parsing
1429 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 1406 } else {
1448   - op->set_results(current.inputs);
  1407 + op->set_results(item.inputs);
1449 1408 op->run_callback();
1450 1409 }
1451 1410 }
1452 1411  
1453   - args.pop_back();
1454 1412 return true;
1455 1413 }
1456 1414  
... ...
include/CLI/CLI.hpp
... ... @@ -20,7 +20,7 @@
20 20  
21 21 #include "CLI/Split.hpp"
22 22  
23   -#include "CLI/Ini.hpp"
  23 +#include "CLI/ConfigFwd.hpp"
24 24  
25 25 #include "CLI/Validators.hpp"
26 26  
... ... @@ -30,4 +30,6 @@
30 30  
31 31 #include "CLI/App.hpp"
32 32  
  33 +#include "CLI/Config.hpp"
  34 +
33 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 11 #include "CLI/StringTools.hpp"
12 12  
13 13 namespace CLI {
  14 +
  15 +class App;
  16 +
14 17 namespace detail {
15 18  
16 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 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 47 /// Listing of inputs
40 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 156 } // namespace CLI
... ...
include/CLI/Error.hpp
... ... @@ -42,7 +42,7 @@ enum class ExitCodes {
42 42 RequiresError,
43 43 ExcludesError,
44 44 ExtrasError,
45   - INIError,
  45 + ConfigError,
46 46 InvalidError,
47 47 HorribleError,
48 48 OptionNotFound,
... ... @@ -257,12 +257,12 @@ class ExtrasError : public ParseError {
257 257 };
258 258  
259 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 5  
6 6 #include <map>
7 7 #include <string>
  8 +#include <utility>
8 9  
9 10 #include "CLI/StringTools.hpp"
10 11  
... ... @@ -88,7 +89,7 @@ class FormatterLambda final : public FormatterBase {
88 89 funct_t lambda_;
89 90  
90 91 public:
91   - explicit FormatterLambda(funct_t funct) : lambda_(funct) {}
  92 + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
92 93  
93 94 /// This will simply call the lambda function
94 95 std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
... ...
include/CLI/Option.hpp
... ... @@ -16,6 +16,7 @@
16 16 #include "CLI/Macros.hpp"
17 17 #include "CLI/Split.hpp"
18 18 #include "CLI/StringTools.hpp"
  19 +#include "CLI/Validators.hpp"
19 20  
20 21 namespace CLI {
21 22  
... ...
tests/FormatterTest.cpp
... ... @@ -33,7 +33,7 @@ TEST(Formatter, Nothing) {
33 33 TEST(Formatter, NothingLambda) {
34 34 CLI::App app{"My prog"};
35 35  
36   - app.formatter(
  36 + app.formatter_fn(
37 37 [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
38 38  
39 39 std::string help = app.help();
... ...
tests/HelpTest.cpp
... ... @@ -519,7 +519,7 @@ TEST_F(CapturedHelp, CallForAllHelpOutput) {
519 519 " --three \n");
520 520 }
521 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 523 EXPECT_EQ(run(CLI::CallForHelp()), 0);
524 524 EXPECT_EQ(out.str(), "New Help");
525 525 EXPECT_EQ(err.str(), "");
... ...
tests/IniTest.cpp
... ... @@ -15,13 +15,13 @@ TEST(StringBased, First) {
15 15  
16 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 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 22 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
23 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 25 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
26 26 EXPECT_EQ("four", output.at(1).inputs.at(0));
27 27 }
... ... @@ -36,13 +36,13 @@ TEST(StringBased, FirstWithComments) {
36 36  
37 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 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 43 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
44 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 46 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
47 47 EXPECT_EQ("four", output.at(1).inputs.at(0));
48 48 }
... ... @@ -56,16 +56,16 @@ TEST(StringBased, Quotes) {
56 56  
57 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 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 63 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
64 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 66 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
67 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 69 EXPECT_EQ((size_t)1, output.at(2).inputs.size());
70 70 EXPECT_EQ("six and seven", output.at(2).inputs.at(0));
71 71 }
... ... @@ -79,16 +79,16 @@ TEST(StringBased, Vector) {
79 79  
80 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 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 86 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
87 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 89 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
90 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 92 EXPECT_EQ((size_t)3, output.at(2).inputs.size());
93 93 EXPECT_EQ("six", output.at(2).inputs.at(0));
94 94 EXPECT_EQ("and", output.at(2).inputs.at(1));
... ... @@ -103,13 +103,13 @@ TEST(StringBased, Spaces) {
103 103  
104 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 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 110 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
111 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 113 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
114 114 EXPECT_EQ("four", output.at(1).inputs.at(0));
115 115 }
... ... @@ -123,16 +123,17 @@ TEST(StringBased, Sections) {
123 123  
124 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 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 130 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
131 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 134 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
135 135 EXPECT_EQ("four", output.at(1).inputs.at(0));
  136 + EXPECT_EQ("second.two", output.at(1).fullname());
136 137 }
137 138  
138 139 TEST(StringBased, SpacesSections) {
... ... @@ -146,14 +147,15 @@ TEST(StringBased, SpacesSections) {
146 147  
147 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 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 154 EXPECT_EQ((size_t)1, output.at(0).inputs.size());
154 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 159 EXPECT_EQ((size_t)1, output.at(1).inputs.size());
158 160 EXPECT_EQ("four", output.at(1).inputs.at(0));
159 161 }
... ... @@ -199,7 +201,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
199 201 TempFile tmpini{"TestIniTmp.ini"};
200 202  
201 203 app.set_config("--config", tmpini);
202   - app.allow_ini_extras(true);
  204 + app.allow_config_extras(true);
203 205  
204 206 {
205 207 std::ofstream out{tmpini};
... ... @@ -209,7 +211,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
209 211  
210 212 int two = 0;
211 213 app.add_option("--two", two);
212   - EXPECT_NO_THROW(run());
  214 + run();
213 215 EXPECT_EQ(99, two);
214 216 }
215 217  
... ... @@ -217,7 +219,7 @@ TEST_F(TApp, IniGetRemainingOption) {
217 219 TempFile tmpini{"TestIniTmp.ini"};
218 220  
219 221 app.set_config("--config", tmpini);
220   - app.allow_ini_extras(true);
  222 + app.allow_config_extras(true);
221 223  
222 224 std::string ExtraOption = "three";
223 225 std::string ExtraOptionValue = "3";
... ... @@ -238,7 +240,7 @@ TEST_F(TApp, IniGetNoRemaining) {
238 240 TempFile tmpini{"TestIniTmp.ini"};
239 241  
240 242 app.set_config("--config", tmpini);
241   - app.allow_ini_extras(true);
  243 + app.allow_config_extras(true);
242 244  
243 245 {
244 246 std::ofstream out{tmpini};
... ... @@ -413,7 +415,7 @@ TEST_F(TApp, IniLayered) {
413 415 auto subsubcom = subcom->add_subcommand("subsubcom");
414 416 subsubcom->add_option("--val", three);
415 417  
416   - ASSERT_NO_THROW(run());
  418 + run();
417 419  
418 420 EXPECT_EQ(1, one);
419 421 EXPECT_EQ(2, two);
... ... @@ -432,7 +434,7 @@ TEST_F(TApp, IniFailure) {
432 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 440 TEST_F(TApp, IniConfigurable) {
... ... @@ -467,7 +469,7 @@ TEST_F(TApp, IniNotConfigurable) {
467 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 475 TEST_F(TApp, IniSubFailure) {
... ... @@ -483,7 +485,7 @@ TEST_F(TApp, IniSubFailure) {
483 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 491 TEST_F(TApp, IniNoSubFailure) {
... ... @@ -498,7 +500,7 @@ TEST_F(TApp, IniNoSubFailure) {
498 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 506 TEST_F(TApp, IniFlagConvertFailure) {
... ... @@ -638,7 +640,7 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
638 640  
639 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 644 EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n"));
643 645 }
644 646  
... ... @@ -652,7 +654,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
652 654  
653 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 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 665  
664 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 669 EXPECT_THAT(str, HasSubstr("; Some short description.\n"));
668 670 EXPECT_THAT(str, HasSubstr("; That has lines.\n"));
669 671 EXPECT_THAT(str, HasSubstr(flag + "=false\n"));
... ...