mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 20:23:55 +00:00
Feature config file format (#1075)
- Polish empty lines and disable description printing for apps and subcommands with empty name, empty group and with no options and subcommands. - Add commentDefaults() method to trigger to comment default value options in case default_also is true, so that the result set of an option is not pollute with default option values. - In case dafault_also is true mark required but not yet configured options as "<REQUIRED>" and not yet configured default options as "" in a commented line in the config file. --------- Co-authored-by: Volker Christian <volker.christian@fh-hagenberg.at>
This commit is contained in:
parent
8cf16fdf3f
commit
f865d2b296
@ -323,6 +323,10 @@ char literalQuote = '\'';
|
|||||||
uint8_t maximumLayers{255};
|
uint8_t maximumLayers{255};
|
||||||
/// the separator used to separator parent layers
|
/// the separator used to separator parent layers
|
||||||
char parentSeparatorChar{'.'};
|
char parentSeparatorChar{'.'};
|
||||||
|
/// comment default values
|
||||||
|
bool commentDefaultsBool = false;
|
||||||
|
/// specify the config reader should collapse repeated field names to a single vector
|
||||||
|
bool allowMultipleDuplicateFields{false};
|
||||||
/// Specify the configuration index to use for arrayed sections
|
/// Specify the configuration index to use for arrayed sections
|
||||||
uint16_t configIndex{0};
|
uint16_t configIndex{0};
|
||||||
/// Specify the configuration section that should be used
|
/// Specify the configuration section that should be used
|
||||||
@ -341,6 +345,10 @@ These can be modified via setter functions
|
|||||||
and value
|
and value
|
||||||
- `ConfigBase *quoteCharacter(char qString, char literalChar)` :specify the
|
- `ConfigBase *quoteCharacter(char qString, char literalChar)` :specify the
|
||||||
characters to use around strings and single characters
|
characters to use around strings and single characters
|
||||||
|
- `ConfigBase *commentDefaults(bool comDef)` : set to true to comment lines with
|
||||||
|
a default value
|
||||||
|
- `ConfigBase *allowDuplicateFields(bool value)` :set to true to allow duplicate
|
||||||
|
fields to be merged even if not sequential
|
||||||
- `ConfigBase *maxLayers(uint8_t layers)` : specify the maximum number of parent
|
- `ConfigBase *maxLayers(uint8_t layers)` : specify the maximum number of parent
|
||||||
layers to process. This is useful to limit processing for larger config files
|
layers to process. This is useful to limit processing for larger config files
|
||||||
- `ConfigBase *parentSeparator(char sep)` : specify the character to separate
|
- `ConfigBase *parentSeparator(char sep)` : specify the character to separate
|
||||||
@ -450,8 +458,8 @@ positional, or the environment variable name. When generating a config file it
|
|||||||
will create an option name in following priority.
|
will create an option name in following priority.
|
||||||
|
|
||||||
1. First long name
|
1. First long name
|
||||||
2. Positional name
|
2. First short name
|
||||||
3. First short name
|
3. Positional name
|
||||||
4. Environment name
|
4. Environment name
|
||||||
|
|
||||||
In config files the name will be enclosed in quotes if there is any potential
|
In config files the name will be enclosed in quotes if there is any potential
|
||||||
|
@ -251,6 +251,11 @@ add_cli_exe(custom_parse custom_parse.cpp)
|
|||||||
add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
|
add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
|
||||||
set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct")
|
set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct")
|
||||||
|
|
||||||
|
#-----------------------------------------------------
|
||||||
|
add_cli_exe(help_usage help_usage.cpp)
|
||||||
|
add_test(NAME help_use COMMAND help_usage --help)
|
||||||
|
set_property(TEST help_use PROPERTY PASS_REGULAR_EXPRESSION "[1..9]")
|
||||||
|
|
||||||
#------------------------------------------------
|
#------------------------------------------------
|
||||||
# This executable is for manual testing and is expected to change regularly
|
# This executable is for manual testing and is expected to change regularly
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner
|
// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner
|
||||||
// under NSF AWARD 1414736 and by the respective contributors.
|
// under NSF AWARD 1414736 and by the respective contributors.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
std::string input_file_name, output_file_name;
|
std::string input_file_name, output_file_name;
|
||||||
int level, subopt;
|
int level{5}, subopt{0};
|
||||||
|
|
||||||
// app caption
|
// app caption
|
||||||
CLI::App app{"CLI11 help"};
|
CLI::App app{"CLI11 help"};
|
||||||
@ -47,26 +47,29 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
$ ./help_usage -h
|
$ ./help_usage -h
|
||||||
CLI11 help
|
CLI11 help
|
||||||
Usage: help_usage <command> [options] <input-file> <output-file>
|
|
||||||
|
|
||||||
Options:
|
OPTIONS:
|
||||||
-h,--help
|
-h, --help
|
||||||
|
|
||||||
Subcommands:
|
SUBCOMMANDS:
|
||||||
e
|
e
|
||||||
encode
|
encode
|
||||||
Positionals:
|
|
||||||
input input file
|
POSITIONALS:
|
||||||
output output file
|
input input file
|
||||||
Options:
|
output output file
|
||||||
-l,--level [1..9] encoding level
|
|
||||||
-K,--remove INT remove input file
|
OPTIONS:
|
||||||
-s,--suboption suboption
|
-l, --level [1..9] encoding level
|
||||||
|
-R, --remove remove input file
|
||||||
|
-s, --suboption suboption
|
||||||
|
|
||||||
|
|
||||||
d
|
d
|
||||||
decode
|
decode
|
||||||
Positionals:
|
|
||||||
input input file
|
POSITIONALS:
|
||||||
output output file
|
input input file
|
||||||
|
output output file
|
||||||
*/
|
*/
|
||||||
|
@ -102,10 +102,12 @@ class ConfigBase : public Config {
|
|||||||
uint8_t maximumLayers{255};
|
uint8_t maximumLayers{255};
|
||||||
/// the separator used to separator parent layers
|
/// the separator used to separator parent layers
|
||||||
char parentSeparatorChar{'.'};
|
char parentSeparatorChar{'.'};
|
||||||
/// Specify the configuration index to use for arrayed sections
|
/// comment default values
|
||||||
int16_t configIndex{-1};
|
bool commentDefaultsBool = false;
|
||||||
/// specify the config reader should collapse repeated field names to a single vector
|
/// specify the config reader should collapse repeated field names to a single vector
|
||||||
bool allowMultipleDuplicateFields{false};
|
bool allowMultipleDuplicateFields{false};
|
||||||
|
/// Specify the configuration index to use for arrayed sections
|
||||||
|
int16_t configIndex{-1};
|
||||||
/// Specify the configuration section that should be used
|
/// Specify the configuration section that should be used
|
||||||
std::string configSection{};
|
std::string configSection{};
|
||||||
|
|
||||||
@ -151,6 +153,11 @@ class ConfigBase : public Config {
|
|||||||
parentSeparatorChar = sep;
|
parentSeparatorChar = sep;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/// comment default value options
|
||||||
|
ConfigBase *commentDefaults(bool comDef = true) {
|
||||||
|
commentDefaultsBool = comDef;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
/// get a reference to the configuration section
|
/// get a reference to the configuration section
|
||||||
std::string §ionRef() { return configSection; }
|
std::string §ionRef() { return configSection; }
|
||||||
/// get the section
|
/// get the section
|
||||||
|
@ -547,7 +547,7 @@ class Option : public OptionBase<Option> {
|
|||||||
|
|
||||||
/// Get the flag names with specified default values
|
/// Get the flag names with specified default values
|
||||||
CLI11_NODISCARD const std::vector<std::string> &get_fnames() const { return fnames_; }
|
CLI11_NODISCARD const std::vector<std::string> &get_fnames() const { return fnames_; }
|
||||||
/// Get a single name for the option, first of lname, pname, sname, envname
|
/// Get a single name for the option, first of lname, sname, pname, envname
|
||||||
CLI11_NODISCARD const std::string &get_single_name() const {
|
CLI11_NODISCARD const std::string &get_single_name() const {
|
||||||
if(!lnames_.empty()) {
|
if(!lnames_.empty()) {
|
||||||
return lnames_[0];
|
return lnames_[0];
|
||||||
|
@ -518,9 +518,7 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
|||||||
std::vector<std::string> groups = app->get_groups();
|
std::vector<std::string> groups = app->get_groups();
|
||||||
bool defaultUsed = false;
|
bool defaultUsed = false;
|
||||||
groups.insert(groups.begin(), std::string("OPTIONS"));
|
groups.insert(groups.begin(), std::string("OPTIONS"));
|
||||||
if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) {
|
|
||||||
out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n';
|
|
||||||
}
|
|
||||||
for(auto &group : groups) {
|
for(auto &group : groups) {
|
||||||
if(group == "OPTIONS" || group.empty()) {
|
if(group == "OPTIONS" || group.empty()) {
|
||||||
if(defaultUsed) {
|
if(defaultUsed) {
|
||||||
@ -529,10 +527,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
|||||||
defaultUsed = true;
|
defaultUsed = true;
|
||||||
}
|
}
|
||||||
if(write_description && group != "OPTIONS" && !group.empty()) {
|
if(write_description && group != "OPTIONS" && !group.empty()) {
|
||||||
out << '\n' << commentLead << group << " Options\n";
|
out << '\n' << commentChar << commentLead << group << " Options\n";
|
||||||
}
|
}
|
||||||
for(const Option *opt : app->get_options({})) {
|
for(const Option *opt : app->get_options({})) {
|
||||||
|
|
||||||
// Only process options that are configurable
|
// Only process options that are configurable
|
||||||
if(opt->get_configurable()) {
|
if(opt->get_configurable()) {
|
||||||
if(opt->get_group() != group) {
|
if(opt->get_group() != group) {
|
||||||
@ -548,14 +545,18 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
|||||||
std::string value = detail::ini_join(
|
std::string value = detail::ini_join(
|
||||||
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
|
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
|
||||||
|
|
||||||
|
bool isDefault = false;
|
||||||
if(value.empty() && default_also) {
|
if(value.empty() && default_also) {
|
||||||
if(!opt->get_default_str().empty()) {
|
if(!opt->get_default_str().empty()) {
|
||||||
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, false);
|
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, false);
|
||||||
} else if(opt->get_expected_min() == 0) {
|
} else if(opt->get_expected_min() == 0) {
|
||||||
value = "false";
|
value = "false";
|
||||||
} else if(opt->get_run_callback_for_default()) {
|
} else if(opt->get_run_callback_for_default() || !opt->get_required()) {
|
||||||
value = "\"\""; // empty string default value
|
value = "\"\""; // empty string default value
|
||||||
|
} else {
|
||||||
|
value = "\"<REQUIRED>\"";
|
||||||
}
|
}
|
||||||
|
isDefault = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!value.empty()) {
|
if(!value.empty()) {
|
||||||
@ -581,18 +582,23 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(write_description && opt->has_description()) {
|
if(write_description && opt->has_description()) {
|
||||||
out << '\n';
|
if(out.tellp() != std::streampos(0)) {
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
|
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
|
||||||
}
|
}
|
||||||
clean_name_string(single_name, keyChars);
|
clean_name_string(single_name, keyChars);
|
||||||
|
|
||||||
std::string name = prefix + single_name;
|
std::string name = prefix + single_name;
|
||||||
|
if(commentDefaultsBool && isDefault) {
|
||||||
|
name = commentChar + name;
|
||||||
|
}
|
||||||
out << name << valueDelimiter << value << '\n';
|
out << name << valueDelimiter << value << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto subcommands = app->get_subcommands({});
|
auto subcommands = app->get_subcommands({});
|
||||||
for(const App *subcom : subcommands) {
|
for(const App *subcom : subcommands) {
|
||||||
if(subcom->get_name().empty()) {
|
if(subcom->get_name().empty()) {
|
||||||
@ -650,6 +656,11 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(write_description && !out.str().empty()) {
|
||||||
|
std::string outString =
|
||||||
|
commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
|
||||||
|
return outString + out.str();
|
||||||
|
}
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
// [CLI11:config_inl_hpp:end]
|
// [CLI11:config_inl_hpp:end]
|
||||||
|
@ -3189,6 +3189,9 @@ TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") {
|
|||||||
TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") {
|
TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") {
|
||||||
app.description("Some short app description.\n"
|
app.description("Some short app description.\n"
|
||||||
"That has multiple lines.");
|
"That has multiple lines.");
|
||||||
|
// for descriptions to show up needs an option that was set
|
||||||
|
app.add_option("--test");
|
||||||
|
args = {"--test", "55"};
|
||||||
run();
|
run();
|
||||||
|
|
||||||
std::string str = app.config_to_str(true, true);
|
std::string str = app.config_to_str(true, true);
|
||||||
@ -3376,6 +3379,24 @@ TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") {
|
|||||||
|
|
||||||
str = app.config_to_str(true);
|
str = app.config_to_str(true);
|
||||||
CHECK_THAT(str, Contains("simple=7"));
|
CHECK_THAT(str, Contains("simple=7"));
|
||||||
|
|
||||||
|
app.get_config_formatter_base()->commentDefaults();
|
||||||
|
str = app.config_to_str(true);
|
||||||
|
CHECK_THAT(str, Contains("#simple=7"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(TApp, "TomlOutputDefaultRequired", "[config]") {
|
||||||
|
|
||||||
|
int v{7};
|
||||||
|
auto *opt = app.add_option("--simple", v);
|
||||||
|
opt->required()->run_callback_for_default(false);
|
||||||
|
|
||||||
|
std::string str = app.config_to_str(true);
|
||||||
|
CHECK_THAT(str, Contains("simple=\"<REQUIRED>\""));
|
||||||
|
|
||||||
|
opt->required(false);
|
||||||
|
str = app.config_to_str(true);
|
||||||
|
CHECK_THAT(str, Contains("simple=\"\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
|
TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
|
||||||
@ -3690,6 +3711,10 @@ TEST_CASE_METHOD(TApp, "IniOutputAppMultiLineDescription", "[config]") {
|
|||||||
app.description("Some short app description.\n"
|
app.description("Some short app description.\n"
|
||||||
"That has multiple lines.");
|
"That has multiple lines.");
|
||||||
app.config_formatter(std::make_shared<CLI::ConfigINI>());
|
app.config_formatter(std::make_shared<CLI::ConfigINI>());
|
||||||
|
|
||||||
|
// for descriptions to show up needs an option that was set
|
||||||
|
app.add_option("--test");
|
||||||
|
args = {"--test", "66"};
|
||||||
run();
|
run();
|
||||||
|
|
||||||
std::string str = app.config_to_str(true, true);
|
std::string str = app.config_to_str(true, true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user