1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

Adding Config class

This commit is contained in:
Henry Fredrick Schreiner 2018-05-08 10:32:04 +02:00 committed by Henry Schreiner
parent 0d9a33d4ca
commit d46c2c5727
14 changed files with 366 additions and 278 deletions

View File

@ -1,12 +1,11 @@
## Version 1.6: Formatting help
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.
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.
* Added `CLI::Formatter` and `formatter` slot for apps, inherited.
* `FormatterBase` is the minimum required
* `FormatterLambda` provides for the easy addition of an arbitrary function
* Added `help_all` support (not added by default)
* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering
* Added `get_groups()` to get groups
* Added getters for the missing parts of options (help no longer uses any private parts)
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):
* Protected function `_has_help_positional` removed
* `format_help` can now be chained
New for Config file reading and writing [#121]:
* Overridable, bidirectional Config
* ConfigINI provided and used by default
* Renamed ini to config in many places
* Has `config_formatter()` and `get_config_formatter()`
* Dropped prefix argument from `config_to_str`
* Added `ConfigItem`
Validators are now much more powerful [#118], all built in validators upgraded to the new form:
* 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
Other changes:
* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering
* Added `get_groups()` to get groups
* Added getters for the missing parts of options (help no longer uses any private parts)
* Better support for manual options with `get_option`, `set_results`, and `empty` [#119]
* `lname` and `sname` have getters, added `const get_parent` [#120]
* Using `add_set` will now capture L-values for sets, allowing further modification [#113]
@ -35,6 +48,7 @@ Other changes:
* Removed `requires` in favor of `needs` (deprecated in last version) [#112]
* Better CMake policy handling [#110]
* Includes are properly sorted [#120]
* Help flags now use new `short_circuit` property to simplify parsing [#121]
[#109]: https://github.com/CLIUtils/CLI11/pull/109
[#110]: https://github.com/CLIUtils/CLI11/pull/110
@ -45,6 +59,7 @@ Other changes:
[#118]: https://github.com/CLIUtils/CLI11/pull/118
[#119]: https://github.com/CLIUtils/CLI11/pull/119
[#120]: https://github.com/CLIUtils/CLI11/pull/120
[#121]: https://github.com/CLIUtils/CLI11/pull/121
### Version 1.5.3: Compiler compatibility
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.

View File

@ -192,7 +192,7 @@ The add commands return a pointer to an internally stored `Option`. If you set t
* `->check(CLI::NonexistentPath)`: Requires that the path does not exist.
* `->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.
* `->transform(std::string(std::string))`: Converts the input string into the output string, in-place in the parsed options.
* `->configurable(false)`: Disable this option from being in an ini configuration file.
* `->configurable(false)`: Disable this option from being in a configuration file.
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).
@ -272,7 +272,7 @@ app.set_config(option_name="",
required=false)
```
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:
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:
```ini
; Commments are supported, using a ;

View File

@ -2,7 +2,7 @@
class MyFormatter : public CLI::Formatter {
public:
using Formatter::Formatter;
MyFormatter() : Formatter() {}
std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; }
};

View File

@ -17,9 +17,9 @@
#include <vector>
// CLI Library includes
#include "CLI/ConfigFwd.hpp"
#include "CLI/Error.hpp"
#include "CLI/FormatterFwd.hpp"
#include "CLI/Ini.hpp"
#include "CLI/Macros.hpp"
#include "CLI/Option.hpp"
#include "CLI/Split.hpp"
@ -75,7 +75,7 @@ class App {
bool allow_extras_{false};
/// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
bool allow_ini_extras_{false};
bool allow_config_extras_{false};
/// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE
bool prefix_command_{false};
@ -170,6 +170,9 @@ class App {
/// Pointer to the config option
Option *config_ptr_{nullptr};
/// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
std::shared_ptr<Config> config_formatter_{new ConfigINI()};
///@}
/// Special private constructor for subcommand
@ -189,13 +192,14 @@ class App {
// INHERITABLE
failure_message_ = parent_->failure_message_;
allow_extras_ = parent_->allow_extras_;
allow_ini_extras_ = parent_->allow_ini_extras_;
allow_config_extras_ = parent_->allow_config_extras_;
prefix_command_ = parent_->prefix_command_;
ignore_case_ = parent_->ignore_case_;
fallthrough_ = parent_->fallthrough_;
group_ = parent_->group_;
footer_ = parent_->footer_;
formatter_ = parent_->formatter_;
config_formatter_ = parent_->config_formatter_;
require_subcommand_max_ = parent_->require_subcommand_max_;
}
}
@ -237,9 +241,9 @@ class App {
/// Remove the error when extras are left over on the command line.
/// Will also call App::allow_extras().
App *allow_ini_extras(bool allow = true) {
App *allow_config_extras(bool allow = true) {
allow_extras(allow);
allow_ini_extras_ = allow;
allow_config_extras_ = allow;
return this;
}
@ -268,11 +272,17 @@ class App {
}
/// Set the help formatter
App *formatter(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
formatter_ = std::make_shared<FormatterLambda>(fmt);
return this;
}
/// Set the config formatter
App *config_formatter(std::shared_ptr<Config> fmt) {
config_formatter_ = fmt;
return this;
}
/// Check to see if this subcommand was parsed, true only if received on command line.
bool parsed() const { return parsed_; }
@ -1015,53 +1025,8 @@ class App {
/// Produce a string that could be read in as a config of the current values of the App. Set default_also to include
/// default arguments. Prefix will add a string to the beginning of each option.
std::string
config_to_str(bool default_also = false, std::string prefix = "", bool write_description = false) const {
std::stringstream out;
for(const Option_p &opt : options_) {
// Only process option with a long-name and configurable
if(!opt->get_lnames().empty() && opt->get_configurable()) {
std::string name = prefix + opt->get_lnames()[0];
std::string value;
// Non-flags
if(opt->get_type_size() != 0) {
// If the option was found on command line
if(opt->count() > 0)
value = detail::inijoin(opt->results());
// If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty())
value = opt->get_defaultval();
// Flag, one passed
} else if(opt->count() == 1) {
value = "true";
// Flag, multiple passed
} else if(opt->count() > 1) {
value = std::to_string(opt->count());
// Flag, not present
} else if(opt->count() == 0 && default_also) {
value = "false";
}
if(!value.empty()) {
if(write_description && opt->has_description()) {
if(static_cast<int>(out.tellp()) != 0) {
out << std::endl;
}
out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl;
}
out << name << "=" << value << std::endl;
}
}
}
for(const App_p &subcom : subcommands_)
out << subcom->config_to_str(default_also, prefix + subcom->name_ + ".");
return out.str();
std::string config_to_str(bool default_also = false, bool write_description = false) const {
return config_formatter_->to_config(this, default_also, write_description, "");
}
/// Makes a help message, using the currently configured formatter
@ -1087,6 +1052,9 @@ class App {
/// Access the formatter
std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
/// Access the config formatter
std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
/// Get the app or subcommand description
std::string get_description() const { return description_; }
@ -1117,6 +1085,16 @@ class App {
throw OptionNotFound(name);
}
/// Get an option by name (non-const version)
Option *get_option(std::string name) {
for(Option_p &opt : options_) {
if(opt->check_name(name)) {
return opt.get();
}
}
throw OptionNotFound(name);
}
/// Check the status of ignore_case
bool get_ignore_case() const { return ignore_case_; }
@ -1142,7 +1120,7 @@ class App {
bool get_allow_extras() const { return allow_extras_; }
/// Get the status of allow extras
bool get_allow_ini_extras() const { return allow_ini_extras_; }
bool get_allow_config_extras() const { return allow_config_extras_; }
/// Get a pointer to the help flag.
Option *get_help_ptr() { return help_ptr_; }
@ -1156,15 +1134,15 @@ class App {
/// Get a pointer to the config option.
Option *get_config_ptr() { return config_ptr_; }
/// Get a pointer to the config option. (const)
const Option *get_config_ptr() const { return config_ptr_; }
/// Get the parent of this subcommand (or nullptr if master app)
App *get_parent() { return parent_; }
/// Get the parent of this subcommand (or nullptr if master app) (const version)
const App *get_parent() const { return parent_; }
/// Get a pointer to the config option. (const)
const Option *get_config_ptr() const { return config_ptr_; }
/// Get the name of the current app
std::string get_name() const { return name_; }
@ -1304,12 +1282,8 @@ class App {
}
if(!config_name_.empty()) {
try {
std::vector<detail::ini_ret_t> values = detail::parse_ini(config_name_);
while(!values.empty()) {
if(!_parse_ini(values)) {
throw INIError::Extras(values.back().fullname);
}
}
std::vector<ConfigItem> values = config_formatter_->from_file(config_name_);
_parse_config(values);
} catch(const FileError &) {
if(config_required_)
throw;
@ -1387,70 +1361,54 @@ class App {
}
}
/// Parse one ini param, return false if not found in any subcommand, remove if it is
/// Parse one config param, return false if not found in any subcommand, remove if it is
///
/// If this has more than one dot.separated.name, go into the subcommand matching it
/// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
bool _parse_ini(std::vector<detail::ini_ret_t> &args) {
detail::ini_ret_t &current = args.back();
std::string parent = current.parent(); // respects current.level
std::string name = current.name();
// If a parent is listed, go to a subcommand
if(!parent.empty()) {
current.level++;
for(const App_p &com : subcommands_)
if(com->check_name(parent))
return com->_parse_ini(args);
return false;
void _parse_config(std::vector<ConfigItem> &args) {
for(ConfigItem item : args) {
if(!_parse_single_config(item) && !allow_config_extras_)
throw ConfigError::Extras(item.fullname());
}
}
auto op_ptr = std::find_if(
std::begin(options_), std::end(options_), [name](const Option_p &v) { return v->check_lname(name); });
if(op_ptr == std::end(options_)) {
if(allow_ini_extras_) {
// Should we worry about classifying the extras properly?
missing_.emplace_back(detail::Classifer::NONE, current.fullname);
args.pop_back();
return true;
/// Fill in a single config option
bool _parse_single_config(const ConfigItem &item, size_t level = 0) {
if(level < item.parents.size()) {
App *subcom;
try {
std::cout << item.parents.at(level) << std::endl;
subcom = get_subcommand(item.parents.at(level));
} catch(const OptionNotFound &) {
return false;
}
return false;
return subcom->_parse_single_config(item, level + 1);
}
// Let's not go crazy with pointer syntax
Option_p &op = *op_ptr;
Option *op;
try {
op = get_option("--" + item.name);
} catch(const OptionNotFound &) {
// If the option was not present
if(get_allow_config_extras())
// Should we worry about classifying the extras properly?
missing_.emplace_back(detail::Classifer::NONE, item.fullname());
return false;
}
if(!op->get_configurable())
throw INIError::NotConfigurable(current.fullname);
throw ConfigError::NotConfigurable(item.fullname());
if(op->empty()) {
// Flag parsing
if(op->get_type_size() == 0) {
if(current.inputs.size() == 1) {
std::string val = current.inputs.at(0);
val = detail::to_lower(val);
if(val == "true" || val == "on" || val == "yes")
op->set_results({""});
else if(val == "false" || val == "off" || val == "no")
;
else
try {
size_t ui = std::stoul(val);
for(size_t i = 0; i < ui; i++)
op->add_result("");
} catch(const std::invalid_argument &) {
throw ConversionError::TrueFalse(current.fullname);
}
} else
throw ConversionError::TooManyInputsFlag(current.fullname);
op->set_results(config_formatter_->to_flag(item));
} else {
op->set_results(current.inputs);
op->set_results(item.inputs);
op->run_callback();
}
}
args.pop_back();
return true;
}

View File

@ -20,7 +20,7 @@
#include "CLI/Split.hpp"
#include "CLI/Ini.hpp"
#include "CLI/ConfigFwd.hpp"
#include "CLI/Validators.hpp"
@ -30,4 +30,6 @@
#include "CLI/App.hpp"
#include "CLI/Config.hpp"
#include "CLI/Formatter.hpp"

68
include/CLI/Config.hpp Normal file
View File

@ -0,0 +1,68 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "CLI/App.hpp"
#include "CLI/ConfigFwd.hpp"
#include "CLI/StringTools.hpp"
namespace CLI {
inline std::string
ConfigINI::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
std::stringstream out;
for(const Option *opt : app->get_options({})) {
// Only process option with a long-name and configurable
if(!opt->get_lnames().empty() && opt->get_configurable()) {
std::string name = prefix + opt->get_lnames()[0];
std::string value;
// Non-flags
if(opt->get_type_size() != 0) {
// If the option was found on command line
if(opt->count() > 0)
value = detail::inijoin(opt->results());
// If the option has a default and is requested by optional argument
else if(default_also && !opt->get_defaultval().empty())
value = opt->get_defaultval();
// Flag, one passed
} else if(opt->count() == 1) {
value = "true";
// Flag, multiple passed
} else if(opt->count() > 1) {
value = std::to_string(opt->count());
// Flag, not present
} else if(opt->count() == 0 && default_also) {
value = "false";
}
if(!value.empty()) {
if(write_description && opt->has_description()) {
if(static_cast<int>(out.tellp()) != 0) {
out << std::endl;
}
out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl;
}
out << name << "=" << value << std::endl;
}
}
}
for(const App *subcom : app->get_subcommands({}))
out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
return out.str();
}
} // namespace CLI

156
include/CLI/ConfigFwd.hpp Normal file
View File

@ -0,0 +1,156 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "CLI/StringTools.hpp"
namespace CLI {
class App;
namespace detail {
inline std::string inijoin(std::vector<std::string> args) {
std::ostringstream s;
size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0)
s << " ";
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); });
if(it == arg.end())
s << arg;
else if(arg.find(R"(")") == std::string::npos)
s << R"(")" << arg << R"(")";
else
s << R"(')" << arg << R"(')";
}
return s.str();
}
} // namespace detail
struct ConfigItem {
/// This is the list of parents
std::vector<std::string> parents;
/// This is the name
std::string name;
/// Listing of inputs
std::vector<std::string> inputs;
/// The list of parents and name joined by "."
std::string fullname() const {
std::vector<std::string> tmp = parents;
tmp.emplace_back(name);
return detail::join(tmp, ".");
}
};
/// This class provides a converter for configuration files.
class Config {
protected:
std::vector<ConfigItem> items;
public:
/// Convert an app into a configuration
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
/// Convert a flag to a bool
virtual std::vector<std::string> to_flag(const ConfigItem &) const = 0;
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
std::vector<ConfigItem> from_file(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError::Missing(name);
return from_config(input);
}
};
/// This converter works with INI files
class ConfigINI : public Config {
public:
std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override;
std::vector<std::string> to_flag(const ConfigItem &item) const override {
if(item.inputs.size() == 1) {
std::string val = item.inputs.at(0);
val = detail::to_lower(val);
if(val == "true" || val == "on" || val == "yes") {
return std::vector<std::string>(1);
} else if(val == "false" || val == "off" || val == "no") {
return std::vector<std::string>();
} else {
try {
size_t ui = std::stoul(val);
return std::vector<std::string>(ui);
} catch(const std::invalid_argument &) {
throw ConversionError::TrueFalse(item.fullname());
}
}
} else {
throw ConversionError::TooManyInputsFlag(item.fullname());
}
}
std::vector<ConfigItem> from_config(std::istream &input) const override {
std::string line;
std::string section = "default";
std::vector<ConfigItem> output;
while(getline(input, line)) {
std::vector<std::string> items;
detail::trim(line);
size_t len = line.length();
if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
section = line.substr(1, len - 2);
} else if(len > 0 && line[0] != ';') {
output.emplace_back();
ConfigItem &out = output.back();
// Find = in string, split and recombine
auto pos = line.find('=');
if(pos != std::string::npos) {
out.name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
items = detail::split_up(item);
} else {
out.name = detail::trim_copy(line);
items = {"ON"};
}
if(detail::to_lower(section) != "default") {
out.parents = {section};
}
if(out.name.find('.') != std::string::npos) {
std::vector<std::string> plist = detail::split(out.name, '.');
out.name = plist.back();
plist.pop_back();
out.parents.insert(out.parents.end(), plist.begin(), plist.end());
}
out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
}
}
return output;
}
};
} // namespace CLI

View File

@ -42,7 +42,7 @@ enum class ExitCodes {
RequiresError,
ExcludesError,
ExtrasError,
INIError,
ConfigError,
InvalidError,
HorribleError,
OptionNotFound,
@ -257,12 +257,12 @@ class ExtrasError : public ParseError {
};
/// Thrown when extra values are found in an INI file
class INIError : public ParseError {
CLI11_ERROR_DEF(ParseError, INIError)
CLI11_ERROR_SIMPLE(INIError)
static INIError Extras(std::string item) { return INIError("INI was not able to parse " + item); }
static INIError NotConfigurable(std::string item) {
return INIError(item + ": This option is not allowed in a configuration file");
class ConfigError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConfigError)
CLI11_ERROR_SIMPLE(ConfigError)
static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
static ConfigError NotConfigurable(std::string item) {
return ConfigError(item + ": This option is not allowed in a configuration file");
}
};

View File

@ -5,6 +5,7 @@
#include <map>
#include <string>
#include <utility>
#include "CLI/StringTools.hpp"
@ -88,7 +89,7 @@ class FormatterLambda final : public FormatterBase {
funct_t lambda_;
public:
explicit FormatterLambda(funct_t funct) : lambda_(funct) {}
explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
/// This will simply call the lambda function
std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {

View File

@ -1,115 +0,0 @@
#pragma once
// Distributed under the 3-Clause BSD License. See accompanying
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include "CLI/StringTools.hpp"
namespace CLI {
namespace detail {
inline std::string inijoin(std::vector<std::string> args) {
std::ostringstream s;
size_t start = 0;
for(const auto &arg : args) {
if(start++ > 0)
s << " ";
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); });
if(it == arg.end())
s << arg;
else if(arg.find(R"(")") == std::string::npos)
s << R"(")" << arg << R"(")";
else
s << R"(')" << arg << R"(')";
}
return s.str();
}
struct ini_ret_t {
/// This is the full name with dots
std::string fullname;
/// Listing of inputs
std::vector<std::string> inputs;
/// Current parent level
size_t level = 0;
/// Return parent or empty string, based on level
///
/// Level 0, a.b.c would return a
/// Level 1, a.b.c could return b
std::string parent() const {
std::vector<std::string> plist = detail::split(fullname, '.');
if(plist.size() > (level + 1))
return plist[level];
else
return "";
}
/// Return name
std::string name() const {
std::vector<std::string> plist = detail::split(fullname, '.');
return plist.at(plist.size() - 1);
}
};
/// Internal parsing function
inline std::vector<ini_ret_t> parse_ini(std::istream &input) {
std::string name, line;
std::string section = "default";
std::vector<ini_ret_t> output;
while(getline(input, line)) {
std::vector<std::string> items;
detail::trim(line);
size_t len = line.length();
if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
section = line.substr(1, len - 2);
} else if(len > 0 && line[0] != ';') {
output.emplace_back();
ini_ret_t &out = output.back();
// Find = in string, split and recombine
auto pos = line.find("=");
if(pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, pos));
std::string item = detail::trim_copy(line.substr(pos + 1));
items = detail::split_up(item);
} else {
name = detail::trim_copy(line);
items = {"ON"};
}
if(detail::to_lower(section) == "default")
out.fullname = name;
else
out.fullname = section + "." + name;
out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
}
}
return output;
}
/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure
inline std::vector<ini_ret_t> parse_ini(const std::string &name) {
std::ifstream input{name};
if(!input.good())
throw FileError::Missing(name);
return parse_ini(input);
}
} // namespace detail
} // namespace CLI

View File

@ -16,6 +16,7 @@
#include "CLI/Macros.hpp"
#include "CLI/Split.hpp"
#include "CLI/StringTools.hpp"
#include "CLI/Validators.hpp"
namespace CLI {

View File

@ -33,7 +33,7 @@ TEST(Formatter, Nothing) {
TEST(Formatter, NothingLambda) {
CLI::App app{"My prog"};
app.formatter(
app.formatter_fn(
[](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
std::string help = app.help();

View File

@ -519,7 +519,7 @@ TEST_F(CapturedHelp, CallForAllHelpOutput) {
" --three \n");
}
TEST_F(CapturedHelp, NewFormattedHelp) {
app.formatter([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
EXPECT_EQ(run(CLI::CallForHelp()), 0);
EXPECT_EQ(out.str(), "New Help");
EXPECT_EQ(err.str(), "");

View File

@ -15,13 +15,13 @@ TEST(StringBased, First) {
ofile.seekg(0, std::ios::beg);
std::vector<CLI::detail::ini_ret_t> output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)2, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
}
@ -36,13 +36,13 @@ TEST(StringBased, FirstWithComments) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)2, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
}
@ -56,16 +56,16 @@ TEST(StringBased, Quotes) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)3, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
EXPECT_EQ("five", output.at(2).name());
EXPECT_EQ("five", output.at(2).name);
EXPECT_EQ((size_t)1, output.at(2).inputs.size());
EXPECT_EQ("six and seven", output.at(2).inputs.at(0));
}
@ -79,16 +79,16 @@ TEST(StringBased, Vector) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)3, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
EXPECT_EQ("five", output.at(2).name());
EXPECT_EQ("five", output.at(2).name);
EXPECT_EQ((size_t)3, output.at(2).inputs.size());
EXPECT_EQ("six", output.at(2).inputs.at(0));
EXPECT_EQ("and", output.at(2).inputs.at(1));
@ -103,13 +103,13 @@ TEST(StringBased, Spaces) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)2, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
}
@ -123,16 +123,17 @@ TEST(StringBased, Sections) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)2, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("second", output.at(1).parent());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ("second", output.at(1).parents.at(0));
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
EXPECT_EQ("second.two", output.at(1).fullname());
}
TEST(StringBased, SpacesSections) {
@ -146,14 +147,15 @@ TEST(StringBased, SpacesSections) {
ofile.seekg(0, std::ios::beg);
auto output = CLI::detail::parse_ini(ofile);
std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
EXPECT_EQ((size_t)2, output.size());
EXPECT_EQ("one", output.at(0).name());
EXPECT_EQ("one", output.at(0).name);
EXPECT_EQ((size_t)1, output.at(0).inputs.size());
EXPECT_EQ("three", output.at(0).inputs.at(0));
EXPECT_EQ("two", output.at(1).name());
EXPECT_EQ("second", output.at(1).parent());
EXPECT_EQ("two", output.at(1).name);
EXPECT_EQ((size_t)1, output.at(1).parents.size());
EXPECT_EQ("second", output.at(1).parents.at(0));
EXPECT_EQ((size_t)1, output.at(1).inputs.size());
EXPECT_EQ("four", output.at(1).inputs.at(0));
}
@ -199,7 +201,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
app.allow_ini_extras(true);
app.allow_config_extras(true);
{
std::ofstream out{tmpini};
@ -209,7 +211,7 @@ TEST_F(TApp, IniSuccessOnUnknownOption) {
int two = 0;
app.add_option("--two", two);
EXPECT_NO_THROW(run());
run();
EXPECT_EQ(99, two);
}
@ -217,7 +219,7 @@ TEST_F(TApp, IniGetRemainingOption) {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
app.allow_ini_extras(true);
app.allow_config_extras(true);
std::string ExtraOption = "three";
std::string ExtraOptionValue = "3";
@ -238,7 +240,7 @@ TEST_F(TApp, IniGetNoRemaining) {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
app.allow_ini_extras(true);
app.allow_config_extras(true);
{
std::ofstream out{tmpini};
@ -413,7 +415,7 @@ TEST_F(TApp, IniLayered) {
auto subsubcom = subcom->add_subcommand("subsubcom");
subsubcom->add_option("--val", three);
ASSERT_NO_THROW(run());
run();
EXPECT_EQ(1, one);
EXPECT_EQ(2, two);
@ -432,7 +434,7 @@ TEST_F(TApp, IniFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::INIError);
EXPECT_THROW(run(), CLI::ConfigError);
}
TEST_F(TApp, IniConfigurable) {
@ -467,7 +469,7 @@ TEST_F(TApp, IniNotConfigurable) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::INIError);
EXPECT_THROW(run(), CLI::ConfigError);
}
TEST_F(TApp, IniSubFailure) {
@ -483,7 +485,7 @@ TEST_F(TApp, IniSubFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::INIError);
EXPECT_THROW(run(), CLI::ConfigError);
}
TEST_F(TApp, IniNoSubFailure) {
@ -498,7 +500,7 @@ TEST_F(TApp, IniNoSubFailure) {
out << "val=1" << std::endl;
}
EXPECT_THROW(run(), CLI::INIError);
EXPECT_THROW(run(), CLI::ConfigError);
}
TEST_F(TApp, IniFlagConvertFailure) {
@ -638,7 +640,7 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
run();
std::string str = app.config_to_str(true, "", true);
std::string str = app.config_to_str(true, true);
EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n"));
}
@ -652,7 +654,7 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
run();
std::string str = app.config_to_str(true, "", true);
std::string str = app.config_to_str(true, true);
EXPECT_EQ(str, "; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n");
}
@ -663,7 +665,7 @@ TEST_F(TApp, IniOutputMultiLineDescription) {
run();
std::string str = app.config_to_str(true, "", true);
std::string str = app.config_to_str(true, true);
EXPECT_THAT(str, HasSubstr("; Some short description.\n"));
EXPECT_THAT(str, HasSubstr("; That has lines.\n"));
EXPECT_THAT(str, HasSubstr(flag + "=false\n"));