19
19
namespace CLI {
20
20
// [CLI11:config_inl_hpp:verbatim]
21
21
22
- static constexpr auto triple_quote = R"( """)" ;
22
+ static constexpr auto multiline_literal_quote = R"( ''')" ;
23
+ static constexpr auto multiline_string_quote = R"( """)" ;
23
24
24
25
namespace detail {
25
26
26
27
CLI11_INLINE bool is_printable (const std::string &test_string) {
27
28
return std::all_of (test_string.begin (), test_string.end (), [](char x) {
28
- return (isprint (static_cast <unsigned char >(x)) != 0 || x == ' \n ' );
29
+ return (isprint (static_cast <unsigned char >(x)) != 0 || x == ' \n ' || x == ' \t ' );
29
30
});
30
31
}
31
32
32
33
CLI11_INLINE std::string
33
- convert_arg_for_ini (const std::string &arg, char stringQuote, char characterQuote , bool disable_multi_line) {
34
+ convert_arg_for_ini (const std::string &arg, char stringQuote, char literalQuote , bool disable_multi_line) {
34
35
if (arg.empty ()) {
35
36
return std::string (2 , stringQuote);
36
37
}
@@ -53,13 +54,10 @@ convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuot
53
54
if (isprint (static_cast <unsigned char >(arg.front ())) == 0 ) {
54
55
return binary_escape_string (arg);
55
56
}
56
- if (arg == " \\ " ) {
57
- return std::string (1 , stringQuote) + " \\\\ " + stringQuote;
58
- }
59
57
if (arg == " '" ) {
60
58
return std::string (1 , stringQuote) + " '" + stringQuote;
61
59
}
62
- return std::string (1 , characterQuote ) + arg + characterQuote ;
60
+ return std::string (1 , literalQuote ) + arg + literalQuote ;
63
61
}
64
62
// handle hex, binary or octal arguments
65
63
if (arg.front () == ' 0' ) {
@@ -82,13 +80,10 @@ convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuot
82
80
if (!is_printable (arg)) {
83
81
return binary_escape_string (arg);
84
82
}
85
- if (arg.find_first_of (' \n ' ) != std::string::npos) {
86
- if (disable_multi_line) {
87
- return binary_escape_string (arg);
88
- }
89
- return std::string (triple_quote) + arg + triple_quote;
90
- }
91
83
if (detail::has_escapable_character (arg)) {
84
+ if (arg.size () > 100 && !disable_multi_line) {
85
+ return std::string (multiline_literal_quote) + arg + multiline_literal_quote;
86
+ }
92
87
return std::string (1 , stringQuote) + detail::add_escaped_characters (arg) + stringQuote;
93
88
}
94
89
return std::string (1 , stringQuote) + arg + stringQuote;
@@ -99,7 +94,7 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
99
94
char arrayStart,
100
95
char arrayEnd,
101
96
char stringQuote,
102
- char characterQuote ) {
97
+ char literalQuote ) {
103
98
bool disable_multi_line{false };
104
99
std::string joined;
105
100
if (args.size () > 1 && arrayStart != ' \0 ' ) {
@@ -114,7 +109,7 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
114
109
joined.push_back (' ' );
115
110
}
116
111
}
117
- joined.append (convert_arg_for_ini (arg, stringQuote, characterQuote , disable_multi_line));
112
+ joined.append (convert_arg_for_ini (arg, stringQuote, literalQuote , disable_multi_line));
118
113
}
119
114
if (args.size () > 1 && arrayEnd != ' \0 ' ) {
120
115
joined.push_back (arrayEnd);
@@ -233,7 +228,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
233
228
if (len < 3 ) {
234
229
continue ;
235
230
}
236
- if (line.compare (0 , 3 , triple_quote ) == 0 || line.compare (0 , 3 , " ''' " ) == 0 ) {
231
+ if (line.compare (0 , 3 , multiline_string_quote ) == 0 || line.compare (0 , 3 , multiline_literal_quote ) == 0 ) {
237
232
inMLineComment = true ;
238
233
auto cchar = line.front ();
239
234
while (inMLineComment) {
@@ -277,29 +272,26 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
277
272
278
273
// comment lines
279
274
if (line.front () == ' ;' || line.front () == ' #' || line.front () == commentChar) {
280
- if (line.compare (2 , 13 , " cli11:literal" ) == 0 ) {
281
- literalName = true ;
282
- getline (input, buffer);
283
- line = detail::trim_copy (buffer);
284
- } else {
285
- continue ;
286
- }
275
+ continue ;
276
+ }
277
+ std::size_t search_start = 0 ;
278
+ if (line.front () == stringQuote || line.front () == literalQuote || line.front () == ' `' ) {
279
+ search_start = detail::close_sequence (line, 0 , line.front ());
287
280
}
288
-
289
281
// Find = in string, split and recombine
290
- auto delimiter_pos = line.find_first_of (valueDelimiter, 1 );
291
- auto comment_pos = (literalName) ? std::string::npos : line.find_first_of (commentChar);
292
-
282
+ auto delimiter_pos = line.find_first_of (valueDelimiter, search_start + 1 );
283
+ auto comment_pos = line.find_first_of (commentChar, search_start);
293
284
if (comment_pos < delimiter_pos) {
294
285
delimiter_pos = std::string::npos;
295
286
}
296
287
if (delimiter_pos != std::string::npos) {
297
288
298
289
name = detail::trim_copy (line.substr (0 , delimiter_pos));
299
290
std::string item = detail::trim_copy (line.substr (delimiter_pos + 1 , std::string::npos));
300
- bool mlquote = (item.compare (0 , 3 , " '''" ) == 0 || item.compare (0 , 3 , triple_quote) == 0 );
291
+ bool mlquote =
292
+ (item.compare (0 , 3 , multiline_literal_quote) == 0 || item.compare (0 , 3 , multiline_string_quote) == 0 );
301
293
if (!mlquote && comment_pos != std::string::npos && !literalName) {
302
- auto citems = detail::split_up (item, commentChar, false );
294
+ auto citems = detail::split_up (item, commentChar);
303
295
item = detail::trim_copy (citems.front ());
304
296
}
305
297
if (mlquote) {
@@ -337,6 +329,9 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
337
329
if (!item.empty () && item.back () == ' \n ' ) {
338
330
item.pop_back ();
339
331
}
332
+ if (keyChar == ' \" ' ) {
333
+ item = detail::remove_escaped_characters (item);
334
+ }
340
335
} else {
341
336
if (lineExtension) {
342
337
detail::trim (l2);
@@ -358,29 +353,27 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
358
353
detail::trim (multiline);
359
354
item += multiline;
360
355
}
361
- items_buffer = detail::split_up (item.substr (1 , item.length () - 2 ), aSep, false );
356
+ items_buffer = detail::split_up (item.substr (1 , item.length () - 2 ), aSep);
362
357
} else if ((isDefaultArray || isINIArray) && item.find_first_of (aSep) != std::string::npos) {
363
- items_buffer = detail::split_up (item, aSep, false );
358
+ items_buffer = detail::split_up (item, aSep);
364
359
} else if ((isDefaultArray || isINIArray) && item.find_first_of (' ' ) != std::string::npos) {
365
- items_buffer = detail::split_up (item, ' \0 ' , false );
360
+ items_buffer = detail::split_up (item, ' \0 ' );
366
361
} else {
367
362
items_buffer = {item};
368
363
}
369
364
} else {
370
365
name = detail::trim_copy (line.substr (0 , comment_pos));
371
366
items_buffer = {" true" };
372
367
}
373
- if (name.find (parentSeparatorChar) == std::string::npos) {
374
- if (!literalName) {
375
- detail::remove_quotes (name);
376
- }
377
- }
378
- // clean up quotes on the items and check for escaped strings
379
- for (auto &it : items_buffer) {
380
- detail::remove_quotes (it);
381
- if (detail::is_binary_escaped_string (it)) {
382
- it = detail::extract_binary_string (it);
368
+ try {
369
+ literalName = detail::process_quoted_string (name, stringQuote, literalQuote);
370
+
371
+ // clean up quotes on the items and check for escaped strings
372
+ for (auto &it : items_buffer) {
373
+ detail::process_quoted_string (it, stringQuote, literalQuote);
383
374
}
375
+ } catch (const std::invalid_argument &ia) {
376
+ throw CLI::ParseError (ia.what (), CLI::ExitCodes::InvalidError);
384
377
}
385
378
std::vector<std::string> parents;
386
379
if (literalName) {
@@ -461,16 +454,17 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
461
454
continue ;
462
455
}
463
456
}
464
- std::string name = prefix + opt->get_single_name ();
465
- if (name == prefix ) {
457
+ std::string single_name = opt->get_single_name ();
458
+ if (single_name. empty () ) {
466
459
continue ;
467
460
}
461
+
468
462
std::string value = detail::ini_join (
469
- opt->reduced_results (), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote );
463
+ opt->reduced_results (), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote );
470
464
471
465
if (value.empty () && default_also) {
472
466
if (!opt->get_default_str ().empty ()) {
473
- value = detail::convert_arg_for_ini (opt->get_default_str (), stringQuote, characterQuote , false );
467
+ value = detail::convert_arg_for_ini (opt->get_default_str (), stringQuote, literalQuote , false );
474
468
} else if (opt->get_expected_min () == 0 ) {
475
469
value = " false" ;
476
470
} else if (opt->get_run_callback_for_default ()) {
@@ -479,37 +473,52 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
479
473
}
480
474
481
475
if (!value.empty ()) {
476
+
482
477
if (!opt->get_fnames ().empty ()) {
483
478
try {
484
- value = opt->get_flag_value (name , value);
479
+ value = opt->get_flag_value (single_name , value);
485
480
} catch (const CLI::ArgumentMismatch &) {
486
481
bool valid{false };
487
482
for (const auto &test_name : opt->get_fnames ()) {
488
483
try {
489
484
value = opt->get_flag_value (test_name, value);
490
- name = test_name;
485
+ single_name = test_name;
491
486
valid = true ;
492
487
} catch (const CLI::ArgumentMismatch &) {
493
488
continue ;
494
489
}
495
490
}
496
491
if (!valid) {
497
492
value = detail::ini_join (
498
- opt->results (), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote );
493
+ opt->results (), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote );
499
494
}
500
495
}
501
496
}
502
497
if (write_description && opt->has_description ()) {
503
498
out << ' \n ' ;
504
499
out << commentLead << detail::fix_newlines (commentLead, opt->get_description ()) << ' \n ' ;
505
500
}
506
- if (name.find_first_of (commentTest) != std::string::npos || name.compare (0 , 3 , triple_quote) == 0 ||
507
- name.compare (0 , 3 , " '''" ) == 0 || (name.front () == ' [' && name.back () == ' ]' ) ||
508
- (name.front () == stringQuote && name.back () == stringQuote) ||
509
- (name.front () == characterQuote && name.back () == characterQuote) ||
510
- (name.front () == ' `' && name.back () == ' `' )) {
511
- out << commentChar << " cli11:literal\n " ;
501
+ if (single_name.find_first_of (commentTest) != std::string::npos ||
502
+ single_name.compare (0 , 3 , multiline_string_quote) == 0 ||
503
+ single_name.compare (0 , 3 , multiline_literal_quote) == 0 ||
504
+ (single_name.front () == ' [' && single_name.back () == ' ]' ) ||
505
+ (single_name.find_first_of (stringQuote) != std::string::npos) ||
506
+ (single_name.find_first_of (literalQuote) != std::string::npos) ||
507
+ (single_name.find_first_of (' `' ) != std::string::npos)) {
508
+ if (single_name.find_first_of (literalQuote) == std::string::npos) {
509
+ single_name.insert (0 , 1 , literalQuote);
510
+ single_name.push_back (literalQuote);
511
+ } else {
512
+ if (detail::has_escapable_character (single_name)) {
513
+ single_name = detail::add_escaped_characters (single_name);
514
+ }
515
+ single_name.insert (0 , 1 , stringQuote);
516
+ single_name.push_back (stringQuote);
517
+ }
512
518
}
519
+
520
+ std::string name = prefix + single_name;
521
+
513
522
out << name << valueDelimiter << value << ' \n ' ;
514
523
}
515
524
}
0 commit comments