12#include "../Config.hpp"
24static constexpr auto multiline_literal_quote = R
"(''')";
25static constexpr auto multiline_string_quote = R
"(""")";
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');
35CLI11_INLINE std::string
36convert_arg_for_ini(
const std::string &arg,
char stringQuote,
char literalQuote,
bool disable_multi_line) {
38 return std::string(2, stringQuote);
41 if(arg ==
"true" || arg ==
"false" || arg ==
"nan" || arg ==
"inf") {
45 if(arg.compare(0, 2,
"0x") != 0 && arg.compare(0, 2,
"0X") != 0) {
46 using CLI::detail::lexical_cast;
48 if(lexical_cast(arg, val)) {
49 if(arg.find_first_not_of(
"0123456789.-+eE") == std::string::npos) {
56 if(isprint(
static_cast<unsigned char>(arg.front())) == 0) {
57 return binary_escape_string(arg);
60 return std::string(1, stringQuote) +
"'" + stringQuote;
62 return std::string(1, literalQuote) + arg + literalQuote;
65 if(arg.front() ==
'0') {
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');
72 }
else if(arg[1] ==
'o') {
73 if(std::all_of(arg.begin() + 2, arg.end(), [](
char x) { return (x >=
'0' && x <=
'7'); })) {
76 }
else if(arg[1] ==
'b') {
77 if(std::all_of(arg.begin() + 2, arg.end(), [](
char x) { return (x ==
'0' || x ==
'1'); })) {
82 if(!is_printable(arg)) {
83 return binary_escape_string(arg);
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);
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');
95 return_string.append(arg);
96 return_string.append(multiline_literal_quote, 3);
99 return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
101 return std::string(1, stringQuote) + arg + stringQuote;
104CLI11_INLINE std::string ini_join(
const std::vector<std::string> &args,
110 bool disable_multi_line{
false};
112 if(args.size() > 1 && arrayStart !=
'\0') {
113 joined.push_back(arrayStart);
114 disable_multi_line =
true;
116 std::size_t start = 0;
117 for(
const auto &arg : args) {
119 joined.push_back(sepChar);
120 if(!std::isspace<char>(sepChar, std::locale())) {
121 joined.push_back(
' ');
124 joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line));
126 if(args.size() > 1 && arrayEnd !=
'\0') {
127 joined.push_back(arrayEnd);
132CLI11_INLINE std::vector<std::string>
133generate_parents(
const std::string §ion, 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);
142 if(name.find(parentSeparator) != std::string::npos) {
143 std::vector<std::string> plist = detail::split_up(name, parentSeparator);
146 parents.insert(parents.end(), plist.begin(), plist.end());
150 detail::remove_quotes(parents);
151 }
catch(
const std::invalid_argument &iarg) {
158checkParentSegments(std::vector<ConfigItem> &output,
const std::string ¤tSection,
char parentSeparator) {
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();
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]) {
178 if(common == mpair) {
181 while(output.back().parents.size() > common + 1) {
182 output.push_back(output.back());
183 output.back().parents.pop_back();
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 =
"++";
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 =
"++";
201 output.emplace_back();
202 output.back().parents = std::move(parents);
203 output.back().name =
"++";
207CLI11_INLINE
bool hasMLString(std::string
const &fullString,
char check) {
208 if(fullString.length() < 3) {
211 auto it = fullString.rbegin();
212 return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check);
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()) {
223 auto search = items.end() - 1;
225 if(search->parents == parents && search->name == name) {
228 if(search == items.begin()) {
240 std::string currentSection =
"default";
241 std::string previousSection =
"default";
242 std::vector<ConfigItem> output;
245 bool inSection{
false};
246 bool inMLineComment{
false};
247 bool inMLineValue{
false};
249 char aStart = (isINIArray) ?
'[' :
arrayStart;
250 char aEnd = (isINIArray) ?
']' :
arrayEnd;
252 int currentSectionIndex{0};
255 while(getline(input, buffer)) {
256 std::vector<std::string> items_buffer;
258 line = detail::trim_copy(buffer);
259 std::size_t len = line.length();
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)) {
273 if(detail::hasMLString(line, cchar)) {
274 inMLineComment =
false;
279 if(line.front() ==
'[' && line.back() ==
']') {
280 if(currentSection !=
"default") {
282 output.emplace_back();
283 output.back().parents = detail::generate_parents(currentSection, name,
parentSeparatorChar);
284 output.back().name =
"--";
286 currentSection = line.substr(1, len - 2);
288 if(currentSection.size() > 1 && currentSection.front() ==
'[' && currentSection.back() ==
']') {
289 currentSection = currentSection.substr(1, currentSection.size() - 2);
291 if(detail::to_lower(currentSection) ==
"default") {
292 currentSection =
"default";
297 if(currentSection == previousSection) {
298 ++currentSectionIndex;
300 currentSectionIndex = 0;
301 previousSection = currentSection;
307 if(line.front() ==
';' || line.front() ==
'#' || line.front() ==
commentChar) {
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]);
323 search_start = line.find_first_of(line_sep_chars, search_start);
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;
333 if(delimiter_pos != std::string::npos) {
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));
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) {
341 item = detail::trim_copy(citems.front());
345 auto keyChar = item.front();
346 item = buffer.substr(delimiter_pos + 1, std::string::npos);
350 bool lineExtension{
false};
351 bool firstLine =
true;
352 if(!item.empty() && item.back() ==
'\\') {
354 lineExtension =
true;
355 }
else if(detail::hasMLString(item, keyChar)) {
360 if(keyChar ==
'\"') {
362 item = detail::remove_escaped_characters(item);
363 }
catch(
const std::invalid_argument &iarg) {
367 inMLineValue =
false;
369 while(inMLineValue) {
371 if(!std::getline(input, l2)) {
376 if(detail::hasMLString(line, keyChar)) {
382 }
else if(!(firstLine && item.empty())) {
383 item.push_back(
'\n');
387 inMLineValue =
false;
388 if(!item.empty() && item.back() ==
'\n') {
391 if(keyChar ==
'\"') {
393 item = detail::remove_escaped_characters(item);
394 }
catch(
const std::invalid_argument &iarg) {
401 }
else if(!(firstLine && item.empty())) {
402 item.push_back(
'\n');
404 lineExtension =
false;
406 if(!l2.empty() && l2.back() ==
'\\' && keyChar ==
'\"') {
407 lineExtension =
true;
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);
419 if(item.back() == aEnd) {
420 items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
422 items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep);
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');
429 items_buffer = {item};
432 name = detail::trim_copy(line.substr(0, comment_pos));
433 items_buffer = {
"true"};
435 std::vector<std::string> parents;
438 detail::process_quoted_string(name);
440 for(
auto &it : items_buffer) {
443 }
catch(
const std::invalid_argument &ia) {
457 parents.erase(parents.begin());
461 if(match != output.end()) {
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;
470 match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end());
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);
478 if(currentSection !=
"default") {
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();
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(
'\'');
499 if(detail::has_escapable_character(name)) {
500 name = detail::add_escaped_characters(name);
502 name.insert(0, 1,
'\"');
503 name.push_back(
'\"');
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;
514 commentLead.push_back(
' ');
516 std::string commentTest =
"#;";
520 std::string keyChars = commentTest;
528 std::vector<std::string> groups = app->get_groups();
529 bool defaultUsed =
false;
530 groups.insert(groups.begin(), std::string(
"OPTIONS"));
532 for(
auto &group : groups) {
533 if(group ==
"OPTIONS" || group.empty()) {
539 if(write_description && group !=
"OPTIONS" && !group.empty()) {
540 out <<
'\n' <<
commentChar << commentLead << group <<
" Options\n";
542 for(
const Option *opt : app->get_options({})) {
544 if(opt->get_configurable()) {
545 if(opt->get_group() != group) {
546 if(!(group ==
"OPTIONS" && opt->get_group().empty())) {
550 std::string single_name = opt->get_single_name();
551 if(single_name.empty()) {
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());
562 bool isDefault =
false;
563 if(value.empty() && default_also) {
564 if(!opt->get_default_str().empty()) {
568 }
else if(opt->get_expected_min() == 0) {
570 }
else if(opt->get_run_callback_for_default() || !opt->get_required()) {
573 value =
"\"<REQUIRED>\"";
579 if(!opt->get_fnames().empty()) {
581 value = opt->get_flag_value(single_name, value);
584 for(
const auto &test_name : opt->get_fnames()) {
586 value = opt->get_flag_value(test_name, value);
587 single_name = test_name;
594 value = detail::ini_join(
599 if(write_description && opt->has_description()) {
600 if(out.tellp() != std::streampos(0)) {
603 out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) <<
'\n';
605 clean_name_string(single_name, keyChars);
607 std::string name = prefix + single_name;
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)) {
623 if(write_description && !subcom->get_group().empty()) {
624 out <<
'\n' << commentLead << subcom->get_group() <<
" Options\n";
638 out <<
to_config(subcom, default_also, write_description, prefix);
642 for(
const App *subcom : subcommands) {
643 if(!subcom->get_name().empty()) {
644 if(!default_also && (subcom->count_all() == 0)) {
647 std::string subname = subcom->get_name();
648 clean_name_string(subname, keyChars);
650 if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) {
651 if(!prefix.empty() || app->get_parent() ==
nullptr) {
653 out <<
'[' << prefix << subname <<
"]\n";
655 std::string appname = app->get_name();
656 clean_name_string(appname, keyChars);
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);
665 out <<
'[' << subname <<
"]\n";
667 out <<
to_config(subcom, default_also, write_description,
"");
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();
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