1 /// Highlighting, case conversions, parsing, and more. 2 module tern..string; 3 4 public import tern.algorithm; 5 public import std.string : strip, stripLeft, stripRight; 6 import std.traits; 7 8 /// Ansi color implementation. 9 public enum AnsiColor 10 { 11 Reset = "\x1B[0m", 12 13 Black = "\x1B[30m", 14 Red = "\x1B[31m", 15 Green = "\x1B[32m", 16 Yellow = "\x1B[33m", 17 Blue = "\x1B[34m", 18 Magenta = "\x1B[35m", 19 Cyan = "\x1B[36m", 20 White = "\x1B[37m", 21 22 BoldBlack = "\x1B[30;1m", 23 BoldRed = "\x1B[31;1m", 24 BoldGreen = "\x1B[32;1m", 25 BoldYellow = "\x1B[33;1m", 26 BoldBlue = "\x1B[34;1m", 27 BoldMagenta = "\x1B[35;1m", 28 BoldCyan = "\x1B[36;1m", 29 BoldWhite = "\x1B[37;1m", 30 31 UnderlineBlack = "\x1B[30;4m", 32 UnderlineRed = "\x1B[31;4m", 33 UnderlineGreen = "\x1B[32;4m", 34 UnderlineYellow = "\x1B[33;4m", 35 UnderlineBlue = "\x1B[34;4m", 36 UnderlineMagenta = "\x1B[35;4m", 37 UnderlineCyan = "\x1B[36;4m", 38 UnderlineWhite = "\x1B[37;4m", 39 40 BackgroundBlack = "\x1B[40m", 41 BackgroundRed = "\x1B[41m", 42 BackgroundGreen = "\x1B[42m", 43 BackgroundYellow = "\x1B[43m", 44 BackgroundBlue = "\x1B[44m", 45 BackgroundMagenta = "\x1B[45m", 46 BackgroundCyan = "\x1B[46m", 47 BackgroundWhite = "\x1B[47m", 48 49 Underline = "\x1B[4m", 50 Italic = "\x1B[3m", 51 Reverse = "\x1B[7m" 52 } 53 54 public: 55 static: 56 pure: 57 /** 58 * Converts all characters in `str` to be uppercase. 59 * 60 * Params: 61 * str = The string to convert to uppercase. 62 * 63 * Returns: 64 * `str` in uppercase. 65 */ 66 string toUpper(string str) 67 { 68 char[] ret = new char[str.length]; 69 foreach (i, c; str) 70 ret[i] = c.toUpper; 71 return cast(string)ret; 72 } 73 74 /** 75 * Converts all characters in `str` to be lowercase. 76 * 77 * Params: 78 * str = The string to convert to lowercase. 79 * 80 * Returns: 81 * `str` in lowercase. 82 */ 83 string toLower(string str) 84 { 85 char[] ret = new char[str.length]; 86 foreach (i, c; str) 87 ret[i] = c.toLower; 88 return cast(string)ret; 89 } 90 91 /** 92 * Converts a character to uppercase. 93 * 94 * Params: 95 * c = The character to convert to uppercase. 96 * 97 * Returns: 98 * `c` in uppercase. 99 */ 100 T toUpper(T)(T c) if (isSomeChar!T) 101 { 102 if (c.isLower) 103 return cast(T)(c - ('a' - 'A')); 104 else 105 return c; 106 } 107 108 /** 109 * Converts a character to lowercase 110 * 111 * Params: 112 * c = The character to convert to lowercase. 113 * 114 * Returns: 115 * `c` in lowercase. 116 */ 117 T toLower(T)(T c) if (isSomeChar!T) 118 { 119 if (c.isUpper) 120 return cast(T)(c + ('a' - 'A')); 121 else 122 return c; 123 } 124 125 /** 126 * Converts `str` to camel case. 127 * 128 * Params: 129 * str = The string to convert to camel case. 130 * 131 * Returns: 132 * The input string `str` converted to camel case. 133 */ 134 string toCamelCase(string str) 135 { 136 char[] ret = new char[str.length]; 137 ret[0] = str[0].toLower; 138 foreach (i, c; str[1..$]) 139 ret[i+1] = c; 140 141 return cast(string)ret; 142 } 143 144 /** 145 * Converts `str` to Pascal case. 146 * 147 * Params: 148 * str = The string to convert to Pascal case. 149 * 150 * Returns: 151 * The input string `str` converted to Pascal case. 152 */ 153 string toPascalCase(string str) 154 { 155 char[] ret = new char[str.length]; 156 ret[0] = str[0].toUpper; 157 foreach (i, c; str[1..$]) 158 ret[i+1] = c; 159 160 return cast(string)ret; 161 } 162 163 /** 164 * Converts `str` to snake case. 165 * 166 * Params: 167 * str = The string to convert to snake case. 168 * 169 * Returns: 170 * The input string `str` converted to snake case. 171 */ 172 string toSnakeCase(string str) 173 { 174 char[] ret; 175 foreach (c; str) 176 { 177 if (c.isUpper) 178 { 179 if (ret.length > 0 && ret[$-1] != '_') 180 ret ~= '_'; 181 ret ~= c.toLower; 182 } 183 else 184 ret ~= c; 185 } 186 return cast(string)ret; 187 } 188 189 /** 190 * Converts `str` to kebab case. 191 * 192 * Params: 193 * str = The string to convert to kebab case. 194 * 195 * Returns: 196 * The input string `str` converted to kebab case. 197 */ 198 string toKebabCase(string str) 199 { 200 char[] ret; 201 foreach (c; str) 202 { 203 if (c.isUpper) 204 { 205 if (ret.length > 0 && ret[$-1] != '-') 206 ret ~= '-'; 207 ret ~= c.toLower; 208 } 209 else 210 ret ~= c; 211 } 212 return cast(string)ret; 213 } 214 215 /** 216 * Mangles `str` to remove special characters. 217 * 218 * Params: 219 * str = The string to mangle. 220 * 221 * Returns: 222 * A mangled version of the input string `str` with special characters replaced and non-alphanumeric characters removed. 223 */ 224 string mangle(string str) 225 { 226 size_t idx = str.lastIndexOf('.'); 227 if (idx != -1) 228 str = str[(idx + 1)..$]; 229 230 str = str.replace("*", "PTR") 231 .replace("[", "OPBRK") 232 .replace("]", "CLBRK") 233 .replace("(", "OPPAR") 234 .replace(")", "CLPAR") 235 .replace(",", "COMMA") 236 .replace("!", "EXCLM"); 237 238 return cast(string)str.filter!(c => isAlphaNum(c) || c == '_').range; 239 } 240 241 /** 242 * Pads `str` to the left with `padding` character until it reaches `length`. 243 * 244 * Params: 245 * str = The string to pad. 246 * length = The desired length of the resulting string. 247 * padding = The character to use for padding. Defaults to space. 248 * 249 * Returns: 250 * The input string `str` padded to the left with `padding` character until it reaches `length`. 251 */ 252 string padLeft(string str, size_t length, char padding = ' ') 253 { 254 while (str.length < length) 255 str = padding~str; 256 return str; 257 } 258 259 /** 260 * Pads `str` to the right with `padding` character until it reaches `length`. 261 * 262 * Params: 263 * str = The string to pad. 264 * length = The desired length of the resulting string. 265 * padding = The character to use for padding. Defaults to space. 266 * 267 * Returns: 268 * The input string `str` padded to the right with `padding` character until it reaches `length`. 269 */ 270 string padRight(string str, size_t length, char padding = ' ') 271 { 272 while (str.length < length) 273 str = str~padding; 274 return str; 275 } 276 277 /** 278 * Highlights `matchOf` in `matchTo` with `color`. 279 * 280 * Params: 281 * color = The color to highlight using. 282 * matchTo = The string being highlighted. 283 * matchOf = The string to highlight. 284 * 285 * Returns: `matchTo` with the color and reset inserted as to highlight `matchOf`. 286 */ 287 string highlight(AnsiColor color, string matchTo, string matchOf) 288 { 289 return matchTo.replace(matchOf, color~matchOf~AnsiColor.Reset); 290 } 291 292 /** 293 * Highlights the string between `matchStart` and `matchEnd` in `matchTo` with `color`. 294 * 295 * Params: 296 * color = The color to highlight using. 297 * matchTo = The string being highlighted. 298 * matchStart = The start index of the string to highlight. 299 * matchEnd = The end index of the string to highlight. 300 * 301 * Returns: `matchTo` with the color and reset inserted as to highlight the specified string. 302 */ 303 string highlight(AnsiColor color, string matchTo, size_t matchStart, size_t matchEnd) 304 { 305 return matchTo[0..matchStart]~color~matchTo[matchStart..matchEnd]~AnsiColor.Reset~matchTo[matchEnd..$]; 306 } 307 308 /** 309 * Splits `str` into an array of string lines. 310 * 311 * Params: 312 * str = The string. 313 * 314 * Returns: 315 * Array of string lines from `str`. 316 */ 317 string[] splitLines(string str) 318 { 319 str = str.replace("\r\n", "\n"); 320 str = str.replace("\r", "\n"); 321 str = str.replace("\f", "\n"); 322 return str.split('\n'); 323 } 324 325 @nogc: 326 /** 327 * Checks if a character is alphabetic. 328 * 329 * Params: 330 * c = The character to check. 331 * 332 * Returns: 333 * `true` if the character is alphabetic. 334 */ 335 bool isAlpha(T)(T c) if (isSomeChar!T) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 336 337 /** 338 * Checks if a character is a digit. 339 * 340 * Params: 341 * c = The character to check. 342 * base = The numeric base to consider when checking for digitness. 343 * 344 * Returns: 345 * `true` if the character is a digit. 346 */ 347 bool isDigit(T)(T c, uint base = 10) if (isSomeChar!T) => (c >= '0' && c <= '9'- (base > 10 ? 0 : 10-base) ) || (base > 10 && c <= 'a' + base-11); 348 349 350 /** 351 * Checks if a character is alphanumeric. 352 * 353 * Params: 354 * c = The character to check. 355 * base = The numeric base to consider when checking for alphanumericity. 356 * 357 * Returns: 358 * `true` if the character is alphanumeric. 359 */ 360 bool isAlphaNum(T)(T c, uint base = 10) if (isSomeChar!T) => c.isAlpha || c.isDigit(base); 361 362 /** 363 * Checks if a character is uppercase. 364 * 365 * Params: 366 * c = The character to check. 367 * 368 * Returns: 369 * `true` if the character is uppercase. 370 */ 371 bool isUpper(T)(T c) if (isSomeChar!T) => (c >= 'A' && c <= 'Z'); 372 373 /** 374 * Checks if a character is lowercase. 375 * 376 * Params: 377 * c = The character to check. 378 * 379 * Returns: 380 * `true` if the character is lowercase. 381 */ 382 bool isLower(T)(T c) if (isSomeChar!T) => (c >= 'a' && c <= 'z'); 383 384 /** 385 * Checks if a string contains only alphabetic characters. 386 * 387 * Params: 388 * str = The string to check. 389 * 390 * Returns: 391 * `true` if the string contains only alphabetic characters. 392 */ 393 bool isAlpha(string str) 394 { 395 foreach (c; str) 396 { 397 if (!c.isAlpha) 398 return false; 399 } 400 return true; 401 } 402 403 /** 404 * Checks if a string contains only alphanumeric characters. 405 * 406 * Params: 407 * str = The string to check. 408 * base = The numeric base to consider when checking for alphanumericity. 409 * 410 * Returns: 411 * `true` if the string contains only alphanumeric characters. 412 */ 413 bool isAlphaNum(string str, uint base = 10) 414 { 415 foreach (c; str) 416 { 417 if (!c.isAlpha && !c.isDigit(base)) 418 return false; 419 } 420 return true; 421 } 422 423 /** 424 * Checks if a string contains only numeric characters. 425 * 426 * Params: 427 * str = The string to check. 428 * base = The numeric base to consider when checking for numericity. 429 * 430 * Returns: 431 * `true` if the string contains only numeric characters. 432 */ 433 bool isNumeric(string str, uint base = 10) 434 { 435 foreach (c; str) 436 { 437 if (!c.isDigit(base)) 438 return false; 439 } 440 return true; 441 } 442 443 /** 444 * Checks if a string contains only uppercase characters. 445 * 446 * Params: 447 * str = The string to check. 448 * 449 * Returns: 450 * `true` if the string contains only uppercase characters. 451 */ 452 bool isUpper(string str) 453 { 454 foreach (c; str) 455 { 456 if (!c.isUpper) 457 return false; 458 } 459 return true; 460 } 461 462 /** 463 * Checks if a string contains only lowercase characters. 464 * 465 * Params: 466 * str = The string to check. 467 * 468 * Returns: 469 * `true` if the string contains only lowercase characters. 470 */ 471 bool isLower(string str) 472 { 473 foreach (c; str) 474 { 475 if (!c.isLower) 476 return false; 477 } 478 return true; 479 }