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 }