Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1429,12 +1429,27 @@ configuration files if the `configurable` flag was set on the subcommand. Then
the use of `[subcommand]` notation will trigger a subcommand and cause it to act
as if it were on the command line.

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 and customization points.
To print a configuration file from the passed arguments, use one of the
following overloads:

- `.config_to_str()`: Print active values only.
- `.config_to_str(bool default_also, bool write_description = false)`: Print
active values, or include defaulted arguments if `default_also` is `true`.
This overload will likely be deprecated in a future release in favor of the
`CLI::ConfigOutputMode` overload.
- `.config_to_str(CLI::ConfigOutputMode, bool write_description = false)`: 🆕
Specify how configuration output should be generated.
- `CLI::ConfigOutputMode::Active`: print active values only. Same as
`.config_to_str()`.
- `CLI::ConfigOutputMode::AllDefaults`: include defaulted arguments for the
app and all subcommands. Same as `.config_to_str(true, ...)`.
- `CLI::ConfigOutputMode::ActiveSubcommandDefaults`: include defaulted
arguments for the app and active subcommands, while omitting defaults from
inactive subcommands.

The `write_description` argument will include the app and option descriptions.
See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html)
for some additional details and customization points.

If it is desired that multiple configuration be allowed. Use

Expand Down
32 changes: 28 additions & 4 deletions book/chapters/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,26 @@ 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
To print a configuration file from the passed arguments, use one of the
following overloads:

- `.config_to_str()`: Print active values only.
- `.config_to_str(bool default_also, bool write_description = false)`: Print
active values, or include defaulted arguments if `default_also` is `true`.
This overload will likely be deprecated in a future release in favor of the
`CLI::ConfigOutputMode` overload.
- `.config_to_str(CLI::ConfigOutputMode, bool write_description = false)`: 🆕
Specify how configuration output should be generated.
- `CLI::ConfigOutputMode::Active`: print active values only. Same as
`.config_to_str()`.
- `CLI::ConfigOutputMode::AllDefaults`: include defaulted arguments for the
app and all subcommands. Same as `.config_to_str(true, ...)`.
- `CLI::ConfigOutputMode::ActiveSubcommandDefaults`: include defaulted
arguments for the app and active subcommands, while omitting defaults from
inactive subcommands.

The `write_description` argument will include option descriptions and the App
description.

```cpp
CLI::App app;
Expand All @@ -332,6 +348,14 @@ for just a subcommand, the config formatter can be obtained directly.
//prefix can be used to set a prefix before each argument, like "sub."
```

The formatter also has an overload taking `CLI::ConfigOutputMode`:

```cpp
auto fmtr=app.get_config_formatter();
//std::string to_config(const App *app, CLI::ConfigOutputMode mode, bool write_description, std::string prefix)
fmtr->to_config(&app,CLI::ConfigOutputMode::ActiveSubcommandDefaults,true,"sub.");
```

### Customization of configure file output

The default config parser/generator has some customization points that allow
Expand Down
15 changes: 13 additions & 2 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1076,8 +1076,19 @@ 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. write_descriptions will print a description for the App and for each option.
CLI11_NODISCARD std::string config_to_str(bool default_also = false, bool write_description = false) const {
return config_formatter_->to_config(this, default_also, write_description, "");
CLI11_NODISCARD std::string config_to_str() const { return config_to_str(ConfigOutputMode::Active, false); }

/// Produce a string that could be read in as a config of the current values of the App.
CLI11_NODISCARD std::string config_to_str(ConfigOutputMode mode, bool write_description = false) const {
return config_formatter_->to_config(this, mode, write_description, "");
}

/// 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. write_descriptions will print a description for the App and for each option.
/// This will be deprecated soon, use the version that takes a ConfigOutputMode instead.
CLI11_NODISCARD std::string config_to_str(bool default_also, bool write_description = false) const {
return config_to_str(default_also ? ConfigOutputMode::AllDefaults : ConfigOutputMode::Active,
write_description);
}

/// Makes a help message, using the currently configured formatter
Expand Down
14 changes: 14 additions & 0 deletions include/CLI/ConfigFwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@

// [CLI11:public_includes:set]
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
#include "Encoding.hpp"
Expand All @@ -25,6 +27,9 @@ namespace CLI {

class App;

/// enumeration of output modes for writing config files
enum class ConfigOutputMode : std::uint8_t { Active = 0, AllDefaults, ActiveSubcommandDefaults };

/// Holds values to load into Options
struct ConfigItem {
/// This is the list of parents
Expand Down Expand Up @@ -54,6 +59,12 @@ class Config {
/// Convert an app into a configuration
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;

/// Convert an app into a configuration
virtual std::string
to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const {
return to_config(app, mode != ConfigOutputMode::Active, write_description, std::move(prefix));
}

/// Convert a configuration into an app
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;

Expand Down Expand Up @@ -117,6 +128,9 @@ class ConfigBase : public Config {
std::string configSection{};

public:
std::string
to_config(const App * /*app*/, ConfigOutputMode mode, bool write_description, std::string prefix) const override;

std::string
to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;

Expand Down
9 changes: 4 additions & 5 deletions include/CLI/Encoding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
// [CLI11:public_includes:end]

// [CLI11:encoding_includes:verbatim]
#ifdef CLI11_CPP17
#if defined(CLI11_CPP17) || (defined(CLI11_HAS_FILESYSTEM) && CLI11_HAS_FILESYSTEM > 0)
#include <string_view>
#endif // CLI11_CPP17

#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem>
#include <string_view> // NOLINT(build/include)
#endif // CLI11_HAS_FILESYSTEM
#endif
#endif

// [CLI11:encoding_includes:end]

namespace CLI {
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Validators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
// [CLI11:validators_hpp_filesystem:verbatim]

#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
#include <filesystem> // NOLINT(build/include)
#include <filesystem>
#else
#include <sys/stat.h>
#include <sys/types.h>
Expand Down
24 changes: 17 additions & 7 deletions include/CLI/impl/Config_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,16 @@ CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string

CLI11_INLINE std::string
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
return to_config(app,
default_also ? ConfigOutputMode::AllDefaults : ConfigOutputMode::Active,
write_description,
std::move(prefix));
}

CLI11_INLINE std::string
ConfigBase::to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const {
std::stringstream out;
const bool include_default_values = (mode != ConfigOutputMode::Active);
std::string commentLead;
commentLead.push_back(commentChar);
commentLead.push_back(' ');
Expand Down Expand Up @@ -597,7 +606,7 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
}

bool isDefault = false;
if(value.empty() && default_also) {
if(value.empty() && include_default_values) {
if(!opt->get_default_str().empty()) {
results_t res;
opt->results(res);
Expand Down Expand Up @@ -654,7 +663,7 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
auto subcommands = app->get_subcommands({});
for(const App *subcom : subcommands) {
if(subcom->get_name().empty()) {
if(!default_also && (subcom->count_all() == 0)) {
if(!include_default_values && (subcom->count_all() == 0)) {
continue;
}
if(write_description && !subcom->get_group().empty()) {
Expand All @@ -672,19 +681,20 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
out << '[' << subname << "]\n";
}
*/
out << to_config(subcom, default_also, write_description, prefix);
out << to_config(subcom, mode, write_description, prefix);
}
}

for(const App *subcom : subcommands) {
if(!subcom->get_name().empty()) {
if(!default_also && (subcom->count_all() == 0)) {
if((!include_default_values && (subcom->count_all() == 0)) ||
(mode == ConfigOutputMode::ActiveSubcommandDefaults && !app->got_subcommand(subcom))) {
continue;
}
std::string subname = subcom->get_name();
clean_name_string(subname, keyChars);

if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) {
if(subcom->get_configurable() && (app->got_subcommand(subcom) || (mode == ConfigOutputMode::AllDefaults))) {
if(!prefix.empty() || app->get_parent() == nullptr) {

out << '[' << prefix << subname << "]\n";
Expand All @@ -701,9 +711,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
}
out << '[' << subname << "]\n";
}
out << to_config(subcom, default_also, write_description, "");
out << to_config(subcom, mode, write_description, "");
} else {
out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar);
out << to_config(subcom, mode, write_description, prefix + subname + parentSeparatorChar);
}
}
}
Expand Down
87 changes: 85 additions & 2 deletions tests/ConfigFileTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -840,13 +840,17 @@ TEST_CASE_METHOD(TApp, "IniRequiredbadConfigurator", "[config]") {
}

app.set_config("--config", tmpini)->required();
app.config_formatter(std::make_shared<EvilConfig>());
auto evil = std::make_shared<EvilConfig>();
std::shared_ptr<CLI::Config> evilptr = evil;
app.config_formatter(evil);
int two{0};
app.add_option("--two", two);
REQUIRE_THROWS_AS(run(), CLI::FileError);

REQUIRE_THROWS_AS(evilptr->to_config(&app, CLI::ConfigOutputMode::Active, true, ""), CLI::FileError);
}

TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") {
TEST_CASE_METHOD(TApp, "IniNotRequiredBadConfigurator", "[config]") {

TempFile tmpini{"TestIniTmp.ini"};

Expand Down Expand Up @@ -3437,6 +3441,12 @@ TEST_CASE_METHOD(TApp, "ConfigOutputVectorCustom", "[config]") {

std::string str = app.config_to_str();
CHECK(str == "vector:{1; 2; 3}\n");
// some extra calls to test the call chain
str = V->to_config(&app, false, false, "");
CHECK(str == "vector:{1; 2; 3}\n");

str = V->to_config(&app, CLI::ConfigOutputMode::AllDefaults, false, "");
CHECK(str == "vector:{1; 2; 3}\n");
}

TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
Expand Down Expand Up @@ -3723,6 +3733,79 @@ TEST_CASE_METHOD(TApp, "ConfigWriteReadNegated", "[config]") {
CHECK_FALSE(flag);
}

// code example based on https://github.com/CLIUtils/CLI11/issues/541
TEST_CASE_METHOD(TApp, "ConfigWriteAllDefaults", "[config]") {

TempFile tmpini{"TestIniTmp.ini"};

app.set_config("--config", tmpini);

TempFile tmpexist{"existing-file.txt"};

{
std::ofstream out{tmpexist};
out << "file-test";
}

auto *create = app.add_subcommand("create");
std::string create_path{"create-path"};
create->add_option("--create-path", create_path, "A file which must not exist")
->capture_default_str()
->check(CLI::NonexistentPath);

auto *load = app.add_subcommand("load");
std::string load_path(tmpexist);
load->add_option("--load-path", load_path, "A file which must exist")
->capture_default_str()
->check(CLI::ExistingFile);
// validators run during the config to string
std::string configOut = app.config_to_str(true, false);
CHECK_THAT(configOut, Contains("create.create-path"));
CHECK_THAT(configOut, Contains("load.load-path"));

{
std::ofstream out{tmpini};
out << configOut;
}

args = {"--config", tmpini};
CHECK_NOTHROW(run());
}

// code example based on https://github.com/CLIUtils/CLI11/issues/541
TEST_CASE_METHOD(TApp, "ConfigWriteDefaultActiveSubcommands", "[config]") {

TempFile tmpini{"TestIniTmp.ini"};

app.set_config("--config", tmpini);

auto *create = app.add_subcommand("create");
std::string create_path{"create-path"};
create->add_option("--create-path", create_path)->capture_default_str()->check(CLI::NonexistentPath);

auto *load = app.add_subcommand("load");
std::string load_path{"load-path"};
load->add_option("--load-path", load_path)->capture_default_str()->check(CLI::ExistingPath);

args = {"create"};
run();

std::string activeConfig = app.config_to_str(CLI::ConfigOutputMode::ActiveSubcommandDefaults, false);
CHECK_THAT(activeConfig, Contains("create.create-path"));
CHECK_THAT(activeConfig, !Contains("load.load-path"));

{
std::ofstream out{tmpini};
out << activeConfig;
}

create_path = "not-right-path";
args = {"--config", tmpini};
CHECK_NOTHROW(run());

CHECK(create_path == "create-path");
}

/////// INI output tests

TEST_CASE_METHOD(TApp, "IniOutputSimple", "[config]") {
Expand Down
4 changes: 2 additions & 2 deletions tests/EncodingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
#endif // CLI11_HAS_FILESYSTEM

// "abcd"
static const std::string abcd_str = "abcd"; // NOLINT(runtime/string)
static const std::wstring abcd_wstr = L"abcd"; // NOLINT(runtime/string)
static const std::string abcd_str = "abcd";
static const std::wstring abcd_wstr = L"abcd";

// "𓂀𓂀𓂀" - 4-byte utf8 characters
static const std::array<uint8_t, 12 + 1> egypt_utf8_codeunits{
Expand Down
Loading