CLI11
C++11 Command Line Interface Parser
Loading...
Searching...
No Matches
Config_inl.hpp
1// Copyright (c) 2017-2025, 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.size() > 1 && 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 std::stringstream out;
515 std::string commentLead;
516 commentLead.push_back(commentChar);
517 commentLead.push_back(' ');
518
519 std::string commentTest = "#;";
520 commentTest.push_back(commentChar);
521 commentTest.push_back(parentSeparatorChar);
522
523 std::string keyChars = commentTest;
524 keyChars.push_back(literalQuote);
525 keyChars.push_back(stringQuote);
526 keyChars.push_back(arrayStart);
527 keyChars.push_back(arrayEnd);
528 keyChars.push_back(valueDelimiter);
529 keyChars.push_back(arraySeparator);
530
531 std::vector<std::string> groups = app->get_groups();
532 bool defaultUsed = false;
533 groups.insert(groups.begin(), std::string("OPTIONS"));
534
535 for(auto &group : groups) {
536 if(group == "OPTIONS" || group.empty()) {
537 if(defaultUsed) {
538 continue;
539 }
540 defaultUsed = true;
541 }
542 if(write_description && group != "OPTIONS" && !group.empty()) {
543 out << '\n' << commentChar << commentLead << group << " Options\n";
544 }
545 for(const Option *opt : app->get_options({})) {
546 // Only process options that are configurable
547 if(opt->get_configurable()) {
548 if(opt->get_group() != group) {
549 if(!(group == "OPTIONS" && opt->get_group().empty())) {
550 continue;
551 }
552 }
553 std::string single_name = opt->get_single_name();
554 if(single_name.empty()) {
555 continue;
556 }
557
558 auto results = opt->reduced_results();
559 if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) {
560 std::reverse(results.begin(), results.end());
561 }
562 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum && opt->count() >= 1 &&
563 results.size() == 1) {
564 // if the multi option policy is sum then there is a possibility of incorrect fields being produced
565 // best to just use the original data for config files
566 auto pos = opt->_validate(results[0], 0);
567 if(!pos.empty()) {
568 results = opt->results();
569 }
570 }
571 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Join && opt->count() > 1) {
572 char delim = opt->get_delimiter();
573 if(delim == '\0') {
574 // this branch deals with a situation where the output would not be readable by a config file
575 results = opt->results();
576 } else {
577 // this branch deals with the case of the strings containing the delimiter itself or empty
578 // strings which would be interpreted incorrectly
579 auto delim_count = std::count(results[0].begin(), results[0].end(), delim);
580 if(results[0].back() == delim ||
581 static_cast<decltype(delim_count)>(opt->count()) < delim_count - 1 ||
582 results[0].find(std::string(2, delim)) != std::string::npos) {
583 results = opt->results();
584 }
585 }
586 }
587 std::string value =
588 detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
589
590 bool isDefault = false;
591 if(value.empty() && default_also) {
592 if(!opt->get_default_str().empty()) {
593 results_t res;
594 opt->results(res);
595 value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
596 } else if(opt->get_expected_min() == 0) {
597 value = "false";
598 } else if(opt->get_run_callback_for_default() || !opt->get_required()) {
599 value = "\"\""; // empty string default value
600 } else {
601 value = "\"<REQUIRED>\"";
602 }
603 isDefault = true;
604 }
605
606 if(!value.empty()) {
607 if(!opt->get_fnames().empty()) {
608 try {
609 value = opt->get_flag_value(single_name, value);
610 } catch(const CLI::ArgumentMismatch &) {
611 bool valid{false};
612 for(const auto &test_name : opt->get_fnames()) {
613 try {
614 value = opt->get_flag_value(test_name, value);
615 single_name = test_name;
616 valid = true;
617 } catch(const CLI::ArgumentMismatch &) {
618 continue;
619 }
620 }
621 if(!valid) {
622 value = detail::ini_join(
624 }
625 }
626 }
627 if(write_description && opt->has_description()) {
628 if(out.tellp() != std::streampos(0)) {
629 out << '\n';
630 }
631 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
632 }
633 clean_name_string(single_name, keyChars);
634
635 std::string name = prefix + single_name;
636 if(commentDefaultsBool && isDefault) {
637 name = commentChar + name;
638 }
639 out << name << valueDelimiter << value << '\n';
640 }
641 }
642 }
643 }
644
645 auto subcommands = app->get_subcommands({});
646 for(const App *subcom : subcommands) {
647 if(subcom->get_name().empty()) {
648 if(!default_also && (subcom->count_all() == 0)) {
649 continue;
650 }
651 if(write_description && !subcom->get_group().empty()) {
652 out << '\n' << commentLead << subcom->get_group() << " Options\n";
653 }
654 /*if (!prefix.empty() || app->get_parent() == nullptr) {
655 out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
656 } else {
657 std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
658 const auto *p = app->get_parent();
659 while(p->get_parent() != nullptr) {
660 subname = p->get_name() + parentSeparatorChar +subname;
661 p = p->get_parent();
662 }
663 out << '[' << subname << "]\n";
664 }
665 */
666 out << to_config(subcom, default_also, write_description, prefix);
667 }
668 }
669
670 for(const App *subcom : subcommands) {
671 if(!subcom->get_name().empty()) {
672 if(!default_also && (subcom->count_all() == 0)) {
673 continue;
674 }
675 std::string subname = subcom->get_name();
676 clean_name_string(subname, keyChars);
677
678 if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) {
679 if(!prefix.empty() || app->get_parent() == nullptr) {
680
681 out << '[' << prefix << subname << "]\n";
682 } else {
683 std::string appname = app->get_name();
684 clean_name_string(appname, keyChars);
685 subname = appname + parentSeparatorChar + subname;
686 const auto *p = app->get_parent();
687 while(p->get_parent() != nullptr) {
688 std::string pname = p->get_name();
689 clean_name_string(pname, keyChars);
690 subname = pname + parentSeparatorChar + subname;
691 p = p->get_parent();
692 }
693 out << '[' << subname << "]\n";
694 }
695 out << to_config(subcom, default_also, write_description, "");
696 } else {
697 out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar);
698 }
699 }
700 }
701
702 if(write_description && !out.str().empty()) {
703 std::string outString =
704 commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
705 return outString + out.str();
706 }
707 return out.str();
708}
709// [CLI11:config_inl_hpp:end]
710} // namespace CLI
Creates a command line program, with very few defaults.
Definition App.hpp:98
App * get_parent()
Get the parent of this subcommand (or nullptr if main app)
Definition App.hpp:1223
CLI11_NODISCARD std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order)
Definition App_inl.hpp:967
CLI11_NODISCARD std::vector< App * > get_subcommands() const
Definition App.hpp:940
bool got_subcommand(const App *subcom) const
Check to see if given subcommand was selected.
Definition App.hpp:951
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:830
CLI11_NODISCARD std::string get_description() const
Get the app or subcommand description.
Definition App.hpp:1074
CLI11_NODISCARD const std::string & get_name() const
Get the name of the current app.
Definition App.hpp:1229
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:112
std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override
Convert an app into a configuration.
Definition Config_inl.hpp:513
char arraySeparator
the character used to separate elements in an array
Definition ConfigFwd.hpp:94
char stringQuote
the character to use around strings
Definition ConfigFwd.hpp:98
uint8_t maximumLayers
the maximum number of layers to allow
Definition ConfigFwd.hpp:102
char valueDelimiter
the character used separate the name from the value
Definition ConfigFwd.hpp:96
char arrayStart
the character used to start an array '\0' is a default to not use
Definition ConfigFwd.hpp:90
char parentSeparatorChar
the separator used to separator parent layers
Definition ConfigFwd.hpp:104
bool allowMultipleDuplicateFields
specify the config reader should collapse repeated field names to a single vector
Definition ConfigFwd.hpp:108
char literalQuote
the character to use around single characters and literal strings
Definition ConfigFwd.hpp:100
char arrayEnd
the character used to end an array '\0' is a default to not use
Definition ConfigFwd.hpp:92
bool commentDefaultsBool
comment default values
Definition ConfigFwd.hpp:106
int16_t configIndex
Specify the configuration index to use for arrayed sections.
Definition ConfigFwd.hpp:110
char commentChar
the character used for comments
Definition ConfigFwd.hpp:88
Definition Option.hpp:233
Anything that can error in Parse.
Definition Error.hpp:159