Skip to content

Commit

Permalink
start working on the book edits for configuration and a few more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
phlptp committed Dec 29, 2019
1 parent 23e77ab commit b9db74a
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 5 deletions.
67 changes: 65 additions & 2 deletions book/chapters/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ is not found and this is set to true.
Here is an example configuration file, in INI format:

```ini
; Commments are supported, using a ;
; Comments are supported, using a ;
; The default section is [default], case insensitive

value = 1
Expand All @@ -23,12 +23,69 @@ in_subcommand = Wow
sub.subcommand = true
```

Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults".
Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`,`enabled`,`active`; or `false`, `off`, `0`, `no`,`disabled`, or `deactivated` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults". If a subcommand is set to `configurable` then passing the subcommand using `[sub]` in a configuration file will trigger the subcommand.

CLI11 also supports configuration file in [TOML](https://github.com/toml-lang/toml) format.

```toml
# Comments are supported, using a #
# The default section is [default], case insensitive

value = 1
str = "A string"
vector = [1,2,3]

# Section map to subcommands
[subcommand]
in_subcommand = Wow
[subcommand.sub]
subcommand = true # could also be give as sub.subcommand=true
```

The main differences are in vector notation and command 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)

## Writing out a configure file

To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions.

### Customization of configure file output
The default config parser/generator has some customization points that allow variations on the INI format. The default formatter has a base configuration that matches the INI format. It defines 5 characters that define how different aspects of the configuration are handled
```cpp
/// the character used for comments
char commentChar = ';';
/// the character used to start an array '\0' is a default to not use
char arrayStart = '\0';
/// the character used to end an array '\0' is a default to not use
char arrayEnd = '\0';
/// the character used to separate elements in an array
char arraySeparator = ' ';
/// the character used separate the name from the value
char valueDelimiter = '=';
```

These can be modified via setter functions

- ` ConfigBase *comment(char cchar)` Specify the character to start a comment block

- `ConfigBase *arrayBounds(char aStart, char aEnd)` Specify the start and end characters for an array
///
- `ConfigBase *arrayDelimiter(char aSep)` Specify the delimiter character for an array
///
- `ConfigBase *valueSeparator(char vSep)` Specify the delimiter between a name and value

For example to specify reading a configure file that used `:` to separate name and values

```cpp
auto config_base=app.get_config_formatter_base();
config_base->valueSeparator(':');
```
The default configuration file will read TOML files, but will write out files in the INI format. To specify outputting TOML formatted files use
```cpp
app.config_formatter(std::make_shared<CLI::ConfigTOML>());
```
which makes use of a predefined modification of the ConfigBase class which INI also uses.

## Custom formats

{% hint style='info' %}
Expand All @@ -51,3 +108,9 @@ app.config_formatter(std::make_shared<NewConfig>());
```

See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/master/examples/json.cpp) for a complete JSON config example.


## Triggering Subcommands
Configuration files can be used to trigger subcommands if a subcommand is set to configure. By default configuration file just set the default values of a subcommand. But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line. Subsubcommands can be triggered via [subname.subsubname]. Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line. This functionality can allow the configuration file to act as a scripting file.

For custom configuration files this behavior can be triggered by specifying the parent subcommands in the structure and '++' as the name to open a new subcommand scope and '--' to close it. These names trigger the different callbacks of configurable subcommands.
2 changes: 1 addition & 1 deletion book/chapters/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ app.add_flag("--CaSeLeSs");
app.get_group() // is "Required"
```
Groups are mostly for visual organisation, but an empty string for a group name will hide the option.
Groups are mostly for visual organization, but an empty string for a group name will hide the option.
## Listing of specialty options:
Expand Down
7 changes: 6 additions & 1 deletion include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,12 @@ class App {
std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }

/// Access the config formatter
std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
std::shared_ptr<Config> get_config_formatter() const {
return config_formatter_;
}

/// Access the config formatter as a configBase pointer
std::shared_ptr<ConfigBase> get_config_formatter_base() const { return std::dynamic_pointer_cast<ConfigBase>(config_formatter_); }

/// Get the app or subcommand description
std::string get_description() const { return description_; }
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
section = line.substr(1, len - 2);
// deal with double brackets for TOML
if(section.size() > 1 && section.front() == '[' && section.back() == ']') {
section = section.substr(1, len - 2);
section = section.substr(1, section.size() - 2);
}
if(detail::to_lower(section) == "default") {
section = "default";
Expand Down
50 changes: 50 additions & 0 deletions tests/ConfigFileTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,32 @@ TEST_F(TApp, TOMLVector) {
EXPECT_EQ(std::vector<int>({1, 2, 3}), three);
}

TEST_F(TApp, ColonValueSep) {

TempFile tmpini{ "TestIniTmp.ini" };

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

{
std::ofstream out{ tmpini };
out << "#this is a comment line\n";
out << "[default]\n";
out << "two:2\n";
out << "three:3\n";
}

int two, three;
app.add_option("--two", two);
app.add_option("--three", three);

app.get_config_formatter_base()->valueSeparator(':');

run();

EXPECT_EQ(2, two);
EXPECT_EQ(3, three);
}

TEST_F(TApp, TOMLVectordirect) {

TempFile tmpini{"TestIniTmp.ini"};
Expand Down Expand Up @@ -741,6 +767,30 @@ TEST_F(TApp, IniSubcommandConfigurableParseComplete) {
EXPECT_EQ(subcom2->count(), 0u);
}

TEST_F(TApp, DuplicateSubcommandCallbacks) {

TempFile tmpini{ "TestIniTmp.ini" };

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

{
std::ofstream out{ tmpini };
out << "[[foo]]" << std::endl;
out << "[[foo]]" << std::endl;
out << "[[foo]]" << std::endl;
}

auto foo = app.add_subcommand("foo");
int count = 0;
foo->callback([&count]() { ++count; });
foo->immediate_callback();
EXPECT_TRUE(foo->get_immediate_callback());
foo->configurable();

run();
EXPECT_EQ(count, 3);
}

TEST_F(TApp, IniFailure) {

TempFile tmpini{"TestIniTmp.ini"};
Expand Down

0 comments on commit b9db74a

Please sign in to comment.