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