Commit 967bfe0e0228afa8f49c2c08603c2b6ecc02babf

Authored by Philip Top
Committed by GitHub
1 parent d8a5bdc2

Set the default configuration file output as TOML (#435)

* Set the default configuration file output as TOML

* update formatting
README.md
@@ -679,39 +679,39 @@ app.set_config(option_name="", @@ -679,39 +679,39 @@ app.set_config(option_name="",
679 required=false) 679 required=false)
680 ``` 680 ```
681 681
682 -If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format by default, The reader can also accept many files in [TOML] format ๐Ÿ†•. (other formats can be added by an adept user, some variations are available through customization points in the default formatter). An example of a file: 682 +If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML] format by default ๐Ÿšง, though the default reader can also accept files in INI format as well ๐Ÿ†•. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file ๐Ÿ†•:
683 683
684 ```ini 684 ```ini
685 -; Comments are supported, using a ;  
686 -; The default section is [default], case insensitive 685 +# Comments are supported, using a #
  686 +# The default section is [default], case insensitive
687 687
688 value = 1 688 value = 1
689 str = "A string" 689 str = "A string"
690 -vector = 1 2 3  
691 -str_vector = "one" "two" "and three" 690 +vector = [1,2,3]
  691 +str_vector = ["one","two","and three"]
692 692
693 -; Sections map to subcommands 693 +# Sections map to subcommands
694 [subcommand] 694 [subcommand]
695 in_subcommand = Wow 695 in_subcommand = Wow
696 sub.subcommand = true 696 sub.subcommand = true
697 ``` 697 ```
698 - or equivalently in TOML ๐Ÿ†•  
699 -```toml  
700 -# Comments are supported, using a #  
701 -# The default section is [default], case insensitive 698 +or equivalently in INI format
  699 +```ini
  700 +; Comments are supported, using a ;
  701 +; The default section is [default], case insensitive
702 702
703 value = 1 703 value = 1
704 str = "A string" 704 str = "A string"
705 -vector = [1,2,3]  
706 -str_vector = ["one","two","and three"] 705 +vector = 1 2 3
  706 +str_vector = "one" "two" "and three"
707 707
708 -# Sections map to subcommands 708 +; Sections map to subcommands
709 [subcommand] 709 [subcommand]
710 in_subcommand = Wow 710 in_subcommand = Wow
711 sub.subcommand = true 711 sub.subcommand = true
712 ``` 712 ```
713 713
714 -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`, `enable`; or `false`, `off`, `0`, `no`, `disable` (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"). You cannot set positional-only arguments. ๐Ÿ†• Subcommands can be triggered from config files if the `configurable` flag was set on the subcommand. Then use `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line. 714 +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`, `enable`; or `false`, `off`, `0`, `no`, `disable` (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"). You cannot set positional-only arguments. ๐Ÿ†• Subcommands can be triggered from 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.
715 715
716 To print a configuration file from the passed 716 To print a configuration file from the passed
717 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. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. 717 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. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details.
@@ -744,7 +744,7 @@ The App class was designed allow toolkits to subclass it, to provide preset defa @@ -744,7 +744,7 @@ The App class was designed allow toolkits to subclass it, to provide preset defa
744 but before run behavior, while 744 but before run behavior, while
745 still giving the user freedom to `callback` on the main app. 745 still giving the user freedom to `callback` on the main app.
746 746
747 -The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a string; the optional bool should be set to true if you are 747 +The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a string; the optional boolean should be set to true if you are
748 including the program name in the string, and false otherwise. 748 including the program name in the string, and false otherwise.
749 749
750 Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner. 750 Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner.
book/chapters/config.md
@@ -41,39 +41,39 @@ If it is needed to get the configuration file name used this can be obtained via @@ -41,39 +41,39 @@ If it is needed to get the configuration file name used this can be obtained via
41 41
42 ## Configure file format 42 ## Configure file format
43 43
44 -Here is an example configuration file, in INI format: 44 +Here is an example configuration file, in [TOML](https://github.com/toml-lang/toml) format:
45 45
46 ```ini 46 ```ini
47 -; Comments are supported, using a ;  
48 -; The default section is [default], case insensitive 47 +# Comments are supported, using a #
  48 +# The default section is [default], case insensitive
49 49
50 value = 1 50 value = 1
51 str = "A string" 51 str = "A string"
52 -vector = 1 2 3 52 +vector = [1,2,3]
53 53
54 -; Section map to subcommands 54 +# Section map to subcommands
55 [subcommand] 55 [subcommand]
56 in_subcommand = Wow 56 in_subcommand = Wow
57 -sub.subcommand = true 57 +[subcommand.sub]
  58 +subcommand = true # could also be give as sub.subcommand=true
58 ``` 59 ```
59 60
60 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`, `y`, `t`, `+`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `n`, `f`, `-`, `disable`, (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.) 61 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`, `y`, `t`, `+`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `n`, `f`, `-`, `disable`, (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.)
61 62
62 -CLI11 also supports configuration file in [TOML](https://github.com/toml-lang/toml) format. 63 +CLI11 also supports configuration file in INI format.
63 64
64 -```toml  
65 -# Comments are supported, using a #  
66 -# The default section is [default], case insensitive 65 +```ini
  66 +; Comments are supported, using a ;
  67 +; The default section is [default], case insensitive
67 68
68 value = 1 69 value = 1
69 str = "A string" 70 str = "A string"
70 -vector = [1,2,3] 71 +vector = 1 2 3
71 72
72 -# Section map to subcommands 73 +; Section map to subcommands
73 [subcommand] 74 [subcommand]
74 in_subcommand = Wow 75 in_subcommand = Wow
75 -[subcommand.sub]  
76 -subcommand = true # could also be give as sub.subcommand=true 76 +sub.subcommand = true
77 ``` 77 ```
78 78
79 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. 79 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.
@@ -83,16 +83,16 @@ The main differences are in vector notation and comment character. Note: CLI11 @@ -83,16 +83,16 @@ The main differences are in vector notation and comment character. Note: CLI11
83 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. 83 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.
84 84
85 ### Customization of configure file output 85 ### Customization of configure file output
86 -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 86 +The default config parser/generator has some customization points that allow variations on the TOML format. The default formatter has a base configuration that matches the TOML format. It defines 5 characters that define how different aspects of the configuration are handled
87 ```cpp 87 ```cpp
88 /// the character used for comments 88 /// the character used for comments
89 -char commentChar = ';'; 89 +char commentChar = '#';
90 /// the character used to start an array '\0' is a default to not use 90 /// the character used to start an array '\0' is a default to not use
91 -char arrayStart = '\0'; 91 +char arrayStart = '[';
92 /// the character used to end an array '\0' is a default to not use 92 /// the character used to end an array '\0' is a default to not use
93 -char arrayEnd = '\0'; 93 +char arrayEnd = ']';
94 /// the character used to separate elements in an array 94 /// the character used to separate elements in an array
95 -char arraySeparator = ' '; 95 +char arraySeparator = ',';
96 /// the character used separate the name from the value 96 /// the character used separate the name from the value
97 char valueDelimiter = '='; 97 char valueDelimiter = '=';
98 ``` 98 ```
@@ -111,11 +111,11 @@ auto config_base=app.get_config_formatter_base(); @@ -111,11 +111,11 @@ auto config_base=app.get_config_formatter_base();
111 config_base->valueSeparator(':'); 111 config_base->valueSeparator(':');
112 ``` 112 ```
113 113
114 -The default configuration file will read TOML files, but will write out files in the INI format. To specify outputting TOML formatted files use 114 +The default configuration file will read INI files, but will write out files in the TOML format. To specify outputting INI formatted files use
115 ```cpp 115 ```cpp
116 -app.config_formatter(std::make_shared<CLI::ConfigTOML>()); 116 +app.config_formatter(std::make_shared<CLI::ConfigINI>());
117 ``` 117 ```
118 -which makes use of a predefined modification of the ConfigBase class which INI also uses. 118 +which makes use of a predefined modification of the ConfigBase class which TOML also uses.
119 119
120 ## Custom formats 120 ## Custom formats
121 121
include/CLI/App.hpp
@@ -247,7 +247,7 @@ class App { @@ -247,7 +247,7 @@ class App {
247 Option *config_ptr_{nullptr}; 247 Option *config_ptr_{nullptr};
248 248
249 /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) 249 /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
250 - std::shared_ptr<Config> config_formatter_{new ConfigINI()}; 250 + std::shared_ptr<Config> config_formatter_{new ConfigTOML()};
251 251
252 ///@} 252 ///@}
253 253
include/CLI/Config.hpp
@@ -167,10 +167,11 @@ inline std::vector&lt;ConfigItem&gt; ConfigBase::from_config(std::istream &amp;input) cons @@ -167,10 +167,11 @@ inline std::vector&lt;ConfigItem&gt; ConfigBase::from_config(std::istream &amp;input) cons
167 std::string section = "default"; 167 std::string section = "default";
168 168
169 std::vector<ConfigItem> output; 169 std::vector<ConfigItem> output;
170 - bool defaultArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;  
171 - char aStart = (defaultArray) ? '[' : arrayStart;  
172 - char aEnd = (defaultArray) ? ']' : arrayEnd;  
173 - char aSep = (defaultArray && arraySeparator == ' ') ? ',' : arraySeparator; 170 + bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
  171 + bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
  172 + char aStart = (isINIArray) ? '[' : arrayStart;
  173 + char aEnd = (isINIArray) ? ']' : arrayEnd;
  174 + char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
174 175
175 while(getline(input, line)) { 176 while(getline(input, line)) {
176 std::vector<std::string> items_buffer; 177 std::vector<std::string> items_buffer;
@@ -212,9 +213,9 @@ inline std::vector&lt;ConfigItem&gt; ConfigBase::from_config(std::istream &amp;input) cons @@ -212,9 +213,9 @@ inline std::vector&lt;ConfigItem&gt; ConfigBase::from_config(std::istream &amp;input) cons
212 std::string item = detail::trim_copy(line.substr(pos + 1)); 213 std::string item = detail::trim_copy(line.substr(pos + 1));
213 if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) { 214 if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) {
214 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); 215 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
215 - } else if(defaultArray && item.find_first_of(aSep) != std::string::npos) { 216 + } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
216 items_buffer = detail::split_up(item, aSep); 217 items_buffer = detail::split_up(item, aSep);
217 - } else if(defaultArray && item.find_first_of(' ') != std::string::npos) { 218 + } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
218 items_buffer = detail::split_up(item); 219 items_buffer = detail::split_up(item);
219 } else { 220 } else {
220 items_buffer = {item}; 221 items_buffer = {item};
include/CLI/ConfigFwd.hpp
@@ -71,17 +71,17 @@ class Config { @@ -71,17 +71,17 @@ class Config {
71 virtual ~Config() = default; 71 virtual ~Config() = default;
72 }; 72 };
73 73
74 -/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML 74 +/// This converter works with INI/TOML files; to write INI files use ConfigINI
75 class ConfigBase : public Config { 75 class ConfigBase : public Config {
76 protected: 76 protected:
77 /// the character used for comments 77 /// the character used for comments
78 - char commentChar = ';'; 78 + char commentChar = '#';
79 /// the character used to start an array '\0' is a default to not use 79 /// the character used to start an array '\0' is a default to not use
80 - char arrayStart = '\0'; 80 + char arrayStart = '[';
81 /// the character used to end an array '\0' is a default to not use 81 /// the character used to end an array '\0' is a default to not use
82 - char arrayEnd = '\0'; 82 + char arrayEnd = ']';
83 /// the character used to separate elements in an array 83 /// the character used to separate elements in an array
84 - char arraySeparator = ' '; 84 + char arraySeparator = ',';
85 /// the character used separate the name from the value 85 /// the character used separate the name from the value
86 char valueDelimiter = '='; 86 char valueDelimiter = '=';
87 87
@@ -113,18 +113,18 @@ class ConfigBase : public Config { @@ -113,18 +113,18 @@ class ConfigBase : public Config {
113 } 113 }
114 }; 114 };
115 115
116 -/// the default Config is the INI file format  
117 -using ConfigINI = ConfigBase; 116 +/// the default Config is the TOML file format
  117 +using ConfigTOML = ConfigBase;
118 118
119 -/// ConfigTOML generates a TOML compliant output  
120 -class ConfigTOML : public ConfigINI { 119 +/// ConfigINI generates a "standard" INI compliant output
  120 +class ConfigINI : public ConfigTOML {
121 121
122 public: 122 public:
123 - ConfigTOML() {  
124 - commentChar = '#';  
125 - arrayStart = '[';  
126 - arrayEnd = ']';  
127 - arraySeparator = ','; 123 + ConfigINI() {
  124 + commentChar = ';';
  125 + arrayStart = '\0';
  126 + arrayEnd = '\0';
  127 + arraySeparator = ' ';
128 valueDelimiter = '='; 128 valueDelimiter = '=';
129 } 129 }
130 }; 130 };
tests/ConfigFileTest.cpp
@@ -690,12 +690,12 @@ TEST_F(TApp, IniVector) { @@ -690,12 +690,12 @@ TEST_F(TApp, IniVector) {
690 } 690 }
691 TEST_F(TApp, TOMLVector) { 691 TEST_F(TApp, TOMLVector) {
692 692
693 - TempFile tmpini{"TestIniTmp.ini"}; 693 + TempFile tmptoml{"TestTomlTmp.toml"};
694 694
695 - app.set_config("--config", tmpini); 695 + app.set_config("--config", tmptoml);
696 696
697 { 697 {
698 - std::ofstream out{tmpini}; 698 + std::ofstream out{tmptoml};
699 out << "#this is a comment line\n"; 699 out << "#this is a comment line\n";
700 out << "[default]\n"; 700 out << "[default]\n";
701 out << "two=[2,3]\n"; 701 out << "two=[2,3]\n";
@@ -1050,12 +1050,12 @@ TEST_F(TApp, IniSubcommandMultipleSections) { @@ -1050,12 +1050,12 @@ TEST_F(TApp, IniSubcommandMultipleSections) {
1050 1050
1051 TEST_F(TApp, DuplicateSubcommandCallbacks) { 1051 TEST_F(TApp, DuplicateSubcommandCallbacks) {
1052 1052
1053 - TempFile tmpini{"TestIniTmp.ini"}; 1053 + TempFile tmptoml{"TesttomlTmp.toml"};
1054 1054
1055 - app.set_config("--config", tmpini); 1055 + app.set_config("--config", tmptoml);
1056 1056
1057 { 1057 {
1058 - std::ofstream out{tmpini}; 1058 + std::ofstream out{tmptoml};
1059 out << "[[foo]]" << std::endl; 1059 out << "[[foo]]" << std::endl;
1060 out << "[[foo]]" << std::endl; 1060 out << "[[foo]]" << std::endl;
1061 out << "[[foo]]" << std::endl; 1061 out << "[[foo]]" << std::endl;
@@ -1195,6 +1195,7 @@ TEST_F(TApp, IniFlagDual) { @@ -1195,6 +1195,7 @@ TEST_F(TApp, IniFlagDual) {
1195 TempFile tmpini{"TestIniTmp.ini"}; 1195 TempFile tmpini{"TestIniTmp.ini"};
1196 1196
1197 bool boo{false}; 1197 bool boo{false};
  1198 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
1198 app.add_flag("--flag", boo); 1199 app.add_flag("--flag", boo);
1199 app.set_config("--config", tmpini); 1200 app.set_config("--config", tmpini);
1200 1201
@@ -1362,7 +1363,7 @@ TEST_F(TApp, IniFalseFlagsDefDisableOverrideSuccess) { @@ -1362,7 +1363,7 @@ TEST_F(TApp, IniFalseFlagsDefDisableOverrideSuccess) {
1362 EXPECT_EQ(15, val); 1363 EXPECT_EQ(15, val);
1363 } 1364 }
1364 1365
1365 -TEST_F(TApp, IniOutputSimple) { 1366 +TEST_F(TApp, TomlOutputSimple) {
1366 1367
1367 int v{0}; 1368 int v{0};
1368 app.add_option("--simple", v); 1369 app.add_option("--simple", v);
@@ -1375,7 +1376,7 @@ TEST_F(TApp, IniOutputSimple) { @@ -1375,7 +1376,7 @@ TEST_F(TApp, IniOutputSimple) {
1375 EXPECT_EQ("simple=3\n", str); 1376 EXPECT_EQ("simple=3\n", str);
1376 } 1377 }
1377 1378
1378 -TEST_F(TApp, IniOutputNoConfigurable) { 1379 +TEST_F(TApp, TomlOutputNoConfigurable) {
1379 1380
1380 int v1{0}, v2{0}; 1381 int v1{0}, v2{0};
1381 app.add_option("--simple", v1); 1382 app.add_option("--simple", v1);
@@ -1389,7 +1390,7 @@ TEST_F(TApp, IniOutputNoConfigurable) { @@ -1389,7 +1390,7 @@ TEST_F(TApp, IniOutputNoConfigurable) {
1389 EXPECT_EQ("simple=3\n", str); 1390 EXPECT_EQ("simple=3\n", str);
1390 } 1391 }
1391 1392
1392 -TEST_F(TApp, IniOutputShortSingleDescription) { 1393 +TEST_F(TApp, TomlOutputShortSingleDescription) {
1393 std::string flag = "some_flag"; 1394 std::string flag = "some_flag";
1394 const std::string description = "Some short description."; 1395 const std::string description = "Some short description.";
1395 app.add_flag("--" + flag, description); 1396 app.add_flag("--" + flag, description);
@@ -1397,10 +1398,10 @@ TEST_F(TApp, IniOutputShortSingleDescription) { @@ -1397,10 +1398,10 @@ TEST_F(TApp, IniOutputShortSingleDescription) {
1397 run(); 1398 run();
1398 1399
1399 std::string str = app.config_to_str(true, true); 1400 std::string str = app.config_to_str(true, true);
1400 - EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n")); 1401 + EXPECT_THAT(str, HasSubstr("# " + description + "\n" + flag + "=false\n"));
1401 } 1402 }
1402 1403
1403 -TEST_F(TApp, IniOutputShortDoubleDescription) { 1404 +TEST_F(TApp, TomlOutputShortDoubleDescription) {
1404 std::string flag1 = "flagnr1"; 1405 std::string flag1 = "flagnr1";
1405 std::string flag2 = "flagnr2"; 1406 std::string flag2 = "flagnr2";
1406 const std::string description1 = "First description."; 1407 const std::string description1 = "First description.";
@@ -1412,10 +1413,10 @@ TEST_F(TApp, IniOutputShortDoubleDescription) { @@ -1412,10 +1413,10 @@ TEST_F(TApp, IniOutputShortDoubleDescription) {
1412 1413
1413 std::string str = app.config_to_str(true, true); 1414 std::string str = app.config_to_str(true, true);
1414 EXPECT_THAT( 1415 EXPECT_THAT(
1415 - str, HasSubstr("; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n")); 1416 + str, HasSubstr("# " + description1 + "\n" + flag1 + "=false\n\n# " + description2 + "\n" + flag2 + "=false\n"));
1416 } 1417 }
1417 1418
1418 -TEST_F(TApp, IniOutputGroups) { 1419 +TEST_F(TApp, TomlOutputGroups) {
1419 std::string flag1 = "flagnr1"; 1420 std::string flag1 = "flagnr1";
1420 std::string flag2 = "flagnr2"; 1421 std::string flag2 = "flagnr2";
1421 const std::string description1 = "First description."; 1422 const std::string description1 = "First description.";
@@ -1430,7 +1431,7 @@ TEST_F(TApp, IniOutputGroups) { @@ -1430,7 +1431,7 @@ TEST_F(TApp, IniOutputGroups) {
1430 EXPECT_THAT(str, HasSubstr("group2")); 1431 EXPECT_THAT(str, HasSubstr("group2"));
1431 } 1432 }
1432 1433
1433 -TEST_F(TApp, IniOutputHiddenOptions) { 1434 +TEST_F(TApp, TomlOutputHiddenOptions) {
1434 std::string flag1 = "flagnr1"; 1435 std::string flag1 = "flagnr1";
1435 std::string flag2 = "flagnr2"; 1436 std::string flag2 = "flagnr2";
1436 double val{12.7}; 1437 double val{12.7};
@@ -1454,7 +1455,7 @@ TEST_F(TApp, IniOutputHiddenOptions) { @@ -1454,7 +1455,7 @@ TEST_F(TApp, IniOutputHiddenOptions) {
1454 EXPECT_EQ(loc, std::string::npos); 1455 EXPECT_EQ(loc, std::string::npos);
1455 } 1456 }
1456 1457
1457 -TEST_F(TApp, IniOutputMultiLineDescription) { 1458 +TEST_F(TApp, TomlOutputMultiLineDescription) {
1458 std::string flag = "some_flag"; 1459 std::string flag = "some_flag";
1459 const std::string description = "Some short description.\nThat has lines."; 1460 const std::string description = "Some short description.\nThat has lines.";
1460 app.add_flag("--" + flag, description); 1461 app.add_flag("--" + flag, description);
@@ -1462,12 +1463,12 @@ TEST_F(TApp, IniOutputMultiLineDescription) { @@ -1462,12 +1463,12 @@ TEST_F(TApp, IniOutputMultiLineDescription) {
1462 run(); 1463 run();
1463 1464
1464 std::string str = app.config_to_str(true, true); 1465 std::string str = app.config_to_str(true, true);
1465 - EXPECT_THAT(str, HasSubstr("; Some short description.\n"));  
1466 - EXPECT_THAT(str, HasSubstr("; That has lines.\n")); 1466 + EXPECT_THAT(str, HasSubstr("# Some short description.\n"));
  1467 + EXPECT_THAT(str, HasSubstr("# That has lines.\n"));
1467 EXPECT_THAT(str, HasSubstr(flag + "=false\n")); 1468 EXPECT_THAT(str, HasSubstr(flag + "=false\n"));
1468 } 1469 }
1469 1470
1470 -TEST_F(TApp, IniOutputOptionGroup) { 1471 +TEST_F(TApp, TomlOutputOptionGroup) {
1471 std::string flag1 = "flagnr1"; 1472 std::string flag1 = "flagnr1";
1472 std::string flag2 = "flagnr2"; 1473 std::string flag2 = "flagnr2";
1473 double val{12.7}; 1474 double val{12.7};
@@ -1496,20 +1497,7 @@ TEST_F(TApp, IniOutputOptionGroup) { @@ -1496,20 +1497,7 @@ TEST_F(TApp, IniOutputOptionGroup) {
1496 EXPECT_GT(locg3, locg1); 1497 EXPECT_GT(locg3, locg1);
1497 } 1498 }
1498 1499
1499 -TEST_F(TApp, IniOutputVector) {  
1500 -  
1501 - std::vector<int> v;  
1502 - app.add_option("--vector", v);  
1503 -  
1504 - args = {"--vector", "1", "2", "3"};  
1505 -  
1506 - run();  
1507 -  
1508 - std::string str = app.config_to_str();  
1509 - EXPECT_EQ("vector=1 2 3\n", str);  
1510 -}  
1511 -  
1512 -TEST_F(TApp, IniOutputVectorTOML) { 1500 +TEST_F(TApp, TomlOutputVector) {
1513 1501
1514 std::vector<int> v; 1502 std::vector<int> v;
1515 app.add_option("--vector", v); 1503 app.add_option("--vector", v);
@@ -1522,7 +1510,7 @@ TEST_F(TApp, IniOutputVectorTOML) { @@ -1522,7 +1510,7 @@ TEST_F(TApp, IniOutputVectorTOML) {
1522 EXPECT_EQ("vector=[1, 2, 3]\n", str); 1510 EXPECT_EQ("vector=[1, 2, 3]\n", str);
1523 } 1511 }
1524 1512
1525 -TEST_F(TApp, IniOutputVectorCustom) { 1513 +TEST_F(TApp, ConfigOutputVectorCustom) {
1526 1514
1527 std::vector<int> v; 1515 std::vector<int> v;
1528 app.add_option("--vector", v); 1516 app.add_option("--vector", v);
@@ -1537,7 +1525,7 @@ TEST_F(TApp, IniOutputVectorCustom) { @@ -1537,7 +1525,7 @@ TEST_F(TApp, IniOutputVectorCustom) {
1537 EXPECT_EQ("vector:{1; 2; 3}\n", str); 1525 EXPECT_EQ("vector:{1; 2; 3}\n", str);
1538 } 1526 }
1539 1527
1540 -TEST_F(TApp, IniOutputFlag) { 1528 +TEST_F(TApp, TomlOutputFlag) {
1541 1529
1542 int v{0}, q{0}; 1530 int v{0}, q{0};
1543 app.add_option("--simple", v); 1531 app.add_option("--simple", v);
@@ -1553,13 +1541,13 @@ TEST_F(TApp, IniOutputFlag) { @@ -1553,13 +1541,13 @@ TEST_F(TApp, IniOutputFlag) {
1553 EXPECT_THAT(str, HasSubstr("simple=3")); 1541 EXPECT_THAT(str, HasSubstr("simple=3"));
1554 EXPECT_THAT(str, Not(HasSubstr("nothing"))); 1542 EXPECT_THAT(str, Not(HasSubstr("nothing")));
1555 EXPECT_THAT(str, HasSubstr("onething=true")); 1543 EXPECT_THAT(str, HasSubstr("onething=true"));
1556 - EXPECT_THAT(str, HasSubstr("something=true true")); 1544 + EXPECT_THAT(str, HasSubstr("something=[true, true]"));
1557 1545
1558 str = app.config_to_str(true); 1546 str = app.config_to_str(true);
1559 EXPECT_THAT(str, HasSubstr("nothing")); 1547 EXPECT_THAT(str, HasSubstr("nothing"));
1560 } 1548 }
1561 1549
1562 -TEST_F(TApp, IniOutputSet) { 1550 +TEST_F(TApp, TomlOutputSet) {
1563 1551
1564 int v{0}; 1552 int v{0};
1565 app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3})); 1553 app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
@@ -1572,7 +1560,7 @@ TEST_F(TApp, IniOutputSet) { @@ -1572,7 +1560,7 @@ TEST_F(TApp, IniOutputSet) {
1572 EXPECT_THAT(str, HasSubstr("simple=2")); 1560 EXPECT_THAT(str, HasSubstr("simple=2"));
1573 } 1561 }
1574 1562
1575 -TEST_F(TApp, IniOutputDefault) { 1563 +TEST_F(TApp, TomlOutputDefault) {
1576 1564
1577 int v{7}; 1565 int v{7};
1578 app.add_option("--simple", v, "", true); 1566 app.add_option("--simple", v, "", true);
@@ -1586,7 +1574,7 @@ TEST_F(TApp, IniOutputDefault) { @@ -1586,7 +1574,7 @@ TEST_F(TApp, IniOutputDefault) {
1586 EXPECT_THAT(str, HasSubstr("simple=7")); 1574 EXPECT_THAT(str, HasSubstr("simple=7"));
1587 } 1575 }
1588 1576
1589 -TEST_F(TApp, IniOutputSubcom) { 1577 +TEST_F(TApp, TomlOutputSubcom) {
1590 1578
1591 app.add_flag("--simple"); 1579 app.add_flag("--simple");
1592 auto subcom = app.add_subcommand("other"); 1580 auto subcom = app.add_subcommand("other");
@@ -1600,7 +1588,7 @@ TEST_F(TApp, IniOutputSubcom) { @@ -1600,7 +1588,7 @@ TEST_F(TApp, IniOutputSubcom) {
1600 EXPECT_THAT(str, HasSubstr("other.newer=true")); 1588 EXPECT_THAT(str, HasSubstr("other.newer=true"));
1601 } 1589 }
1602 1590
1603 -TEST_F(TApp, IniOutputSubcomConfigurable) { 1591 +TEST_F(TApp, TomlOutputSubcomConfigurable) {
1604 1592
1605 app.add_flag("--simple"); 1593 app.add_flag("--simple");
1606 auto subcom = app.add_subcommand("other")->configurable(); 1594 auto subcom = app.add_subcommand("other")->configurable();
@@ -1616,7 +1604,7 @@ TEST_F(TApp, IniOutputSubcomConfigurable) { @@ -1616,7 +1604,7 @@ TEST_F(TApp, IniOutputSubcomConfigurable) {
1616 EXPECT_EQ(str.find("other.newer=true"), std::string::npos); 1604 EXPECT_EQ(str.find("other.newer=true"), std::string::npos);
1617 } 1605 }
1618 1606
1619 -TEST_F(TApp, IniOutputSubsubcom) { 1607 +TEST_F(TApp, TomlOutputSubsubcom) {
1620 1608
1621 app.add_flag("--simple"); 1609 app.add_flag("--simple");
1622 auto subcom = app.add_subcommand("other"); 1610 auto subcom = app.add_subcommand("other");
@@ -1633,7 +1621,7 @@ TEST_F(TApp, IniOutputSubsubcom) { @@ -1633,7 +1621,7 @@ TEST_F(TApp, IniOutputSubsubcom) {
1633 EXPECT_THAT(str, HasSubstr("other.sub2.newest=true")); 1621 EXPECT_THAT(str, HasSubstr("other.sub2.newest=true"));
1634 } 1622 }
1635 1623
1636 -TEST_F(TApp, IniOutputSubsubcomConfigurable) { 1624 +TEST_F(TApp, TomlOutputSubsubcomConfigurable) {
1637 1625
1638 app.add_flag("--simple"); 1626 app.add_flag("--simple");
1639 auto subcom = app.add_subcommand("other")->configurable(); 1627 auto subcom = app.add_subcommand("other")->configurable();
@@ -1654,7 +1642,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurable) { @@ -1654,7 +1642,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurable) {
1654 EXPECT_EQ(str.find("sub2.newest=true"), std::string::npos); 1642 EXPECT_EQ(str.find("sub2.newest=true"), std::string::npos);
1655 } 1643 }
1656 1644
1657 -TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) { 1645 +TEST_F(TApp, TomlOutputSubsubcomConfigurableDeep) {
1658 1646
1659 app.add_flag("--simple"); 1647 app.add_flag("--simple");
1660 auto subcom = app.add_subcommand("other")->configurable(); 1648 auto subcom = app.add_subcommand("other")->configurable();
@@ -1677,7 +1665,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) { @@ -1677,7 +1665,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) {
1677 EXPECT_EQ(str.find(".absolute_newest=true"), std::string::npos); 1665 EXPECT_EQ(str.find(".absolute_newest=true"), std::string::npos);
1678 } 1666 }
1679 1667
1680 -TEST_F(TApp, IniQuotedOutput) { 1668 +TEST_F(TApp, TomlOutputQuoted) {
1681 1669
1682 std::string val1; 1670 std::string val1;
1683 app.add_option("--val1", val1); 1671 app.add_option("--val1", val1);
@@ -1697,7 +1685,7 @@ TEST_F(TApp, IniQuotedOutput) { @@ -1697,7 +1685,7 @@ TEST_F(TApp, IniQuotedOutput) {
1697 EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'")); 1685 EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'"));
1698 } 1686 }
1699 1687
1700 -TEST_F(TApp, DefaultsIniQuotedOutput) { 1688 +TEST_F(TApp, DefaultsTomlOutputQuoted) {
1701 1689
1702 std::string val1{"I am a string"}; 1690 std::string val1{"I am a string"};
1703 app.add_option("--val1", val1, "", true); 1691 app.add_option("--val1", val1, "", true);
@@ -1755,3 +1743,327 @@ TEST_F(TApp, ConfigWriteReadWrite) { @@ -1755,3 +1743,327 @@ TEST_F(TApp, ConfigWriteReadWrite) {
1755 1743
1756 EXPECT_EQ(config1, config2); 1744 EXPECT_EQ(config1, config2);
1757 } 1745 }
  1746 +
  1747 +///////INI output tests
  1748 +
  1749 +TEST_F(TApp, IniOutputSimple) {
  1750 +
  1751 + int v{0};
  1752 + app.add_option("--simple", v);
  1753 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1754 + args = {"--simple=3"};
  1755 +
  1756 + run();
  1757 +
  1758 + std::string str = app.config_to_str();
  1759 + EXPECT_EQ("simple=3\n", str);
  1760 +}
  1761 +
  1762 +TEST_F(TApp, IniOutputNoConfigurable) {
  1763 +
  1764 + int v1{0}, v2{0};
  1765 + app.add_option("--simple", v1);
  1766 + app.add_option("--noconf", v2)->configurable(false);
  1767 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1768 + args = {"--simple=3", "--noconf=2"};
  1769 +
  1770 + run();
  1771 +
  1772 + std::string str = app.config_to_str();
  1773 + EXPECT_EQ("simple=3\n", str);
  1774 +}
  1775 +
  1776 +TEST_F(TApp, IniOutputShortSingleDescription) {
  1777 + std::string flag = "some_flag";
  1778 + const std::string description = "Some short description.";
  1779 + app.add_flag("--" + flag, description);
  1780 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1781 + run();
  1782 +
  1783 + std::string str = app.config_to_str(true, true);
  1784 + EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n"));
  1785 +}
  1786 +
  1787 +TEST_F(TApp, IniOutputShortDoubleDescription) {
  1788 + std::string flag1 = "flagnr1";
  1789 + std::string flag2 = "flagnr2";
  1790 + const std::string description1 = "First description.";
  1791 + const std::string description2 = "Second description.";
  1792 + app.add_flag("--" + flag1, description1);
  1793 + app.add_flag("--" + flag2, description2);
  1794 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1795 + run();
  1796 +
  1797 + std::string str = app.config_to_str(true, true);
  1798 + EXPECT_THAT(
  1799 + str, HasSubstr("; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n"));
  1800 +}
  1801 +
  1802 +TEST_F(TApp, IniOutputGroups) {
  1803 + std::string flag1 = "flagnr1";
  1804 + std::string flag2 = "flagnr2";
  1805 + const std::string description1 = "First description.";
  1806 + const std::string description2 = "Second description.";
  1807 + app.add_flag("--" + flag1, description1)->group("group1");
  1808 + app.add_flag("--" + flag2, description2)->group("group2");
  1809 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1810 + run();
  1811 +
  1812 + std::string str = app.config_to_str(true, true);
  1813 + EXPECT_THAT(str, HasSubstr("group1"));
  1814 + EXPECT_THAT(str, HasSubstr("group2"));
  1815 +}
  1816 +
  1817 +TEST_F(TApp, IniOutputHiddenOptions) {
  1818 + std::string flag1 = "flagnr1";
  1819 + std::string flag2 = "flagnr2";
  1820 + double val{12.7};
  1821 + const std::string description1 = "First description.";
  1822 + const std::string description2 = "Second description.";
  1823 + app.add_flag("--" + flag1, description1)->group("group1");
  1824 + app.add_flag("--" + flag2, description2)->group("group2");
  1825 + app.add_option("--dval", val, "", true)->group("");
  1826 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1827 + run();
  1828 +
  1829 + std::string str = app.config_to_str(true, true);
  1830 + EXPECT_THAT(str, HasSubstr("group1"));
  1831 + EXPECT_THAT(str, HasSubstr("group2"));
  1832 + EXPECT_THAT(str, HasSubstr("dval=12.7"));
  1833 + auto loc = str.find("dval=12.7");
  1834 + auto locg1 = str.find("group1");
  1835 + EXPECT_GT(locg1, loc);
  1836 + // make sure it doesn't come twice
  1837 + loc = str.find("dval=12.7", loc + 4);
  1838 + EXPECT_EQ(loc, std::string::npos);
  1839 +}
  1840 +
  1841 +TEST_F(TApp, IniOutputMultiLineDescription) {
  1842 + std::string flag = "some_flag";
  1843 + const std::string description = "Some short description.\nThat has lines.";
  1844 + app.add_flag("--" + flag, description);
  1845 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1846 + run();
  1847 +
  1848 + std::string str = app.config_to_str(true, true);
  1849 + EXPECT_THAT(str, HasSubstr("; Some short description.\n"));
  1850 + EXPECT_THAT(str, HasSubstr("; That has lines.\n"));
  1851 + EXPECT_THAT(str, HasSubstr(flag + "=false\n"));
  1852 +}
  1853 +
  1854 +TEST_F(TApp, IniOutputOptionGroup) {
  1855 + std::string flag1 = "flagnr1";
  1856 + std::string flag2 = "flagnr2";
  1857 + double val{12.7};
  1858 + const std::string description1 = "First description.";
  1859 + const std::string description2 = "Second description.";
  1860 + app.add_flag("--" + flag1, description1)->group("group1");
  1861 + app.add_flag("--" + flag2, description2)->group("group2");
  1862 + auto og = app.add_option_group("group3", "g3 desc");
  1863 + og->add_option("--dval", val, "", true)->group("");
  1864 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1865 + run();
  1866 +
  1867 + std::string str = app.config_to_str(true, true);
  1868 + EXPECT_THAT(str, HasSubstr("group1"));
  1869 + EXPECT_THAT(str, HasSubstr("group2"));
  1870 + EXPECT_THAT(str, HasSubstr("dval=12.7"));
  1871 + EXPECT_THAT(str, HasSubstr("group3"));
  1872 + EXPECT_THAT(str, HasSubstr("g3 desc"));
  1873 + auto loc = str.find("dval=12.7");
  1874 + auto locg1 = str.find("group1");
  1875 + auto locg3 = str.find("group3");
  1876 + EXPECT_LT(locg1, loc);
  1877 + // make sure it doesn't come twice
  1878 + loc = str.find("dval=12.7", loc + 4);
  1879 + EXPECT_EQ(loc, std::string::npos);
  1880 + EXPECT_GT(locg3, locg1);
  1881 +}
  1882 +
  1883 +TEST_F(TApp, IniOutputVector) {
  1884 +
  1885 + std::vector<int> v;
  1886 + app.add_option("--vector", v);
  1887 +
  1888 + args = {"--vector", "1", "2", "3"};
  1889 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1890 + run();
  1891 +
  1892 + std::string str = app.config_to_str();
  1893 + EXPECT_EQ("vector=1 2 3\n", str);
  1894 +}
  1895 +
  1896 +TEST_F(TApp, IniOutputFlag) {
  1897 +
  1898 + int v{0}, q{0};
  1899 + app.add_option("--simple", v);
  1900 + app.add_flag("--nothing");
  1901 + app.add_flag("--onething");
  1902 + app.add_flag("--something", q);
  1903 +
  1904 + args = {"--simple=3", "--onething", "--something", "--something"};
  1905 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1906 + run();
  1907 +
  1908 + std::string str = app.config_to_str();
  1909 + EXPECT_THAT(str, HasSubstr("simple=3"));
  1910 + EXPECT_THAT(str, Not(HasSubstr("nothing")));
  1911 + EXPECT_THAT(str, HasSubstr("onething=true"));
  1912 + EXPECT_THAT(str, HasSubstr("something=true true"));
  1913 +
  1914 + str = app.config_to_str(true);
  1915 + EXPECT_THAT(str, HasSubstr("nothing"));
  1916 +}
  1917 +
  1918 +TEST_F(TApp, IniOutputSet) {
  1919 +
  1920 + int v{0};
  1921 + app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
  1922 +
  1923 + args = {"--simple=2"};
  1924 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1925 + run();
  1926 +
  1927 + std::string str = app.config_to_str();
  1928 + EXPECT_THAT(str, HasSubstr("simple=2"));
  1929 +}
  1930 +
  1931 +TEST_F(TApp, IniOutputDefault) {
  1932 +
  1933 + int v{7};
  1934 + app.add_option("--simple", v, "", true);
  1935 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1936 + run();
  1937 +
  1938 + std::string str = app.config_to_str();
  1939 + EXPECT_THAT(str, Not(HasSubstr("simple=7")));
  1940 +
  1941 + str = app.config_to_str(true);
  1942 + EXPECT_THAT(str, HasSubstr("simple=7"));
  1943 +}
  1944 +
  1945 +TEST_F(TApp, IniOutputSubcom) {
  1946 +
  1947 + app.add_flag("--simple");
  1948 + auto subcom = app.add_subcommand("other");
  1949 + subcom->add_flag("--newer");
  1950 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1951 + args = {"--simple", "other", "--newer"};
  1952 + run();
  1953 +
  1954 + std::string str = app.config_to_str();
  1955 + EXPECT_THAT(str, HasSubstr("simple=true"));
  1956 + EXPECT_THAT(str, HasSubstr("other.newer=true"));
  1957 +}
  1958 +
  1959 +TEST_F(TApp, IniOutputSubcomConfigurable) {
  1960 +
  1961 + app.add_flag("--simple");
  1962 + auto subcom = app.add_subcommand("other")->configurable();
  1963 + subcom->add_flag("--newer");
  1964 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1965 + args = {"--simple", "other", "--newer"};
  1966 + run();
  1967 +
  1968 + std::string str = app.config_to_str();
  1969 + EXPECT_THAT(str, HasSubstr("simple=true"));
  1970 + EXPECT_THAT(str, HasSubstr("[other]"));
  1971 + EXPECT_THAT(str, HasSubstr("newer=true"));
  1972 + EXPECT_EQ(str.find("other.newer=true"), std::string::npos);
  1973 +}
  1974 +
  1975 +TEST_F(TApp, IniOutputSubsubcom) {
  1976 +
  1977 + app.add_flag("--simple");
  1978 + auto subcom = app.add_subcommand("other");
  1979 + subcom->add_flag("--newer");
  1980 + auto subsubcom = subcom->add_subcommand("sub2");
  1981 + subsubcom->add_flag("--newest");
  1982 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  1983 + args = {"--simple", "other", "--newer", "sub2", "--newest"};
  1984 + run();
  1985 +
  1986 + std::string str = app.config_to_str();
  1987 + EXPECT_THAT(str, HasSubstr("simple=true"));
  1988 + EXPECT_THAT(str, HasSubstr("other.newer=true"));
  1989 + EXPECT_THAT(str, HasSubstr("other.sub2.newest=true"));
  1990 +}
  1991 +
  1992 +TEST_F(TApp, IniOutputSubsubcomConfigurable) {
  1993 +
  1994 + app.add_flag("--simple");
  1995 + auto subcom = app.add_subcommand("other")->configurable();
  1996 + subcom->add_flag("--newer");
  1997 +
  1998 + auto subsubcom = subcom->add_subcommand("sub2");
  1999 + subsubcom->add_flag("--newest");
  2000 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  2001 + args = {"--simple", "other", "--newer", "sub2", "--newest"};
  2002 + run();
  2003 +
  2004 + std::string str = app.config_to_str();
  2005 + EXPECT_THAT(str, HasSubstr("simple=true"));
  2006 + EXPECT_THAT(str, HasSubstr("[other]"));
  2007 + EXPECT_THAT(str, HasSubstr("newer=true"));
  2008 + EXPECT_THAT(str, HasSubstr("[other.sub2]"));
  2009 + EXPECT_THAT(str, HasSubstr("newest=true"));
  2010 + EXPECT_EQ(str.find("sub2.newest=true"), std::string::npos);
  2011 +}
  2012 +
  2013 +TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) {
  2014 +
  2015 + app.add_flag("--simple");
  2016 + auto subcom = app.add_subcommand("other")->configurable();
  2017 + subcom->add_flag("--newer");
  2018 +
  2019 + auto subsubcom = subcom->add_subcommand("sub2");
  2020 + subsubcom->add_flag("--newest");
  2021 + auto sssscom = subsubcom->add_subcommand("sub-level2");
  2022 + subsubcom->add_flag("--still_newer");
  2023 + auto s5com = sssscom->add_subcommand("sub-level3");
  2024 + s5com->add_flag("--absolute_newest");
  2025 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  2026 + args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
  2027 + run();
  2028 +
  2029 + std::string str = app.config_to_str();
  2030 + EXPECT_THAT(str, HasSubstr("simple=true"));
  2031 + EXPECT_THAT(str, HasSubstr("[other.sub2.sub-level2.sub-level3]"));
  2032 + EXPECT_THAT(str, HasSubstr("absolute_newest=true"));
  2033 + EXPECT_EQ(str.find(".absolute_newest=true"), std::string::npos);
  2034 +}
  2035 +
  2036 +TEST_F(TApp, IniOutputQuoted) {
  2037 +
  2038 + std::string val1;
  2039 + app.add_option("--val1", val1);
  2040 +
  2041 + std::string val2;
  2042 + app.add_option("--val2", val2);
  2043 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  2044 + args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"};
  2045 +
  2046 + run();
  2047 +
  2048 + EXPECT_EQ("I am a string", val1);
  2049 + EXPECT_EQ("I am a \"confusing\" string", val2);
  2050 +
  2051 + std::string str = app.config_to_str();
  2052 + EXPECT_THAT(str, HasSubstr("val1=\"I am a string\""));
  2053 + EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'"));
  2054 +}
  2055 +
  2056 +TEST_F(TApp, DefaultsIniOutputQuoted) {
  2057 +
  2058 + std::string val1{"I am a string"};
  2059 + app.add_option("--val1", val1, "", true);
  2060 +
  2061 + std::string val2{R"(I am a "confusing" string)"};
  2062 + app.add_option("--val2", val2, "", true);
  2063 + app.config_formatter(std::make_shared<CLI::ConfigINI>());
  2064 + run();
  2065 +
  2066 + std::string str = app.config_to_str(true);
  2067 + EXPECT_THAT(str, HasSubstr("val1=\"I am a string\""));
  2068 + EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'"));
  2069 +}