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