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};
|
||||
/// the separator used to separator parent layers
|
||||
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
|
||||
uint16_t configIndex{0};
|
||||
/// Specify the configuration section that should be used
|
||||
@ -341,6 +345,10 @@ These can be modified via setter functions
|
||||
and value
|
||||
- `ConfigBase *quoteCharacter(char qString, char literalChar)` :specify the
|
||||
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
|
||||
layers to process. This is useful to limit processing for larger config files
|
||||
- `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.
|
||||
|
||||
1. First long name
|
||||
2. Positional name
|
||||
3. First short name
|
||||
2. First short name
|
||||
3. Positional name
|
||||
4. Environment name
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
// All rights reserved.
|
||||
//
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string input_file_name, output_file_name;
|
||||
int level, subopt;
|
||||
int level{5}, subopt{0};
|
||||
|
||||
// app caption
|
||||
CLI::App app{"CLI11 help"};
|
||||
@ -47,26 +47,29 @@ int main(int argc, char **argv) {
|
||||
|
||||
/*
|
||||
$ ./help_usage -h
|
||||
CLI11 help
|
||||
Usage: help_usage <command> [options] <input-file> <output-file>
|
||||
CLI11 help
|
||||
|
||||
Options:
|
||||
-h,--help
|
||||
OPTIONS:
|
||||
-h, --help
|
||||
|
||||
Subcommands:
|
||||
SUBCOMMANDS:
|
||||
e
|
||||
encode
|
||||
Positionals:
|
||||
input input file
|
||||
output output file
|
||||
Options:
|
||||
-l,--level [1..9] encoding level
|
||||
-K,--remove INT remove input file
|
||||
-s,--suboption suboption
|
||||
|
||||
POSITIONALS:
|
||||
input input file
|
||||
output output file
|
||||
|
||||
OPTIONS:
|
||||
-l, --level [1..9] encoding level
|
||||
-R, --remove remove input file
|
||||
-s, --suboption suboption
|
||||
|
||||
|
||||
d
|
||||
decode
|
||||
Positionals:
|
||||
input input file
|
||||
output output file
|
||||
|
||||
POSITIONALS:
|
||||
input input file
|
||||
output output file
|
||||
*/
|
||||
|
@ -102,10 +102,12 @@ class ConfigBase : public Config {
|
||||
uint8_t maximumLayers{255};
|
||||
/// the separator used to separator parent layers
|
||||
char parentSeparatorChar{'.'};
|
||||
/// Specify the configuration index to use for arrayed sections
|
||||
int16_t configIndex{-1};
|
||||
/// 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
|
||||
int16_t configIndex{-1};
|
||||
/// Specify the configuration section that should be used
|
||||
std::string configSection{};
|
||||
|
||||
@ -151,6 +153,11 @@ class ConfigBase : public Config {
|
||||
parentSeparatorChar = sep;
|
||||
return this;
|
||||
}
|
||||
/// comment default value options
|
||||
ConfigBase *commentDefaults(bool comDef = true) {
|
||||
commentDefaultsBool = comDef;
|
||||
return this;
|
||||
}
|
||||
/// get a reference to the configuration section
|
||||
std::string §ionRef() { return configSection; }
|
||||
/// get the section
|
||||
|
@ -547,7 +547,7 @@ class Option : public OptionBase<Option> {
|
||||
|
||||
/// Get the flag names with specified default values
|
||||
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 {
|
||||
if(!lnames_.empty()) {
|
||||
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();
|
||||
bool defaultUsed = false;
|
||||
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) {
|
||||
if(group == "OPTIONS" || group.empty()) {
|
||||
if(defaultUsed) {
|
||||
@ -529,10 +527,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
|
||||
defaultUsed = true;
|
||||
}
|
||||
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({})) {
|
||||
|
||||
// Only process options that are configurable
|
||||
if(opt->get_configurable()) {
|
||||
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(
|
||||
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
|
||||
|
||||
bool isDefault = false;
|
||||
if(value.empty() && default_also) {
|
||||
if(!opt->get_default_str().empty()) {
|
||||
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, false);
|
||||
} else if(opt->get_expected_min() == 0) {
|
||||
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
|
||||
} else {
|
||||
value = "\"<REQUIRED>\"";
|
||||
}
|
||||
isDefault = true;
|
||||
}
|
||||
|
||||
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()) {
|
||||
out << '\n';
|
||||
if(out.tellp() != std::streampos(0)) {
|
||||
out << '\n';
|
||||
}
|
||||
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
|
||||
}
|
||||
clean_name_string(single_name, keyChars);
|
||||
|
||||
std::string name = prefix + single_name;
|
||||
|
||||
if(commentDefaultsBool && isDefault) {
|
||||
name = commentChar + name;
|
||||
}
|
||||
out << name << valueDelimiter << value << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto subcommands = app->get_subcommands({});
|
||||
for(const App *subcom : subcommands) {
|
||||
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();
|
||||
}
|
||||
// [CLI11:config_inl_hpp:end]
|
||||
|
@ -3189,6 +3189,9 @@ TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") {
|
||||
TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") {
|
||||
app.description("Some short app description.\n"
|
||||
"That has multiple lines.");
|
||||
// for descriptions to show up needs an option that was set
|
||||
app.add_option("--test");
|
||||
args = {"--test", "55"};
|
||||
run();
|
||||
|
||||
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);
|
||||
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]") {
|
||||
@ -3690,6 +3711,10 @@ TEST_CASE_METHOD(TApp, "IniOutputAppMultiLineDescription", "[config]") {
|
||||
app.description("Some short app description.\n"
|
||||
"That has multiple lines.");
|
||||
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();
|
||||
|
||||
std::string str = app.config_to_str(true, true);
|
||||
|
Loading…
x
Reference in New Issue
Block a user