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 return_string.append(multiline_literal_quote, 3);
97 return return_string;
98 }
99 return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
100 }
101 return std::string(1, stringQuote) + arg + stringQuote;
102}
103
104CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
105 char sepChar,
106 char arrayStart,
107 char arrayEnd,
108 char stringQuote,
109 char literalQuote) {
110 bool disable_multi_line{false};
111 std::string joined;
112 if(args.size() > 1 && arrayStart != '\0') {
113 joined.push_back(arrayStart);
114 disable_multi_line = true;
115 }
116 std::size_t start = 0;
117 for(const auto &arg : args) {
118 if(start++ > 0) {
119 joined.push_back(sepChar);
120 if(!std::isspace<char>(sepChar, std::locale())) {
121 joined.push_back(' ');
122 }
123 }
124 joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line));
125 }
126 if(args.size() > 1 && arrayEnd != '\0') {
127 joined.push_back(arrayEnd);
128 }
129 return joined;
130}
131
132CLI11_INLINE std::vector<std::string>
133generate_parents(const std::string &section, std::string &name, char parentSeparator) {
134 std::vector<std::string> parents;
135 if(detail::to_lower(section) != "default") {
136 if(section.find(parentSeparator) != std::string::npos) {
137 parents = detail::split_up(section, parentSeparator);
138 } else {
139 parents = {section};
140 }
141 }
142 if(name.find(parentSeparator) != std::string::npos) {
143 std::vector<std::string> plist = detail::split_up(name, parentSeparator);
144 name = plist.back();
145 plist.pop_back();
146 parents.insert(parents.end(), plist.begin(), plist.end());
147 }
148 // clean up quotes on the parents
149 try {
150 detail::remove_quotes(parents);
151 } catch(const std::invalid_argument &iarg) {
152 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
153 }
154 return parents;
155}
156
157CLI11_INLINE void
158checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, char parentSeparator) {
159
160 std::string estring;
161 auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
162 if(!output.empty() && output.back().name == "--") {
163 std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
164 while(output.back().parents.size() >= msize) {
165 output.push_back(output.back());
166 output.back().parents.pop_back();
167 }
168
169 if(parents.size() > 1) {
170 std::size_t common = 0;
171 std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
172 for(std::size_t ii = 0; ii < mpair; ++ii) {
173 if(output.back().parents[ii] != parents[ii]) {
174 break;
175 }
176 ++common;
177 }
178 if(common == mpair) {
179 output.pop_back();
180 } else {
181 while(output.back().parents.size() > common + 1) {
182 output.push_back(output.back());
183 output.back().parents.pop_back();
184 }
185 }
186 for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
187 output.emplace_back();
188 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
189 output.back().name = "++";
190 }
191 }
192 } else if(parents.size() > 1) {
193 for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
194 output.emplace_back();
195 output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
196 output.back().name = "++";
197 }
198 }
199
200 // insert a section end which is just an empty items_buffer
201 output.emplace_back();
202 output.back().parents = std::move(parents);
203 output.back().name = "++";
204}
205
207CLI11_INLINE bool hasMLString(std::string const &fullString, char check) {
208 if(fullString.length() < 3) {
209 return false;
210 }
211 auto it = fullString.rbegin();
212 return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check);
213}
214
216inline auto find_matching_config(std::vector<ConfigItem> &items,
217 const std::vector<std::string> &parents,
218 const std::string &name,
219 bool fullSearch) -> decltype(items.begin()) {
220 if(items.empty()) {
221 return items.end();
222 }
223 auto search = items.end() - 1;
224 do {
225 if(search->parents == parents && search->name == name) {
226 return search;
227 }
228 if(search == items.begin()) {
229 break;
230 }
231 --search;
232 } while(fullSearch);
233 return items.end();
234}
235} // namespace detail
236
237inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
238 std::string line;
239 std::string buffer;
240 std::string currentSection = "default";
241 std::string previousSection = "default";
242 std::vector<ConfigItem> output;
243 bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
244 bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
245 bool inSection{false};
246 bool inMLineComment{false};
247 bool inMLineValue{false};
248
249 char aStart = (isINIArray) ? '[' : arrayStart;
250 char aEnd = (isINIArray) ? ']' : arrayEnd;
251 char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
252 int currentSectionIndex{0};
253
254 std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter};
255 while(getline(input, buffer)) {
256 std::vector<std::string> items_buffer;
257 std::string name;
258 line = detail::trim_copy(buffer);
259 std::size_t len = line.length();
260 // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
261 if(len < 3) {
262 continue;
263 }
264 if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) {
265 inMLineComment = true;
266 auto cchar = line.front();
267 while(inMLineComment) {
268 if(getline(input, line)) {
269 detail::trim(line);
270 } else {
271 break;
272 }
273 if(detail::hasMLString(line, cchar)) {
274 inMLineComment = false;
275 }
276 }
277 continue;
278 }
279 if(line.front() == '[' && line.back() == ']') {
280 if(currentSection != "default") {
281 // insert a section end which is just an empty items_buffer
282 output.emplace_back();
283 output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
284 output.back().name = "--";
285 }
286 currentSection = line.substr(1, len - 2);
287 // deal with double brackets for TOML
288 if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
289 currentSection = currentSection.substr(1, currentSection.size() - 2);
290 }
291 if(detail::to_lower(currentSection) == "default") {
292 currentSection = "default";
293 } else {
294 detail::checkParentSegments(output, currentSection, parentSeparatorChar);
295 }
296 inSection = false;
297 if(currentSection == previousSection) {
298 ++currentSectionIndex;
299 } else {
300 currentSectionIndex = 0;
301 previousSection = currentSection;
302 }
303 continue;
304 }
305
306 // comment lines
307 if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
308 continue;
309 }
310 std::size_t search_start = 0;
311 if(line.find_first_of("\"'`") != std::string::npos) {
312 while(search_start < line.size()) {
313 auto test_char = line[search_start];
314 if(test_char == '\"' || test_char == '\'' || test_char == '`') {
315 search_start = detail::close_sequence(line, search_start, line[search_start]);
316 ++search_start;
317 } else if(test_char == valueDelimiter || test_char == commentChar) {
318 --search_start;
319 break;
320 } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) {
321 ++search_start;
322 } else {
323 search_start = line.find_first_of(line_sep_chars, search_start);
324 }
325 }
326 }
327 // Find = in string, split and recombine
328 auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1);
329 auto comment_pos = line.find_first_of(commentChar, search_start);
330 if(comment_pos < delimiter_pos) {
331 delimiter_pos = std::string::npos;
332 }
333 if(delimiter_pos != std::string::npos) {
334
335 name = detail::trim_copy(line.substr(0, delimiter_pos));
336 std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
337 bool mlquote =
338 (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0);
339 if(!mlquote && comment_pos != std::string::npos) {
340 auto citems = detail::split_up(item, commentChar);
341 item = detail::trim_copy(citems.front());
342 }
343 if(mlquote) {
344 // multiline string
345 auto keyChar = item.front();
346 item = buffer.substr(delimiter_pos + 1, std::string::npos);
347 detail::ltrim(item);
348 item.erase(0, 3);
349 inMLineValue = true;
350 bool lineExtension{false};
351 bool firstLine = true;
352 if(!item.empty() && item.back() == '\\') {
353 item.pop_back();
354 lineExtension = true;
355 } else if(detail::hasMLString(item, keyChar)) {
356 // deal with the first line closing the multiline literal
357 item.pop_back();
358 item.pop_back();
359 item.pop_back();
360 if(keyChar == '\"') {
361 try {
362 item = detail::remove_escaped_characters(item);
363 } catch(const std::invalid_argument &iarg) {
364 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
365 }
366 }
367 inMLineValue = false;
368 }
369 while(inMLineValue) {
370 std::string l2;
371 if(!std::getline(input, l2)) {
372 break;
373 }
374 line = l2;
375 detail::rtrim(line);
376 if(detail::hasMLString(line, keyChar)) {
377 line.pop_back();
378 line.pop_back();
379 line.pop_back();
380 if(lineExtension) {
381 detail::ltrim(line);
382 } else if(!(firstLine && item.empty())) {
383 item.push_back('\n');
384 }
385 firstLine = false;
386 item += line;
387 inMLineValue = false;
388 if(!item.empty() && item.back() == '\n') {
389 item.pop_back();
390 }
391 if(keyChar == '\"') {
392 try {
393 item = detail::remove_escaped_characters(item);
394 } catch(const std::invalid_argument &iarg) {
395 throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
396 }
397 }
398 } else {
399 if(lineExtension) {
400 detail::trim(l2);
401 } else if(!(firstLine && item.empty())) {
402 item.push_back('\n');
403 }
404 lineExtension = false;
405 firstLine = false;
406 if(!l2.empty() && l2.back() == '\\' && keyChar == '\"') {
407 lineExtension = true;
408 l2.pop_back();
409 }
410 item += l2;
411 }
412 }
413 items_buffer = {item};
414 } else if(item.size() > 1 && item.front() == aStart) {
415 for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
416 detail::trim(multiline);
417 item += multiline;
418 }
419 if(item.back() == aEnd) {
420 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
421 } else {
422 items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
423 }
424 } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
425 items_buffer = detail::split_up(item, aSep);
426 } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
427 items_buffer = detail::split_up(item, '\0');
428 } else {
429 items_buffer = {item};
430 }
431 } else {
432 name = detail::trim_copy(line.substr(0, comment_pos));
433 items_buffer = {"true"};
434 }
435 std::vector<std::string> parents;
436 try {
437 parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
438 detail::process_quoted_string(name);
439 // clean up quotes on the items and check for escaped strings
440 for(auto &it : items_buffer) {
441 detail::process_quoted_string(it, stringQuote, literalQuote);
442 }
443 } catch(const std::invalid_argument &ia) {
444 throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError);
445 }
446
447 if(parents.size() > maximumLayers) {
448 continue;
449 }
450 if(!configSection.empty() && !inSection) {
451 if(parents.empty() || parents.front() != configSection) {
452 continue;
453 }
454 if(configIndex >= 0 && currentSectionIndex != configIndex) {
455 continue;
456 }
457 parents.erase(parents.begin());
458 inSection = true;
459 }
460 auto match = detail::find_matching_config(output, parents, name, allowMultipleDuplicateFields);
461 if(match != output.end()) {
462 if((match->inputs.size() > 1 && items_buffer.size() > 1) || allowMultipleDuplicateFields) {
463 // insert a separator if one is not already present
464 if(!(match->inputs.back().empty() || items_buffer.front().empty() || match->inputs.back() == "%%" ||
465 items_buffer.front() == "%%")) {
466 match->inputs.emplace_back("%%");
467 match->multiline = true;
468 }
469 }
470 match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end());
471 } else {
472 output.emplace_back();
473 output.back().parents = std::move(parents);
474 output.back().name = std::move(name);
475 output.back().inputs = std::move(items_buffer);
476 }
477 }
478 if(currentSection != "default") {
479 // insert a section end which is just an empty items_buffer
480 std::string ename;
481 output.emplace_back();
482 output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
483 output.back().name = "--";
484 while(output.back().parents.size() > 1) {
485 output.push_back(output.back());
486 output.back().parents.pop_back();
487 }
488 }
489 return output;
490}
491
492CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string &keyChars) {
493 if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') ||
494 (name.find_first_of("'`\"\\") != std::string::npos)) {
495 if(name.find_first_of('\'') == std::string::npos) {
496 name.insert(0, 1, '\'');
497 name.push_back('\'');
498 } else {
499 if(detail::has_escapable_character(name)) {
500 name = detail::add_escaped_characters(name);
501 }
502 name.insert(0, 1, '\"');
503 name.push_back('\"');
504 }
505 }
506 return name;
507}
508
509CLI11_INLINE std::string
510ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
511 std::stringstream out;
512 std::string commentLead;
513 commentLead.push_back(commentChar);
514 commentLead.push_back(' ');
515
516 std::string commentTest = "#;";
517 commentTest.push_back(commentChar);
518 commentTest.push_back(parentSeparatorChar);
519
520 std::string keyChars = commentTest;
521 keyChars.push_back(literalQuote);
522 keyChars.push_back(stringQuote);
523 keyChars.push_back(arrayStart);
524 keyChars.push_back(arrayEnd);
525 keyChars.push_back(valueDelimiter);
526 keyChars.push_back(arraySeparator);
527
528 std::vector<std::string> groups = app->get_groups();
529 bool defaultUsed = false;
530 groups.insert(groups.begin(), std::string("OPTIONS"));
531
532 for(auto &group : groups) {
533 if(group == "OPTIONS" || group.empty()) {
534 if(defaultUsed) {
535 continue;
536 }
537 defaultUsed = true;
538 }
539 if(write_description && group != "OPTIONS" && !group.empty()) {
540 out << '\n' << commentChar << commentLead << group << " Options\n";
541 }
542 for(const Option *opt : app->get_options({})) {
543 // Only process options that are configurable
544 if(opt->get_configurable()) {
545 if(opt->get_group() != group) {
546 if(!(group == "OPTIONS" && opt->get_group().empty())) {
547 continue;
548 }
549 }
550 std::string single_name = opt->get_single_name();
551 if(single_name.empty()) {
552 continue;
553 }
554
555 auto results = opt->reduced_results();
556 if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) {
557 std::reverse(results.begin(), results.end());
558 }
559 std::string value =
560 detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
561
562 bool isDefault = false;
563 if(value.empty() && default_also) {
564 if(!opt->get_default_str().empty()) {
565 results_t res;
566 opt->results(res);
567 value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
568 } else if(opt->get_expected_min() == 0) {
569 value = "false";
570 } else if(opt->get_run_callback_for_default() || !opt->get_required()) {
571 value = "\"\""; // empty string default value
572 } else {
573 value = "\"<REQUIRED>\"";
574 }
575 isDefault = true;
576 }
577
578 if(!value.empty()) {
579 if(!opt->get_fnames().empty()) {
580 try {
581 value = opt->get_flag_value(single_name, value);
582 } catch(const CLI::ArgumentMismatch &) {
583 bool valid{false};
584 for(const auto &test_name : opt->get_fnames()) {
585 try {
586 value = opt->get_flag_value(test_name, value);
587 single_name = test_name;
588 valid = true;
589 } catch(const CLI::ArgumentMismatch &) {
590 continue;
591 }
592 }
593 if(!valid) {
594 value = detail::ini_join(
596 }
597 }
598 }
599 if(write_description && opt->has_description()) {
600 if(out.tellp() != std::streampos(0)) {
601 out << '\n';
602 }
603 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
604 }
605 clean_name_string(single_name, keyChars);
606
607 std::string name = prefix + single_name;
608 if(commentDefaultsBool && isDefault) {
609 name = commentChar + name;
610 }
611 out << name << valueDelimiter << value << '\n';
612 }
613 }
614 }
615 }
616
617 auto subcommands = app->get_subcommands({});
618 for(const App *subcom : subcommands) {
619 if(subcom->get_name().empty()) {
620 if(!default_also && (subcom->count_all() == 0)) {
621 continue;
622 }
623 if(write_description && !subcom->get_group().empty()) {
624 out << '\n' << commentLead << subcom->get_group() << " Options\n";
625 }
626 /*if (!prefix.empty() || app->get_parent() == nullptr) {
627 out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
628 } else {
629 std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
630 const auto *p = app->get_parent();
631 while(p->get_parent() != nullptr) {
632 subname = p->get_name() + parentSeparatorChar +subname;
633 p = p->get_parent();
634 }
635 out << '[' << subname << "]\n";
636 }
637 */
638 out << to_config(subcom, default_also, write_description, prefix);
639 }
640 }
641
642 for(const App *subcom : subcommands) {
643 if(!subcom->get_name().empty()) {
644 if(!default_also && (subcom->count_all() == 0)) {
645 continue;
646 }
647 std::string subname = subcom->get_name();
648 clean_name_string(subname, keyChars);
649
650 if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) {
651 if(!prefix.empty() || app->get_parent() == nullptr) {
652
653 out << '[' << prefix << subname << "]\n";
654 } else {
655 std::string appname = app->get_name();
656 clean_name_string(appname, keyChars);
657 subname = appname + parentSeparatorChar + subname;
658 const auto *p = app->get_parent();
659 while(p->get_parent() != nullptr) {
660 std::string pname = p->get_name();
661 clean_name_string(pname, keyChars);
662 subname = pname + parentSeparatorChar + subname;
663 p = p->get_parent();
664 }
665 out << '[' << subname << "]\n";
666 }
667 out << to_config(subcom, default_also, write_description, "");
668 } else {
669 out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar);
670 }
671 }
672 }
673
674 if(write_description && !out.str().empty()) {
675 std::string outString =
676 commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n';
677 return outString + out.str();
678 }
679 return out.str();
680}
681// [CLI11:config_inl_hpp:end]
682} // namespace CLI
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.
std::string configSection
Specify the configuration section that should be used.
Definition ConfigFwd.hpp:112
char arraySeparator
the character used to separate elements in an array
Definition ConfigFwd.hpp:94
std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override
Convert an app into a configuration.
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
Anything that can error in Parse.
Definition Error.hpp:159