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