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
265CLI11_INLINE std::size_t find_ml_close(const std::string &str, char quote, char comment) {
266 const std::string closer(3, quote);
267 std::size_t pos = 0;
268 while((pos = str.find(closer, pos)) != std::string::npos) {
269 if(quote == '"') {
270 std::size_t slashes = 0;
271 while(slashes < pos && str[pos - slashes - 1] == '\\') {
272 ++slashes;
273 }
274 if(slashes % 2 == 1) {
275 ++pos;
276 continue;
277 }
278 }
279 // the content can end with up to two quote characters
280 auto end = str.find_first_not_of(quote, pos);
281 if(end == std::string::npos) {
282 end = str.size();
283 }
284 auto rest = str.find_first_not_of(" \t\r", end);
285 if(rest == std::string::npos || str[rest] == comment) {
286 return end - 3;
287 }
288 pos = end;
289 }
290 return std::string::npos;
291}
292
294CLI11_INLINE auto find_matching_config(std::vector<ConfigItem> &items,
295 const std::vector<std::string> &parents,
296 const std::string &name,
297 bool fullSearch) -> decltype(items.begin()) {
298 if(items.empty()) {
299 return items.end();
300 }
301 auto search = items.end() - 1;
302 do {
303 if(search->parents == parents && search->name == name) {
304 return search;
305 }
306 if(search == items.begin()) {
307 break;
308 }
309 --search;
310 } while(fullSearch);
311 return items.end();
312}
313
314CLI11_INLINE void clean_name_string(std::string &name, const std::string &keyChars) {
315 if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') ||
316 (name.find_first_of("'`\"\\") != std::string::npos)) {
317 if(name.find_first_of('\'') == std::string::npos) {
318 name.insert(0, 1, '\'');
319 name.push_back('\'');
320 } else {
321 if(detail::has_escapable_character(name)) {
322 name = detail::add_escaped_characters(name);
323 }
324 name.insert(0, 1, '\"');
325 name.push_back('\"');
326 }
327 }
328}
329} // namespace detail
330
331CLI11_INLINE std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
332 std::string line;
333 std::string buffer;
334 std::string currentSection = "default";
335 std::string previousSection = "default";
336 std::vector<ConfigItem> output;
337 bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
338 bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
339 bool inSection{false};
340 bool inMLineComment{false};
341 bool inMLineValue{false};
342
343 char aStart = (isINIArray) ? '[' : arrayStart;
344 char aEnd = (isINIArray) ? ']' : arrayEnd;
345 char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
346 int currentSectionIndex{0};
347
348 std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter};
349 while(getline(input, buffer)) {
350 std::vector<std::string> items_buffer;
351 std::string name;
352 line = detail::trim_copy(buffer);
353 std::size_t len = line.length();
354 // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
355 if(len < 3) {
356 continue;
357 }
358 if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) {
359 // check if the multiline comment opens and closes on the same line; the opening quotes
360 // themselves must not be counted as the closer hence the length requirement
361 if(len >= 6 && detail::hasMLString(line, line.front())) {
362 continue;
363 }
364 inMLineComment = true;
365 auto cchar = line.front();
366 while(inMLineComment) {
367 if(getline(input, line)) {
368 detail::trim(line);
369 } else {
370 break;
371 }
372 if(detail::hasMLString(line, cchar)) {
373 inMLineComment = false;
374 }
375 }
376 continue;
377 }
378 // strip a trailing comment (quote aware) for section headers so that "[section] # comment"
379 // is recognized as a section; value lines handle their own trailing comments later
380 if(line.front() == '[' && line.back() != ']' && line.find_first_of(commentChar) != std::string::npos) {
381 std::size_t comment_search = 0;
382 while(comment_search < line.size()) {
383 auto test_char = line[comment_search];
384 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
385 comment_search = detail::close_sequence(line, comment_search, line[comment_search]);
386 ++comment_search;
387 } else if(test_char == commentChar) {
388 break;
389 } else {
390 ++comment_search;
391 }
392 }
393 if(comment_search < line.size() && line[comment_search] == commentChar) {
394 line = detail::trim_copy(line.substr(0, comment_search));
395 len = line.length();
396 if(len < 3) {
397 continue;
398 }
399 }
400 }
401 if(line.front() == '[' && line.back() == ']') {
402 if(currentSection != "default") {
403 // insert a section end which is just an empty items_buffer
404 output.emplace_back();
405 output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
406 output.back().name = "--";
407 }
408 currentSection = line.substr(1, len - 2);
409 // deal with double brackets for TOML
410 if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
411 currentSection = currentSection.substr(1, currentSection.size() - 2);
412 }
413 if(detail::to_lower(currentSection) == "default") {
414 currentSection = "default";
415 } else {
416 detail::checkParentSegments(output, currentSection, parentSeparatorChar);
417 }
418 inSection = false;
419 if(currentSection == previousSection) {
420 ++currentSectionIndex;
421 } else {
422 currentSectionIndex = 0;
423 previousSection = currentSection;
424 }
425 continue;
426 }
427
428 // comment lines
429 if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
430 continue;
431 }
432 std::size_t search_start = 0;
433 if(line.find_first_of("\"'`") != std::string::npos) {
434 while(search_start < line.size()) {
435 auto test_char = line[search_start];
436 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
437 search_start = detail::close_sequence(line, search_start, line[search_start]);
438 ++search_start;
439 } else if(test_char == valueDelimiter || test_char == commentChar) {
440 --search_start;
441 break;
442 } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) {
443 ++search_start;
444 } else {
445 search_start = line.find_first_of(line_sep_chars, search_start);
446 }
447 }
448 }
449 // Find = in string, split and recombine
450 auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1);
451 auto comment_pos = line.find_first_of(commentChar, search_start);
452 if(comment_pos < delimiter_pos) {
453 delimiter_pos = std::string::npos;
454 }
455 bool mlquote{false};
456 if(delimiter_pos != std::string::npos) {
457
458 name = detail::trim_copy(line.substr(0, delimiter_pos));
459 std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
460 mlquote =
461 (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0);
462 if(!mlquote && comment_pos != std::string::npos) {
463 auto citems = detail::split_up(item, commentChar);
464 item = detail::trim_copy(citems.front());
465 }
466 if(mlquote) {
467 // multiline string
468 auto keyChar = item.front();
469 auto offset = buffer.find_first_not_of(" \t");
470 item = buffer.substr((offset == std::string::npos ? 0 : offset) + delimiter_pos + 1, std::string::npos);
471 detail::ltrim(item);
472 item.erase(0, 3);
473 inMLineValue = true;
474 bool lineExtension{false};
475 bool firstLine = true;
476 auto close = detail::find_ml_close(item, keyChar, commentChar);
477 if(close != std::string::npos) {
478 // deal with the first line closing the multiline literal
479 item.erase(close);
480 if(keyChar == '\"') {
481 try {
482 item = detail::remove_escaped_characters(item);
483 } catch(const std::invalid_argument &iarg) {
484 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
485 }
486 }
487 inMLineValue = false;
488 } else if(!item.empty() && item.back() == '\\' && keyChar == '\"') {
489 item.pop_back();
490 lineExtension = true;
491 }
492 while(inMLineValue) {
493 std::string l2;
494 if(!std::getline(input, l2)) {
495 break;
496 }
497 close = detail::find_ml_close(l2, keyChar, commentChar);
498 if(close != std::string::npos) {
499 line = l2.substr(0, close);
500 if(lineExtension) {
501 detail::ltrim(line);
502 } else if(!(firstLine && item.empty())) {
503 item.push_back('\n');
504 }
505 firstLine = false;
506 item += line;
507 inMLineValue = false;
508 if(!item.empty() && item.back() == '\n') {
509 item.pop_back();
510 }
511 if(keyChar == '\"') {
512 try {
513 item = detail::remove_escaped_characters(item);
514 } catch(const std::invalid_argument &iarg) {
515 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
516 }
517 }
518 } else {
519 if(lineExtension) {
520 detail::trim(l2);
521 } else if(!(firstLine && item.empty())) {
522 item.push_back('\n');
523 }
524 lineExtension = false;
525 firstLine = false;
526 if(!l2.empty() && l2.back() == '\\' && keyChar == '\"') {
527 lineExtension = true;
528 l2.pop_back();
529 }
530 item += l2;
531 }
532 }
533 if(detail::is_binary_escaped_string(item)) {
534 item = detail::extract_binary_string(item);
535 }
536 items_buffer = {item};
537 } else if(!item.empty() && item.front() == aStart) {
538 for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
539 detail::trim(multiline);
540 item += multiline;
541 }
542 if(item.back() == aEnd) {
543 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
544 } else {
545 items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
546 }
547 } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
548 items_buffer = detail::split_up(item, aSep);
549 } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
550 items_buffer = detail::split_up(item, '\0');
551 } else {
552 items_buffer = {item};
553 }
554 } else {
555 name = detail::trim_copy(line.substr(0, comment_pos));
556 items_buffer = {"true"};
557 }
558 std::vector<std::string> parents;
559 try {
560 parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
561 detail::process_quoted_string(name, '"', '\'', true);
562 // clean up quotes on the items and check for escaped strings
563 if(!mlquote) {
564 for(auto &it : items_buffer) {
565 detail::process_quoted_string(it, stringQuote, literalQuote);
566 }
567 }
568 } catch(const std::invalid_argument &ia) {
569 throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError);
570 }
571
572 if(parents.size() > maximumLayers) {
573 continue;
574 }
575 if(!configSection.empty() && !inSection) {
576 if(parents.empty() || parents.front() != configSection) {
577 continue;
578 }
579 if(configIndex >= 0 && currentSectionIndex != configIndex) {
580 continue;
581 }
582 parents.erase(parents.begin());
583 inSection = true;
584 }
585 auto match = detail::find_matching_config(output, parents, name, allowMultipleDuplicateFields);
586 if(match != output.end()) {
587 if((match->inputs.size() > 1 && items_buffer.size() > 1) || allowMultipleDuplicateFields) {
588 // insert a separator if one is not already present
589 if(!(match->inputs.back().empty() || items_buffer.front().empty() || match->inputs.back() == "%%" ||
590 items_buffer.front() == "%%")) {
591 match->inputs.emplace_back("%%");
592 match->multiline = true;
593 }
594 }
595 match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end());
596 } else {
597 output.emplace_back();
598 output.back().parents = std::move(parents);
599 output.back().name = std::move(name);
600 output.back().inputs = std::move(items_buffer);
601 }
602 }
603 if(currentSection != "default") {
604 // insert a section end which is just an empty items_buffer
605 std::string ename;
606 output.emplace_back();
607 output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
608 output.back().name = "--";
609 while(output.back().parents.size() > 1) {
610 output.push_back(output.back());
611 output.back().parents.pop_back();
612 }
613 }
614 return output;
615}
616
617CLI11_INLINE std::string
618ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
619 return to_config(app,
620 default_also ? ConfigOutputMode::AllDefaults : ConfigOutputMode::Active,
621 write_description,
622 std::move(prefix));
623}
624
625CLI11_INLINE std::string
626ConfigBase::to_config(const App *app, ConfigOutputMode mode, bool write_description, std::string prefix) const {
627 std::stringstream out;
628 const bool include_default_values = (mode != ConfigOutputMode::Active);
629 std::string commentLead;
630 commentLead.push_back(commentChar);
631 commentLead.push_back(' ');
632
633 std::string commentTest = "#;";
634 commentTest.push_back(commentChar);
635 commentTest.push_back(parentSeparatorChar);
636
637 std::string keyChars = commentTest;
638 keyChars.push_back(literalQuote);
639 keyChars.push_back(stringQuote);
640 keyChars.push_back(arrayStart);
641 keyChars.push_back(arrayEnd);
642 keyChars.push_back(valueDelimiter);
643 keyChars.push_back(arraySeparator);
644
645 std::vector<std::string> groups = app->get_groups();
646 bool defaultUsed = false;
647 groups.insert(groups.begin(), std::string("OPTIONS"));
648
649 // get_options() also merges options from '+' groups and fallthrough parents; those are written by
650 // their own App (the '+' groups through the nameless subcommand loop below), so keep only ours
651 const std::vector<const Option *> options =
652 app->get_options([app](const Option *opt) { return opt->parent_ == app; });
653 for(auto &group : groups) {
654 if(group == "OPTIONS" || group.empty()) {
655 if(defaultUsed) {
656 continue;
657 }
658 defaultUsed = true;
659 }
660 if(write_description && group != "OPTIONS" && !group.empty()) {
661 out << '\n' << commentChar << commentLead << group << " Options\n";
662 }
663 for(const Option *opt : options) {
664 // Only process options that are configurable
665 if(opt->get_configurable()) {
666 if(opt->get_group() != group) {
667 if(!(group == "OPTIONS" && opt->get_group().empty())) {
668 continue;
669 }
670 }
671 std::string single_name = opt->get_single_name();
672 if(single_name.empty()) {
673 continue;
674 }
675
676 auto results = opt->reduced_results();
677 if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) {
678 std::reverse(results.begin(), results.end());
679 }
680 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum && opt->count() >= 1 &&
681 results.size() == 1) {
682 // if the multi option policy is sum then there is a possibility of incorrect fields being produced
683 // best to just use the original data for config files
684 auto pos = opt->_validate(results[0], 0);
685 if(!pos.empty()) {
686 results = opt->results();
687 }
688 }
689 if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Join && opt->count() > 1) {
690 char delim = opt->get_delimiter();
691 if(delim == '\0') {
692 // this branch deals with a situation where the output would not be readable by a config file
693 results = opt->results();
694 } else {
695 // this branch deals with the case of the strings containing the delimiter itself or empty
696 // strings which would be interpreted incorrectly
697 auto delim_count = std::count(results[0].begin(), results[0].end(), delim);
698 if(results[0].back() == delim ||
699 static_cast<decltype(delim_count)>(opt->count()) <= delim_count ||
700 results[0].find(std::string(2, delim)) != std::string::npos) {
701 results = opt->results();
702 }
703 }
704 }
705 std::string value;
706
707 if(opt->count() == 1 && results.size() == 2 && results.front() == "{}" && results.back() == "%%") {
708 // there is a catch to allow for {} to used as as string in the output
709 // it will append a sequence terminator to the output so the lexical conversion handles it
710 // correctly but that is meant for config files so when outputting for a config file we need to
711 // makes sure to get the correct output
712 value = "\"{}\"";
713 } else {
714 value = detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
715 }
716
717 bool isDefault = false;
718 if(value.empty() && include_default_values) {
719 if(!opt->get_default_str().empty()) {
720 results_t res;
721 opt->results(res);
722 value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
723 } else if(opt->get_expected_min() == 0) {
724 value = "false";
725 } else if(opt->get_run_callback_for_default() || !opt->get_required()) {
726 value = "\"\""; // empty string default value
727 } else {
728 value = "\"<REQUIRED>\"";
729 }
730 isDefault = true;
731 }
732
733 if(!value.empty()) {
734 if(!opt->get_fnames().empty()) {
735 try {
736 value = opt->get_flag_value(single_name, value);
737 } catch(const CLI::ArgumentMismatch &) {
738 bool valid{false};
739 for(const auto &test_name : opt->get_fnames()) {
740 try {
741 value = opt->get_flag_value(test_name, value);
742 single_name = test_name;
743 valid = true;
744 } catch(const CLI::ArgumentMismatch &) {
745 continue;
746 }
747 }
748 if(!valid) {
749 value = detail::ini_join(
751 }
752 }
753 }
754 if(write_description && opt->has_description()) {
755 if(out.tellp() != std::streampos(0)) {
756 out << '\n';
757 }
758 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
759 }
760 detail::clean_name_string(single_name, keyChars);
761
762 std::string name = prefix + single_name;
763 if(commentDefaultsBool && isDefault) {
764 name = commentChar + name;
765 }
766 out << name << valueDelimiter << value << '\n';
767 }
768 }
769 }
770 }
771
772 auto subcommands = app->get_subcommands({});
773 for(const App *subcom : subcommands) {
774 if(subcom->get_name().empty()) {
775 if(!include_default_values && (subcom->count_all() == 0)) {
776 continue;
777 }
778 // a '+' group is displayed as part of its parent, so it gets no header of its own
779 if(write_description && !subcom->get_group().empty() && subcom->get_group().front() != '+') {
780 out << '\n' << commentChar << commentLead << subcom->get_group() << " Options\n";
781 }
782 /*if (!prefix.empty() || app->get_parent() == nullptr) {
783 out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
784 } else {
785 std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
786 const auto *p = app->get_parent();
787 while(p->get_parent() != nullptr) {
788 subname = p->get_name() + parentSeparatorChar +subname;
789 p = p->get_parent();
790 }
791 out << '[' << subname << "]\n";
792 }
793 */
794 out << to_config(subcom, mode, write_description, prefix);
795 }
796 }
797
798 for(const App *subcom : subcommands) {
799 if(!subcom->get_name().empty()) {
800 if((!include_default_values && (subcom->count_all() == 0)) ||
801 (mode == ConfigOutputMode::ActiveSubcommandDefaults && !app->got_subcommand(subcom))) {
802 continue;
803 }
804 std::string subname = subcom->get_name();
805 detail::clean_name_string(subname, keyChars);
806
807 if(subcom->get_configurable() && (app->got_subcommand(subcom) || (mode == ConfigOutputMode::AllDefaults))) {
808 if(!prefix.empty() || app->get_parent() == nullptr) {
809
810 out << '[' << prefix << subname << "]\n";
811 } else {
812 std::string appname = app->get_name();
813 detail::clean_name_string(appname, keyChars);
814 subname = appname + parentSeparatorChar + subname;
815 const auto *p = app->get_parent();
816 while(p->get_parent() != nullptr) {
817 std::string pname = p->get_name();
818 detail::clean_name_string(pname, keyChars);
819 subname = pname + parentSeparatorChar + subname;
820 p = p->get_parent();
821 }
822 out << '[' << subname << "]\n";
823 }
824 out << to_config(subcom, mode, write_description, "");
825 } else {
826 out << to_config(subcom, mode, write_description, prefix + subname + parentSeparatorChar);
827 }
828 }
829 }
830
831 if(write_description && !out.str().empty()) {
832 std::string outString =
833 commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
834 return outString + out.str();
835 }
836 return out.str();
837}
838
839CLI11_INLINE ConfigINI::ConfigINI() {
840 commentChar = ';';
841 arrayStart = '\0';
842 arrayEnd = '\0';
843 arraySeparator = ' ';
844 valueDelimiter = '=';
845}
846// [CLI11:config_inl_hpp:end]
847} // 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:1177
CLI11_NODISCARD std::vector< std::string > get_groups() const
Get the groups available directly from this option (in order).
Definition App_inl.hpp:1182
bool got_subcommand(const App *subcom) const
Check to see if given subcommand was selected.
Definition App_inl.hpp:852
CLI11_NODISCARD std::vector< App * > get_subcommands() const
Definition App.hpp:935
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:984
CLI11_NODISCARD std::string get_description() const
Get the app or subcommand description.
Definition App.hpp:1035
CLI11_NODISCARD const std::string & get_name() const
Get the name of the current app.
Definition App.hpp:1183
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:626
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:331
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.
CLI11_NODISCARD char get_delimiter() const
Get the current delimiter char.
Definition Option.hpp:154
CLI11_NODISCARD bool get_required() const
True if this is a required option.
Definition Option.hpp:139
CLI11_NODISCARD MultiOptionPolicy get_multi_option_policy() const
The status of the multi option policy.
Definition Option.hpp:160
CLI11_NODISCARD bool get_configurable() const
The status of configurable.
Definition Option.hpp:148
CLI11_NODISCARD const std::string & get_group() const
Get the group of this option.
Definition Option.hpp:136
Definition Option.hpp:261
CLI11_NODISCARD results_t reduced_results() const
Get a copy of the results.
Definition Option_inl.hpp:513
CLI11_NODISCARD bool get_run_callback_for_default() const
Get the current value of run_callback_for_default.
Definition Option.hpp:440
CLI11_NODISCARD std::string get_default_str() const
The default value (for help printing).
Definition Option.hpp:576
CLI11_NODISCARD const results_t & results() const
Get the current complete results set.
Definition Option.hpp:697
CLI11_NODISCARD std::size_t count() const
Count the total number of times an option was passed.
Definition Option.hpp:388
CLI11_NODISCARD const std::string & get_single_name() const
Get a single name for the option, first of lname, sname, pname, envname.
Definition Option_inl.hpp:574
App * parent_
link back up to the parent App for fallthrough
Definition Option.hpp:338
CLI11_NODISCARD std::string get_flag_value(const std::string &name, std::string input_value) const
Definition Option_inl.hpp:447
CLI11_NODISCARD const std::string & get_description() const
Get the description.
Definition Option.hpp:620
CLI11_NODISCARD int get_expected_min() const
The number of times the option expects to be included.
Definition Option.hpp:595
CLI11_NODISCARD const std::vector< std::string > & get_fnames() const
Get the flag names with specified default values.
Definition Option.hpp:588
CLI11_NODISCARD bool has_description() const
True if option has description.
Definition Option.hpp:617
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