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