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