1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-30 04:33:53 +00:00

Add an ability to deal handle multiple config files (#494)

This commit is contained in:
Philip Top 2020-09-03 16:42:35 -07:00 committed by GitHub
parent 8ce1594eae
commit 6aa58d5828
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 16 deletions

View File

@ -748,6 +748,14 @@ Spaces before and after the name and argument are ignored. Multiple arguments ar
To print a configuration file from the passed 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. 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 ### 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. 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.

View File

@ -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. 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 ## 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 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

View File

@ -2053,15 +2053,16 @@ class App {
void _process_config_file() { void _process_config_file() {
if(config_ptr_ != nullptr) { if(config_ptr_ != nullptr) {
bool config_required = config_ptr_->get_required(); bool config_required = config_ptr_->get_required();
bool file_given = config_ptr_->count() > 0; auto file_given = config_ptr_->count() > 0;
auto config_file = config_ptr_->as<std::string>(); auto config_files = config_ptr_->as<std::vector<std::string>>();
if(config_file.empty()) { if(config_files.empty() || config_files.front().empty()) {
if(config_required) { if(config_required) {
throw FileError::Missing("no specified config file"); throw FileError::Missing("no specified config file");
} }
return; return;
} }
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()); auto path_result = detail::check_path(config_file.c_str());
if(path_result == detail::path_type::file) { if(path_result == detail::path_type::file) {
try { try {
@ -2079,6 +2080,7 @@ class App {
} }
} }
} }
}
/// Get envname options if not yet passed. Runs on *all* subcommands. /// Get envname options if not yet passed. Runs on *all* subcommands.
void _process_env() { void _process_env() {

View File

@ -591,6 +591,89 @@ TEST_F(TApp, IniNotRequiredNotDefault) {
EXPECT_EQ(app.get_config_ptr()->as<std::string>(), tmpini2.c_str()); 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) { TEST_F(TApp, IniRequiredNotFound) {
std::string noini = "TestIniNotExist.ini"; std::string noini = "TestIniNotExist.ini";

View File

@ -7,6 +7,7 @@
#include "app_helper.hpp" #include "app_helper.hpp"
#include <array> #include <array>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <unordered_map> #include <unordered_map>
@ -850,6 +851,22 @@ TEST_F(TApp, AsSizeValue1000_1024) {
EXPECT_EQ(value, ki_value); 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) { TEST_F(TApp, AsSizeValue1024) {
std::uint64_t value{0}; std::uint64_t value{0};
app.add_option("-s", value)->transform(CLI::AsSizeValue(false)); app.add_option("-s", value)->transform(CLI::AsSizeValue(false));