File indexing completed on 2026-09-14 09:04:07
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009
0010
0011
0012 #include "../Config.hpp"
0013
0014
0015 #include <algorithm>
0016 #include <string>
0017 #include <utility>
0018 #include <vector>
0019
0020
0021 namespace CLI {
0022
0023
0024 static constexpr auto multiline_literal_quote = R"(''')";
0025 static constexpr auto multiline_string_quote = R"(""")";
0026
0027 namespace detail {
0028
0029 CLI11_INLINE bool is_printable(const std::string &test_string) {
0030 return std::all_of(test_string.begin(), test_string.end(), [](char x) {
0031 return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n' || x == '\t');
0032 });
0033 }
0034
0035 CLI11_INLINE std::string
0036 convert_arg_for_ini(const std::string &arg, char stringQuote, char literalQuote, bool disable_multi_line) {
0037 if(arg.empty()) {
0038 return std::string(2, stringQuote);
0039 }
0040 // some specifically supported strings
0041 if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
0042 return arg;
0043 }
0044 // floating point conversion can convert some hex codes, but don't try that here
0045 if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
0046 using CLI::detail::lexical_cast;
0047 double val = 0.0;
0048 if(lexical_cast(arg, val)) {
0049 if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) {
0050 return arg;
0051 }
0052 }
0053 }
0054 // just quote a single non numeric character
0055 if(arg.size() == 1) {
0056 if(isprint(static_cast<unsigned char>(arg.front())) == 0) {
0057 return binary_escape_string(arg);
0058 }
0059 if(arg == "'") {
0060 return std::string(1, stringQuote) + "'" + stringQuote;
0061 }
0062 return std::string(1, literalQuote) + arg + literalQuote;
0063 }
0064 // handle hex, binary or octal arguments
0065 if(arg.front() == '0') {
0066 if(arg[1] == 'x') {
0067 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
0068 return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
0069 })) {
0070 return arg;
0071 }
0072 } else if(arg[1] == 'o') {
0073 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
0074 return arg;
0075 }
0076 } else if(arg[1] == 'b') {
0077 if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
0078 return arg;
0079 }
0080 }
0081 }
0082 if(!is_printable(arg)) {
0083 return binary_escape_string(arg);
0084 }
0085 if(detail::has_escapable_character(arg)) {
0086 if(arg.size() > 100 && !disable_multi_line) {
0087 if(arg.find(multiline_literal_quote) != std::string::npos) {
0088 return binary_escape_string(arg, true);
0089 }
0090 std::string return_string{multiline_literal_quote};
0091 return_string.reserve(7 + arg.size());
0092 if(arg.front() == '\n' || arg.front() == '\r') {
0093 return_string.push_back('\n');
0094 }
0095 return_string.append(arg);
0096 if(arg.back() == '\n' || arg.back() == '\r') {
0097 return_string.push_back('\n');
0098 }
0099 return_string.append(multiline_literal_quote, 3);
0100 return return_string;
0101 }
0102 return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
0103 }
0104 return std::string(1, stringQuote) + arg + stringQuote;
0105 }
0106
0107 CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
0108 char sepChar,
0109 char arrayStart,
0110 char arrayEnd,
0111 char stringQuote,
0112 char literalQuote) {
0113 bool disable_multi_line{false};
0114 std::string joined;
0115 if(args.size() > 1 && arrayStart != '\0') {
0116 joined.push_back(arrayStart);
0117 disable_multi_line = true;
0118 }
0119 std::size_t start = 0;
0120 for(const auto &arg : args) {
0121 if(start++ > 0) {
0122 joined.push_back(sepChar);
0123 if(!std::isspace<char>(sepChar, std::locale())) {
0124 joined.push_back(' ');
0125 }
0126 }
0127 joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line));
0128 }
0129 if(args.size() > 1 && arrayEnd != '\0') {
0130 joined.push_back(arrayEnd);
0131 }
0132 return joined;
0133 }
0134
0135 CLI11_INLINE std::vector<std::string>
0136 generate_parents(const std::string §ion, std::string &name, char parentSeparator) {
0137 std::vector<std::string> parents;
0138 if(detail::to_lower(section) != "default") {
0139 if(section.find(parentSeparator) != std::string::npos) {
0140 parents = detail::split_up(section, parentSeparator);
0141 } else {
0142 parents = {section};
0143 }
0144 }
0145 if(name.find(parentSeparator) != std::string::npos) {
0146 std::vector<std::string> plist = detail::split_up(name, parentSeparator);
0147 name = plist.back();
0148 plist.pop_back();
0149 parents.insert(parents.end(), plist.begin(), plist.end());
0150 }
0151 // clean up quotes on the parents
0152 try {
0153 detail::remove_quotes(parents);
0154 } catch(const std::invalid_argument &iarg) {
0155 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
0156 }
0157 return parents;
0158 }
0159
0160 CLI11_INLINE void
0161 checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) {
0162
0163 std::string estring;
0164 auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
0165 if(!output.empty() && output.back().name == "--") {
0166 std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
0167 while(output.back().parents.size() >= msize) {
0168 output.push_back(output.back());
0169 output.back().parents.pop_back();
0170 }
0171
0172 if(parents.size() > 1) {
0173 std::size_t common = 0;
0174 std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
0175 for(std::size_t ii = 0; ii < mpair; ++ii) {
0176 if(output.back().parents[ii] != parents[ii]) {
0177 break;
0178 }
0179 ++common;
0180 }
0181 if(common == mpair) {
0182 output.pop_back();
0183 } else {
0184 while(output.back().parents.size() > common + 1) {
0185 output.push_back(output.back());
0186 output.back().parents.pop_back();
0187 }
0188 }
0189 for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
0190 output.emplace_back();
0191 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
0192 output.back().name = "++";
0193 }
0194 }
0195 } else if(parents.size() > 1) {
0196 for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
0197 output.emplace_back();
0198 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
0199 output.back().name = "++";
0200 }
0201 }
0202
0203 // insert a section end which is just an empty items_buffer
0204 output.emplace_back();
0205 output.back().parents = std::move(parents);
0206 output.back().name = "++";
0207 }
0208
0209 /// @brief checks if a string represents a multiline comment
0210 CLI11_INLINE bool hasMLString(std::string const &fullString, char check) {
0211 if(fullString.length() < 3) {
0212 return false;
0213 }
0214 auto it = fullString.rbegin();
0215 return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check);
0216 }
0217
0218 /// @brief find a matching configItem in a list
0219 inline auto find_matching_config(std::vector<ConfigItem> &items,
0220 const std::vector<std::string> &parents,
0221 const std::string &name,
0222 bool fullSearch) -> decltype(items.begin()) {
0223 if(items.empty()) {
0224 return items.end();
0225 }
0226 auto search = items.end() - 1;
0227 do {
0228 if(search->parents == parents && search->name == name) {
0229 return search;
0230 }
0231 if(search == items.begin()) {
0232 break;
0233 }
0234 --search;
0235 } while(fullSearch);
0236 return items.end();
0237 }
0238 } // namespace detail
0239
0240 inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
0241 std::string line;
0242 std::string buffer;
0243 std::string currentSection = "default";
0244 std::string previousSection = "default";
0245 std::vector<ConfigItem> output;
0246 bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
0247 bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
0248 bool inSection{false};
0249 bool inMLineComment{false};
0250 bool inMLineValue{false};
0251
0252 char aStart = (isINIArray) ? '[' : arrayStart;
0253 char aEnd = (isINIArray) ? ']' : arrayEnd;
0254 char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
0255 int currentSectionIndex{0};
0256
0257 std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter};
0258 while(getline(input, buffer)) {
0259 std::vector<std::string> items_buffer;
0260 std::string name;
0261 line = detail::trim_copy(buffer);
0262 std::size_t len = line.length();
0263 // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
0264 if(len < 3) {
0265 continue;
0266 }
0267 if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) {
0268 inMLineComment = true;
0269 auto cchar = line.front();
0270 while(inMLineComment) {
0271 if(getline(input, line)) {
0272 detail::trim(line);
0273 } else {
0274 break;
0275 }
0276 if(detail::hasMLString(line, cchar)) {
0277 inMLineComment = false;
0278 }
0279 }
0280 continue;
0281 }
0282 if(line.front() == '[' && line.back() == ']') {
0283 if(currentSection != "default") {
0284 // insert a section end which is just an empty items_buffer
0285 output.emplace_back();
0286 output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
0287 output.back().name = "--";
0288 }
0289 currentSection = line.substr(1, len - 2);
0290 // deal with double brackets for TOML
0291 if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
0292 currentSection = currentSection.substr(1, currentSection.size() - 2);
0293 }
0294 if(detail::to_lower(currentSection) == "default") {
0295 currentSection = "default";
0296 } else {
0297 detail::checkParentSegments(output, currentSection, parentSeparatorChar);
0298 }
0299 inSection = false;
0300 if(currentSection == previousSection) {
0301 ++currentSectionIndex;
0302 } else {
0303 currentSectionIndex = 0;
0304 previousSection = currentSection;
0305 }
0306 continue;
0307 }
0308
0309 // comment lines
0310 if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
0311 continue;
0312 }
0313 std::size_t search_start = 0;
0314 if(line.find_first_of("\"'`") != std::string::npos) {
0315 while(search_start < line.size()) {
0316 auto test_char = line[search_start];
0317 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
0318 search_start = detail::close_sequence(line, search_start, line[search_start]);
0319 ++search_start;
0320 } else if(test_char == valueDelimiter || test_char == commentChar) {
0321 --search_start;
0322 break;
0323 } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) {
0324 ++search_start;
0325 } else {
0326 search_start = line.find_first_of(line_sep_chars, search_start);
0327 }
0328 }
0329 }
0330
0331 auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1);
0332 auto comment_pos = line.find_first_of(commentChar, search_start);
0333 if(comment_pos < delimiter_pos) {
0334 delimiter_pos = std::string::npos;
0335 }
0336 if(delimiter_pos != std::string::npos) {
0337
0338 name = detail::trim_copy(line.substr(0, delimiter_pos));
0339 std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
0340 bool mlquote =
0341 (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0);
0342 if(!mlquote && comment_pos != std::string::npos) {
0343 auto citems = detail::split_up(item, commentChar);
0344 item = detail::trim_copy(citems.front());
0345 }
0346 if(mlquote) {
0347
0348 auto keyChar = item.front();
0349 item = buffer.substr(delimiter_pos + 1, std::string::npos);
0350 detail::ltrim(item);
0351 item.erase(0, 3);
0352 inMLineValue = true;
0353 bool lineExtension{false};
0354 bool firstLine = true;
0355 if(!item.empty() && item.back() == '\\' && keyChar == '\"') {
0356 item.pop_back();
0357 lineExtension = true;
0358 } else if(detail::hasMLString(item, keyChar)) {
0359
0360 item.pop_back();
0361 item.pop_back();
0362 item.pop_back();
0363 if(keyChar == '\"') {
0364 try {
0365 item = detail::remove_escaped_characters(item);
0366 } catch(const std::invalid_argument &iarg) {
0367 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
0368 }
0369 }
0370 inMLineValue = false;
0371 }
0372 while(inMLineValue) {
0373 std::string l2;
0374 if(!std::getline(input, l2)) {
0375 break;
0376 }
0377 line = l2;
0378 detail::rtrim(line);
0379 if(detail::hasMLString(line, keyChar)) {
0380 line.pop_back();
0381 line.pop_back();
0382 line.pop_back();
0383 if(lineExtension) {
0384 detail::ltrim(line);
0385 } else if(!(firstLine && item.empty())) {
0386 item.push_back('\n');
0387 }
0388 firstLine = false;
0389 item += line;
0390 inMLineValue = false;
0391 if(!item.empty() && item.back() == '\n') {
0392 item.pop_back();
0393 }
0394 if(keyChar == '\"') {
0395 try {
0396 item = detail::remove_escaped_characters(item);
0397 } catch(const std::invalid_argument &iarg) {
0398 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
0399 }
0400 }
0401 } else {
0402 if(lineExtension) {
0403 detail::trim(l2);
0404 } else if(!(firstLine && item.empty())) {
0405 item.push_back('\n');
0406 }
0407 lineExtension = false;
0408 firstLine = false;
0409 if(!l2.empty() && l2.back() == '\\' && keyChar == '\"') {
0410 lineExtension = true;
0411 l2.pop_back();
0412 }
0413 item += l2;
0414 }
0415 }
0416 items_buffer = {item};
0417 } else if(!item.empty() && item.front() == aStart) {
0418 for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
0419 detail::trim(multiline);
0420 item += multiline;
0421 }
0422 if(item.back() == aEnd) {
0423 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
0424 } else {
0425 items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
0426 }
0427 } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
0428 items_buffer = detail::split_up(item, aSep);
0429 } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
0430 items_buffer = detail::split_up(item, '\0');
0431 } else {
0432 items_buffer = {item};
0433 }
0434 } else {
0435 name = detail::trim_copy(line.substr(0, comment_pos));
0436 items_buffer = {"true"};
0437 }
0438 std::vector<std::string> parents;
0439 try {
0440 parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
0441 detail::process_quoted_string(name, '"', '\'', true);
0442
0443 for(auto &it : items_buffer) {
0444 detail::process_quoted_string(it, stringQuote, literalQuote);
0445 }
0446 } catch(const std::invalid_argument &ia) {
0447 throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError);
0448 }
0449
0450 if(parents.size() > maximumLayers) {
0451 continue;
0452 }
0453 if(!configSection.empty() && !inSection) {
0454 if(parents.empty() || parents.front() != configSection) {
0455 continue;
0456 }
0457 if(configIndex >= 0 && currentSectionIndex != configIndex) {
0458 continue;
0459 }
0460 parents.erase(parents.begin());
0461 inSection = true;
0462 }
0463 auto match = detail::find_matching_config(output, parents, name, allowMultipleDuplicateFields);
0464 if(match != output.end()) {
0465 if((match->inputs.size() > 1 && items_buffer.size() > 1) || allowMultipleDuplicateFields) {
0466
0467 if(!(match->inputs.back().empty() || items_buffer.front().empty() || match->inputs.back() == "%%" ||
0468 items_buffer.front() == "%%")) {
0469 match->inputs.emplace_back("%%");
0470 match->multiline = true;
0471 }
0472 }
0473 match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end());
0474 } else {
0475 output.emplace_back();
0476 output.back().parents = std::move(parents);
0477 output.back().name = std::move(name);
0478 output.back().inputs = std::move(items_buffer);
0479 }
0480 }
0481 if(currentSection != "default") {
0482
0483 std::string ename;
0484 output.emplace_back();
0485 output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
0486 output.back().name = "--";
0487 while(output.back().parents.size() > 1) {
0488 output.push_back(output.back());
0489 output.back().parents.pop_back();
0490 }
0491 }
0492 return output;
0493 }
0494
0495 CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string &keyChars) {
0496 if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') ||
0497 (name.find_first_of("'`\"\\") != std::string::npos)) {
0498 if(name.find_first_of('\'') == std::string::npos) {
0499 name.insert(0, 1, '\'');
0500 name.push_back('\'');
0501 } else {
0502 if(detail::has_escapable_character(name)) {
0503 name = detail::add_escaped_characters(name);
0504 }
0505 name.insert(0, 1, '\"');
0506 name.push_back('\"');
0507 }
0508 }
0509 return name;
0510 }
0511
0512 CLI11_INLINE std::string
0513 ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
0514 std::stringstream out;
0515 std::string commentLead;
0516 commentLead.push_back(commentChar);
0517 commentLead.push_back(' ');
0518
0519 std::string commentTest = "#;";
0520 commentTest.push_back(commentChar);
0521 commentTest.push_back(parentSeparatorChar);
0522
0523 std::string keyChars = commentTest;
0524 keyChars.push_back(literalQuote);
0525 keyChars.push_back(stringQuote);
0526 keyChars.push_back(arrayStart);
0527 keyChars.push_back(arrayEnd);
0528 keyChars.push_back(valueDelimiter);
0529 keyChars.push_back(arraySeparator);
0530
0531 std::vector<std::string> groups = app->get_groups();
0532 bool defaultUsed = false;
0533 groups.insert(groups.begin(), std::string("OPTIONS"));
0534
0535 for(auto &group : groups) {
0536 if(group == "OPTIONS" || group.empty()) {
0537 if(defaultUsed) {
0538 continue;
0539 }
0540 defaultUsed = true;
0541 }
0542 if(write_description && group != "OPTIONS" && !group.empty()) {
0543 out << '\n' << commentChar << commentLead << group << " Options\n";
0544 }
0545 for(const Option *opt : app->get_options({})) {
0546
0547 if(opt->get_configurable()) {
0548 if(opt->get_group() != group) {
0549 if(!(group == "OPTIONS" && opt->get_group().empty())) {
0550 continue;
0551 }
0552 }
0553 std::string single_name = opt->get_single_name();
0554 if(single_name.empty()) {
0555 continue;
0556 }
0557
0558 auto results = opt->reduced_results();
0559 if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) {
0560 std::reverse(results.begin(), results.end());
0561 }
0562 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum && opt->count() >= 1 &&
0563 results.size() == 1) {
0564
0565
0566 auto pos = opt->_validate(results[0], 0);
0567 if(!pos.empty()) {
0568 results = opt->results();
0569 }
0570 }
0571 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Join && opt->count() > 1) {
0572 char delim = opt->get_delimiter();
0573 if(delim == '\0') {
0574
0575 results = opt->results();
0576 } else {
0577
0578
0579 auto delim_count = std::count(results[0].begin(), results[0].end(), delim);
0580 if(results[0].back() == delim ||
0581 static_cast<decltype(delim_count)>(opt->count()) < delim_count - 1 ||
0582 results[0].find(std::string(2, delim)) != std::string::npos) {
0583 results = opt->results();
0584 }
0585 }
0586 }
0587 std::string value;
0588
0589 if(opt->count() == 1 && results.size() == 2 && results.front() == "{}" && results.back() == "%%") {
0590
0591
0592
0593
0594 value = "\"{}\"";
0595 } else {
0596 value = detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
0597 }
0598
0599 bool isDefault = false;
0600 if(value.empty() && default_also) {
0601 if(!opt->get_default_str().empty()) {
0602 results_t res;
0603 opt->results(res);
0604 value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
0605 } else if(opt->get_expected_min() == 0) {
0606 value = "false";
0607 } else if(opt->get_run_callback_for_default() || !opt->get_required()) {
0608 value = "\"\"";
0609 } else {
0610 value = "\"<REQUIRED>\"";
0611 }
0612 isDefault = true;
0613 }
0614
0615 if(!value.empty()) {
0616 if(!opt->get_fnames().empty()) {
0617 try {
0618 value = opt->get_flag_value(single_name, value);
0619 } catch(const CLI::ArgumentMismatch &) {
0620 bool valid{false};
0621 for(const auto &test_name : opt->get_fnames()) {
0622 try {
0623 value = opt->get_flag_value(test_name, value);
0624 single_name = test_name;
0625 valid = true;
0626 } catch(const CLI::ArgumentMismatch &) {
0627 continue;
0628 }
0629 }
0630 if(!valid) {
0631 value = detail::ini_join(
0632 opt->results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
0633 }
0634 }
0635 }
0636 if(write_description && opt->has_description()) {
0637 if(out.tellp() != std::streampos(0)) {
0638 out << '\n';
0639 }
0640 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
0641 }
0642 clean_name_string(single_name, keyChars);
0643
0644 std::string name = prefix + single_name;
0645 if(commentDefaultsBool && isDefault) {
0646 name = commentChar + name;
0647 }
0648 out << name << valueDelimiter << value << '\n';
0649 }
0650 }
0651 }
0652 }
0653
0654 auto subcommands = app->get_subcommands({});
0655 for(const App *subcom : subcommands) {
0656 if(subcom->get_name().empty()) {
0657 if(!default_also && (subcom->count_all() == 0)) {
0658 continue;
0659 }
0660 if(write_description && !subcom->get_group().empty()) {
0661 out << '\n' << commentLead << subcom->get_group() << " Options\n";
0662 }
0663
0664
0665
0666
0667
0668
0669
0670
0671
0672
0673
0674
0675 out << to_config(subcom, default_also, write_description, prefix);
0676 }
0677 }
0678
0679 for(const App *subcom : subcommands) {
0680 if(!subcom->get_name().empty()) {
0681 if(!default_also && (subcom->count_all() == 0)) {
0682 continue;
0683 }
0684 std::string subname = subcom->get_name();
0685 clean_name_string(subname, keyChars);
0686
0687 if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) {
0688 if(!prefix.empty() || app->get_parent() == nullptr) {
0689
0690 out << '[' << prefix << subname << "]\n";
0691 } else {
0692 std::string appname = app->get_name();
0693 clean_name_string(appname, keyChars);
0694 subname = appname + parentSeparatorChar + subname;
0695 const auto *p = app->get_parent();
0696 while(p->get_parent() != nullptr) {
0697 std::string pname = p->get_name();
0698 clean_name_string(pname, keyChars);
0699 subname = pname + parentSeparatorChar + subname;
0700 p = p->get_parent();
0701 }
0702 out << '[' << subname << "]\n";
0703 }
0704 out << to_config(subcom, default_also, write_description, "");
0705 } else {
0706 out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar);
0707 }
0708 }
0709 }
0710
0711 if(write_description && !out.str().empty()) {
0712 std::string outString =
0713 commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
0714 return outString + out.str();
0715 }
0716 return out.str();
0717 }
0718
0719 }