mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-01-16 15:18:01 +00:00
Compare commits
3 Commits
b21f2e1bbe
...
6aa58d5828
Author | SHA1 | Date | |
---|---|---|---|
|
6aa58d5828 | ||
|
8ce1594eae | ||
|
11141525ac |
@ -351,6 +351,7 @@ Before parsing, you can set the following options:
|
||||
- `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
|
||||
- `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested.
|
||||
- `default_val(value)`: 🆕 Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator).
|
||||
- `->option_text(string)`: Sets the text between the option name and description.
|
||||
|
||||
|
||||
These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function 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 `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
|
||||
@ -747,6 +748,14 @@ Spaces before and after the name and argument are ignored. Multiple arguments ar
|
||||
To print a configuration file from the passed
|
||||
arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details.
|
||||
|
||||
If it is desired that multiple configuration be allowed. Use
|
||||
|
||||
```cpp
|
||||
app.set_config("--config")->expected(1, X);
|
||||
```
|
||||
|
||||
Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments.
|
||||
|
||||
### Inheriting defaults
|
||||
|
||||
Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`,`immediate_callback` and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
|
||||
|
@ -78,6 +78,16 @@ sub.subcommand = true
|
||||
|
||||
The main differences are in vector notation and comment character. Note: CLI11 is not a full TOML parser as it just reads values as strings. It is possible (but not recommended) to mix notation.
|
||||
|
||||
## Multiple configuration files
|
||||
|
||||
If it is desired that multiple configuration be allowed. Use
|
||||
|
||||
```cpp
|
||||
app.set_config("--config")->expected(1, X);
|
||||
```
|
||||
|
||||
Where X is some positive integer and will allow up to `X` configuration files to be specified by separate `--config` arguments.
|
||||
|
||||
## Writing out a configure file
|
||||
|
||||
To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description
|
||||
|
@ -30,7 +30,7 @@ You can further configure pieces of the code while still keeping most of the for
|
||||
```cpp
|
||||
class MyFormatter : public CLI::Formatter {
|
||||
public:
|
||||
std::string make_opts(const CLI::Option *) const override {return "";}
|
||||
std::string make_option_opts(const CLI::Option *) const override {return "";}
|
||||
};
|
||||
app.formatter(std::make_shared<MyFormatter>());
|
||||
```
|
||||
|
@ -2053,29 +2053,31 @@ class App {
|
||||
void _process_config_file() {
|
||||
if(config_ptr_ != nullptr) {
|
||||
bool config_required = config_ptr_->get_required();
|
||||
bool file_given = config_ptr_->count() > 0;
|
||||
auto config_file = config_ptr_->as<std::string>();
|
||||
if(config_file.empty()) {
|
||||
auto file_given = config_ptr_->count() > 0;
|
||||
auto config_files = config_ptr_->as<std::vector<std::string>>();
|
||||
if(config_files.empty() || config_files.front().empty()) {
|
||||
if(config_required) {
|
||||
throw FileError::Missing("no specified config file");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto path_result = detail::check_path(config_file.c_str());
|
||||
if(path_result == detail::path_type::file) {
|
||||
try {
|
||||
std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
|
||||
_parse_config(values);
|
||||
if(!file_given) {
|
||||
config_ptr_->add_result(config_file);
|
||||
for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
|
||||
const auto &config_file = *rit;
|
||||
auto path_result = detail::check_path(config_file.c_str());
|
||||
if(path_result == detail::path_type::file) {
|
||||
try {
|
||||
std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
|
||||
_parse_config(values);
|
||||
if(!file_given) {
|
||||
config_ptr_->add_result(config_file);
|
||||
}
|
||||
} catch(const FileError &) {
|
||||
if(config_required || file_given)
|
||||
throw;
|
||||
}
|
||||
} catch(const FileError &) {
|
||||
if(config_required || file_given)
|
||||
throw;
|
||||
} else if(config_required || file_given) {
|
||||
throw FileError::Missing(config_file);
|
||||
}
|
||||
} else if(config_required || file_given) {
|
||||
throw FileError::Missing(config_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -236,30 +236,34 @@ inline std::string Formatter::make_option_name(const Option *opt, bool is_positi
|
||||
inline std::string Formatter::make_option_opts(const Option *opt) const {
|
||||
std::stringstream out;
|
||||
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_type_name().empty())
|
||||
out << " " << get_label(opt->get_type_name());
|
||||
if(!opt->get_default_str().empty())
|
||||
out << "=" << opt->get_default_str();
|
||||
if(opt->get_expected_max() == detail::expected_max_vector_size)
|
||||
out << " ...";
|
||||
else if(opt->get_expected_min() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
if(!opt->get_option_text().empty()) {
|
||||
out << " " << opt->get_option_text();
|
||||
} else {
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_type_name().empty())
|
||||
out << " " << get_label(opt->get_type_name());
|
||||
if(!opt->get_default_str().empty())
|
||||
out << "=" << opt->get_default_str();
|
||||
if(opt->get_expected_max() == detail::expected_max_vector_size)
|
||||
out << " ...";
|
||||
else if(opt->get_expected_min() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
@ -264,6 +264,9 @@ class Option : public OptionBase<Option> {
|
||||
/// A human readable default value, either manually set, captured, or captured by default
|
||||
std::string default_str_{};
|
||||
|
||||
/// If given, replace the text that describes the option type and usage in the help text
|
||||
std::string option_text_{};
|
||||
|
||||
/// A human readable type value, set when App creates this
|
||||
///
|
||||
/// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
|
||||
@ -736,6 +739,13 @@ class Option : public OptionBase<Option> {
|
||||
return this;
|
||||
}
|
||||
|
||||
Option *option_text(std::string text) {
|
||||
option_text_ = std::move(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
const std::string &get_option_text() const { return option_text_; }
|
||||
|
||||
///@}
|
||||
/// @name Help tools
|
||||
///@{
|
||||
|
@ -591,6 +591,89 @@ TEST_F(TApp, IniNotRequiredNotDefault) {
|
||||
EXPECT_EQ(app.get_config_ptr()->as<std::string>(), tmpini2.c_str());
|
||||
}
|
||||
|
||||
TEST_F(TApp, MultiConfig) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
TempFile tmpini2{"TestIniTmp2.ini"};
|
||||
|
||||
app.set_config("--config")->expected(1, 3);
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini};
|
||||
out << "[default]" << std::endl;
|
||||
out << "two=99" << std::endl;
|
||||
out << "three=3" << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini2};
|
||||
out << "[default]" << std::endl;
|
||||
out << "one=55" << std::endl;
|
||||
out << "three=4" << std::endl;
|
||||
}
|
||||
|
||||
int one{0}, two{0}, three{0};
|
||||
app.add_option("--one", one);
|
||||
app.add_option("--two", two);
|
||||
app.add_option("--three", three);
|
||||
|
||||
args = {"--config", tmpini2, "--config", tmpini};
|
||||
run();
|
||||
|
||||
EXPECT_EQ(99, two);
|
||||
EXPECT_EQ(3, three);
|
||||
EXPECT_EQ(55, one);
|
||||
|
||||
args = {"--config", tmpini, "--config", tmpini2};
|
||||
run();
|
||||
|
||||
EXPECT_EQ(99, two);
|
||||
EXPECT_EQ(4, three);
|
||||
EXPECT_EQ(55, one);
|
||||
}
|
||||
|
||||
TEST_F(TApp, MultiConfig_single) {
|
||||
|
||||
TempFile tmpini{"TestIniTmp.ini"};
|
||||
TempFile tmpini2{"TestIniTmp2.ini"};
|
||||
|
||||
app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini};
|
||||
out << "[default]" << std::endl;
|
||||
out << "two=99" << std::endl;
|
||||
out << "three=3" << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream out{tmpini2};
|
||||
out << "[default]" << std::endl;
|
||||
out << "one=55" << std::endl;
|
||||
out << "three=4" << std::endl;
|
||||
}
|
||||
|
||||
int one{0}, two{0}, three{0};
|
||||
app.add_option("--one", one);
|
||||
app.add_option("--two", two);
|
||||
app.add_option("--three", three);
|
||||
|
||||
args = {"--config", tmpini2, "--config", tmpini};
|
||||
run();
|
||||
|
||||
EXPECT_EQ(99, two);
|
||||
EXPECT_EQ(3, three);
|
||||
EXPECT_EQ(0, one);
|
||||
|
||||
two = 0;
|
||||
args = {"--config", tmpini, "--config", tmpini2};
|
||||
run();
|
||||
|
||||
EXPECT_EQ(0, two);
|
||||
EXPECT_EQ(4, three);
|
||||
EXPECT_EQ(55, one);
|
||||
}
|
||||
|
||||
TEST_F(TApp, IniRequiredNotFound) {
|
||||
|
||||
std::string noini = "TestIniNotExist.ini";
|
||||
|
@ -89,6 +89,25 @@ TEST(Formatter, OptCustomizeSimple) {
|
||||
" --opt INT (MUST HAVE) Something\n\n");
|
||||
}
|
||||
|
||||
TEST(Formatter, OptCustomizeOptionText) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
app.get_formatter()->column_width(25);
|
||||
|
||||
int v{0};
|
||||
app.add_option("--opt", v, "Something")->option_text("(ARG)");
|
||||
|
||||
std::string help = app.help();
|
||||
|
||||
EXPECT_THAT(help, HasSubstr("(ARG)"));
|
||||
EXPECT_EQ(help,
|
||||
"My prog\n"
|
||||
"Usage: [OPTIONS]\n\n"
|
||||
"Options:\n"
|
||||
" -h,--help Print this help message and exit\n"
|
||||
" --opt (ARG) Something\n\n");
|
||||
}
|
||||
|
||||
TEST(Formatter, FalseFlagExample) {
|
||||
CLI::App app{"My prog"};
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "app_helper.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -850,6 +851,22 @@ TEST_F(TApp, AsSizeValue1000_1024) {
|
||||
EXPECT_EQ(value, ki_value);
|
||||
}
|
||||
|
||||
TEST_F(TApp, duration_test) {
|
||||
std::chrono::seconds duration{1};
|
||||
|
||||
app.option_defaults()->ignore_case();
|
||||
app.add_option_function<std::size_t>(
|
||||
"--duration",
|
||||
[&](size_t a_value) { duration = std::chrono::seconds{a_value}; },
|
||||
"valid units: sec, min, h, day.")
|
||||
->capture_default_str()
|
||||
->transform(CLI::AsNumberWithUnit(
|
||||
std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}}));
|
||||
EXPECT_NO_THROW(app.parse(std::vector<std::string>{"1 day", "--duration"}));
|
||||
|
||||
EXPECT_EQ(duration, std::chrono::seconds(86400));
|
||||
}
|
||||
|
||||
TEST_F(TApp, AsSizeValue1024) {
|
||||
std::uint64_t value{0};
|
||||
app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
|
||||
|
Loading…
Reference in New Issue
Block a user