File indexing completed on 2026-11-11 10:24:30
0001
0002
0003
0004
0005
0006
0007 #pragma once
0008
0009
0010
0011
0012 #include "../StringTools.hpp"
0013
0014
0015 #include <cstdint>
0016 #include <string>
0017 #include <utility>
0018 #include <vector>
0019
0020
0021 namespace CLI {
0022
0023
0024 namespace detail {
0025 CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim) {
0026 std::vector<std::string> elems;
0027 if(s.empty()) {
0028 elems.emplace_back();
0029 return elems;
0030 }
0031
0032 std::size_t start = 0;
0033 std::size_t end = 0;
0034
0035 while((end = s.find(delim, start)) != std::string::npos) {
0036 elems.push_back(s.substr(start, end - start));
0037 start = end + 1;
0038 }
0039 elems.push_back(s.substr(start));
0040 return elems;
0041 }
0042
0043 CLI11_INLINE std::string <rim(std::string &str) {
0044 auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
0045 str.erase(str.begin(), it);
0046 return str;
0047 }
0048
0049 CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) {
0050 auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
0051 str.erase(str.begin(), it);
0052 return str;
0053 }
0054
0055 CLI11_INLINE std::string &rtrim(std::string &str) {
0056 auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
0057 str.erase(it.base(), str.end());
0058 return str;
0059 }
0060
0061 CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) {
0062 auto it =
0063 std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
0064 str.erase(it.base(), str.end());
0065 return str;
0066 }
0067
0068 CLI11_INLINE std::string &remove_quotes(std::string &str) {
0069 if(!str.empty() && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) {
0070 remove_outer(str, str.front());
0071 }
0072 return str;
0073 }
0074
0075 CLI11_INLINE std::string &remove_outer(std::string &str, char key) {
0076 if(str.length() > 1 && (str.front() == key)) {
0077 if(str.front() == str.back()) {
0078 str.pop_back();
0079 str.erase(str.begin(), str.begin() + 1);
0080 }
0081 }
0082 return str;
0083 }
0084
0085 CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) {
0086 std::string::size_type n = 0;
0087 while(n != std::string::npos && n < input.size()) {
0088 n = input.find_first_of("\r\n", n);
0089 if(n != std::string::npos) {
0090 input.insert(n + 1, leader);
0091 n += leader.size() + 1;
0092 }
0093 }
0094 return input;
0095 }
0096
0097 CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
0098 if(!aliases.empty()) {
0099 out << std::setw(static_cast<int>(wid)) << " aliases: ";
0100 bool front = true;
0101 for(const auto &alias : aliases) {
0102 if(!front) {
0103 out << ", ";
0104 } else {
0105 front = false;
0106 }
0107 out << detail::fix_newlines(" ", alias);
0108 }
0109 out << "\n";
0110 }
0111 return out;
0112 }
0113
0114 CLI11_INLINE bool valid_name_string(const std::string &str) {
0115 if(str.empty() || !valid_first_char(str[0])) {
0116 return false;
0117 }
0118 auto e = str.end();
0119 for(auto c = str.begin() + 1; c != e; ++c)
0120 if(!valid_later_char(*c))
0121 return false;
0122 return true;
0123 }
0124
0125 CLI11_INLINE std::string get_group_separators() {
0126 std::string separators{"_'"};
0127 #if CLI11_HAS_RTTI != 0
0128 char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep();
0129 separators.push_back(group_separator);
0130 #endif
0131 return separators;
0132 }
0133
0134 CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) {
0135
0136 std::size_t start_pos = 0;
0137
0138 while((start_pos = str.find(from, start_pos)) != std::string::npos) {
0139 str.replace(start_pos, from.length(), to);
0140 start_pos += to.length();
0141 }
0142
0143 return str;
0144 }
0145
0146 CLI11_INLINE void remove_default_flag_values(std::string &flags) {
0147 auto loc = flags.find_first_of('{', 2);
0148 while(loc != std::string::npos) {
0149 auto finish = flags.find_first_of("},", loc + 1);
0150 if((finish != std::string::npos) && (flags[finish] == '}')) {
0151 flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
0152 flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
0153 }
0154 loc = flags.find_first_of('{', loc + 1);
0155 }
0156 flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
0157 }
0158
0159 CLI11_INLINE std::ptrdiff_t
0160 find_member(std::string name, const std::vector<std::string> &names, bool ignore_case, bool ignore_underscore) {
0161 auto it = std::end(names);
0162 if(ignore_case) {
0163 if(ignore_underscore) {
0164 name = detail::to_lower(detail::remove_underscore(name));
0165 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
0166 return detail::to_lower(detail::remove_underscore(local_name)) == name;
0167 });
0168 } else {
0169 name = detail::to_lower(name);
0170 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
0171 return detail::to_lower(local_name) == name;
0172 });
0173 }
0174
0175 } else if(ignore_underscore) {
0176 name = detail::remove_underscore(name);
0177 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
0178 return detail::remove_underscore(local_name) == name;
0179 });
0180 } else {
0181 it = std::find(std::begin(names), std::end(names), name);
0182 }
0183
0184 return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
0185 }
0186
0187 CLI11_MODULE_INLINE const std::string &escapedChars() {
0188 static const std::string s{"\b\t\n\f\r\"\\"};
0189 return s;
0190 }
0191 CLI11_MODULE_INLINE const std::string &escapedCharsCode() {
0192 static const std::string s{"btnfr\"\\"};
0193 return s;
0194 }
0195 CLI11_MODULE_INLINE const std::string &bracketChars() {
0196 static const std::string s{"\"'`[(<{"};
0197 return s;
0198 }
0199 CLI11_MODULE_INLINE const std::string &matchBracketChars() {
0200 static const std::string s{"\"'`])>}"};
0201 return s;
0202 }
0203
0204 CLI11_INLINE bool has_escapable_character(const std::string &str) {
0205 return (str.find_first_of(escapedChars()) != std::string::npos);
0206 }
0207
0208 CLI11_INLINE std::string add_escaped_characters(const std::string &str) {
0209 std::string out;
0210 out.reserve(str.size() + 4);
0211 for(char s : str) {
0212 auto sloc = escapedChars().find_first_of(s);
0213 if(sloc != std::string::npos) {
0214 out.push_back('\\');
0215 out.push_back(escapedCharsCode()[sloc]);
0216 } else {
0217 out.push_back(s);
0218 }
0219 }
0220 return out;
0221 }
0222
0223 CLI11_INLINE std::uint32_t hexConvert(char hc) {
0224 int hcode{0};
0225 if(hc >= '0' && hc <= '9') {
0226 hcode = (hc - '0');
0227 } else if(hc >= 'A' && hc <= 'F') {
0228 hcode = (hc - 'A' + 10);
0229 } else if(hc >= 'a' && hc <= 'f') {
0230 hcode = (hc - 'a' + 10);
0231 } else {
0232 hcode = -1;
0233 }
0234 return static_cast<uint32_t>(hcode);
0235 }
0236
0237 CLI11_INLINE char make_char(std::uint32_t code) { return static_cast<char>(static_cast<unsigned char>(code)); }
0238
0239 CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) {
0240 if(code < 0x80) {
0241 str.push_back(static_cast<char>(code));
0242 } else if(code < 0x800) {
0243
0244 str.push_back(make_char(0xC0 | code >> 6));
0245 str.push_back(make_char(0x80 | (code & 0x3F)));
0246 } else if(code < 0x10000) {
0247 if(0xD800 <= code && code <= 0xDFFF) {
0248 throw std::invalid_argument("[0xD800, 0xDFFF] are not valid code points.");
0249 }
0250
0251 str.push_back(make_char(0xE0 | code >> 12));
0252 str.push_back(make_char(0x80 | (code >> 6 & 0x3F)));
0253 str.push_back(make_char(0x80 | (code & 0x3F)));
0254 } else if(code < 0x110000) {
0255
0256 str.push_back(make_char(0xF0 | code >> 18));
0257 str.push_back(make_char(0x80 | (code >> 12 & 0x3F)));
0258 str.push_back(make_char(0x80 | (code >> 6 & 0x3F)));
0259 str.push_back(make_char(0x80 | (code & 0x3F)));
0260 } else {
0261 throw std::invalid_argument("values above 0x10FFFF are not valid code points.");
0262 }
0263 }
0264
0265 CLI11_INLINE std::string remove_escaped_characters(const std::string &str) {
0266
0267 std::string out;
0268 out.reserve(str.size());
0269 for(auto loc = str.begin(); loc < str.end(); ++loc) {
0270 if(*loc == '\\') {
0271 if(str.end() - loc < 2) {
0272 throw std::invalid_argument("invalid escape sequence " + str);
0273 }
0274 auto ecloc = escapedCharsCode().find_first_of(*(loc + 1));
0275 if(ecloc != std::string::npos) {
0276 out.push_back(escapedChars()[ecloc]);
0277 ++loc;
0278 } else if(*(loc + 1) == 'u') {
0279
0280 if(str.end() - loc < 6) {
0281 throw std::invalid_argument("unicode sequence must have 4 hex codes " + str);
0282 }
0283 std::uint32_t code{0};
0284 std::uint32_t mplier{16 * 16 * 16};
0285 for(int ii = 2; ii < 6; ++ii) {
0286 std::uint32_t res = hexConvert(*(loc + ii));
0287 if(res > 0x0F) {
0288 throw std::invalid_argument("unicode sequence must have 4 hex codes " + str);
0289 }
0290 code += res * mplier;
0291 mplier = mplier / 16;
0292 }
0293 append_codepoint(out, code);
0294 loc += 5;
0295 } else if(*(loc + 1) == 'U') {
0296
0297 if(str.end() - loc < 10) {
0298 throw std::invalid_argument("unicode sequence must have 8 hex codes " + str);
0299 }
0300 std::uint32_t code{0};
0301 std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16};
0302 for(int ii = 2; ii < 10; ++ii) {
0303 std::uint32_t res = hexConvert(*(loc + ii));
0304 if(res > 0x0F) {
0305 throw std::invalid_argument("unicode sequence must have 8 hex codes " + str);
0306 }
0307 code += res * mplier;
0308 mplier = mplier / 16;
0309 }
0310 append_codepoint(out, code);
0311 loc += 9;
0312 } else if(*(loc + 1) == '0') {
0313 out.push_back('\0');
0314 ++loc;
0315 } else {
0316 throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str);
0317 }
0318 } else {
0319 out.push_back(*loc);
0320 }
0321 }
0322 return out;
0323 }
0324
0325 CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) {
0326 std::size_t loc{0};
0327 for(loc = start + 1; loc < str.size(); ++loc) {
0328 if(str[loc] == closure_char) {
0329 break;
0330 }
0331 if(str[loc] == '\\') {
0332
0333 ++loc;
0334 }
0335 }
0336 return loc;
0337 }
0338
0339 CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) {
0340 auto loc = str.find_first_of(closure_char, start + 1);
0341 return (loc != std::string::npos ? loc : str.size());
0342 }
0343
0344 CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) {
0345
0346 auto bracket_loc = matchBracketChars().find(closure_char);
0347 switch(bracket_loc) {
0348 case 0:
0349 return close_string_quote(str, start, closure_char);
0350 case 1:
0351 case 2:
0352 #if defined(_MSC_VER) && _MSC_VER < 1920
0353 case(std::size_t)-1:
0354 #else
0355 case std::string::npos:
0356 #endif
0357 return close_literal_quote(str, start, closure_char);
0358 default:
0359 break;
0360 }
0361
0362 std::string closures(1, closure_char);
0363 auto loc = start + 1;
0364
0365 while(loc < str.size()) {
0366 if(str[loc] == closures.back()) {
0367 closures.pop_back();
0368 if(closures.empty()) {
0369 return loc;
0370 }
0371 }
0372 bracket_loc = bracketChars().find(str[loc]);
0373 if(bracket_loc != std::string::npos) {
0374 switch(bracket_loc) {
0375 case 0:
0376 loc = close_string_quote(str, loc, str[loc]);
0377 break;
0378 case 1:
0379 case 2:
0380 loc = close_literal_quote(str, loc, str[loc]);
0381 break;
0382 default:
0383 closures.push_back(matchBracketChars()[bracket_loc]);
0384 break;
0385 }
0386 }
0387 ++loc;
0388 }
0389 if(loc > str.size()) {
0390 loc = str.size();
0391 }
0392 return loc;
0393 }
0394
0395 CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) {
0396
0397 auto find_ws = [delimiter](char ch) {
0398 return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
0399 };
0400 trim(str);
0401
0402 std::vector<std::string> output;
0403 while(!str.empty()) {
0404 if(bracketChars().find_first_of(str[0]) != std::string::npos) {
0405 auto bracketLoc = bracketChars().find_first_of(str[0]);
0406 auto end = close_sequence(str, 0, matchBracketChars()[bracketLoc]);
0407 if(end >= str.size()) {
0408 output.push_back(std::move(str));
0409 str.clear();
0410 } else {
0411 output.push_back(str.substr(0, end + 1));
0412
0413
0414
0415
0416
0417
0418 char follow = str[end + 1];
0419 bool skip_follow = find_ws(follow) || (bracketChars().find_first_of(follow) != std::string::npos);
0420 auto next = skip_follow ? end + 2 : end + 1;
0421 if(next < str.size()) {
0422 str = str.substr(next);
0423 } else {
0424 str.clear();
0425 }
0426 }
0427
0428 } else {
0429 auto it = std::find_if(std::begin(str), std::end(str), find_ws);
0430 if(it != std::end(str)) {
0431 std::string value = std::string(str.begin(), it);
0432 output.push_back(value);
0433 str = std::string(it + 1, str.end());
0434 } else {
0435 output.push_back(str);
0436 str.clear();
0437 }
0438 }
0439 trim(str);
0440 }
0441 return output;
0442 }
0443
0444 CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
0445 auto next = str[offset + 1];
0446 if((next == '\"') || (next == '\'') || (next == '`')) {
0447 if(offset == 0) {
0448
0449 return offset + 1;
0450 }
0451 auto astart = str.find_last_of("-/ \"\'`", offset - 1);
0452 if(astart != std::string::npos) {
0453 if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
0454 str[offset] = ' ';
0455 }
0456 }
0457 return offset + 1;
0458 }
0459
0460 CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force) {
0461
0462 std::string escaped_string{};
0463
0464 for(char c : string_to_escape) {
0465
0466
0467 if(isprint(static_cast<unsigned char>(c)) == 0) {
0468 std::stringstream stream;
0469
0470
0471
0472 stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c));
0473 std::string code = stream.str();
0474 escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code;
0475 } else if(c == 'x' || c == 'X') {
0476
0477 if(!escaped_string.empty() && escaped_string.back() == '\\') {
0478 escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58");
0479 } else {
0480 escaped_string.push_back(c);
0481 }
0482
0483 } else {
0484 escaped_string.push_back(c);
0485 }
0486 }
0487 if(escaped_string != string_to_escape || force) {
0488 auto sqLoc = escaped_string.find('\'');
0489 while(sqLoc != std::string::npos) {
0490 escaped_string[sqLoc] = '\\';
0491 escaped_string.insert(sqLoc + 1, "x27");
0492 sqLoc = escaped_string.find('\'', sqLoc + 4);
0493 }
0494 escaped_string.insert(0, "'B\"(");
0495 escaped_string.push_back(')');
0496 escaped_string.push_back('"');
0497 escaped_string.push_back('\'');
0498 }
0499 return escaped_string;
0500 }
0501
0502 CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) {
0503 size_t ssize = escaped_string.size();
0504 if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
0505 return true;
0506 }
0507 return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0);
0508 }
0509
0510 CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) {
0511 std::size_t start{0};
0512 std::size_t tail{0};
0513 size_t ssize = escaped_string.size();
0514 if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
0515 start = 3;
0516 tail = 2;
0517 } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) {
0518 start = 4;
0519 tail = 3;
0520 }
0521
0522 if(start == 0) {
0523 return escaped_string;
0524 }
0525 std::string outstring;
0526
0527 outstring.reserve(ssize - start - tail);
0528 std::size_t loc = start;
0529 while(loc < ssize - tail) {
0530
0531 if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) {
0532 auto c1 = escaped_string[loc + 2];
0533 auto c2 = escaped_string[loc + 3];
0534
0535 std::uint32_t res1 = hexConvert(c1);
0536 std::uint32_t res2 = hexConvert(c2);
0537 if(res1 <= 0x0F && res2 <= 0x0F) {
0538 loc += 4;
0539 outstring.push_back(static_cast<char>(res1 * 16 + res2));
0540 continue;
0541 }
0542 }
0543 outstring.push_back(escaped_string[loc]);
0544 ++loc;
0545 }
0546 return outstring;
0547 }
0548
0549 CLI11_INLINE void remove_quotes(std::vector<std::string> &args) {
0550 for(auto &arg : args) {
0551 if(arg.empty()) {
0552 continue;
0553 }
0554 if(arg.front() == '\"' && arg.back() == '\"') {
0555 remove_quotes(arg);
0556
0557 arg = remove_escaped_characters(arg);
0558 } else {
0559 remove_quotes(arg);
0560 }
0561 }
0562 }
0563
0564 CLI11_INLINE void handle_secondary_array(std::string &str) {
0565 if(str.size() >= 2 && str.front() == '[' && str.back() == ']') {
0566
0567 std::string tstr{"[["};
0568 for(std::size_t ii = 1; ii < str.size(); ++ii) {
0569 tstr.push_back(str[ii]);
0570 tstr.push_back(str[ii]);
0571 }
0572 str = std::move(tstr);
0573 }
0574 }
0575
0576 CLI11_INLINE bool
0577 process_quoted_string(std::string &str, char string_char, char literal_char, bool disable_secondary_array_processing) {
0578 if(str.size() <= 1) {
0579 return false;
0580 }
0581 if(detail::is_binary_escaped_string(str)) {
0582 str = detail::extract_binary_string(str);
0583 if(!disable_secondary_array_processing)
0584 handle_secondary_array(str);
0585 return true;
0586 }
0587 if(str.front() == string_char && str.back() == string_char) {
0588 detail::remove_outer(str, string_char);
0589 if(str.find_first_of('\\') != std::string::npos) {
0590 str = detail::remove_escaped_characters(str);
0591 }
0592 if(!disable_secondary_array_processing)
0593 handle_secondary_array(str);
0594 return true;
0595 }
0596 if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) {
0597 detail::remove_outer(str, str.front());
0598 if(!disable_secondary_array_processing)
0599 handle_secondary_array(str);
0600 return true;
0601 }
0602 return false;
0603 }
0604
0605 CLI11_INLINE std::string get_environment_value(const std::string &env_name) {
0606 std::string ename_string;
0607
0608 #ifdef _MSC_VER
0609
0610 char *buffer = nullptr;
0611 std::size_t sz = 0;
0612 if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) {
0613 ename_string = std::string(buffer);
0614 free(buffer);
0615 }
0616 #else
0617
0618
0619
0620 const char *buffer = nullptr;
0621 buffer = std::getenv(env_name.c_str());
0622 if(buffer != nullptr) {
0623 ename_string = std::string(buffer);
0624 }
0625 #endif
0626 return ename_string;
0627 }
0628
0629 CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out,
0630 const std::string &text,
0631 std::size_t paragraphWidth,
0632 const std::string &linePrefix,
0633 bool skipPrefixOnFirstLine) {
0634 std::istringstream lss(text);
0635 std::string line = "";
0636 while(std::getline(lss, line)) {
0637 std::istringstream iss(line);
0638 std::string word = "";
0639 std::size_t charsWritten = 0;
0640
0641 if(!skipPrefixOnFirstLine)
0642 out << linePrefix;
0643 skipPrefixOnFirstLine = false;
0644
0645 while(iss >> word) {
0646 if(charsWritten > 0 && (word.length() + 1 + charsWritten > paragraphWidth)) {
0647 out << '\n' << linePrefix;
0648 charsWritten = 0;
0649 }
0650 if(charsWritten == 0) {
0651 out << word;
0652 charsWritten += word.length();
0653 } else {
0654 out << ' ' << word;
0655 charsWritten += word.length() + 1;
0656 }
0657 }
0658
0659 if(!lss.eof())
0660 out << '\n';
0661 }
0662 return out;
0663 }
0664
0665 }
0666
0667 }