CLI11 2.7.1
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
Config_inl.hpp
1// Copyright (c) 2017-2026, 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#pragma once
8
9// IWYU pragma: private, include "CLI/CLI.hpp"
10
11// This include is only needed for IDEs to discover symbols
12#include "../Config.hpp"
13
14#include "../Encoding.hpp"
15
16// [CLI11:public_includes:set]
17#include <algorithm>
18#include <cctype>
19#include <fstream>
20#include <istream>
21#include <sstream>
22#include <string>
23#include <utility>
24#include <vector>
25// [CLI11:public_includes:end]
26
27namespace CLI {
28// [CLI11:config_inl_hpp:verbatim]
29
30CLI11_NODISCARD CLI11_INLINE std::string ConfigItem::fullname() const {
31 std::vector<std::string> tmp = parents;
32 tmp.emplace_back(name);
33 return detail::join(tmp, ".");
34 (void)multiline; // suppression for cppcheck false positive
35}
36
37CLI11_INLINE std::string
38Config::to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const {
39 return to_config(app, mode != ConfigOutputMode::Active, write_description, std::move(prefix));
40}
41
42CLI11_NODISCARD CLI11_INLINE std::string Config::to_flag(const ConfigItem &item) const {
43 if(item.inputs.size() == 1) {
44 return item.inputs.at(0);
45 }
46 if(item.inputs.empty()) {
47 return "{}";
48 }
49 throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE
50}
51
52CLI11_INLINE std::vector<ConfigItem> Config::from_file(const std::string &name) const {
53#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
54 std::ifstream input{to_path(name)};
55#else
56 std::ifstream input{name};
57#endif
58
59 if(!input.good())
60 throw FileError::Missing(name);
61
62 return from_config(input);
63}
64
65static constexpr auto multiline_literal_quote = R"(''')";
66static constexpr auto multiline_string_quote = R"(""")";
67
68namespace detail {
69
70CLI11_INLINE bool is_printable(const std::string &test_string) {
71 return std::all_of(test_string.begin(), test_string.end(), [](char x) {
72 return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n' || x == '\t');
73 });
74}
75
76CLI11_INLINE std::string
77convert_arg_for_ini(const std::string &arg, char stringQuote, char literalQuote, bool disable_multi_line) {
78 if(arg.empty()) {
79 return std::string(2, stringQuote);
80 }
81 // some specifically supported strings
82 if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
83 return arg;
84 }
85 // floating point conversion can convert some hex codes, but don't try that here
86 if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
87 using CLI::detail::lexical_cast;
88 double val = 0.0;
89 if(lexical_cast(arg, val)) {
90 if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) {
91 return arg;
92 }
93 }
94 }
95 // just quote a single non numeric character
96 if(arg.size() == 1) {
97 if(isprint(static_cast<unsigned char>(arg.front())) == 0) {
98 return binary_escape_string(arg);
99 }
100 if(arg == "'") {
101 return std::string(1, stringQuote) + "'" + stringQuote;
102 }
103 return std::string(1, literalQuote) + arg + literalQuote;
104 }
105 // handle hex, binary or octal arguments
106 if(arg.front() == '0') {
107 if(arg[1] == 'x') {
108 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
109 return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
110 })) {
111 return arg;
112 }
113 } else if(arg[1] == 'o') {
114 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
115 return arg;
116 }
117 } else if(arg[1] == 'b') {
118 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
119 return arg;
120 }
121 }
122 }
123 if(!is_printable(arg)) {
124 return binary_escape_string(arg);
125 }
126 if(detail::has_escapable_character(arg)) {
127 if(arg.size() > 100 && !disable_multi_line) {
128 if(arg.find(multiline_literal_quote) != std::string::npos) {
129 return binary_escape_string(arg, true);
130 }
131 std::string return_string{multiline_literal_quote};
132 return_string.reserve(7 + arg.size());
133 if(arg.front() == '\n') {
134 return_string.push_back('\n');
135 }
136 return_string.append(arg);
137 if(arg.back() == '\n') {
138 return_string.push_back('\n');
139 }
140 return_string.append(multiline_literal_quote, 3);
141 return return_string;
142 }
143 return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
144 }
145 return std::string(1, stringQuote) + arg + stringQuote;
146}
147
148CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
149 char sepChar,
150 char arrayStart,
151 char arrayEnd,
152 char stringQuote,
153 char literalQuote) {
154 bool disable_multi_line{false};
155 std::string joined;
156 if(args.size() > 1 && arrayStart != '\0') {
157 joined.push_back(arrayStart);
158 disable_multi_line = true;
159 }
160 const bool sep_is_space = std::isspace<char>(sepChar, std::locale());
161 std::size_t start = 0;
162 for(const auto &arg : args) {
163 if(start++ > 0) {
164 joined.push_back(sepChar);
165 if(!sep_is_space) {
166 joined.push_back(' ');
167 }
168 }
169 joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line));
170 }
171 if(args.size() > 1 && arrayEnd != '\0') {
172 joined.push_back(arrayEnd);
173 }
174 return joined;
175}
176
177CLI11_INLINE std::vector<std::string>
178generate_parents(const std::string &section, std::string &name, char parentSeparator) {
179 std::vector<std::string> parents;
180 if(detail::to_lower(section) != "default") {
181 if(section.find(parentSeparator) != std::string::npos) {
182 parents = detail::split_up(section, parentSeparator);
183 } else {
184 parents = {section};
185 }
186 }
187 if(name.find(parentSeparator) != std::string::npos) {
188 std::vector<std::string> plist = detail::split_up(name, parentSeparator);
189 name = plist.back();
190 plist.pop_back();
191 parents.insert(parents.end(), plist.begin(), plist.end());
192 }
193 // clean up quotes on the parents
194 try {
195 detail::remove_quotes(parents);
196 } catch(const std::invalid_argument &iarg) {
197 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
198 }
199 return parents;
200}
201
202CLI11_INLINE void
203checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, char parentSeparator) {
204
205 std::string estring;
206 auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
207 if(!output.empty() && output.back().name == "--") {
208 std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
209 while(output.back().parents.size() >= msize) {
210 output.push_back(output.back());
211 output.back().parents.pop_back();
212 }
213
214 if(parents.size() > 1) {
215 std::size_t common = 0;
216 std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
217 for(std::size_t ii = 0; ii < mpair; ++ii) {
218 if(output.back().parents[ii] != parents[ii]) {
219 break;
220 }
221 ++common;
222 }
223 if(common == mpair) {
224 output.pop_back();
225 } else {
226 while(output.back().parents.size() > common + 1) {
227 output.push_back(output.back());
228 output.back().parents.pop_back();
229 }
230 }
231 for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
232 output.emplace_back();
233 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
234 output.back().name = "++";
235 }
236 }
237 } else if(parents.size() > 1) {
238 for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
239 output.emplace_back();
240 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
241 output.back().name = "++";
242 }
243 }
244
245 // insert a section end which is just an empty items_buffer
246 output.emplace_back();
247 output.back().parents = std::move(parents);
248 output.back().name = "++";
249}
250
252CLI11_INLINE bool hasMLString(std::string const &fullString, char check) {
253 if(fullString.length() < 3) {
254 return false;
255 }
256 auto it = fullString.rbegin();
257 return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check);
258}
259
261CLI11_INLINE auto find_matching_config(std::vector<ConfigItem> &items,
262 const std::vector<std::string> &parents,
263 const std::string &name,
264 bool fullSearch) -> decltype(items.begin()) {
265 if(items.empty()) {
266 return items.end();
267 }
268 auto search = items.end() - 1;
269 do {
270 if(search->parents == parents && search->name == name) {
271 return search;
272 }
273 if(search == items.begin()) {
274 break;
275 }
276 --search;
277 } while(fullSearch);
278 return items.end();
279}
280
281CLI11_INLINE void clean_name_string(std::string &name, const std::string &keyChars) {
282 if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') ||
283 (name.find_first_of("'`\"\\") != std::string::npos)) {
284 if(name.find_first_of('\'') == std::string::npos) {
285 name.insert(0, 1, '\'');
286 name.push_back('\'');
287 } else {
288 if(detail::has_escapable_character(name)) {
289 name = detail::add_escaped_characters(name);
290 }
291 name.insert(0, 1, '\"');
292 name.push_back('\"');
293 }
294 }
295}
296} // namespace detail
297
298CLI11_INLINE std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
299 std::string line;
300 std::string buffer;
301 std::string currentSection = "default";
302 std::string previousSection = "default";
303 std::vector<ConfigItem> output;
304 bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
305 bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
306 bool inSection{false};
307 bool inMLineComment{false};
308 bool inMLineValue{false};
309
310 char aStart = (isINIArray) ? '[' : arrayStart;
311 char aEnd = (isINIArray) ? ']' : arrayEnd;
312 char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
313 int currentSectionIndex{0};
314
315 std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter};
316 while(getline(input, buffer)) {
317 std::vector<std::string> items_buffer;
318 std::string name;
319 line = detail::trim_copy(buffer);
320 std::size_t len = line.length();
321 // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
322 if(len < 3) {
323 continue;
324 }
325 if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) {
326 // check if the multiline comment opens and closes on the same line; the opening quotes
327 // themselves must not be counted as the closer hence the length requirement
328 if(len >= 6 && detail::hasMLString(line, line.front())) {
329 continue;
330 }
331 inMLineComment = true;
332 auto cchar = line.front();
333 while(inMLineComment) {
334 if(getline(input, line)) {
335 detail::trim(line);
336 } else {
337 break;
338 }
339 if(detail::hasMLString(line, cchar)) {
340 inMLineComment = false;
341 }
342 }
343 continue;
344 }
345 // strip a trailing comment (quote aware) for section headers so that "[section] # comment"
346 // is recognized as a section; value lines handle their own trailing comments later
347 if(line.front() == '[' && line.back() != ']' && line.find_first_of(commentChar) != std::string::npos) {
348 std::size_t comment_search = 0;
349 while(comment_search < line.size()) {
350 auto test_char = line[comment_search];
351 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
352 comment_search = detail::close_sequence(line, comment_search, line[comment_search]);
353 ++comment_search;
354 } else if(test_char == commentChar) {
355 break;
356 } else {
357 ++comment_search;
358 }
359 }
360 if(comment_search < line.size() && line[comment_search] == commentChar) {
361 line = detail::trim_copy(line.substr(0, comment_search));
362 len = line.length();
363 if(len < 3) {
364 continue;
365 }
366 }
367 }
368 if(line.front() == '[' && line.back() == ']') {
369 if(currentSection != "default") {
370 // insert a section end which is just an empty items_buffer
371 output.emplace_back();
372 output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
373 output.back().name = "--";
374 }
375 currentSection = line.substr(1, len - 2);
376 // deal with double brackets for TOML
377 if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
378 currentSection = currentSection.substr(1, currentSection.size() - 2);
379 }
380 if(detail::to_lower(currentSection) == "default") {
381 currentSection = "default";
382 } else {
383 detail::checkParentSegments(output, currentSection, parentSeparatorChar);
384 }
385 inSection = false;
386 if(currentSection == previousSection) {
387 ++currentSectionIndex;
388 } else {
389 currentSectionIndex = 0;
390 previousSection = currentSection;
391 }
392 continue;
393 }
394
395 // comment lines
396 if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
397 continue;
398 }
399 std::size_t search_start = 0;
400 if(line.find_first_of("\"'`") != std::string::npos) {
401 while(search_start < line.size()) {
402 auto test_char = line[search_start];
403 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
404 search_start = detail::close_sequence(line, search_start, line[search_start]);
405 ++search_start;
406 } else if(test_char == valueDelimiter || test_char == commentChar) {
407 --search_start;
408 break;
409 } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) {
410 ++search_start;
411 } else {
412 search_start = line.find_first_of(line_sep_chars, search_start);
413 }
414 }
415 }
416 // Find = in string, split and recombine
417 auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1);
418 auto comment_pos = line.find_first_of(commentChar, search_start);
419 if(comment_pos < delimiter_pos) {
420 delimiter_pos = std::string::npos;
421 }
422 if(delimiter_pos != std::string::npos) {
423
424 name = detail::trim_copy(line.substr(0, delimiter_pos));
425 std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
426 bool mlquote =
427 (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0);
428 if(!mlquote && comment_pos != std::string::npos) {
429 auto citems = detail::split_up(item, commentChar);
430 item = detail::trim_copy(citems.front());
431 }
432 if(mlquote) {
433 // multiline string
434 auto keyChar = item.front();
435 auto offset = buffer.find_first_not_of(" \t");
436 item = buffer.substr((offset == std::string::npos ? 0 : offset) + delimiter_pos + 1, std::string::npos);
437 detail::ltrim(item);
438 item.erase(0, 3);
439 inMLineValue = true;
440 bool lineExtension{false};
441 bool firstLine = true;
442 if(!item.empty() && item.back() == '\\' && keyChar == '\"') {
443 item.pop_back();
444 lineExtension = true;
445 } else if(detail::hasMLString(item, keyChar)) {
446 // deal with the first line closing the multiline literal
447 item.pop_back();
448 item.pop_back();
449 item.pop_back();
450 if(keyChar == '\"') {
451 try {
452 item = detail::remove_escaped_characters(item);
453 } catch(const std::invalid_argument &iarg) {
454 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
455 }
456 }
457 inMLineValue = false;
458 }
459 while(inMLineValue) {
460 std::string l2;
461 if(!std::getline(input, l2)) {
462 break;
463 }
464 line = l2;
465 detail::rtrim(line);
466 if(detail::hasMLString(line, keyChar)) {
467 line.pop_back();
468 line.pop_back();
469 line.pop_back();
470 if(lineExtension) {
471 detail::ltrim(line);
472 } else if(!(firstLine && item.empty())) {
473 item.push_back('\n');
474 }
475 firstLine = false;
476 item += line;
477 inMLineValue = false;
478 if(!item.empty() && item.back() == '\n') {
479 item.pop_back();
480 }
481 if(keyChar == '\"') {
482 try {
483 item = detail::remove_escaped_characters(item);
484 } catch(const std::invalid_argument &iarg) {
485 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
486 }
487 }
488 } else {
489 if(lineExtension) {
490 detail::trim(l2);
491 } else if(!(firstLine && item.empty())) {
492 item.push_back('\n');
493 }
494 lineExtension = false;
495 firstLine = false;
496 if(!l2.empty() && l2.back() == '\\' && keyChar == '\"') {
497 lineExtension = true;
498 l2.pop_back();
499 }
500 item += l2;
501 }
502 }
503 items_buffer = {item};
504 } else if(!item.empty() && item.front() == aStart) {
505 for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
506 detail::trim(multiline);
507 item += multiline;
508 }
509 if(item.back() == aEnd) {
510 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
511 } else {
512 items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
513 }
514 } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
515 items_buffer = detail::split_up(item, aSep);
516 } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
517 items_buffer = detail::split_up(item, '\0');
518 } else {
519 items_buffer = {item};
520 }
521 } else {
522 name = detail::trim_copy(line.substr(0, comment_pos));
523 items_buffer = {"true"};
524 }
525 std::vector<std::string> parents;
526 try {
527 parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
528 detail::process_quoted_string(name, '"', '\'', true);
529 // clean up quotes on the items and check for escaped strings
530 for(auto &it : items_buffer) {
531 detail::process_quoted_string(it, stringQuote, literalQuote);
532 }
533 } catch(const std::invalid_argument &ia) {
534 throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError);
535 }
536
537 if(parents.size() > maximumLayers) {
538 continue;
539 }
540 if(!configSection.empty() && !inSection) {
541 if(parents.empty() || parents.front() != configSection) {
542 continue;
543 }
544 if(configIndex >= 0 && currentSectionIndex != configIndex) {
545 continue;
546 }
547 parents.erase(parents.begin());
548 inSection = true;
549 }
550 auto match = detail::find_matching_config(output, parents, name, allowMultipleDuplicateFields);
551 if(match != output.end()) {
552 if((match->inputs.size() > 1 && items_buffer.size() > 1) || allowMultipleDuplicateFields) {
553 // insert a separator if one is not already present
554 if(!(match->inputs.back().empty() || items_buffer.front().empty() || match->inputs.back() == "%%" ||
555 items_buffer.front() == "%%")) {
556 match->inputs.emplace_back("%%");
557 match->multiline = true;
558 }
559 }
560 match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end());
561 } else {
562 output.emplace_back();
563 output.back().parents = std::move(parents);
564 output.back().name = std::move(name);
565 output.back().inputs = std::move(items_buffer);
566 }
567 }
568 if(currentSection != "default") {
569 // insert a section end which is just an empty items_buffer
570 std::string ename;
571 output.emplace_back();
572 output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
573 output.back().name = "--";
574 while(output.back().parents.size() > 1) {
575 output.push_back(output.back());
576 output.back().parents.pop_back();
577 }
578 }
579 return output;
580}
581
582CLI11_INLINE std::string
583ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
584 return to_config(app,
585 default_also ? ConfigOutputMode::AllDefaults : ConfigOutputMode::Active,
586 write_description,
587 std::move(prefix));
588}
589
590CLI11_INLINE std::string
591ConfigBase::to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const {
592 std::stringstream out;
593 const bool include_default_values = (mode != ConfigOutputMode::Active);
594 std::string commentLead;
595 commentLead.push_back(commentChar);
596 commentLead.push_back(' ');
597
598 std::string commentTest = "#;";
599 commentTest.push_back(commentChar);
600 commentTest.push_back(parentSeparatorChar);
601
602 std::string keyChars = commentTest;
603 keyChars.push_back(literalQuote);
604 keyChars.push_back(stringQuote);
605 keyChars.push_back(arrayStart);
606 keyChars.push_back(arrayEnd);
607 keyChars.push_back(valueDelimiter);
608 keyChars.push_back(arraySeparator);
609
610 std::vector<std::string> groups = app->get_groups();
611 bool defaultUsed = false;
612 groups.insert(groups.begin(), std::string("OPTIONS"));
613
614 const std::vector<const Option *> options = app->get_options({});
615 for(auto &group : groups) {
616 if(group == "OPTIONS" || group.empty()) {
617 if(defaultUsed) {
618 continue;
619 }
620 defaultUsed = true;
621 }
622 if(write_description && group != "OPTIONS" && !group.empty()) {
623 out << '\n' << commentChar << commentLead << group << " Options\n";
624 }
625 for(const Option *opt : app->get_options({})) {
626 // Only process options that are configurable
627 if(opt->get_configurable()) {
628 if(opt->get_group() != group) {
629 if(!(group == "OPTIONS" && opt->get_group().empty())) {
630 continue;
631 }
632 }
633 std::string single_name = opt->get_single_name();
634 if(single_name.empty()) {
635 continue;
636 }
637
638 auto results = opt->reduced_results();
639 if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) {
640 std::reverse(results.begin(), results.end());
641 }
642 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum && opt->count() >= 1 &&
643 results.size() == 1) {
644 // if the multi option policy is sum then there is a possibility of incorrect fields being produced
645 // best to just use the original data for config files
646 auto pos = opt->_validate(results[0], 0);
647 if(!pos.empty()) {
648 results = opt->results();
649 }
650 }
651 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Join && opt->count() > 1) {
652 char delim = opt->get_delimiter();
653 if(delim == '\0') {
654 // this branch deals with a situation where the output would not be readable by a config file
655 results = opt->results();
656 } else {
657 // this branch deals with the case of the strings containing the delimiter itself or empty
658 // strings which would be interpreted incorrectly
659 auto delim_count = std::count(results[0].begin(), results[0].end(), delim);
660 if(results[0].back() == delim ||
661 static_cast<decltype(delim_count)>(opt->count()) <= delim_count ||
662 results[0].find(std::string(2, delim)) != std::string::npos) {
663 results = opt->results();
664 }
665 }
666 }
667 std::string value;
668
669 if(opt->count() == 1 && results.size() == 2 && results.front() == "{}" && results.back() == "%%") {
670 // there is a catch to allow for {} to used as as string in the output
671 // it will append a sequence terminator to the output so the lexical conversion handles it
672 // correctly but that is meant for config files so when outputting for a config file we need to
673 // makes sure to get the correct output
674 value = "\"{}\"";
675 } else {
676 value = detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
677 }
678
679 bool isDefault = false;
680 if(value.empty() && include_default_values) {
681 if(!opt->get_default_str().empty()) {
682 results_t res;
683 opt->results(res);
684 value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
685 } else if(opt->get_expected_min() == 0) {
686 value = "false";
687 } else if(opt->get_run_callback_for_default() || !opt->get_required()) {
688 value = "\"\""; // empty string default value
689 } else {
690 value = "\"<REQUIRED>\"";
691 }
692 isDefault = true;
693 }
694
695 if(!value.empty()) {
696 if(!opt->get_fnames().empty()) {
697 try {
698 value = opt->get_flag_value(single_name, value);
699 } catch(const CLI::ArgumentMismatch &) {
700 bool valid{false};
701 for(const auto &test_name : opt->get_fnames()) {
702 try {
703 value = opt->get_flag_value(test_name, value);
704 single_name = test_name;
705 valid = true;
706 } catch(const CLI::ArgumentMismatch &) {
707 continue;
708 }
709 }
710 if(!valid) {
711 value = detail::ini_join(
713 }
714 }
715 }
716 if(write_description && opt->has_description()) {
717 if(out.tellp() != std::streampos(0)) {
718 out << '\n';
719 }
720 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
721 }
722 detail::clean_name_string(single_name, keyChars);
723
724 std::string name = prefix + single_name;
725 if(commentDefaultsBool && isDefault) {
726 name = commentChar + name;
727 }
728 out << name << valueDelimiter << value << '\n';
729 }
730 }
731 }
732 }
733
734 auto subcommands = app->get_subcommands({});
735 for(const App *subcom : subcommands) {
736 if(subcom->get_name().empty()) {
737 if(!include_default_values && (subcom->count_all() == 0)) {
738 continue;
739 }
740 if(write_description && !subcom->get_group().empty()) {
741 out << '\n' << commentChar << commentLead << subcom->get_group() << " Options\n";
742 }
743 /*if (!prefix.empty() || app->get_parent() == nullptr) {
744 out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
745 } else {
746 std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
747 const auto *p = app->get_parent();
748 while(p->get_parent() != nullptr) {
749 subname = p->get_name() + parentSeparatorChar +subname;
750 p = p->get_parent();
751 }
752 out << '[' << subname << "]\n";
753 }
754 */
755 out << to_config(subcom, mode, write_description, prefix);
756 }
757 }
758
759 for(const App *subcom : subcommands) {
760 if(!subcom->get_name().empty()) {
761 if((!include_default_values && (subcom->count_all() == 0)) ||
762 (mode == ConfigOutputMode::ActiveSubcommandDefaults && !app->got_subcommand(subcom))) {
763 continue;
764 }
765 std::string subname = subcom->get_name();
766 detail::clean_name_string(subname, keyChars);
767
768 if(subcom->get_configurable() && (app->got_subcommand(subcom) || (mode == ConfigOutputMode::AllDefaults))) {
769 if(!prefix.empty() || app->get_parent() == nullptr) {
770
771 out << '[' << prefix << subname << "]\n";
772 } else {
773 std::string appname = app->get_name();
774 detail::clean_name_string(appname, keyChars);
775 subname = appname + parentSeparatorChar + subname;
776 const auto *p = app->get_parent();
777 while(p->get_parent() != nullptr) {
778 std::string pname = p->get_name();
779 detail::clean_name_string(pname, keyChars);
780 subname = pname + parentSeparatorChar + subname;
781 p = p->get_parent();
782 }
783 out << '[' << subname << "]\n";
784 }
785 out << to_config(subcom, mode, write_description, "");
786 } else {
787 out << to_config(subcom, mode, write_description, prefix + subname + parentSeparatorChar);
788 }
789 }
790 }
791
792 if(write_description && !out.str().empty()) {
793 std::string outString =
794 commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
795 return outString + out.str();
796 }
797 return out.str();
798}
799
800CLI11_INLINE ConfigINI::ConfigINI() {
801 commentChar = ';';
802 arrayStart = '\0';
803 arrayEnd = '\0';
804 arraySeparator = ' ';
805 valueDelimiter = '=';
806}
807// [CLI11:config_inl_hpp:end]
808} // namespace CLI
Creates a command line program, with very few defaults.
Definition App.hpp:114
App * get_parent()
Get the parent of this subcommand (or nullptr if main app).
Definition App.hpp:1172
CLI11_NODISCARD std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order).
Definition App_inl.hpp:1173
bool got_subcommand(const App *subcom) const
Check to see if given subcommand was selected.
Definition App_inl.hpp:833
CLI11_NODISCARD std::vector< App * > get_subcommands() const
Definition App.hpp:931
std::vector< const Option * > get_options(const std::function< bool(const Option *)> filter={}) const
Get the list of options (user facing function, so returns raw pointers), has optional filter function...
Definition App_inl.hpp:975
CLI11_NODISCARD std::string get_description() const
Get the app or subcommand description.
Definition App.hpp:1031
CLI11_NODISCARD const std::string & get_name() const
Get the name of the current app.
Definition App.hpp:1178
Thrown when the wrong number of arguments has been received.
Definition Error.hpp:264
std::string configSection
Specify the configuration section that should be used.
Definition ConfigFwd.hpp:100
std::string to_config(const App *, ConfigOutputMode mode, bool write_description, std::string prefix) const override
Convert an app into a configuration.
Definition Config_inl.hpp:591
char arraySeparator
the character used to separate elements in an array
Definition ConfigFwd.hpp:82
std::vector< ConfigItem > from_config(std::istream &input) const override
Convert a configuration into an app.
Definition Config_inl.hpp:298
std::uint8_t maximumLayers
the maximum number of layers to allow
Definition ConfigFwd.hpp:90
char stringQuote
the character to use around strings
Definition ConfigFwd.hpp:86
char valueDelimiter
the character used separate the name from the value
Definition ConfigFwd.hpp:84
char arrayStart
the character used to start an array '\0' is a default to not use
Definition ConfigFwd.hpp:78
char parentSeparatorChar
the separator used to separator parent layers
Definition ConfigFwd.hpp:92
bool allowMultipleDuplicateFields
specify the config reader should collapse repeated field names to a single vector
Definition ConfigFwd.hpp:96
char literalQuote
the character to use around single characters and literal strings
Definition ConfigFwd.hpp:88
char arrayEnd
the character used to end an array '\0' is a default to not use
Definition ConfigFwd.hpp:80
bool commentDefaultsBool
comment default values
Definition ConfigFwd.hpp:94
int16_t configIndex
Specify the configuration index to use for arrayed sections.
Definition ConfigFwd.hpp:98
char commentChar
the character used for comments
Definition ConfigFwd.hpp:76
virtual CLI11_NODISCARD std::string to_flag(const ConfigItem &item) const
Get a flag value.
Definition Config_inl.hpp:42
virtual std::string to_config(const App *, bool, bool, std::string) const =0
Convert an app into a configuration.
CLI11_NODISCARD std::vector< ConfigItem > from_file(const std::string &name) const
Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure.
Definition Config_inl.hpp:52
virtual std::vector< ConfigItem > from_config(std::istream &) const =0
Convert a configuration into an app.
Definition Option.hpp:259
Anything that can error in Parse.
Definition Error.hpp:160
Holds values to load into Options.
Definition ConfigFwd.hpp:32
CLI11_NODISCARD std::string fullname() const
The list of parents and name joined by ".".
Definition Config_inl.hpp:30
std::vector< std::string > inputs
Listing of inputs.
Definition ConfigFwd.hpp:39
std::string name
This is the name.
Definition ConfigFwd.hpp:37
bool multiline
indicator if a multiline vector separator was inserted
Definition ConfigFwd.hpp:41
std::vector< std::string > parents
This is the list of parents.
Definition ConfigFwd.hpp:34