A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.IO;
5using System.Linq;
6using System.Text;
7using System.Text.RegularExpressions;
8
9namespace Unity.VisualScripting
10{
11 public static class StringUtility
12 {
13 public static bool IsNullOrWhiteSpace(string s)
14 {
15 return s == null || s.Trim() == string.Empty;
16 }
17
18 public static string FallbackEmpty(string s, string fallback)
19 {
20 if (string.IsNullOrEmpty(s))
21 {
22 s = fallback;
23 }
24
25 return s;
26 }
27
28 public static string FallbackWhitespace(string s, string fallback)
29 {
30 if (IsNullOrWhiteSpace(s))
31 {
32 s = fallback;
33 }
34
35 return s;
36 }
37
38 public static void AppendLineFormat(this StringBuilder sb, string format, params object[] args)
39 {
40 sb.AppendFormat(format, args);
41 sb.AppendLine();
42 }
43
44 public static string ToSeparatedString(this IEnumerable enumerable, string separator)
45 {
46 return string.Join(separator, enumerable.Cast<object>().Select(o => o?.ToString() ?? "(null)").ToArray());
47 }
48
49 public static string ToCommaSeparatedString(this IEnumerable enumerable)
50 {
51 return ToSeparatedString(enumerable, ", ");
52 }
53
54 public static string ToLineSeparatedString(this IEnumerable enumerable)
55 {
56 return ToSeparatedString(enumerable, Environment.NewLine);
57 }
58
59 public static bool ContainsInsensitive(this string haystack, string needle)
60 {
61 return haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0;
62 }
63
64 public static IEnumerable<int> AllIndexesOf(this string haystack, string needle)
65 {
66 if (string.IsNullOrEmpty(needle))
67 {
68 yield break;
69 }
70
71 for (var index = 0; ; index += needle.Length)
72 {
73 index = haystack.IndexOf(needle, index, StringComparison.OrdinalIgnoreCase);
74
75 if (index == -1)
76 {
77 break;
78 }
79
80 yield return index;
81 }
82 }
83
84 public static string Filter(this string s, bool letters = true, bool numbers = true, bool whitespace = true, bool symbols = true, bool punctuation = true)
85 {
86 var sb = new StringBuilder();
87
88 foreach (var c in s)
89 {
90 if ((!letters && char.IsLetter(c)) ||
91 (!numbers && char.IsNumber(c)) ||
92 (!whitespace && char.IsWhiteSpace(c)) ||
93 (!symbols && char.IsSymbol(c)) ||
94 (!punctuation && char.IsPunctuation(c)))
95 {
96 continue;
97 }
98
99 sb.Append(c);
100 }
101
102 return sb.ToString();
103 }
104
105 public static string FilterReplace(this string s, char replacement, bool merge, bool letters = true, bool numbers = true, bool whitespace = true, bool symbols = true, bool punctuation = true)
106 {
107 var sb = new StringBuilder();
108
109 var wasFiltered = false;
110
111 foreach (var c in s)
112 {
113 if ((!letters && char.IsLetter(c)) ||
114 (!numbers && char.IsNumber(c)) ||
115 (!whitespace && char.IsWhiteSpace(c)) ||
116 (!symbols && char.IsSymbol(c)) ||
117 (!punctuation && char.IsPunctuation(c)))
118 {
119 if (!merge || !wasFiltered)
120 {
121 sb.Append(replacement);
122 }
123
124 wasFiltered = true;
125 }
126 else
127 {
128 sb.Append(c);
129
130 wasFiltered = false;
131 }
132 }
133
134 return sb.ToString();
135 }
136
137 public static string Prettify(this string s)
138 {
139 return s.FirstCharacterToUpper().SplitWords(' ');
140 }
141
142 public static bool IsWordDelimiter(char c)
143 {
144 return char.IsWhiteSpace(c) || char.IsSymbol(c) || char.IsPunctuation(c);
145 }
146
147 public static bool IsWordBeginning(char? previous, char current, char? next)
148 {
149 var isFirst = previous == null;
150 var isLast = next == null;
151
152 var isLetter = char.IsLetter(current);
153 var wasLetter = previous != null && char.IsLetter(previous.Value);
154
155 var isNumber = char.IsNumber(current);
156 var wasNumber = previous != null && char.IsNumber(previous.Value);
157
158 var isUpper = char.IsUpper(current);
159 var wasUpper = previous != null && char.IsUpper(previous.Value);
160
161 var isDelimiter = IsWordDelimiter(current);
162 var wasDelimiter = previous != null && IsWordDelimiter(previous.Value);
163
164 var willBeLower = next != null && char.IsLower(next.Value);
165
166 return
167 (!isDelimiter && isFirst) ||
168 (!isDelimiter && wasDelimiter) ||
169 (isLetter && wasLetter && isUpper && !wasUpper) || // camelCase => camel_Case
170 (isLetter && wasLetter && isUpper && wasUpper && !isLast && willBeLower) || // => ABBRWord => ABBR_Word
171 (isNumber && wasLetter) || // Vector3 => Vector_3
172 (isLetter && wasNumber && isUpper && willBeLower); // Word1Word => Word_1_Word, Word1word => Word_1word
173 }
174
175 public static bool IsWordBeginning(string s, int index)
176 {
177 Ensure.That(nameof(index)).IsGte(index, 0);
178 Ensure.That(nameof(index)).IsLt(index, s.Length);
179
180 var previous = index > 0 ? s[index - 1] : (char?)null;
181 var current = s[index];
182 var next = index < s.Length - 1 ? s[index + 1] : (char?)null;
183
184 return IsWordBeginning(previous, current, next);
185 }
186
187 public static string SplitWords(this string s, char separator)
188 {
189 var sb = new StringBuilder();
190
191 for (var i = 0; i < s.Length; i++)
192 {
193 var c = s[i];
194
195 if (i > 0 && IsWordBeginning(s, i))
196 {
197 sb.Append(separator);
198 }
199
200 sb.Append(c);
201 }
202
203 return sb.ToString();
204 }
205
206 public static string RemoveConsecutiveCharacters(this string s, char c)
207 {
208 var sb = new StringBuilder();
209
210 var previous = '\0';
211
212 foreach (var current in s)
213 {
214 if (current != c || current != previous)
215 {
216 sb.Append(current);
217 previous = current;
218 }
219 }
220
221 return sb.ToString();
222 }
223
224 public static string ReplaceMultiple(this string s, HashSet<char> haystacks, char replacement)
225 {
226 Ensure.That(nameof(haystacks)).IsNotNull(haystacks);
227
228 var sb = new StringBuilder();
229
230 foreach (var current in s)
231 {
232 if (haystacks.Contains(current))
233 {
234 sb.Append(replacement);
235 }
236 else
237 {
238 sb.Append(current);
239 }
240 }
241
242 return sb.ToString();
243 }
244
245 public static string Truncate(this string value, int maxLength, string suffix = "...")
246 {
247 return value.Length <= maxLength ? value : value.Substring(0, maxLength) + suffix;
248 }
249
250 public static string TrimEnd(this string source, string value)
251 {
252 if (!source.EndsWith(value))
253 {
254 return source;
255 }
256
257 return source.Remove(source.LastIndexOf(value));
258 }
259
260 public static string TrimStart(this string source, string value)
261 {
262 if (!source.StartsWith(value))
263 {
264 return source;
265 }
266
267 return source.Substring(value.Length);
268 }
269
270 public static string FirstCharacterToLower(this string s)
271 {
272 if (string.IsNullOrEmpty(s) || char.IsLower(s, 0))
273 {
274 return s;
275 }
276
277 return char.ToLowerInvariant(s[0]) + s.Substring(1);
278 }
279
280 public static string FirstCharacterToUpper(this string s)
281 {
282 if (string.IsNullOrEmpty(s) || char.IsUpper(s, 0))
283 {
284 return s;
285 }
286
287 return char.ToUpperInvariant(s[0]) + s.Substring(1);
288 }
289
290 public static string PartBefore(this string s, char c)
291 {
292 Ensure.That(nameof(s)).IsNotNull(s);
293
294 var index = s.IndexOf(c);
295
296 if (index > 0)
297 {
298 return s.Substring(0, index);
299 }
300 else
301 {
302 return s;
303 }
304 }
305
306 public static string PartAfter(this string s, char c)
307 {
308 Ensure.That(nameof(s)).IsNotNull(s);
309
310 var index = s.IndexOf(c);
311
312 if (index > 0)
313 {
314 return s.Substring(index + 1);
315 }
316 else
317 {
318 return s;
319 }
320 }
321
322 public static void PartsAround(this string s, char c, out string before, out string after)
323 {
324 Ensure.That(nameof(s)).IsNotNull(s);
325
326 var index = s.IndexOf(c);
327
328 if (index > 0)
329 {
330 before = s.Substring(0, index);
331 after = s.Substring(index + 1);
332 }
333 else
334 {
335 before = s;
336 after = null;
337 }
338 }
339
340 // Faster equivalents for chars
341
342 public static bool EndsWith(this string s, char c)
343 {
344 Ensure.That(nameof(s)).IsNotNull(s);
345
346 return s[s.Length - 1] == c;
347 }
348
349 public static bool StartsWith(this string s, char c)
350 {
351 Ensure.That(nameof(s)).IsNotNull(s);
352
353 return s[0] == c;
354 }
355
356 public static bool Contains(this string s, char c)
357 {
358 Ensure.That(nameof(s)).IsNotNull(s);
359
360 for (int i = 0; i < s.Length; i++)
361 {
362 if (s[i] == c)
363 {
364 return true;
365 }
366 }
367
368 return false;
369 }
370
371 public static string NullIfEmpty(this string s)
372 {
373 if (s == string.Empty)
374 {
375 return null;
376 }
377 else
378 {
379 return s;
380 }
381 }
382
383 public static string ToBinaryString(this int value)
384 {
385 return Convert.ToString(value, 2).PadLeft(8, '0');
386 }
387
388 public static string ToBinaryString(this long value)
389 {
390 return Convert.ToString(value, 2).PadLeft(16, '0');
391 }
392
393 public static string ToBinaryString(this Enum value)
394 {
395 return Convert.ToString(Convert.ToInt64(value), 2).PadLeft(16, '0');
396 }
397
398 public static int CountIndices(this string s, char c)
399 {
400 var count = 0;
401
402 foreach (var _c in s)
403 {
404 if (c == _c)
405 {
406 count++;
407 }
408 }
409
410 return count;
411 }
412
413 public static bool IsGuid(string value)
414 {
415 return guidRegex.IsMatch(value);
416 }
417
418 private static readonly Regex guidRegex = new Regex(@"[a-fA-F0-9]{8}(\-[a-fA-F0-9]{4}){3}\-[a-fA-F0-9]{12}");
419
420 public static string PathEllipsis(string s, int maxLength)
421 {
422 var ellipsis = "...";
423
424 if (s.Length < maxLength)
425 {
426 return s;
427 }
428
429 var fileName = Path.GetFileName(s);
430 var directory = Path.GetDirectoryName(s);
431
432 var maxDirectoryLength = maxLength - fileName.Length - ellipsis.Length;
433
434 if (maxDirectoryLength > 0)
435 {
436 return directory.Substring(0, maxDirectoryLength) + ellipsis + Path.DirectorySeparatorChar + fileName;
437 }
438 else
439 {
440 return ellipsis + Path.DirectorySeparatorChar + fileName;
441 }
442 }
443
444 public static string ToHexString(this byte[] bytes)
445 {
446 return BitConverter.ToString(bytes).Replace("-", "");
447 }
448 }
449}