1 // Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
2 // under NSF AWARD 1414736 and by the respective contributors.
3 // All rights reserved.
4 //
5 // SPDX-License-Identifier: BSD-3-Clause
6 
7 #include "app_helper.hpp"
8 
9 #include <cstdio>
10 #include <sstream>
11 
12 using Catch::Matchers::Contains;
13 
14 TEST_CASE("StringBased: convert_arg_for_ini", "[config]") {
15 
16     CHECK("\"\"" == CLI::detail::convert_arg_for_ini(std::string{}));
17 
18     CHECK("true" == CLI::detail::convert_arg_for_ini("true"));
19 
20     CHECK("nan" == CLI::detail::convert_arg_for_ini("nan"));
21 
22     CHECK("\"happy hippo\"" == CLI::detail::convert_arg_for_ini("happy hippo"));
23 
24     CHECK("47" == CLI::detail::convert_arg_for_ini("47"));
25 
26     CHECK("47.365225" == CLI::detail::convert_arg_for_ini("47.365225"));
27 
28     CHECK("+3.28e-25" == CLI::detail::convert_arg_for_ini("+3.28e-25"));
29     CHECK("-22E14" == CLI::detail::convert_arg_for_ini("-22E14"));
30 
31     CHECK("'a'" == CLI::detail::convert_arg_for_ini("a"));
32     // hex
33     CHECK("0x5461FAED" == CLI::detail::convert_arg_for_ini("0x5461FAED"));
34     // hex fail
35     CHECK("\"0x5461FAEG\"" == CLI::detail::convert_arg_for_ini("0x5461FAEG"));
36 
37     // octal
38     CHECK("0o546123567" == CLI::detail::convert_arg_for_ini("0o546123567"));
39     // octal fail
40     CHECK("\"0o546123587\"" == CLI::detail::convert_arg_for_ini("0o546123587"));
41 
42     // binary
43     CHECK("0b01101110010" == CLI::detail::convert_arg_for_ini("0b01101110010"));
44     // binary fail
45     CHECK("\"0b01102110010\"" == CLI::detail::convert_arg_for_ini("0b01102110010"));
46 }
47 
48 TEST_CASE("StringBased: IniJoin", "[config]") {
49     std::vector<std::string> items = {"one", "two", "three four"};
50     std::string result = "\"one\" \"two\" \"three four\"";
51 
52     CHECK(result == CLI::detail::ini_join(items, ' ', '\0', '\0'));
53 
54     result = "[\"one\", \"two\", \"three four\"]";
55 
56     CHECK(result == CLI::detail::ini_join(items));
57 
58     result = "{\"one\"; \"two\"; \"three four\"}";
59 
60     CHECK(result == CLI::detail::ini_join(items, ';', '{', '}'));
61 }
62 
63 TEST_CASE("StringBased: First", "[config]") {
64     std::stringstream ofile;
65 
66     ofile << "one=three\n";
67     ofile << "two=four\n";
68 
69     ofile.seekg(0, std::ios::beg);
70 
71     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
72 
73     CHECK(output.size() == 2u);
74     CHECK(output.at(0).name == "one");
75     CHECK(output.at(0).inputs.size() == 1u);
76     CHECK(output.at(0).inputs.at(0) == "three");
77     CHECK(output.at(1).name == "two");
78     CHECK(output.at(1).inputs.size() == 1u);
79     CHECK(output.at(1).inputs.at(0) == "four");
80 }
81 
82 TEST_CASE("StringBased: FirstWithComments", "[config]") {
83     std::stringstream ofile;
84 
85     ofile << ";this is a comment\n";
86     ofile << "one=three\n";
87     ofile << "two=four\n";
88     ofile << "; and another one\n";
89 
90     ofile.seekg(0, std::ios::beg);
91 
92     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
93 
94     CHECK(output.size() == 2u);
95     CHECK(output.at(0).name == "one");
96     CHECK(output.at(0).inputs.size() == 1u);
97     CHECK(output.at(0).inputs.at(0) == "three");
98     CHECK(output.at(1).name == "two");
99     CHECK(output.at(1).inputs.size() == 1u);
100     CHECK(output.at(1).inputs.at(0) == "four");
101 }
102 
103 TEST_CASE("StringBased: Quotes", "[config]") {
104     std::stringstream ofile;
105 
106     ofile << R"(one = "three")" << '\n';
107     ofile << R"(two = 'four')" << '\n';
108     ofile << R"(five = "six and seven")" << '\n';
109 
110     ofile.seekg(0, std::ios::beg);
111 
112     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
113 
114     CHECK(output.size() == 3u);
115     CHECK(output.at(0).name == "one");
116     CHECK(output.at(0).inputs.size() == 1u);
117     CHECK(output.at(0).inputs.at(0) == "three");
118     CHECK(output.at(1).name == "two");
119     CHECK(output.at(1).inputs.size() == 1u);
120     CHECK(output.at(1).inputs.at(0) == "four");
121     CHECK(output.at(2).name == "five");
122     CHECK(output.at(2).inputs.size() == 1u);
123     CHECK(output.at(2).inputs.at(0) == "six and seven");
124 }
125 
126 TEST_CASE("StringBased: Vector", "[config]") {
127     std::stringstream ofile;
128 
129     ofile << "one = three\n";
130     ofile << "two = four\n";
131     ofile << "five = six and seven\n";
132 
133     ofile.seekg(0, std::ios::beg);
134 
135     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
136 
137     CHECK(output.size() == 3u);
138     CHECK(output.at(0).name == "one");
139     CHECK(output.at(0).inputs.size() == 1u);
140     CHECK(output.at(0).inputs.at(0) == "three");
141     CHECK(output.at(1).name == "two");
142     CHECK(output.at(1).inputs.size() == 1u);
143     CHECK(output.at(1).inputs.at(0) == "four");
144     CHECK(output.at(2).name == "five");
145     CHECK(output.at(2).inputs.size() == 3u);
146     CHECK(output.at(2).inputs.at(0) == "six");
147     CHECK(output.at(2).inputs.at(1) == "and");
148     CHECK(output.at(2).inputs.at(2) == "seven");
149 }
150 
151 TEST_CASE("StringBased: TomlVector", "[config]") {
152     std::stringstream ofile;
153 
154     ofile << "one = [three]\n";
155     ofile << "two = [four]\n";
156     ofile << "five = [six, and, seven]\n";
157     ofile << "eight = [nine, \n"
158              "ten, eleven,     twelve    \n"
159              "]\n";
160     ofile << "one_more = [one, \n"
161              "two,     three  ]    \n";
162 
163     ofile.seekg(0, std::ios::beg);
164 
165     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
166 
167     CHECK(output.size() == 5u);
168     CHECK(output.at(0).name == "one");
169     CHECK(output.at(0).inputs.size() == 1u);
170     CHECK(output.at(0).inputs.at(0) == "three");
171     CHECK(output.at(1).name == "two");
172     CHECK(output.at(1).inputs.size() == 1u);
173     CHECK(output.at(1).inputs.at(0) == "four");
174     CHECK(output.at(2).name == "five");
175     CHECK(output.at(2).inputs.size() == 3u);
176     CHECK(output.at(2).inputs.at(0) == "six");
177     CHECK(output.at(2).inputs.at(1) == "and");
178     CHECK(output.at(2).inputs.at(2) == "seven");
179     CHECK(output.at(3).name == "eight");
180     CHECK(output.at(3).inputs.size() == 4u);
181     CHECK(output.at(3).inputs.at(0) == "nine");
182     CHECK(output.at(3).inputs.at(1) == "ten");
183     CHECK(output.at(3).inputs.at(2) == "eleven");
184     CHECK(output.at(3).inputs.at(3) == "twelve");
185     CHECK(output.at(4).name == "one_more");
186     CHECK(output.at(4).inputs.size() == 3u);
187     CHECK(output.at(4).inputs.at(0) == "one");
188     CHECK(output.at(4).inputs.at(1) == "two");
189     CHECK(output.at(4).inputs.at(2) == "three");
190 }
191 
192 TEST_CASE("StringBased: Spaces", "[config]") {
193     std::stringstream ofile;
194 
195     ofile << "one = three\n";
196     ofile << "two = four";
197 
198     ofile.seekg(0, std::ios::beg);
199 
200     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
201 
202     CHECK(output.size() == 2u);
203     CHECK(output.at(0).name == "one");
204     CHECK(output.at(0).inputs.size() == 1u);
205     CHECK(output.at(0).inputs.at(0) == "three");
206     CHECK(output.at(1).name == "two");
207     CHECK(output.at(1).inputs.size() == 1u);
208     CHECK(output.at(1).inputs.at(0) == "four");
209 }
210 
211 TEST_CASE("StringBased: Sections", "[config]") {
212     std::stringstream ofile;
213 
214     ofile << "one=three\n";
215     ofile << "[second]\n";
216     ofile << "  two=four\n";
217 
218     ofile.seekg(0, std::ios::beg);
219 
220     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
221 
222     CHECK(output.size() == 4u);
223     CHECK(output.at(0).name == "one");
224     CHECK(output.at(0).inputs.size() == 1u);
225     CHECK(output.at(0).inputs.at(0) == "three");
226     CHECK(output.at(2).name == "two");
227     CHECK(output.at(2).parents.at(0) == "second");
228     CHECK(output.at(2).inputs.size() == 1u);
229     CHECK(output.at(2).inputs.at(0) == "four");
230     CHECK(output.at(2).fullname() == "second.two");
231 }
232 
233 TEST_CASE("StringBased: SpacesSections", "[config]") {
234     std::stringstream ofile;
235 
236     ofile << "one=three\n\n";
237     ofile << "[second]   \n";
238     ofile << "   \n";
239     ofile << "  two=four\n";
240 
241     ofile.seekg(0, std::ios::beg);
242 
243     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
244 
245     CHECK(output.size() == 4u);
246     CHECK(output.at(0).name == "one");
247     CHECK(output.at(0).inputs.size() == 1u);
248     CHECK(output.at(0).inputs.at(0) == "three");
249     CHECK(output.at(1).parents.at(0) == "second");
250     CHECK(output.at(1).name == "++");
251     CHECK(output.at(2).name == "two");
252     CHECK(output.at(2).parents.size() == 1u);
253     CHECK(output.at(2).parents.at(0) == "second");
254     CHECK(output.at(2).inputs.size() == 1u);
255     CHECK(output.at(2).inputs.at(0) == "four");
256     CHECK(output.at(3).parents.at(0) == "second");
257     CHECK(output.at(3).name == "--");
258 }
259 
260 // check function to make sure that open sections match close sections
checkSections(const std::vector<CLI::ConfigItem> & output)261 bool checkSections(const std::vector<CLI::ConfigItem> &output) {
262     std::set<std::string> open;
263     for(auto &ci : output) {
264         if(ci.name == "++") {
265             auto nm = ci.fullname();
266             nm.pop_back();
267             nm.pop_back();
268             auto rv = open.insert(nm);
269             if(!rv.second) {
270                 return false;
271             }
272         }
273         if(ci.name == "--") {
274             auto nm = ci.fullname();
275             nm.pop_back();
276             nm.pop_back();
277             auto rv = open.erase(nm);
278             if(rv != 1U) {
279                 return false;
280             }
281         }
282     }
283     return open.empty();
284 }
285 TEST_CASE("StringBased: Layers", "[config]") {
286     std::stringstream ofile;
287 
288     ofile << "simple = true\n\n";
289     ofile << "[other]\n";
290     ofile << "[other.sub2]\n";
291     ofile << "[other.sub2.sub-level2]\n";
292     ofile << "[other.sub2.sub-level2.sub-level3]\n";
293     ofile << "absolute_newest = true\n";
294     ofile.seekg(0, std::ios::beg);
295 
296     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
297 
298     // 2 flags and 4 openings and 4 closings
299     CHECK(output.size() == 10u);
300     CHECK(checkSections(output));
301 }
302 
303 TEST_CASE("StringBased: LayersSkip", "[config]") {
304     std::stringstream ofile;
305 
306     ofile << "simple = true\n\n";
307     ofile << "[other.sub2]\n";
308     ofile << "[other.sub2.sub-level2.sub-level3]\n";
309     ofile << "absolute_newest = true\n";
310     ofile.seekg(0, std::ios::beg);
311 
312     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
313 
314     // 2 flags and 4 openings and 4 closings
315     CHECK(output.size() == 10u);
316     CHECK(checkSections(output));
317 }
318 
319 TEST_CASE("StringBased: LayersSkipOrdered", "[config]") {
320     std::stringstream ofile;
321 
322     ofile << "simple = true\n\n";
323     ofile << "[other.sub2.sub-level2.sub-level3]\n";
324     ofile << "[other.sub2]\n";
325     ofile << "absolute_newest = true\n";
326     ofile.seekg(0, std::ios::beg);
327 
328     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
329 
330     // 2 flags and 4 openings and 4 closings
331     CHECK(output.size() == 12u);
332     CHECK(checkSections(output));
333 }
334 
335 TEST_CASE("StringBased: LayersChange", "[config]") {
336     std::stringstream ofile;
337 
338     ofile << "simple = true\n\n";
339     ofile << "[other.sub2]\n";
340     ofile << "[other.sub3]\n";
341     ofile << "absolute_newest = true\n";
342     ofile.seekg(0, std::ios::beg);
343 
344     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
345 
346     // 2 flags and 3 openings and 3 closings
347     CHECK(output.size() == 8u);
348     CHECK(checkSections(output));
349 }
350 
351 TEST_CASE("StringBased: Layers2LevelChange", "[config]") {
352     std::stringstream ofile;
353 
354     ofile << "simple = true\n\n";
355     ofile << "[other.sub2.cmd]\n";
356     ofile << "[other.sub3.cmd]\n";
357     ofile << "absolute_newest = true\n";
358     ofile.seekg(0, std::ios::beg);
359 
360     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
361 
362     // 2 flags and 5 openings and 5 closings
363     CHECK(output.size() == 12u);
364     CHECK(checkSections(output));
365 }
366 
367 TEST_CASE("StringBased: Layers3LevelChange", "[config]") {
368     std::stringstream ofile;
369 
370     ofile << "[other.sub2.subsub.cmd]\n";
371     ofile << "[other.sub3.subsub.cmd]\n";
372     ofile << "absolute_newest = true\n";
373     ofile.seekg(0, std::ios::beg);
374 
375     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
376 
377     // 1 flags and 7 openings and 7 closings
378     CHECK(output.size() == 15u);
379     CHECK(checkSections(output));
380 }
381 
382 TEST_CASE("StringBased: newSegment", "[config]") {
383     std::stringstream ofile;
384 
385     ofile << "[other.sub2.subsub.cmd]\n";
386     ofile << "flag = true\n";
387     ofile << "[another]\n";
388     ofile << "absolute_newest = true\n";
389     ofile.seekg(0, std::ios::beg);
390 
391     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
392 
393     // 2 flags and 5 openings and 5 closings
394     CHECK(output.size() == 12u);
395     CHECK(checkSections(output));
396 }
397 
398 TEST_CASE("StringBased: LayersDirect", "[config]") {
399     std::stringstream ofile;
400 
401     ofile << "simple = true\n\n";
402     ofile << "[other.sub2.sub-level2.sub-level3]\n";
403     ofile << "absolute_newest = true\n";
404 
405     ofile.seekg(0, std::ios::beg);
406 
407     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
408 
409     // 2 flags and 4 openings and 4 closings
410     CHECK(output.size() == 10u);
411     CHECK(checkSections(output));
412 }
413 
414 TEST_CASE("StringBased: LayersComplex", "[config]") {
415     std::stringstream ofile;
416 
417     ofile << "simple = true\n\n";
418     ofile << "[other.sub2.sub-level2.sub-level3]\n";
419     ofile << "absolute_newest = true\n";
420     ofile << "[other.sub2.sub-level2]\n";
421     ofile << "still_newer = true\n";
422     ofile << "[other.sub2]\n";
423     ofile << "newest = true\n";
424 
425     ofile.seekg(0, std::ios::beg);
426 
427     std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
428 
429     // 4 flags and 6 openings and 6 closings
430     CHECK(output.size() == 16u);
431     CHECK(checkSections(output));
432 }
433 
434 TEST_CASE("StringBased: file_error", "[config]") {
435     CHECK_THROWS_AS(CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError);
436 }
437 
438 TEST_CASE_METHOD(TApp, "IniNotRequired", "[config]") {
439 
440     TempFile tmpini{"TestIniTmp.ini"};
441 
442     app.set_config("--config", tmpini);
443 
444     {
445         std::ofstream out{tmpini};
446         out << "[default]" << std::endl;
447         out << "two=99" << std::endl;
448         out << "three=3" << std::endl;
449     }
450 
451     int one = 0, two = 0, three = 0;
452     app.add_option("--one", one);
453     app.add_option("--two", two);
454     app.add_option("--three", three);
455 
456     args = {"--one=1"};
457 
458     run();
459 
460     CHECK(one == 1);
461     CHECK(two == 99);
462     CHECK(three == 3);
463 
464     one = two = three = 0;
465     args = {"--one=1", "--two=2"};
466 
467     run();
468 
469     CHECK(one == 1);
470     CHECK(two == 2);
471     CHECK(three == 3);
472     CHECK("TestIniTmp.ini" == app["--config"]->as<std::string>());
473 }
474 
475 TEST_CASE_METHOD(TApp, "IniSuccessOnUnknownOption", "[config]") {
476     TempFile tmpini{"TestIniTmp.ini"};
477 
478     app.set_config("--config", tmpini);
479     app.allow_config_extras(true);
480 
481     {
482         std::ofstream out{tmpini};
483         out << "three=3" << std::endl;
484         out << "two=99" << std::endl;
485     }
486 
487     int two{0};
488     app.add_option("--two", two);
489     run();
490     CHECK(two == 99);
491 }
492 
493 TEST_CASE_METHOD(TApp, "IniGetRemainingOption", "[config]") {
494     TempFile tmpini{"TestIniTmp.ini"};
495 
496     app.set_config("--config", tmpini);
497     app.allow_config_extras(true);
498 
499     std::string ExtraOption = "three";
500     std::string ExtraOptionValue = "3";
501     {
502         std::ofstream out{tmpini};
503         out << ExtraOption << "=" << ExtraOptionValue << std::endl;
504         out << "two=99" << std::endl;
505     }
506 
507     int two{0};
508     app.add_option("--two", two);
509     REQUIRE_NOTHROW(run());
510     std::vector<std::string> ExpectedRemaining = {ExtraOption};
511     CHECK(ExpectedRemaining == app.remaining());
512 }
513 
514 TEST_CASE_METHOD(TApp, "IniGetNoRemaining", "[config]") {
515     TempFile tmpini{"TestIniTmp.ini"};
516 
517     app.set_config("--config", tmpini);
518     app.allow_config_extras(true);
519 
520     {
521         std::ofstream out{tmpini};
522         out << "two=99" << std::endl;
523     }
524 
525     int two{0};
526     app.add_option("--two", two);
527     REQUIRE_NOTHROW(run());
528     CHECK(0u == app.remaining().size());
529 }
530 
531 TEST_CASE_METHOD(TApp, "IniRequiredNoDefault", "[config]") {
532 
533     app.set_config("--config")->required();
534 
535     int two{0};
536     app.add_option("--two", two);
537     REQUIRE_THROWS_AS(run(), CLI::FileError);
538     // test to make sure help still gets called correctly
539     // GitHub issue #533 https://github.com/CLIUtils/CLI11/issues/553
540     args = {"--help"};
541     REQUIRE_THROWS_AS(run(), CLI::CallForHelp);
542 }
543 
544 TEST_CASE_METHOD(TApp, "IniNotRequiredNoDefault", "[config]") {
545 
546     app.set_config("--config");
547 
548     int two{0};
549     app.add_option("--two", two);
550     REQUIRE_NOTHROW(run());
551 }
552 
553 /// Define a class for testing purposes that does bad things
554 class EvilConfig : public CLI::Config {
555   public:
556     EvilConfig() = default;
to_config(const CLI::App *,bool,bool,std::string) const557     virtual std::string to_config(const CLI::App *, bool, bool, std::string) const { throw CLI::FileError("evil"); }
558 
from_config(std::istream &) const559     virtual std::vector<CLI::ConfigItem> from_config(std::istream &) const { throw CLI::FileError("evil"); }
560 };
561 
562 TEST_CASE_METHOD(TApp, "IniRequiredbadConfigurator", "[config]") {
563 
564     TempFile tmpini{"TestIniTmp.ini"};
565 
566     {
567         std::ofstream out{tmpini};
568         out << "[default]" << std::endl;
569         out << "two=99" << std::endl;
570         out << "three=3" << std::endl;
571     }
572 
573     app.set_config("--config", tmpini)->required();
574     app.config_formatter(std::make_shared<EvilConfig>());
575     int two{0};
576     app.add_option("--two", two);
577     REQUIRE_THROWS_AS(run(), CLI::FileError);
578 }
579 
580 TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") {
581 
582     TempFile tmpini{"TestIniTmp.ini"};
583 
584     {
585         std::ofstream out{tmpini};
586         out << "[default]" << std::endl;
587         out << "two=99" << std::endl;
588         out << "three=3" << std::endl;
589     }
590 
591     app.set_config("--config", tmpini);
592     app.config_formatter(std::make_shared<EvilConfig>());
593     int two{0};
594     app.add_option("--two", two);
595     REQUIRE_NOTHROW(run());
596 }
597 
598 TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") {
599 
600     TempFile tmpini{"TestIniTmp.ini"};
601     TempFile tmpini2{"TestIniTmp2.ini"};
602 
603     app.set_config("--config", tmpini);
604 
605     {
606         std::ofstream out{tmpini};
607         out << "[default]" << std::endl;
608         out << "two=99" << std::endl;
609         out << "three=3" << std::endl;
610     }
611 
612     {
613         std::ofstream out{tmpini2};
614         out << "[default]" << std::endl;
615         out << "two=98" << std::endl;
616         out << "three=4" << std::endl;
617     }
618 
619     int one{0}, two{0}, three{0};
620     app.add_option("--one", one);
621     app.add_option("--two", two);
622     app.add_option("--three", three);
623 
624     run();
625     CHECK(tmpini.c_str() == app["--config"]->as<std::string>());
626     CHECK(two == 99);
627     CHECK(three == 3);
628 
629     args = {"--config", tmpini2};
630     run();
631 
632     CHECK(two == 98);
633     CHECK(three == 4);
634     CHECK(tmpini2.c_str() == app.get_config_ptr()->as<std::string>());
635 }
636 
637 TEST_CASE_METHOD(TApp, "MultiConfig", "[config]") {
638 
639     TempFile tmpini{"TestIniTmp.ini"};
640     TempFile tmpini2{"TestIniTmp2.ini"};
641 
642     app.set_config("--config")->expected(1, 3);
643 
644     {
645         std::ofstream out{tmpini};
646         out << "[default]" << std::endl;
647         out << "two=99" << std::endl;
648         out << "three=3" << std::endl;
649     }
650 
651     {
652         std::ofstream out{tmpini2};
653         out << "[default]" << std::endl;
654         out << "one=55" << std::endl;
655         out << "three=4" << std::endl;
656     }
657 
658     int one{0}, two{0}, three{0};
659     app.add_option("--one", one);
660     app.add_option("--two", two);
661     app.add_option("--three", three);
662 
663     args = {"--config", tmpini2, "--config", tmpini};
664     run();
665 
666     CHECK(two == 99);
667     CHECK(three == 3);
668     CHECK(one == 55);
669 
670     args = {"--config", tmpini, "--config", tmpini2};
671     run();
672 
673     CHECK(two == 99);
674     CHECK(three == 4);
675     CHECK(one == 55);
676 }
677 
678 TEST_CASE_METHOD(TApp, "MultiConfig_single", "[config]") {
679 
680     TempFile tmpini{"TestIniTmp.ini"};
681     TempFile tmpini2{"TestIniTmp2.ini"};
682 
683     app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
684 
685     {
686         std::ofstream out{tmpini};
687         out << "[default]" << std::endl;
688         out << "two=99" << std::endl;
689         out << "three=3" << std::endl;
690     }
691 
692     {
693         std::ofstream out{tmpini2};
694         out << "[default]" << std::endl;
695         out << "one=55" << std::endl;
696         out << "three=4" << std::endl;
697     }
698 
699     int one{0}, two{0}, three{0};
700     app.add_option("--one", one);
701     app.add_option("--two", two);
702     app.add_option("--three", three);
703 
704     args = {"--config", tmpini2, "--config", tmpini};
705     run();
706 
707     CHECK(two == 99);
708     CHECK(three == 3);
709     CHECK(one == 0);
710 
711     two = 0;
712     args = {"--config", tmpini, "--config", tmpini2};
713     run();
714 
715     CHECK(two == 0);
716     CHECK(three == 4);
717     CHECK(one == 55);
718 }
719 
720 TEST_CASE_METHOD(TApp, "IniRequiredNotFound", "[config]") {
721 
722     std::string noini = "TestIniNotExist.ini";
723     app.set_config("--config", noini, "", true);
724 
725     CHECK_THROWS_AS(run(), CLI::FileError);
726 }
727 
728 TEST_CASE_METHOD(TApp, "IniNotRequiredPassedNotFound", "[config]") {
729 
730     std::string noini = "TestIniNotExist.ini";
731     app.set_config("--config", "", "", false);
732 
733     args = {"--config", noini};
734     CHECK_THROWS_AS(run(), CLI::FileError);
735 }
736 
737 TEST_CASE_METHOD(TApp, "IniOverwrite", "[config]") {
738 
739     TempFile tmpini{"TestIniTmp.ini"};
740     {
741         std::ofstream out{tmpini};
742         out << "[default]" << std::endl;
743         out << "two=99" << std::endl;
744     }
745 
746     std::string orig = "filename_not_exist.ini";
747     std::string next = "TestIniTmp.ini";
748     app.set_config("--config", orig);
749     // Make sure this can be overwritten
750     app.set_config("--conf", next);
751     int two{7};
752     app.add_option("--two", two);
753 
754     run();
755 
756     CHECK(two == 99);
757 }
758 
759 TEST_CASE_METHOD(TApp, "IniRequired", "[config]") {
760 
761     TempFile tmpini{"TestIniTmp.ini"};
762 
763     app.set_config("--config", tmpini, "", true);
764 
765     {
766         std::ofstream out{tmpini};
767         out << "[default]" << std::endl;
768         out << "two=99" << std::endl;
769         out << "three=3" << std::endl;
770     }
771 
772     int one{0}, two{0}, three{0};
773     app.add_option("--one", one)->required();
774     app.add_option("--two", two)->required();
775     app.add_option("--three", three)->required();
776 
777     args = {"--one=1"};
778 
779     run();
780     CHECK(1 == one);
781     CHECK(99 == two);
782     CHECK(3 == three);
783 
784     one = two = three = 0;
785     args = {"--one=1", "--two=2"};
786 
787     CHECK_NOTHROW(run());
788     CHECK(1 == one);
789     CHECK(2 == two);
790     CHECK(3 == three);
791 
792     args = {};
793 
794     CHECK_THROWS_AS(run(), CLI::RequiredError);
795 
796     args = {"--two=2"};
797 
798     CHECK_THROWS_AS(run(), CLI::RequiredError);
799 }
800 
801 TEST_CASE_METHOD(TApp, "IniInlineComment", "[config]") {
802 
803     TempFile tmpini{"TestIniTmp.ini"};
804 
805     app.set_config("--config", tmpini, "", true);
806     app.config_formatter(std::make_shared<CLI::ConfigINI>());
807 
808     {
809         std::ofstream out{tmpini};
810         out << "[default]" << std::endl;
811         out << "two=99 ; this is a two" << std::endl;
812         out << "three=3; this is a three" << std::endl;
813     }
814 
815     int one{0}, two{0}, three{0};
816     app.add_option("--one", one)->required();
817     app.add_option("--two", two)->required();
818     app.add_option("--three", three)->required();
819 
820     args = {"--one=1"};
821 
822     run();
823     CHECK(1 == one);
824     CHECK(99 == two);
825     CHECK(3 == three);
826 
827     one = two = three = 0;
828     args = {"--one=1", "--two=2"};
829 
830     CHECK_NOTHROW(run());
831     CHECK(1 == one);
832     CHECK(2 == two);
833     CHECK(3 == three);
834 
835     args = {};
836 
837     CHECK_THROWS_AS(run(), CLI::RequiredError);
838 
839     args = {"--two=2"};
840 
841     CHECK_THROWS_AS(run(), CLI::RequiredError);
842 }
843 
844 TEST_CASE_METHOD(TApp, "TomlInlineComment", "[config]") {
845 
846     TempFile tmpini{"TestIniTmp.ini"};
847 
848     app.set_config("--config", tmpini, "", true);
849 
850     {
851         std::ofstream out{tmpini};
852         out << "[default]" << std::endl;
853         out << "two=99 # this is a two" << std::endl;
854         out << "three=3# this is a three" << std::endl;
855     }
856 
857     int one{0}, two{0}, three{0};
858     app.add_option("--one", one)->required();
859     app.add_option("--two", two)->required();
860     app.add_option("--three", three)->required();
861 
862     args = {"--one=1"};
863 
864     run();
865     CHECK(1 == one);
866     CHECK(99 == two);
867     CHECK(3 == three);
868 
869     one = two = three = 0;
870     args = {"--one=1", "--two=2"};
871 
872     CHECK_NOTHROW(run());
873     CHECK(1 == one);
874     CHECK(2 == two);
875     CHECK(3 == three);
876 
877     args = {};
878 
879     CHECK_THROWS_AS(run(), CLI::RequiredError);
880 
881     args = {"--two=2"};
882 
883     CHECK_THROWS_AS(run(), CLI::RequiredError);
884 }
885 
886 TEST_CASE_METHOD(TApp, "ConfigModifiers", "[config]") {
887 
888     app.set_config("--config", "test.ini", "", true);
889 
890     auto cfgptr = app.get_config_formatter_base();
891 
892     cfgptr->section("test");
893     CHECK(cfgptr->section() == "test");
894 
895     CHECK(cfgptr->sectionRef() == "test");
896     auto &sref = cfgptr->sectionRef();
897     sref = "this";
898     CHECK(cfgptr->section() == "this");
899 
900     cfgptr->index(5);
901     CHECK(cfgptr->index() == 5);
902 
903     CHECK(cfgptr->indexRef() == 5);
904     auto &iref = cfgptr->indexRef();
905     iref = 7;
906     CHECK(cfgptr->index() == 7);
907 }
908 
909 TEST_CASE_METHOD(TApp, "IniVector", "[config]") {
910 
911     TempFile tmpini{"TestIniTmp.ini"};
912 
913     app.set_config("--config", tmpini);
914 
915     {
916         std::ofstream out{tmpini};
917         out << "[default]" << std::endl;
918         out << "two=2 3" << std::endl;
919         out << "three=1 2 3" << std::endl;
920     }
921 
922     std::vector<int> two, three;
923     app.add_option("--two", two)->expected(2)->required();
924     app.add_option("--three", three)->required();
925 
926     run();
927 
928     CHECK(two == std::vector<int>({2, 3}));
929     CHECK(three == std::vector<int>({1, 2, 3}));
930 }
931 TEST_CASE_METHOD(TApp, "TOMLVector", "[config]") {
932 
933     TempFile tmptoml{"TestTomlTmp.toml"};
934 
935     app.set_config("--config", tmptoml);
936 
937     {
938         std::ofstream out{tmptoml};
939         out << "#this is a comment line\n";
940         out << "[default]\n";
941         out << "two=[2,3]\n";
942         out << "three=[1,2,3]\n";
943     }
944 
945     std::vector<int> two, three;
946     app.add_option("--two", two)->expected(2)->required();
947     app.add_option("--three", three)->required();
948 
949     run();
950 
951     CHECK(two == std::vector<int>({2, 3}));
952     CHECK(three == std::vector<int>({1, 2, 3}));
953 }
954 
955 TEST_CASE_METHOD(TApp, "ColonValueSep", "[config]") {
956 
957     TempFile tmpini{"TestIniTmp.ini"};
958 
959     app.set_config("--config", tmpini);
960 
961     {
962         std::ofstream out{tmpini};
963         out << "#this is a comment line\n";
964         out << "[default]\n";
965         out << "two:2\n";
966         out << "three:3\n";
967     }
968 
969     int two{0}, three{0};
970     app.add_option("--two", two);
971     app.add_option("--three", three);
972 
973     app.get_config_formatter_base()->valueSeparator(':');
974 
975     run();
976 
977     CHECK(two == 2);
978     CHECK(three == 3);
979 }
980 
981 TEST_CASE_METHOD(TApp, "TOMLVectordirect", "[config]") {
982 
983     TempFile tmpini{"TestIniTmp.ini"};
984 
985     app.set_config("--config", tmpini);
986 
987     app.config_formatter(std::make_shared<CLI::ConfigTOML>());
988 
989     {
990         std::ofstream out{tmpini};
991         out << "#this is a comment line\n";
992         out << "[default]\n";
993         out << "two=[2,3]\n";
994         out << "three=[1,2,3]\n";
995     }
996 
997     std::vector<int> two, three;
998     app.add_option("--two", two)->expected(2)->required();
999     app.add_option("--three", three)->required();
1000 
1001     run();
1002 
1003     CHECK(two == std::vector<int>({2, 3}));
1004     CHECK(three == std::vector<int>({1, 2, 3}));
1005 }
1006 
1007 TEST_CASE_METHOD(TApp, "TOMLStringVector", "[config]") {
1008 
1009     TempFile tmptoml{"TestTomlTmp.toml"};
1010 
1011     app.set_config("--config", tmptoml);
1012 
1013     {
1014         std::ofstream out{tmptoml};
1015         out << "#this is a comment line\n";
1016         out << "[default]\n";
1017         out << "two=[\"2\",\"3\"]\n";
1018         out << "three=[\"1\",\"2\",\"3\"]\n";
1019     }
1020 
1021     std::vector<std::string> two, three;
1022     app.add_option("--two", two)->required();
1023     app.add_option("--three", three)->required();
1024 
1025     run();
1026 
1027     CHECK(two == std::vector<std::string>({"2", "3"}));
1028     CHECK(three == std::vector<std::string>({"1", "2", "3"}));
1029 }
1030 
1031 TEST_CASE_METHOD(TApp, "IniVectorCsep", "[config]") {
1032 
1033     TempFile tmpini{"TestIniTmp.ini"};
1034 
1035     app.set_config("--config", tmpini);
1036 
1037     {
1038         std::ofstream out{tmpini};
1039         out << "#this is a comment line\n";
1040         out << "[default]\n";
1041         out << "two=[2,3]\n";
1042         out << "three=1,2,3\n";
1043     }
1044 
1045     std::vector<int> two, three;
1046     app.add_option("--two", two)->expected(2)->required();
1047     app.add_option("--three", three)->required();
1048 
1049     run();
1050 
1051     CHECK(two == std::vector<int>({2, 3}));
1052     CHECK(three == std::vector<int>({1, 2, 3}));
1053 }
1054 
1055 TEST_CASE_METHOD(TApp, "IniVectorMultiple", "[config]") {
1056 
1057     TempFile tmpini{"TestIniTmp.ini"};
1058 
1059     app.set_config("--config", tmpini);
1060 
1061     {
1062         std::ofstream out{tmpini};
1063         out << "#this is a comment line\n";
1064         out << "[default]\n";
1065         out << "two=2\n";
1066         out << "two=3\n";
1067         out << "three=1\n";
1068         out << "three=2\n";
1069         out << "three=3\n";
1070     }
1071 
1072     std::vector<int> two, three;
1073     app.add_option("--two", two)->expected(2)->required();
1074     app.add_option("--three", three)->required();
1075 
1076     run();
1077 
1078     CHECK(two == std::vector<int>({2, 3}));
1079     CHECK(three == std::vector<int>({1, 2, 3}));
1080 }
1081 
1082 TEST_CASE_METHOD(TApp, "IniLayered", "[config]") {
1083 
1084     TempFile tmpini{"TestIniTmp.ini"};
1085 
1086     app.set_config("--config", tmpini);
1087 
1088     {
1089         std::ofstream out{tmpini};
1090         out << "[default]" << std::endl;
1091         out << "val=1" << std::endl;
1092         out << "[subcom]" << std::endl;
1093         out << "val=2" << std::endl;
1094         out << "subsubcom.val=3" << std::endl;
1095     }
1096 
1097     int one{0}, two{0}, three{0};
1098     app.add_option("--val", one);
1099     auto subcom = app.add_subcommand("subcom");
1100     subcom->add_option("--val", two);
1101     auto subsubcom = subcom->add_subcommand("subsubcom");
1102     subsubcom->add_option("--val", three);
1103 
1104     run();
1105 
1106     CHECK(one == 1);
1107     CHECK(two == 2);
1108     CHECK(three == 3);
1109 
1110     CHECK(0U == subcom->count());
1111     CHECK(!*subcom);
1112 }
1113 
1114 TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") {
1115 
1116     TempFile tmpini{"TestIniTmp.ini"};
1117 
1118     app.set_config("--config", tmpini);
1119 
1120     {
1121         std::ofstream out{tmpini};
1122         out << "[default]" << std::endl;
1123         out << "val=1" << std::endl;
1124         out << "[subcom]" << std::endl;
1125         out << "val=2" << std::endl;
1126         out << "subsubcom.val=3" << std::endl;
1127     }
1128 
1129     int one{0}, two{0}, three{0};
1130     app.add_option("--val", one);
1131     auto subcom = app.add_subcommand("subcom");
1132     subcom->add_option("--val", two);
1133     auto subsubcom = subcom->add_subcommand("subsubcom");
1134     subsubcom->add_option("--val", three);
1135 
1136     std::ifstream in{tmpini};
1137     app.parse_from_stream(in);
1138 
1139     CHECK(one == 1);
1140     CHECK(two == 2);
1141     CHECK(three == 3);
1142 
1143     CHECK(0U == subcom->count());
1144     CHECK(!*subcom);
1145 }
1146 
1147 TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") {
1148 
1149     TempFile tmpini{"TestIniTmp.ini"};
1150 
1151     app.set_config("--config", tmpini);
1152 
1153     {
1154         std::ofstream out{tmpini};
1155         out << "[default]" << std::endl;
1156         out << "val=1" << std::endl;
1157         out << "[subcom]" << std::endl;
1158         out << "val=2" << std::endl;
1159         out << "[subcom.subsubcom]" << std::endl;
1160         out << "val=3" << std::endl;
1161     }
1162 
1163     int one{0}, two{0}, three{0};
1164     app.add_option("--val", one);
1165     auto subcom = app.add_subcommand("subcom");
1166     subcom->add_option("--val", two);
1167     auto subsubcom = subcom->add_subcommand("subsubcom");
1168     subsubcom->add_option("--val", three);
1169 
1170     run();
1171 
1172     CHECK(one == 1);
1173     CHECK(two == 2);
1174     CHECK(three == 3);
1175 
1176     CHECK(0U == subcom->count());
1177     CHECK(!*subcom);
1178 
1179     three = 0;
1180     // check maxlayers
1181     app.get_config_formatter_base()->maxLayers(1);
1182     run();
1183     CHECK(three == 0);
1184 }
1185 
1186 TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") {
1187 
1188     TempFile tmpini{"TestIniTmp.ini"};
1189 
1190     app.set_config("--config", tmpini);
1191 
1192     {
1193         std::ofstream out{tmpini};
1194         out << "[default]" << std::endl;
1195         out << "val=1" << std::endl;
1196         out << "[subcom]" << std::endl;
1197         out << "val=2" << std::endl;
1198         out << "[subcom|subsubcom]" << std::endl;
1199         out << "val=3" << std::endl;
1200     }
1201     app.get_config_formatter_base()->parentSeparator('|');
1202     int one{0}, two{0}, three{0};
1203     app.add_option("--val", one);
1204     auto subcom = app.add_subcommand("subcom");
1205     subcom->add_option("--val", two);
1206     auto subsubcom = subcom->add_subcommand("subsubcom");
1207     subsubcom->add_option("--val", three);
1208 
1209     run();
1210 
1211     CHECK(one == 1);
1212     CHECK(two == 2);
1213     CHECK(three == 3);
1214 
1215     CHECK(0U == subcom->count());
1216     CHECK(!*subcom);
1217 }
1218 
1219 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") {
1220 
1221     TempFile tmpini{"TestIniTmp.ini"};
1222 
1223     app.set_config("--config", tmpini);
1224 
1225     {
1226         std::ofstream out{tmpini};
1227         out << "[default]" << std::endl;
1228         out << "val=1" << std::endl;
1229         out << "[subcom]" << std::endl;
1230         out << "val=2" << std::endl;
1231         out << "subsubcom.val=3" << std::endl;
1232     }
1233 
1234     int one{0}, two{0}, three{0};
1235     app.add_option("--val", one);
1236     auto subcom = app.add_subcommand("subcom");
1237     subcom->configurable();
1238     subcom->add_option("--val", two);
1239     auto subsubcom = subcom->add_subcommand("subsubcom");
1240     subsubcom->add_option("--val", three);
1241 
1242     run();
1243 
1244     CHECK(one == 1);
1245     CHECK(two == 2);
1246     CHECK(three == 3);
1247 
1248     CHECK(1U == subcom->count());
1249     CHECK(*subcom);
1250     CHECK(app.got_subcommand(subcom));
1251 }
1252 
1253 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") {
1254 
1255     TempFile tmpini{"TestIniTmp.ini"};
1256 
1257     app.set_config("--config", tmpini);
1258 
1259     {
1260         std::ofstream out{tmpini};
1261         out << "[default]" << std::endl;
1262         out << "val=1" << std::endl;
1263         out << "[subcom]" << std::endl;
1264         out << "val=2" << std::endl;
1265         out << "subsubcom.val=3" << std::endl;
1266     }
1267 
1268     int one{0}, two{0}, three{0}, four{0};
1269     app.add_option("--val", one);
1270     auto subcom = app.add_subcommand("subcom");
1271     auto subcom2 = app.add_subcommand("subcom2");
1272     subcom->configurable();
1273     std::vector<std::size_t> parse_c;
__anonecffff220102(std::size_t cnt) 1274     subcom->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt); });
1275     subcom->add_option("--val", two);
1276     subcom2->add_option("--val", four);
__anonecffff220202(std::size_t cnt) 1277     subcom2->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt + 2623); });
1278     auto subsubcom = subcom->add_subcommand("subsubcom");
1279     subsubcom->add_option("--val", three);
1280 
1281     run();
1282 
1283     CHECK(one == 1);
1284     CHECK(two == 2);
1285     CHECK(three == 3);
1286     CHECK(four == 0);
1287 
1288     CHECK(1U == parse_c.size());
1289     CHECK(2U == parse_c[0]);
1290 
1291     CHECK(0U == subcom2->count());
1292 }
1293 
1294 TEST_CASE_METHOD(TApp, "IniSection", "[config]") {
1295 
1296     TempFile tmpini{"TestIniTmp.ini"};
1297 
1298     app.set_config("--config", tmpini);
1299     app.get_config_formatter_base()->section("config");
1300 
1301     {
1302         std::ofstream out{tmpini};
1303         out << "[config]" << std::endl;
1304         out << "val=2" << std::endl;
1305         out << "subsubcom.val=3" << std::endl;
1306         out << "[default]" << std::endl;
1307         out << "val=1" << std::endl;
1308     }
1309 
1310     int val{0};
1311     app.add_option("--val", val);
1312 
1313     run();
1314 
1315     CHECK(2 == val);
1316 }
1317 
1318 TEST_CASE_METHOD(TApp, "IniSection2", "[config]") {
1319 
1320     TempFile tmpini{"TestIniTmp.ini"};
1321 
1322     app.set_config("--config", tmpini);
1323     app.get_config_formatter_base()->section("config");
1324 
1325     {
1326         std::ofstream out{tmpini};
1327         out << "[default]" << std::endl;
1328         out << "val=1" << std::endl;
1329         out << "[config]" << std::endl;
1330         out << "val=2" << std::endl;
1331         out << "subsubcom.val=3" << std::endl;
1332     }
1333 
1334     int val{0};
1335     app.add_option("--val", val);
1336 
1337     run();
1338 
1339     CHECK(2 == val);
1340 }
1341 
1342 TEST_CASE_METHOD(TApp, "jsonLikeParsing", "[config]") {
1343 
1344     TempFile tmpjson{"TestJsonTmp.json"};
1345 
1346     app.set_config("--config", tmpjson);
1347     app.get_config_formatter_base()->valueSeparator(':');
1348 
1349     {
1350         std::ofstream out{tmpjson};
1351         out << "{" << std::endl;
1352         out << "\"val\":1," << std::endl;
1353         out << "\"val2\":\"test\"," << std::endl;
1354         out << "\"flag\":true" << std::endl;
1355         out << "}" << std::endl;
1356     }
1357 
1358     int val{0};
1359     app.add_option("--val", val);
1360     std::string val2{0};
1361     app.add_option("--val2", val2);
1362 
1363     bool flag{false};
1364     app.add_flag("--flag", flag);
1365 
1366     run();
1367 
1368     CHECK(1 == val);
1369     CHECK(val2 == "test");
1370     CHECK(flag);
1371 }
1372 
1373 TEST_CASE_METHOD(TApp, "TomlSectionNumber", "[config]") {
1374 
1375     TempFile tmpini{"TestTomlTmp.toml"};
1376 
1377     app.set_config("--config", tmpini);
1378     app.get_config_formatter_base()->section("config")->index(0);
1379 
1380     {
1381         std::ofstream out{tmpini};
1382         out << "[default]" << std::endl;
1383         out << "val=1" << std::endl;
1384         out << "[[config]]" << std::endl;
1385         out << "val=2" << std::endl;
1386         out << "subsubcom.val=3" << std::endl;
1387         out << "[[config]]" << std::endl;
1388         out << "val=4" << std::endl;
1389         out << "subsubcom.val=3" << std::endl;
1390         out << "[[config]]" << std::endl;
1391         out << "val=6" << std::endl;
1392         out << "subsubcom.val=3" << std::endl;
1393     }
1394 
1395     int val{0};
1396     app.add_option("--val", val);
1397 
1398     run();
1399 
1400     CHECK(2 == val);
1401 
1402     auto &index = app.get_config_formatter_base()->indexRef();
1403     index = 1;
1404     run();
1405 
1406     CHECK(4 == val);
1407 
1408     index = -1;
1409     run();
1410     // Take the first section in this case
1411     CHECK(2 == val);
1412     index = 2;
1413     run();
1414 
1415     CHECK(6 == val);
1416 }
1417 
1418 TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableParseComplete", "[config]") {
1419 
1420     TempFile tmpini{"TestIniTmp.ini"};
1421 
1422     app.set_config("--config", tmpini);
1423 
1424     {
1425         std::ofstream out{tmpini};
1426         out << "[default]" << std::endl;
1427         out << "val=1" << std::endl;
1428         out << "[subcom]" << std::endl;
1429         out << "val=2" << std::endl;
1430         out << "[subcom.subsubcom]" << std::endl;
1431         out << "val=3" << std::endl;
1432     }
1433 
1434     int one{0}, two{0}, three{0}, four{0};
1435     app.add_option("--val", one);
1436     auto subcom = app.add_subcommand("subcom");
1437     auto subcom2 = app.add_subcommand("subcom2");
1438     subcom->configurable();
1439     std::vector<std::size_t> parse_c;
__anonecffff220302() 1440     subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
1441     subcom->add_option("--val", two);
1442     subcom2->add_option("--val", four);
__anonecffff220402() 1443     subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
1444     auto subsubcom = subcom->add_subcommand("subsubcom");
1445     // configurable should be inherited
__anonecffff220502() 1446     subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
1447     subsubcom->add_option("--val", three);
1448 
1449     run();
1450 
1451     CHECK(one == 1);
1452     CHECK(two == 2);
1453     CHECK(three == 3);
1454     CHECK(four == 0);
1455 
1456     REQUIRE(2u == parse_c.size());
1457     CHECK(68U == parse_c[0]);
1458     CHECK(58U == parse_c[1]);
1459     CHECK(1u == subsubcom->count());
1460     CHECK(0u == subcom2->count());
1461 }
1462 
1463 TEST_CASE_METHOD(TApp, "IniSubcommandMultipleSections", "[config]") {
1464 
1465     TempFile tmpini{"TestIniTmp.ini"};
1466 
1467     app.set_config("--config", tmpini);
1468 
1469     {
1470         std::ofstream out{tmpini};
1471         out << "[default]" << std::endl;
1472         out << "val=1" << std::endl;
1473         out << "[subcom]" << std::endl;
1474         out << "val=2" << std::endl;
1475         out << "[subcom.subsubcom]" << std::endl;
1476         out << "val=3" << std::endl;
1477         out << "[subcom2]" << std::endl;
1478         out << "val=4" << std::endl;
1479     }
1480 
1481     int one{0}, two{0}, three{0}, four{0};
1482     app.add_option("--val", one);
1483     auto subcom = app.add_subcommand("subcom");
1484     auto subcom2 = app.add_subcommand("subcom2");
1485     subcom->configurable();
1486     std::vector<std::size_t> parse_c;
__anonecffff220602() 1487     subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
1488     subcom->add_option("--val", two);
1489     subcom2->add_option("--val", four);
__anonecffff220702() 1490     subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
1491     subcom2->configurable(false);
1492     auto subsubcom = subcom->add_subcommand("subsubcom");
1493     // configurable should be inherited
__anonecffff220802() 1494     subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
1495     subsubcom->add_option("--val", three);
1496 
1497     run();
1498 
1499     CHECK(one == 1);
1500     CHECK(two == 2);
1501     CHECK(three == 3);
1502     CHECK(four == 4);
1503 
1504     REQUIRE(2u == parse_c.size());
1505     CHECK(68U == parse_c[0]);
1506     CHECK(58U == parse_c[1]);
1507     CHECK(1u == subsubcom->count());
1508     CHECK(0u == subcom2->count());
1509 }
1510 
1511 TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[config]") {
1512 
1513     TempFile tmptoml{"TesttomlTmp.toml"};
1514 
1515     app.set_config("--config", tmptoml);
1516 
1517     {
1518         std::ofstream out{tmptoml};
1519         out << "[[foo]]" << std::endl;
1520         out << "[[foo]]" << std::endl;
1521         out << "[[foo]]" << std::endl;
1522     }
1523 
1524     auto foo = app.add_subcommand("foo");
1525     int count{0};
__anonecffff220902() 1526     foo->callback([&count]() { ++count; });
1527     foo->immediate_callback();
1528     CHECK(foo->get_immediate_callback());
1529     foo->configurable();
1530 
1531     run();
1532     CHECK(3 == count);
1533 }
1534 
1535 TEST_CASE_METHOD(TApp, "IniFailure", "[config]") {
1536 
1537     TempFile tmpini{"TestIniTmp.ini"};
1538 
1539     app.set_config("--config", tmpini);
1540     app.allow_config_extras(false);
1541     {
1542         std::ofstream out{tmpini};
1543         out << "[default]" << std::endl;
1544         out << "val=1" << std::endl;
1545     }
1546 
1547     CHECK_THROWS_AS(run(), CLI::ConfigError);
1548 }
1549 
1550 TEST_CASE_METHOD(TApp, "IniConfigurable", "[config]") {
1551 
1552     TempFile tmpini{"TestIniTmp.ini"};
1553 
1554     app.set_config("--config", tmpini);
1555     bool value{false};
1556     app.add_flag("--val", value)->configurable(true);
1557 
1558     {
1559         std::ofstream out{tmpini};
1560         out << "[default]" << std::endl;
1561         out << "val=1" << std::endl;
1562     }
1563 
1564     REQUIRE_NOTHROW(run());
1565     CHECK(value);
1566 }
1567 
1568 TEST_CASE_METHOD(TApp, "IniNotConfigurable", "[config]") {
1569 
1570     TempFile tmpini{"TestIniTmp.ini"};
1571 
1572     app.set_config("--config", tmpini);
1573     bool value{false};
1574     app.add_flag("--val", value)->configurable(false);
1575 
1576     {
1577         std::ofstream out{tmpini};
1578         out << "[default]" << std::endl;
1579         out << "val=1" << std::endl;
1580     }
1581 
1582     CHECK_THROWS_AS(run(), CLI::ConfigError);
1583 }
1584 
1585 TEST_CASE_METHOD(TApp, "IniSubFailure", "[config]") {
1586 
1587     TempFile tmpini{"TestIniTmp.ini"};
1588 
1589     app.add_subcommand("other");
1590     app.set_config("--config", tmpini);
1591     app.allow_config_extras(false);
1592     {
1593         std::ofstream out{tmpini};
1594         out << "[other]" << std::endl;
1595         out << "val=1" << std::endl;
1596     }
1597 
1598     CHECK_THROWS_AS(run(), CLI::ConfigError);
1599 }
1600 
1601 TEST_CASE_METHOD(TApp, "IniNoSubFailure", "[config]") {
1602 
1603     TempFile tmpini{"TestIniTmp.ini"};
1604 
1605     app.set_config("--config", tmpini);
1606     app.allow_config_extras(CLI::config_extras_mode::error);
1607     {
1608         std::ofstream out{tmpini};
1609         out << "[other]" << std::endl;
1610         out << "val=1" << std::endl;
1611     }
1612 
1613     CHECK_THROWS_AS(run(), CLI::ConfigError);
1614 }
1615 
1616 TEST_CASE_METHOD(TApp, "IniFlagConvertFailure", "[config]") {
1617 
1618     TempFile tmpini{"TestIniTmp.ini"};
1619 
1620     app.add_flag("--flag");
1621     app.set_config("--config", tmpini);
1622 
1623     {
1624         std::ofstream out{tmpini};
1625         out << "flag=moobook" << std::endl;
1626     }
1627     run();
1628     bool result{false};
1629     auto *opt = app.get_option("--flag");
1630     CHECK_THROWS_AS(opt->results(result), CLI::ConversionError);
1631     std::string res;
1632     opt->results(res);
1633     CHECK("moobook" == res);
1634 }
1635 
1636 TEST_CASE_METHOD(TApp, "IniFlagNumbers", "[config]") {
1637 
1638     TempFile tmpini{"TestIniTmp.ini"};
1639 
1640     bool boo{false};
1641     app.add_flag("--flag", boo);
1642     app.set_config("--config", tmpini);
1643 
1644     {
1645         std::ofstream out{tmpini};
1646         out << "flag=3" << std::endl;
1647     }
1648 
1649     REQUIRE_NOTHROW(run());
1650     CHECK(boo);
1651 }
1652 
1653 TEST_CASE_METHOD(TApp, "IniFlagDual", "[config]") {
1654 
1655     TempFile tmpini{"TestIniTmp.ini"};
1656 
1657     bool boo{false};
1658     app.config_formatter(std::make_shared<CLI::ConfigINI>());
1659     app.add_flag("--flag", boo);
1660     app.set_config("--config", tmpini);
1661 
1662     {
1663         std::ofstream out{tmpini};
1664         out << "flag=1 1" << std::endl;
1665     }
1666 
1667     CHECK_THROWS_AS(run(), CLI::ConversionError);
1668 }
1669 
1670 TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
1671 
1672     TempFile tmpini{"TestIniTmp.ini"};
1673 
1674     int key{0};
1675     app.add_option("--flag,-f", key);
1676     app.set_config("--config", tmpini);
1677 
1678     {
1679         std::ofstream out{tmpini};
1680         out << "f=3" << std::endl;
1681     }
1682 
1683     REQUIRE_NOTHROW(run());
1684     CHECK(3 == key);
1685 }
1686 
1687 TEST_CASE_METHOD(TApp, "IniPositional", "[config]") {
1688 
1689     TempFile tmpini{"TestIniTmp.ini"};
1690 
1691     int key{0};
1692     app.add_option("key", key);
1693     app.set_config("--config", tmpini);
1694 
1695     {
1696         std::ofstream out{tmpini};
1697         out << "key=3" << std::endl;
1698     }
1699 
1700     REQUIRE_NOTHROW(run());
1701     CHECK(3 == key);
1702 }
1703 
1704 TEST_CASE_METHOD(TApp, "IniEnvironmental", "[config]") {
1705 
1706     TempFile tmpini{"TestIniTmp.ini"};
1707 
1708     int key{0};
1709     app.add_option("key", key)->envname("CLI11_TEST_ENV_KEY_TMP");
1710     app.set_config("--config", tmpini);
1711 
1712     {
1713         std::ofstream out{tmpini};
1714         out << "CLI11_TEST_ENV_KEY_TMP=3" << std::endl;
1715     }
1716 
1717     REQUIRE_NOTHROW(run());
1718     CHECK(3 == key);
1719 }
1720 
1721 TEST_CASE_METHOD(TApp, "IniFlagText", "[config]") {
1722 
1723     TempFile tmpini{"TestIniTmp.ini"};
1724 
1725     bool flag1{false}, flag2{false}, flag3{false}, flag4{false};
1726     app.add_flag("--flag1", flag1);
1727     app.add_flag("--flag2", flag2);
1728     app.add_flag("--flag3", flag3);
1729     app.add_flag("--flag4", flag4);
1730     app.set_config("--config", tmpini);
1731 
1732     {
1733         std::ofstream out{tmpini};
1734         out << "flag1=true" << std::endl;
1735         out << "flag2=on" << std::endl;
1736         out << "flag3=off" << std::endl;
1737         out << "flag4=1" << std::endl;
1738     }
1739 
1740     run();
1741 
1742     CHECK(flag1);
1743     CHECK(flag2);
1744     CHECK(!flag3);
1745     CHECK(flag4);
1746 }
1747 
1748 TEST_CASE_METHOD(TApp, "IniFlags", "[config]") {
1749     TempFile tmpini{"TestIniTmp.ini"};
1750     app.set_config("--config", tmpini);
1751 
1752     {
1753         std::ofstream out{tmpini};
1754         out << "[default]" << std::endl;
1755         out << "two=2" << std::endl;
1756         out << "three=true" << std::endl;
1757         out << "four=on" << std::endl;
1758         out << "five" << std::endl;
1759     }
1760 
1761     int two{0};
1762     bool three{false}, four{false}, five{false};
1763     app.add_flag("--two", two);
1764     app.add_flag("--three", three);
1765     app.add_flag("--four", four);
1766     app.add_flag("--five", five);
1767 
1768     run();
1769 
1770     CHECK(two == 2);
1771     CHECK(three);
1772     CHECK(four);
1773     CHECK(five);
1774 }
1775 
1776 TEST_CASE_METHOD(TApp, "IniFalseFlags", "[config]") {
1777     TempFile tmpini{"TestIniTmp.ini"};
1778     app.set_config("--config", tmpini);
1779 
1780     {
1781         std::ofstream out{tmpini};
1782         out << "[default]" << std::endl;
1783         out << "two=-2" << std::endl;
1784         out << "three=false" << std::endl;
1785         out << "four=1" << std::endl;
1786         out << "five" << std::endl;
1787     }
1788 
1789     int two{0};
1790     bool three{false}, four{false}, five{false};
1791     app.add_flag("--two", two);
1792     app.add_flag("--three", three);
1793     app.add_flag("--four", four);
1794     app.add_flag("--five", five);
1795 
1796     run();
1797 
1798     CHECK(two == -2);
1799     CHECK(!three);
1800     CHECK(four);
1801     CHECK(five);
1802 }
1803 
1804 TEST_CASE_METHOD(TApp, "IniFalseFlagsDef", "[config]") {
1805     TempFile tmpini{"TestIniTmp.ini"};
1806     app.set_config("--config", tmpini);
1807 
1808     {
1809         std::ofstream out{tmpini};
1810         out << "[default]" << std::endl;
1811         out << "two=2" << std::endl;
1812         out << "three=true" << std::endl;
1813         out << "four=on" << std::endl;
1814         out << "five" << std::endl;
1815     }
1816 
1817     int two{0};
1818     bool three{false}, four{false}, five{false};
1819     app.add_flag("--two{false}", two);
1820     app.add_flag("--three", three);
1821     app.add_flag("!--four", four);
1822     app.add_flag("--five", five);
1823 
1824     run();
1825 
1826     CHECK(two == -2);
1827     CHECK(three);
1828     CHECK(!four);
1829     CHECK(five);
1830 }
1831 
1832 TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideError", "[config]") {
1833     TempFile tmpini{"TestIniTmp.ini"};
1834     app.set_config("--config", tmpini);
1835 
1836     {
1837         std::ofstream out{tmpini};
1838         out << "[default]" << std::endl;
1839         out << "two=2" << std::endl;
1840         out << "four=on" << std::endl;
1841         out << "five" << std::endl;
1842     }
1843 
1844     int two{0};
1845     bool four{false}, five{false};
1846     app.add_flag("--two{false}", two)->disable_flag_override();
1847     app.add_flag("!--four", four);
1848     app.add_flag("--five", five);
1849 
1850     CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
1851 }
1852 
1853 TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideSuccess", "[config]") {
1854     TempFile tmpini{"TestIniTmp.ini"};
1855     app.set_config("--config", tmpini);
1856 
1857     {
1858         std::ofstream out{tmpini};
1859         out << "[default]" << std::endl;
1860         out << "two=2" << std::endl;
1861         out << "four={}" << std::endl;
1862         out << "val=15" << std::endl;
1863     }
1864 
1865     int two{0}, four{0}, val{0};
1866     app.add_flag("--two{2}", two)->disable_flag_override();
1867     app.add_flag("--four{4}", four)->disable_flag_override();
1868     app.add_flag("--val", val);
1869 
1870     run();
1871 
1872     CHECK(two == 2);
1873     CHECK(four == 4);
1874     CHECK(val == 15);
1875 }
1876 
1877 TEST_CASE_METHOD(TApp, "TomlOutputSimple", "[config]") {
1878 
1879     int v{0};
1880     app.add_option("--simple", v);
1881 
1882     args = {"--simple=3"};
1883 
1884     run();
1885 
1886     std::string str = app.config_to_str();
1887     CHECK(str == "simple=3\n");
1888 }
1889 
1890 TEST_CASE_METHOD(TApp, "TomlOutputShort", "[config]") {
1891 
1892     int v{0};
1893     app.add_option("-s", v);
1894 
1895     args = {"-s3"};
1896 
1897     run();
1898 
1899     std::string str = app.config_to_str();
1900     CHECK(str == "s=3\n");
1901 }
1902 
1903 TEST_CASE_METHOD(TApp, "TomlOutputPositional", "[config]") {
1904 
1905     int v{0};
1906     app.add_option("pos", v);
1907 
1908     args = {"3"};
1909 
1910     run();
1911 
1912     std::string str = app.config_to_str();
1913     CHECK(str == "pos=3\n");
1914 }
1915 
1916 // try the output with environmental only arguments
1917 TEST_CASE_METHOD(TApp, "TomlOutputEnvironmental", "[config]") {
1918 
1919     put_env("CLI11_TEST_ENV_TMP", "2");
1920 
1921     int val{1};
1922     app.add_option(std::string{}, val)->envname("CLI11_TEST_ENV_TMP");
1923 
1924     run();
1925 
1926     CHECK(val == 2);
1927     std::string str = app.config_to_str();
1928     CHECK(str == "CLI11_TEST_ENV_TMP=2\n");
1929 
1930     unset_env("CLI11_TEST_ENV_TMP");
1931 }
1932 
1933 TEST_CASE_METHOD(TApp, "TomlOutputNoConfigurable", "[config]") {
1934 
1935     int v1{0}, v2{0};
1936     app.add_option("--simple", v1);
1937     app.add_option("--noconf", v2)->configurable(false);
1938 
1939     args = {"--simple=3", "--noconf=2"};
1940 
1941     run();
1942 
1943     std::string str = app.config_to_str();
1944     CHECK(str == "simple=3\n");
1945 }
1946 
1947 TEST_CASE_METHOD(TApp, "TomlOutputShortSingleDescription", "[config]") {
1948     std::string flag = "some_flag";
1949     const std::string description = "Some short description.";
1950     app.add_flag("--" + flag, description);
1951 
1952     run();
1953 
1954     std::string str = app.config_to_str(true, true);
1955     CHECK_THAT(str, Contains("# " + description + "\n" + flag + "=false\n"));
1956 }
1957 
1958 TEST_CASE_METHOD(TApp, "TomlOutputShortDoubleDescription", "[config]") {
1959     std::string flag1 = "flagnr1";
1960     std::string flag2 = "flagnr2";
1961     const std::string description1 = "First description.";
1962     const std::string description2 = "Second description.";
1963     app.add_flag("--" + flag1, description1);
1964     app.add_flag("--" + flag2, description2);
1965 
1966     run();
1967 
1968     std::string str = app.config_to_str(true, true);
1969     std::string ans = "# " + description1 + "\n" + flag1 + "=false\n\n# " + description2 + "\n" + flag2 + "=false\n";
1970     CHECK_THAT(str, Contains(ans));
1971 }
1972 
1973 TEST_CASE_METHOD(TApp, "TomlOutputGroups", "[config]") {
1974     std::string flag1 = "flagnr1";
1975     std::string flag2 = "flagnr2";
1976     const std::string description1 = "First description.";
1977     const std::string description2 = "Second description.";
1978     app.add_flag("--" + flag1, description1)->group("group1");
1979     app.add_flag("--" + flag2, description2)->group("group2");
1980 
1981     run();
1982 
1983     std::string str = app.config_to_str(true, true);
1984     CHECK_THAT(str, Contains("group1"));
1985     CHECK_THAT(str, Contains("group2"));
1986 }
1987 
1988 TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") {
1989     std::string flag1 = "flagnr1";
1990     std::string flag2 = "flagnr2";
1991     double val{12.7};
1992     const std::string description1 = "First description.";
1993     const std::string description2 = "Second description.";
1994     app.add_flag("--" + flag1, description1)->group("group1");
1995     app.add_flag("--" + flag2, description2)->group("group2");
1996     app.add_option("--dval", val)->capture_default_str()->group("");
1997 
1998     run();
1999 
2000     std::string str = app.config_to_str(true, true);
2001     CHECK_THAT(str, Contains("group1"));
2002     CHECK_THAT(str, Contains("group2"));
2003     CHECK_THAT(str, Contains("dval=12.7"));
2004     auto loc = str.find("dval=12.7");
2005     auto locg1 = str.find("group1");
2006     CHECK(loc < locg1);
2007     // make sure it doesn't come twice
2008     loc = str.find("dval=12.7", loc + 4);
2009     CHECK(std::string::npos == loc);
2010 }
2011 
2012 TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") {
2013     app.description("Some short app description.\n"
2014                     "That has multiple lines.");
2015     run();
2016 
2017     std::string str = app.config_to_str(true, true);
2018     CHECK_THAT(str, Contains("# Some short app description.\n"));
2019     CHECK_THAT(str, Contains("# That has multiple lines.\n"));
2020 }
2021 
2022 TEST_CASE_METHOD(TApp, "TomlOutputMultiLineDescription", "[config]") {
2023     std::string flag = "some_flag";
2024     const std::string description = "Some short description.\nThat has lines.";
2025     app.add_flag("--" + flag, description);
2026 
2027     run();
2028 
2029     std::string str = app.config_to_str(true, true);
2030     CHECK_THAT(str, Contains("# Some short description.\n"));
2031     CHECK_THAT(str, Contains("# That has lines.\n"));
2032     CHECK_THAT(str, Contains(flag + "=false\n"));
2033 }
2034 
2035 TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") {
2036     std::string flag = "flag";
2037     const std::string description = "Short flag description.\n";
2038     auto og = app.add_option_group("group");
2039     og->description("Option group description.\n"
2040                     "That has multiple lines.");
2041     og->add_flag("--" + flag, description);
2042     run();
2043 
2044     std::string str = app.config_to_str(true, true);
2045     CHECK_THAT(str, Contains("# Option group description.\n"));
2046     CHECK_THAT(str, Contains("# That has multiple lines.\n"));
2047 }
2048 
2049 TEST_CASE_METHOD(TApp, "TomlOutputSubcommandMultiLineDescription", "[config]") {
2050     std::string flag = "flag";
2051     const std::string description = "Short flag description.\n";
2052     auto subcom = app.add_subcommand("subcommand");
2053     subcom->configurable();
2054     subcom->description("Subcommand description.\n"
2055                         "That has multiple lines.");
2056     subcom->add_flag("--" + flag, description);
2057     run();
2058 
2059     std::string str = app.config_to_str(true, true);
2060     CHECK_THAT(str, Contains("# Subcommand description.\n"));
2061     CHECK_THAT(str, Contains("# That has multiple lines.\n"));
2062 }
2063 
2064 TEST_CASE_METHOD(TApp, "TomlOutputOptionGroup", "[config]") {
2065     std::string flag1 = "flagnr1";
2066     std::string flag2 = "flagnr2";
2067     double val{12.7};
2068     const std::string description1 = "First description.";
2069     const std::string description2 = "Second description.";
2070     app.add_flag("--" + flag1, description1)->group("group1");
2071     app.add_flag("--" + flag2, description2)->group("group2");
2072     auto og = app.add_option_group("group3", "g3 desc");
2073     og->add_option("--dval", val)->capture_default_str()->group("");
2074 
2075     run();
2076 
2077     std::string str = app.config_to_str(true, true);
2078     CHECK_THAT(str, Contains("group1"));
2079     CHECK_THAT(str, Contains("group2"));
2080     CHECK_THAT(str, Contains("dval=12.7"));
2081     CHECK_THAT(str, Contains("group3"));
2082     CHECK_THAT(str, Contains("g3 desc"));
2083     auto loc = str.find("dval=12.7");
2084     auto locg1 = str.find("group1");
2085     auto locg3 = str.find("group3");
2086     CHECK(loc > locg1);
2087     // make sure it doesn't come twice
2088     loc = str.find("dval=12.7", loc + 4);
2089     CHECK(std::string::npos == loc);
2090     CHECK(locg1 < locg3);
2091 }
2092 
2093 TEST_CASE_METHOD(TApp, "TomlOutputVector", "[config]") {
2094 
2095     std::vector<int> v;
2096     app.add_option("--vector", v);
2097     app.config_formatter(std::make_shared<CLI::ConfigTOML>());
2098     args = {"--vector", "1", "2", "3"};
2099 
2100     run();
2101 
2102     std::string str = app.config_to_str();
2103     CHECK(str == "vector=[1, 2, 3]\n");
2104 }
2105 
2106 TEST_CASE_METHOD(TApp, "ConfigOutputVectorCustom", "[config]") {
2107 
2108     std::vector<int> v;
2109     app.add_option("--vector", v);
2110     auto V = std::make_shared<CLI::ConfigBase>();
2111     V->arrayBounds('{', '}')->arrayDelimiter(';')->valueSeparator(':');
2112     app.config_formatter(V);
2113     args = {"--vector", "1", "2", "3"};
2114 
2115     run();
2116 
2117     std::string str = app.config_to_str();
2118     CHECK(str == "vector:{1; 2; 3}\n");
2119 }
2120 
2121 TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
2122 
2123     int v{0}, q{0};
2124     app.add_option("--simple", v);
2125     app.add_flag("--nothing");
2126     app.add_flag("--onething");
2127     app.add_flag("--something", q);
2128 
2129     args = {"--simple=3", "--onething", "--something", "--something"};
2130 
2131     run();
2132 
2133     std::string str = app.config_to_str();
2134     CHECK_THAT(str, Contains("simple=3"));
2135     CHECK_THAT(str, !Contains("nothing"));
2136     CHECK_THAT(str, Contains("onething=true"));
2137     CHECK_THAT(str, Contains("something=[true, true]"));
2138 
2139     str = app.config_to_str(true);
2140     CHECK_THAT(str, Contains("nothing"));
2141 }
2142 
2143 TEST_CASE_METHOD(TApp, "TomlOutputSet", "[config]") {
2144 
2145     int v{0};
2146     app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
2147 
2148     args = {"--simple=2"};
2149 
2150     run();
2151 
2152     std::string str = app.config_to_str();
2153     CHECK_THAT(str, Contains("simple=2"));
2154 }
2155 
2156 TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") {
2157 
2158     int v{7};
2159     app.add_option("--simple", v)->capture_default_str();
2160 
2161     run();
2162 
2163     std::string str = app.config_to_str();
2164     CHECK_THAT(str, !Contains("simple=7"));
2165 
2166     str = app.config_to_str(true);
2167     CHECK_THAT(str, Contains("simple=7"));
2168 }
2169 
2170 TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
2171 
2172     app.add_flag("--simple");
2173     auto subcom = app.add_subcommand("other");
2174     subcom->add_flag("--newer");
2175 
2176     args = {"--simple", "other", "--newer"};
2177     run();
2178 
2179     std::string str = app.config_to_str();
2180     CHECK_THAT(str, Contains("simple=true"));
2181     CHECK_THAT(str, Contains("other.newer=true"));
2182 }
2183 
2184 TEST_CASE_METHOD(TApp, "TomlOutputSubcomConfigurable", "[config]") {
2185 
2186     app.add_flag("--simple");
2187     auto subcom = app.add_subcommand("other")->configurable();
2188     subcom->add_flag("--newer");
2189 
2190     args = {"--simple", "other", "--newer"};
2191     run();
2192 
2193     std::string str = app.config_to_str();
2194     CHECK_THAT(str, Contains("simple=true"));
2195     CHECK_THAT(str, Contains("[other]"));
2196     CHECK_THAT(str, Contains("newer=true"));
2197     CHECK(std::string::npos == str.find("other.newer=true"));
2198 }
2199 
2200 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcom", "[config]") {
2201 
2202     app.add_flag("--simple");
2203     auto subcom = app.add_subcommand("other");
2204     subcom->add_flag("--newer");
2205     auto subsubcom = subcom->add_subcommand("sub2");
2206     subsubcom->add_flag("--newest");
2207 
2208     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2209     run();
2210 
2211     std::string str = app.config_to_str();
2212     CHECK_THAT(str, Contains("simple=true"));
2213     CHECK_THAT(str, Contains("other.newer=true"));
2214     CHECK_THAT(str, Contains("other.sub2.newest=true"));
2215 }
2216 
2217 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurable", "[config]") {
2218 
2219     app.add_flag("--simple");
2220     auto subcom = app.add_subcommand("other")->configurable();
2221     subcom->add_flag("--newer");
2222 
2223     auto subsubcom = subcom->add_subcommand("sub2");
2224     subsubcom->add_flag("--newest");
2225 
2226     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2227     run();
2228 
2229     std::string str = app.config_to_str();
2230     CHECK_THAT(str, Contains("simple=true"));
2231     CHECK_THAT(str, Contains("[other]"));
2232     CHECK_THAT(str, Contains("newer=true"));
2233     CHECK_THAT(str, Contains("[other.sub2]"));
2234     CHECK_THAT(str, Contains("newest=true"));
2235     CHECK(std::string::npos == str.find("sub2.newest=true"));
2236 }
2237 
2238 TEST_CASE_METHOD(TApp, "TomlOutputSubcomNonConfigurable", "[config]") {
2239 
2240     app.add_flag("--simple");
2241     auto subcom = app.add_subcommand("other", "other_descriptor")->configurable();
2242     subcom->add_flag("--newer");
2243 
2244     auto subcom2 = app.add_subcommand("sub2", "descriptor2");
2245     subcom2->add_flag("--newest")->configurable(false);
2246 
2247     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2248     run();
2249 
2250     std::string str = app.config_to_str(true, true);
2251     CHECK_THAT(str, Contains("other_descriptor"));
2252     CHECK_THAT(str, Contains("simple=true"));
2253     CHECK_THAT(str, Contains("[other]"));
2254     CHECK_THAT(str, Contains("newer=true"));
2255     CHECK_THAT(str, !Contains("newest"));
2256     CHECK_THAT(str, !Contains("descriptor2"));
2257 }
2258 
2259 TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurableDeep", "[config]") {
2260 
2261     app.add_flag("--simple");
2262     auto subcom = app.add_subcommand("other")->configurable();
2263     subcom->add_flag("--newer");
2264 
2265     auto subsubcom = subcom->add_subcommand("sub2");
2266     subsubcom->add_flag("--newest");
2267     auto sssscom = subsubcom->add_subcommand("sub-level2");
2268     subsubcom->add_flag("--still_newer");
2269     auto s5com = sssscom->add_subcommand("sub-level3");
2270     s5com->add_flag("--absolute_newest");
2271 
2272     args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
2273     run();
2274 
2275     std::string str = app.config_to_str();
2276     CHECK_THAT(str, Contains("simple=true"));
2277     CHECK_THAT(str, Contains("[other.sub2.sub-level2.sub-level3]"));
2278     CHECK_THAT(str, Contains("absolute_newest=true"));
2279     CHECK(std::string::npos == str.find(".absolute_newest=true"));
2280 }
2281 
2282 TEST_CASE_METHOD(TApp, "TomlOutputQuoted", "[config]") {
2283 
2284     std::string val1;
2285     app.add_option("--val1", val1);
2286 
2287     std::string val2;
2288     app.add_option("--val2", val2);
2289 
2290     args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"};
2291 
2292     run();
2293 
2294     CHECK(val1 == "I am a string");
2295     CHECK(val2 == "I am a \"confusing\" string");
2296 
2297     std::string str = app.config_to_str();
2298     CHECK_THAT(str, Contains("val1=\"I am a string\""));
2299     CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
2300 }
2301 
2302 TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") {
2303 
2304     std::string val1{"I am a string"};
2305     app.add_option("--val1", val1)->capture_default_str();
2306 
2307     std::string val2{R"(I am a "confusing" string)"};
2308     app.add_option("--val2", val2)->capture_default_str();
2309 
2310     run();
2311 
2312     std::string str = app.config_to_str(true);
2313     CHECK_THAT(str, Contains("val1=\"I am a string\""));
2314     CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
2315 }
2316 
2317 // #298
2318 TEST_CASE_METHOD(TApp, "StopReadingConfigOnClear", "[config]") {
2319 
2320     TempFile tmpini{"TestIniTmp.ini"};
2321 
2322     app.set_config("--config", tmpini);
2323     auto ptr = app.set_config();  // Should *not* read config file
2324     CHECK(nullptr == ptr);
2325 
2326     {
2327         std::ofstream out{tmpini};
2328         out << "volume=1" << std::endl;
2329     }
2330 
2331     int volume{0};
2332     app.add_option("--volume", volume, "volume1");
2333 
2334     run();
2335 
2336     CHECK(0 == volume);
2337 }
2338 
2339 TEST_CASE_METHOD(TApp, "ConfigWriteReadWrite", "[config]") {
2340 
2341     TempFile tmpini{"TestIniTmp.ini"};
2342 
2343     app.add_flag("--flag");
2344     run();
2345 
2346     // Save config, with default values too
2347     std::string config1 = app.config_to_str(true, true);
2348     {
2349         std::ofstream out{tmpini};
2350         out << config1 << std::endl;
2351     }
2352 
2353     app.set_config("--config", tmpini, "Read an ini file", true);
2354     run();
2355 
2356     std::string config2 = app.config_to_str(true, true);
2357 
2358     CHECK(config2 == config1);
2359 }
2360 
2361 /////// INI output tests
2362 
2363 TEST_CASE_METHOD(TApp, "IniOutputSimple", "[config]") {
2364 
2365     int v{0};
2366     app.add_option("--simple", v);
2367     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2368     args = {"--simple=3"};
2369 
2370     run();
2371 
2372     std::string str = app.config_to_str();
2373     CHECK(str == "simple=3\n");
2374 }
2375 
2376 TEST_CASE_METHOD(TApp, "IniOutputNoConfigurable", "[config]") {
2377 
2378     int v1{0}, v2{0};
2379     app.add_option("--simple", v1);
2380     app.add_option("--noconf", v2)->configurable(false);
2381     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2382     args = {"--simple=3", "--noconf=2"};
2383 
2384     run();
2385 
2386     std::string str = app.config_to_str();
2387     CHECK(str == "simple=3\n");
2388 }
2389 
2390 TEST_CASE_METHOD(TApp, "IniOutputShortSingleDescription", "[config]") {
2391     std::string flag = "some_flag";
2392     const std::string description = "Some short description.";
2393     app.add_flag("--" + flag, description);
2394     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2395     run();
2396 
2397     std::string str = app.config_to_str(true, true);
2398     CHECK_THAT(str, Contains("; " + description + "\n" + flag + "=false\n"));
2399 }
2400 
2401 TEST_CASE_METHOD(TApp, "IniOutputShortDoubleDescription", "[config]") {
2402     std::string flag1 = "flagnr1";
2403     std::string flag2 = "flagnr2";
2404     const std::string description1 = "First description.";
2405     const std::string description2 = "Second description.";
2406     app.add_flag("--" + flag1, description1);
2407     app.add_flag("--" + flag2, description2);
2408     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2409     run();
2410 
2411     std::string str = app.config_to_str(true, true);
2412     std::string ans = "; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n";
2413     CHECK_THAT(str, Contains(ans));
2414 }
2415 
2416 TEST_CASE_METHOD(TApp, "IniOutputGroups", "[config]") {
2417     std::string flag1 = "flagnr1";
2418     std::string flag2 = "flagnr2";
2419     const std::string description1 = "First description.";
2420     const std::string description2 = "Second description.";
2421     app.add_flag("--" + flag1, description1)->group("group1");
2422     app.add_flag("--" + flag2, description2)->group("group2");
2423     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2424     run();
2425 
2426     std::string str = app.config_to_str(true, true);
2427     CHECK_THAT(str, Contains("group1"));
2428     CHECK_THAT(str, Contains("group2"));
2429 }
2430 
2431 TEST_CASE_METHOD(TApp, "IniOutputHiddenOptions", "[config]") {
2432     std::string flag1 = "flagnr1";
2433     std::string flag2 = "flagnr2";
2434     double val{12.7};
2435     const std::string description1 = "First description.";
2436     const std::string description2 = "Second description.";
2437     app.add_flag("--" + flag1, description1)->group("group1");
2438     app.add_flag("--" + flag2, description2)->group("group2");
2439     app.add_option("--dval", val)->capture_default_str()->group("");
2440     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2441     run();
2442 
2443     std::string str = app.config_to_str(true, true);
2444     CHECK_THAT(str, Contains("group1"));
2445     CHECK_THAT(str, Contains("group2"));
2446     CHECK_THAT(str, Contains("dval=12.7"));
2447     auto loc = str.find("dval=12.7");
2448     auto locg1 = str.find("group1");
2449     CHECK(loc < locg1);
2450     // make sure it doesn't come twice
2451     loc = str.find("dval=12.7", loc + 4);
2452     CHECK(std::string::npos == loc);
2453 }
2454 
2455 TEST_CASE_METHOD(TApp, "IniOutputAppMultiLineDescription", "[config]") {
2456     app.description("Some short app description.\n"
2457                     "That has multiple lines.");
2458     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2459     run();
2460 
2461     std::string str = app.config_to_str(true, true);
2462     CHECK_THAT(str, Contains("; Some short app description.\n"));
2463     CHECK_THAT(str, Contains("; That has multiple lines.\n"));
2464 }
2465 
2466 TEST_CASE_METHOD(TApp, "IniOutputMultiLineDescription", "[config]") {
2467     std::string flag = "some_flag";
2468     const std::string description = "Some short description.\nThat has lines.";
2469     app.add_flag("--" + flag, description);
2470     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2471     run();
2472 
2473     std::string str = app.config_to_str(true, true);
2474     CHECK_THAT(str, Contains("; Some short description.\n"));
2475     CHECK_THAT(str, Contains("; That has lines.\n"));
2476     CHECK_THAT(str, Contains(flag + "=false\n"));
2477 }
2478 
2479 TEST_CASE_METHOD(TApp, "IniOutputOptionGroupMultiLineDescription", "[config]") {
2480     std::string flag = "flag";
2481     const std::string description = "Short flag description.\n";
2482     auto og = app.add_option_group("group");
2483     og->description("Option group description.\n"
2484                     "That has multiple lines.");
2485     og->add_flag("--" + flag, description);
2486     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2487     run();
2488 
2489     std::string str = app.config_to_str(true, true);
2490     CHECK_THAT(str, Contains("; Option group description.\n"));
2491     CHECK_THAT(str, Contains("; That has multiple lines.\n"));
2492 }
2493 
2494 TEST_CASE_METHOD(TApp, "IniOutputSubcommandMultiLineDescription", "[config]") {
2495     std::string flag = "flag";
2496     const std::string description = "Short flag description.\n";
2497     auto subcom = app.add_subcommand("subcommand");
2498     subcom->configurable();
2499     subcom->description("Subcommand description.\n"
2500                         "That has multiple lines.");
2501     subcom->add_flag("--" + flag, description);
2502     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2503     run();
2504 
2505     std::string str = app.config_to_str(true, true);
2506     CHECK_THAT(str, Contains("; Subcommand description.\n"));
2507     CHECK_THAT(str, Contains("; That has multiple lines.\n"));
2508 }
2509 
2510 TEST_CASE_METHOD(TApp, "IniOutputOptionGroup", "[config]") {
2511     std::string flag1 = "flagnr1";
2512     std::string flag2 = "flagnr2";
2513     double val{12.7};
2514     const std::string description1 = "First description.";
2515     const std::string description2 = "Second description.";
2516     app.add_flag("--" + flag1, description1)->group("group1");
2517     app.add_flag("--" + flag2, description2)->group("group2");
2518     auto og = app.add_option_group("group3", "g3 desc");
2519     og->add_option("--dval", val)->capture_default_str()->group("");
2520     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2521     run();
2522 
2523     std::string str = app.config_to_str(true, true);
2524     CHECK_THAT(str, Contains("group1"));
2525     CHECK_THAT(str, Contains("group2"));
2526     CHECK_THAT(str, Contains("dval=12.7"));
2527     CHECK_THAT(str, Contains("group3"));
2528     CHECK_THAT(str, Contains("g3 desc"));
2529     auto loc = str.find("dval=12.7");
2530     auto locg1 = str.find("group1");
2531     auto locg3 = str.find("group3");
2532     CHECK(loc > locg1);
2533     // make sure it doesn't come twice
2534     loc = str.find("dval=12.7", loc + 4);
2535     CHECK(std::string::npos == loc);
2536     CHECK(locg1 < locg3);
2537 }
2538 
2539 TEST_CASE_METHOD(TApp, "IniOutputVector", "[config]") {
2540 
2541     std::vector<int> v;
2542     app.add_option("--vector", v);
2543 
2544     args = {"--vector", "1", "2", "3"};
2545     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2546     run();
2547 
2548     std::string str = app.config_to_str();
2549     CHECK(str == "vector=1 2 3\n");
2550 }
2551 
2552 TEST_CASE_METHOD(TApp, "IniOutputFlag", "[config]") {
2553 
2554     int v{0}, q{0};
2555     app.add_option("--simple", v);
2556     app.add_flag("--nothing");
2557     app.add_flag("--onething");
2558     app.add_flag("--something", q);
2559 
2560     args = {"--simple=3", "--onething", "--something", "--something"};
2561     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2562     run();
2563 
2564     std::string str = app.config_to_str();
2565     CHECK_THAT(str, Contains("simple=3"));
2566     CHECK_THAT(str, !Contains("nothing"));
2567     CHECK_THAT(str, Contains("onething=true"));
2568     CHECK_THAT(str, Contains("something=true true"));
2569 
2570     str = app.config_to_str(true);
2571     CHECK_THAT(str, Contains("nothing"));
2572 }
2573 
2574 TEST_CASE_METHOD(TApp, "IniOutputSet", "[config]") {
2575 
2576     int v{0};
2577     app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
2578 
2579     args = {"--simple=2"};
2580     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2581     run();
2582 
2583     std::string str = app.config_to_str();
2584     CHECK_THAT(str, Contains("simple=2"));
2585 }
2586 
2587 TEST_CASE_METHOD(TApp, "IniOutputDefault", "[config]") {
2588 
2589     int v{7};
2590     app.add_option("--simple", v)->capture_default_str();
2591     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2592     run();
2593 
2594     std::string str = app.config_to_str();
2595     CHECK_THAT(str, !Contains("simple=7"));
2596 
2597     str = app.config_to_str(true);
2598     CHECK_THAT(str, Contains("simple=7"));
2599 }
2600 
2601 TEST_CASE_METHOD(TApp, "IniOutputSubcom", "[config]") {
2602 
2603     app.add_flag("--simple");
2604     auto subcom = app.add_subcommand("other");
2605     subcom->add_flag("--newer");
2606     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2607     args = {"--simple", "other", "--newer"};
2608     run();
2609 
2610     std::string str = app.config_to_str();
2611     CHECK_THAT(str, Contains("simple=true"));
2612     CHECK_THAT(str, Contains("other.newer=true"));
2613 }
2614 
2615 TEST_CASE_METHOD(TApp, "IniOutputSubcomCustomSep", "[config]") {
2616 
2617     app.add_flag("--simple");
2618     auto subcom = app.add_subcommand("other");
2619     subcom->add_flag("--newer");
2620     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2621     app.get_config_formatter_base()->parentSeparator(':');
2622     args = {"--simple", "other", "--newer"};
2623     run();
2624 
2625     std::string str = app.config_to_str();
2626     CHECK_THAT(str, Contains("simple=true"));
2627     CHECK_THAT(str, Contains("other:newer=true"));
2628 }
2629 
2630 TEST_CASE_METHOD(TApp, "IniOutputSubcomConfigurable", "[config]") {
2631 
2632     app.add_flag("--simple");
2633     auto subcom = app.add_subcommand("other")->configurable();
2634     subcom->add_flag("--newer");
2635     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2636     args = {"--simple", "other", "--newer"};
2637     run();
2638 
2639     std::string str = app.config_to_str();
2640     CHECK_THAT(str, Contains("simple=true"));
2641     CHECK_THAT(str, Contains("[other]"));
2642     CHECK_THAT(str, Contains("newer=true"));
2643     CHECK(std::string::npos == str.find("other.newer=true"));
2644 }
2645 
2646 TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") {
2647 
2648     app.add_flag("--simple");
2649     auto subcom = app.add_subcommand("other");
2650     subcom->add_flag("--newer");
2651     auto subsubcom = subcom->add_subcommand("sub2");
2652     subsubcom->add_flag("--newest");
2653     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2654     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2655     run();
2656 
2657     std::string str = app.config_to_str();
2658     CHECK_THAT(str, Contains("simple=true"));
2659     CHECK_THAT(str, Contains("other.newer=true"));
2660     CHECK_THAT(str, Contains("other.sub2.newest=true"));
2661 }
2662 
2663 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") {
2664 
2665     app.add_flag("--simple");
2666     auto subcom = app.add_subcommand("other");
2667     subcom->add_flag("--newer");
2668     auto subsubcom = subcom->add_subcommand("sub2");
2669     subsubcom->add_flag("--newest");
2670     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2671     app.get_config_formatter_base()->parentSeparator('|');
2672     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2673     run();
2674 
2675     std::string str = app.config_to_str();
2676     CHECK_THAT(str, Contains("simple=true"));
2677     CHECK_THAT(str, Contains("other|newer=true"));
2678     CHECK_THAT(str, Contains("other|sub2|newest=true"));
2679 }
2680 
2681 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") {
2682 
2683     app.add_flag("--simple");
2684     auto subcom = app.add_subcommand("other")->configurable();
2685     subcom->add_flag("--newer");
2686 
2687     auto subsubcom = subcom->add_subcommand("sub2");
2688     subsubcom->add_flag("--newest");
2689     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2690     args = {"--simple", "other", "--newer", "sub2", "--newest"};
2691     run();
2692 
2693     std::string str = app.config_to_str();
2694     CHECK_THAT(str, Contains("simple=true"));
2695     CHECK_THAT(str, Contains("[other]"));
2696     CHECK_THAT(str, Contains("newer=true"));
2697     CHECK_THAT(str, Contains("[other.sub2]"));
2698     CHECK_THAT(str, Contains("newest=true"));
2699     CHECK(std::string::npos == str.find("sub2.newest=true"));
2700 }
2701 
2702 TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurableDeep", "[config]") {
2703 
2704     app.add_flag("--simple");
2705     auto subcom = app.add_subcommand("other")->configurable();
2706     subcom->add_flag("--newer");
2707 
2708     auto subsubcom = subcom->add_subcommand("sub2");
2709     subsubcom->add_flag("--newest");
2710     auto sssscom = subsubcom->add_subcommand("sub-level2");
2711     subsubcom->add_flag("--still_newer");
2712     auto s5com = sssscom->add_subcommand("sub-level3");
2713     s5com->add_flag("--absolute_newest");
2714     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2715     args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
2716     run();
2717 
2718     std::string str = app.config_to_str();
2719     CHECK_THAT(str, Contains("simple=true"));
2720     CHECK_THAT(str, Contains("[other.sub2.sub-level2.sub-level3]"));
2721     CHECK_THAT(str, Contains("absolute_newest=true"));
2722     CHECK(std::string::npos == str.find(".absolute_newest=true"));
2723 }
2724 
2725 TEST_CASE_METHOD(TApp, "IniOutputQuoted", "[config]") {
2726 
2727     std::string val1;
2728     app.add_option("--val1", val1);
2729 
2730     std::string val2;
2731     app.add_option("--val2", val2);
2732     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2733     args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"};
2734 
2735     run();
2736 
2737     CHECK(val1 == "I am a string");
2738     CHECK(val2 == "I am a \"confusing\" string");
2739 
2740     std::string str = app.config_to_str();
2741     CHECK_THAT(str, Contains("val1=\"I am a string\""));
2742     CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
2743 }
2744 
2745 TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") {
2746 
2747     std::string val1{"I am a string"};
2748     app.add_option("--val1", val1)->capture_default_str();
2749 
2750     std::string val2{R"(I am a "confusing" string)"};
2751     app.add_option("--val2", val2)->capture_default_str();
2752     app.config_formatter(std::make_shared<CLI::ConfigINI>());
2753     run();
2754 
2755     std::string str = app.config_to_str(true);
2756     CHECK_THAT(str, Contains("val1=\"I am a string\""));
2757     CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
2758 }
2759