1 /***********************************************************************************************************************************
2 String Handler
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5
6 #include <ctype.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include "common/debug.h"
13 #include "common/macro.h"
14 #include "common/memContext.h"
15 #include "common/type/string.h"
16 #include "common/type/stringList.h"
17 #include "common/type/stringZ.h"
18
19 /***********************************************************************************************************************************
20 Constant strings that are generally useful
21 ***********************************************************************************************************************************/
22 STRING_EXTERN(BRACKETL_STR, BRACKETL_Z);
23 STRING_EXTERN(BRACKETR_STR, BRACKETR_Z);
24 STRING_EXTERN(COLON_STR, COLON_Z);
25 STRING_EXTERN(CR_STR, CR_Z);
26 STRING_EXTERN(CRLF_STR, CRLF_Z);
27 STRING_EXTERN(DASH_STR, DASH_Z);
28 STRING_EXTERN(DOT_STR, DOT_Z);
29 STRING_EXTERN(DOTDOT_STR, DOTDOT_Z);
30 STRING_EXTERN(EMPTY_STR, EMPTY_Z);
31 STRING_EXTERN(EQ_STR, EQ_Z);
32 STRING_EXTERN(FALSE_STR, FALSE_Z);
33 STRING_EXTERN(FSLASH_STR, FSLASH_Z);
34 STRING_EXTERN(LF_STR, LF_Z);
35 STRING_EXTERN(N_STR, N_Z);
36 STRING_EXTERN(NULL_STR, NULL_Z);
37 STRING_EXTERN(QUOTED_STR, QUOTED_Z);
38 STRING_EXTERN(TRUE_STR, TRUE_Z);
39 STRING_EXTERN(Y_STR, Y_Z);
40 STRING_EXTERN(ZERO_STR, ZERO_Z);
41
42 /***********************************************************************************************************************************
43 Maximum size of a string
44 ***********************************************************************************************************************************/
45 #define STRING_SIZE_MAX 1073741824
46
47 #define CHECK_SIZE(size) \
48 do \
49 { \
50 if ((size) > STRING_SIZE_MAX) \
51 THROW(AssertError, "string size must be <= " STRINGIFY(STRING_SIZE_MAX) " bytes"); \
52 } \
53 while (0)
54
55 /***********************************************************************************************************************************
56 Object type
57 ***********************************************************************************************************************************/
58 struct String
59 {
60 StringPub pub; // Publicly accessible variables
61 MemContext *memContext; // Required for dynamically allocated strings
62 };
63
64 /**********************************************************************************************************************************/
65 String *
strNew(void)66 strNew(void)
67 {
68 FUNCTION_TEST_VOID();
69
70 // Create object
71 String *this = memNew(sizeof(String));
72
73 *this = (String)
74 {
75 .pub =
76 {
77 // A zero-length string is not very useful so assume this string is being created for appending and allocate extra space
78 .extra = STRING_EXTRA_MIN,
79 },
80 .memContext = memContextCurrent(),
81 };
82
83 // Allocate and assign string
84 this->pub.buffer = memNew(STRING_EXTRA_MIN + 1);
85 this->pub.buffer[0] = '\0';
86
87 FUNCTION_TEST_RETURN(this);
88 }
89
90 /**********************************************************************************************************************************/
91 String *
strNewZ(const char * const string)92 strNewZ(const char *const string)
93 {
94 FUNCTION_TEST_BEGIN();
95 FUNCTION_TEST_PARAM(STRINGZ, string);
96 FUNCTION_TEST_END();
97
98 ASSERT(string != NULL);
99
100 // Check size
101 size_t stringSize = strlen(string);
102 CHECK_SIZE(stringSize);
103
104 // Create object
105 String *this = memNew(sizeof(String));
106
107 *this = (String)
108 {
109 .pub =
110 {
111 .size = (unsigned int)stringSize,
112 },
113 .memContext = memContextCurrent(),
114 };
115
116 // Allocate and assign string
117 this->pub.buffer = memNew(strSize(this) + this->pub.extra + 1);
118 strcpy(this->pub.buffer, string);
119
120 FUNCTION_TEST_RETURN(this);
121 }
122
123 /**********************************************************************************************************************************/
strNewDbl(double value)124 String *strNewDbl(double value)
125 {
126 FUNCTION_TEST_BEGIN();
127 FUNCTION_TEST_PARAM(DOUBLE, value);
128 FUNCTION_TEST_END();
129
130 char working[CVT_BASE10_BUFFER_SIZE];
131
132 cvtDoubleToZ(value, working, sizeof(working));
133
134 FUNCTION_TEST_RETURN(strNewZ(working));
135 }
136
137 /**********************************************************************************************************************************/
138 String *
strNewBuf(const Buffer * buffer)139 strNewBuf(const Buffer *buffer)
140 {
141 FUNCTION_TEST_BEGIN();
142 FUNCTION_TEST_PARAM(BUFFER, buffer);
143 FUNCTION_TEST_END();
144
145 ASSERT(buffer != NULL);
146
147 // Check size
148 CHECK_SIZE(bufUsed(buffer));
149
150 // Create object
151 String *this = memNew(sizeof(String));
152
153 *this = (String)
154 {
155 .pub =
156 {
157 .size = (unsigned int)bufUsed(buffer),
158 },
159 .memContext = memContextCurrent(),
160 };
161
162 // Allocate and assign string
163 this->pub.buffer = memNew(strSize(this) + 1);
164 memcpy(this->pub.buffer, bufPtrConst(buffer), strSize(this));
165 this->pub.buffer[strSize(this)] = 0;
166
167 FUNCTION_TEST_RETURN(this);
168 }
169
170 /**********************************************************************************************************************************/
171 String *
strNewEncode(EncodeType type,const Buffer * buffer)172 strNewEncode(EncodeType type, const Buffer *buffer)
173 {
174 FUNCTION_TEST_BEGIN();
175 FUNCTION_TEST_PARAM(ENUM, type);
176 FUNCTION_TEST_PARAM(BUFFER, buffer);
177 FUNCTION_TEST_END();
178
179 ASSERT(buffer != NULL);
180
181 // Check encoded size
182 size_t size = encodeToStrSize(type, bufUsed(buffer));
183 CHECK_SIZE(size);
184
185 // Create object
186 String *this = memNew(sizeof(String));
187
188 *this = (String)
189 {
190 .pub =
191 {
192 .size = (unsigned int)size,
193 },
194 .memContext = memContextCurrent(),
195 };
196
197 // Allocate and encode buffer
198 this->pub.buffer = memNew(strSize(this) + 1);
199 encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer);
200
201 FUNCTION_TEST_RETURN(this);
202 }
203
204 /**********************************************************************************************************************************/
205 String *
strNewFmt(const char * format,...)206 strNewFmt(const char *format, ...)
207 {
208 FUNCTION_TEST_BEGIN();
209 FUNCTION_TEST_PARAM(STRINGZ, format);
210 FUNCTION_TEST_END();
211
212 ASSERT(format != NULL);
213
214 // Create object
215 String *this = memNew(sizeof(String));
216
217 *this = (String)
218 {
219 .memContext = memContextCurrent(),
220 };
221
222 // Determine how long the allocated string needs to be
223 va_list argumentList;
224 va_start(argumentList, format);
225 size_t formatSize = (size_t)vsnprintf(NULL, 0, format, argumentList);
226 va_end(argumentList);
227
228 // Check size
229 CHECK_SIZE(formatSize);
230
231 // Allocate and assign string
232 this->pub.size = (unsigned int)formatSize;
233 this->pub.buffer = memNew(strSize(this) + 1);
234 va_start(argumentList, format);
235 vsnprintf(this->pub.buffer, strSize(this) + 1, format, argumentList);
236 va_end(argumentList);
237
238 FUNCTION_TEST_RETURN(this);
239 }
240
241 /**********************************************************************************************************************************/
242 String *
strNewN(const char * string,size_t size)243 strNewN(const char *string, size_t size)
244 {
245 FUNCTION_TEST_BEGIN();
246 FUNCTION_TEST_PARAM_P(CHARDATA, string);
247 FUNCTION_TEST_PARAM(SIZE, size);
248 FUNCTION_TEST_END();
249
250 ASSERT(string != NULL);
251
252 // Check size
253 CHECK_SIZE(size);
254
255 // Create object
256 String *this = memNew(sizeof(String));
257
258 *this = (String)
259 {
260 .pub =
261 {
262 .size = (unsigned int)size,
263 },
264 .memContext = memContextCurrent(),
265 };
266
267 // Allocate and assign string
268 this->pub.buffer = memNew(strSize(this) + 1);
269 strncpy(this->pub.buffer, string, strSize(this));
270 this->pub.buffer[strSize(this)] = 0;
271
272 // Return buffer
273 FUNCTION_TEST_RETURN(this);
274 }
275
276 /**********************************************************************************************************************************/
277 String *
strBase(const String * this)278 strBase(const String *this)
279 {
280 FUNCTION_TEST_BEGIN();
281 FUNCTION_TEST_PARAM(STRING, this);
282 FUNCTION_TEST_END();
283
284 ASSERT(this != NULL);
285
286 FUNCTION_TEST_RETURN(strNewZ(strBaseZ(this)));
287 }
288
289 const char *
strBaseZ(const String * this)290 strBaseZ(const String *this)
291 {
292 FUNCTION_TEST_BEGIN();
293 FUNCTION_TEST_PARAM(STRING, this);
294 FUNCTION_TEST_END();
295
296 ASSERT(this != NULL);
297
298 const char *end = this->pub.buffer + strSize(this);
299
300 while (end > this->pub.buffer && *(end - 1) != '/')
301 end--;
302
303 FUNCTION_TEST_RETURN(end);
304 }
305
306 /**********************************************************************************************************************************/
307 bool
strBeginsWith(const String * this,const String * beginsWith)308 strBeginsWith(const String *this, const String *beginsWith)
309 {
310 FUNCTION_TEST_BEGIN();
311 FUNCTION_TEST_PARAM(STRING, this);
312 FUNCTION_TEST_PARAM(STRING, beginsWith);
313 FUNCTION_TEST_END();
314
315 ASSERT(this != NULL);
316 ASSERT(beginsWith != NULL);
317
318 FUNCTION_TEST_RETURN(strBeginsWithZ(this, strZ(beginsWith)));
319 }
320
321 bool
strBeginsWithZ(const String * this,const char * beginsWith)322 strBeginsWithZ(const String *this, const char *beginsWith)
323 {
324 FUNCTION_TEST_BEGIN();
325 FUNCTION_TEST_PARAM(STRING, this);
326 FUNCTION_TEST_PARAM(STRINGZ, beginsWith);
327 FUNCTION_TEST_END();
328
329 ASSERT(this != NULL);
330 ASSERT(beginsWith != NULL);
331
332 bool result = false;
333 unsigned int beginsWithSize = (unsigned int)strlen(beginsWith);
334
335 if (strSize(this) >= beginsWithSize)
336 result = strncmp(strZ(this), beginsWith, beginsWithSize) == 0;
337
338 FUNCTION_TEST_RETURN(result);
339 }
340
341 /***********************************************************************************************************************************
342 Resize the string to allow the requested number of characters to be appended
343 ***********************************************************************************************************************************/
344 static void
strResize(String * this,size_t requested)345 strResize(String *this, size_t requested)
346 {
347 FUNCTION_TEST_BEGIN();
348 FUNCTION_TEST_PARAM(STRING, this);
349 FUNCTION_TEST_PARAM(SIZE, requested);
350 FUNCTION_TEST_END();
351
352 if (requested > this->pub.extra)
353 {
354 // Check size
355 CHECK_SIZE(strSize(this) + requested);
356
357 // Calculate new extra needs to satisfy request and leave extra space for new growth
358 this->pub.extra = (unsigned int)(requested + ((strSize(this) + requested) / 2));
359
360 // Adding too little extra space usually leads to immediate resizing so enforce a minimum
361 if (this->pub.extra < STRING_EXTRA_MIN)
362 this->pub.extra = STRING_EXTRA_MIN;
363
364 MEM_CONTEXT_BEGIN(this->memContext)
365 {
366 this->pub.buffer = memResize(this->pub.buffer, strSize(this) + this->pub.extra + 1);
367 }
368 MEM_CONTEXT_END();
369 }
370
371 FUNCTION_TEST_RETURN_VOID();
372 }
373
374 /**********************************************************************************************************************************/
375 String *
strCat(String * this,const String * cat)376 strCat(String *this, const String *cat)
377 {
378 FUNCTION_TEST_BEGIN();
379 FUNCTION_TEST_PARAM(STRING, this);
380 FUNCTION_TEST_PARAM(STRING, cat);
381 FUNCTION_TEST_END();
382
383 ASSERT(this != NULL);
384 ASSERT(cat != NULL);
385
386 FUNCTION_TEST_RETURN(strCatZN(this, strZ(cat), strSize(cat)));
387 }
388
389 String *
strCatZ(String * this,const char * cat)390 strCatZ(String *this, const char *cat)
391 {
392 FUNCTION_TEST_BEGIN();
393 FUNCTION_TEST_PARAM(STRING, this);
394 FUNCTION_TEST_PARAM(STRINGZ, cat);
395 FUNCTION_TEST_END();
396
397 ASSERT(this != NULL);
398 ASSERT(cat != NULL);
399
400 // Determine length of string to append
401 size_t sizeGrow = strlen(cat);
402
403 // Ensure there is enough space to grow the string
404 strResize(this, sizeGrow);
405
406 // Append the string
407 strcpy(this->pub.buffer + strSize(this), cat);
408 this->pub.size += (unsigned int)sizeGrow;
409 this->pub.extra -= (unsigned int)sizeGrow;
410
411 FUNCTION_TEST_RETURN(this);
412 }
413
414 String *
strCatZN(String * this,const char * cat,size_t size)415 strCatZN(String *this, const char *cat, size_t size)
416 {
417 FUNCTION_TEST_BEGIN();
418 FUNCTION_TEST_PARAM(STRING, this);
419 FUNCTION_TEST_PARAM(STRINGZ, cat);
420 FUNCTION_TEST_PARAM(SIZE, size);
421 FUNCTION_TEST_END();
422
423 ASSERT(this != NULL);
424 ASSERT(cat != NULL);
425
426 // Ensure there is enough space to grow the string
427 strResize(this, size);
428
429 // Append the string
430 strncpy(this->pub.buffer + strSize(this), cat, size);
431 this->pub.buffer[strSize(this) + size] = '\0';
432
433 // Update size/extra
434 this->pub.size += (unsigned int)size;
435 this->pub.extra -= (unsigned int)size;
436
437 FUNCTION_TEST_RETURN(this);
438 }
439
440 /**********************************************************************************************************************************/
441 String *
strCatChr(String * this,char cat)442 strCatChr(String *this, char cat)
443 {
444 FUNCTION_TEST_BEGIN();
445 FUNCTION_TEST_PARAM(STRING, this);
446 FUNCTION_TEST_PARAM(CHAR, cat);
447 FUNCTION_TEST_END();
448
449 ASSERT(this != NULL);
450 ASSERT(cat != 0);
451
452 // Ensure there is enough space to grow the string
453 strResize(this, 1);
454
455 // Append the character
456 this->pub.buffer[this->pub.size++] = cat;
457 this->pub.buffer[this->pub.size] = 0;
458 this->pub.extra--;
459
460 FUNCTION_TEST_RETURN(this);
461 }
462
463 /**********************************************************************************************************************************/
464 String *
strCatEncode(String * this,EncodeType type,const Buffer * buffer)465 strCatEncode(String *this, EncodeType type, const Buffer *buffer)
466 {
467 FUNCTION_TEST_BEGIN();
468 FUNCTION_TEST_PARAM(STRING, this);
469 FUNCTION_TEST_PARAM(ENUM, type);
470 FUNCTION_TEST_PARAM(BUFFER, buffer);
471 FUNCTION_TEST_END();
472
473 ASSERT(this != NULL);
474 ASSERT(buffer != NULL);
475
476 // Ensure there is enough space to grow the string
477 size_t encodeSize = encodeToStrSize(type, bufUsed(buffer));
478 strResize(this, encodeSize);
479
480 // Append the encoded string
481 encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer + strSize(this));
482
483 // Update size/extra
484 this->pub.size += (unsigned int)encodeSize;
485 this->pub.extra -= (unsigned int)encodeSize;
486
487 FUNCTION_TEST_RETURN(this);
488 }
489
490 /**********************************************************************************************************************************/
491 String *
strCatFmt(String * this,const char * format,...)492 strCatFmt(String *this, const char *format, ...)
493 {
494 FUNCTION_TEST_BEGIN();
495 FUNCTION_TEST_PARAM(STRING, this);
496 FUNCTION_TEST_PARAM(STRINGZ, format);
497 FUNCTION_TEST_END();
498
499 ASSERT(this != NULL);
500 ASSERT(format != NULL);
501
502 // Determine how long the allocated string needs to be
503 va_list argumentList;
504 va_start(argumentList, format);
505 size_t sizeGrow = (size_t)vsnprintf(NULL, 0, format, argumentList);
506 va_end(argumentList);
507
508 // Ensure there is enough space to grow the string
509 strResize(this, sizeGrow);
510
511 // Append the formatted string
512 va_start(argumentList, format);
513 vsnprintf(this->pub.buffer + strSize(this), sizeGrow + 1, format, argumentList);
514 va_end(argumentList);
515
516 this->pub.size += (unsigned int)sizeGrow;
517 this->pub.extra -= (unsigned int)sizeGrow;
518
519 FUNCTION_TEST_RETURN(this);
520 }
521
522 /**********************************************************************************************************************************/
523 int
strCmp(const String * this,const String * compare)524 strCmp(const String *this, const String *compare)
525 {
526 FUNCTION_TEST_BEGIN();
527 FUNCTION_TEST_PARAM(STRING, this);
528 FUNCTION_TEST_PARAM(STRING, compare);
529 FUNCTION_TEST_END();
530
531 if (this != NULL && compare != NULL)
532 FUNCTION_TEST_RETURN(strcmp(strZ(this), strZ(compare)));
533 else if (this == NULL)
534 {
535 if (compare == NULL)
536 FUNCTION_TEST_RETURN(0);
537
538 FUNCTION_TEST_RETURN(-1);
539 }
540
541 FUNCTION_TEST_RETURN(1);
542 }
543
544 int
strCmpZ(const String * this,const char * compare)545 strCmpZ(const String *this, const char *compare)
546 {
547 FUNCTION_TEST_BEGIN();
548 FUNCTION_TEST_PARAM(STRING, this);
549 FUNCTION_TEST_PARAM(STRINGZ, compare);
550 FUNCTION_TEST_END();
551
552 FUNCTION_TEST_RETURN(strCmp(this, compare == NULL ? NULL : STRDEF(compare)));
553 }
554
555 /**********************************************************************************************************************************/
556 String *
strDup(const String * this)557 strDup(const String *this)
558 {
559 FUNCTION_TEST_BEGIN();
560 FUNCTION_TEST_PARAM(STRING, this);
561 FUNCTION_TEST_END();
562
563 String *result = NULL;
564
565 if (this != NULL)
566 result = strNewZ(strZ(this));
567
568 FUNCTION_TEST_RETURN(result);
569 }
570
571 /**********************************************************************************************************************************/
572 bool
strEmpty(const String * this)573 strEmpty(const String *this)
574 {
575 FUNCTION_TEST_BEGIN();
576 FUNCTION_TEST_PARAM(STRING, this);
577 FUNCTION_TEST_END();
578
579 FUNCTION_TEST_RETURN(strSize(this) == 0);
580 }
581
582 /**********************************************************************************************************************************/
583 bool
strEndsWith(const String * this,const String * endsWith)584 strEndsWith(const String *this, const String *endsWith)
585 {
586 FUNCTION_TEST_BEGIN();
587 FUNCTION_TEST_PARAM(STRING, this);
588 FUNCTION_TEST_PARAM(STRING, endsWith);
589 FUNCTION_TEST_END();
590
591 ASSERT(this != NULL);
592 ASSERT(endsWith != NULL);
593
594 FUNCTION_TEST_RETURN(strEndsWithZ(this, strZ(endsWith)));
595 }
596
597 bool
strEndsWithZ(const String * this,const char * endsWith)598 strEndsWithZ(const String *this, const char *endsWith)
599 {
600 FUNCTION_TEST_BEGIN();
601 FUNCTION_TEST_PARAM(STRING, this);
602 FUNCTION_TEST_PARAM(STRINGZ, endsWith);
603 FUNCTION_TEST_END();
604
605 ASSERT(this != NULL);
606 ASSERT(endsWith != NULL);
607
608 bool result = false;
609 unsigned int endsWithSize = (unsigned int)strlen(endsWith);
610
611 if (strSize(this) >= endsWithSize)
612 result = strcmp(strZ(this) + (strSize(this) - endsWithSize), endsWith) == 0;
613
614 FUNCTION_TEST_RETURN(result);
615 }
616
617 /***********************************************************************************************************************************
618 There are two separate implementations because string objects can get the size very efficiently whereas the zero-terminated strings
619 would need a call to strlen().
620 ***********************************************************************************************************************************/
621 bool
strEq(const String * this,const String * compare)622 strEq(const String *this, const String *compare)
623 {
624 FUNCTION_TEST_BEGIN();
625 FUNCTION_TEST_PARAM(STRING, this);
626 FUNCTION_TEST_PARAM(STRING, compare);
627 FUNCTION_TEST_END();
628
629 bool result = false;
630
631 if (this != NULL && compare != NULL)
632 {
633 if (strSize(this) == strSize(compare))
634 result = strcmp(strZ(this), strZ(compare)) == 0;
635 }
636 else
637 result = this == NULL && compare == NULL;
638
639 FUNCTION_TEST_RETURN(result);
640 }
641
642 bool
strEqZ(const String * this,const char * compare)643 strEqZ(const String *this, const char *compare)
644 {
645 FUNCTION_TEST_BEGIN();
646 FUNCTION_TEST_PARAM(STRING, this);
647 FUNCTION_TEST_PARAM(STRINGZ, compare);
648 FUNCTION_TEST_END();
649
650 ASSERT(this != NULL);
651 ASSERT(compare != NULL);
652
653 FUNCTION_TEST_RETURN(strcmp(strZ(this), compare) == 0);
654 }
655
656 /**********************************************************************************************************************************/
657 String *
strFirstUpper(String * this)658 strFirstUpper(String *this)
659 {
660 FUNCTION_TEST_BEGIN();
661 FUNCTION_TEST_PARAM(STRING, this);
662 FUNCTION_TEST_END();
663
664 ASSERT(this != NULL);
665
666 if (strSize(this) > 0)
667 this->pub.buffer[0] = (char)toupper(this->pub.buffer[0]);
668
669 FUNCTION_TEST_RETURN(this);
670 }
671
672 /**********************************************************************************************************************************/
673 String *
strFirstLower(String * this)674 strFirstLower(String *this)
675 {
676 FUNCTION_TEST_BEGIN();
677 FUNCTION_TEST_PARAM(STRING, this);
678 FUNCTION_TEST_END();
679
680 ASSERT(this != NULL);
681
682 if (strSize(this) > 0)
683 this->pub.buffer[0] = (char)tolower(this->pub.buffer[0]);
684
685 FUNCTION_TEST_RETURN(this);
686 }
687
688 /**********************************************************************************************************************************/
689 String *
strUpper(String * this)690 strUpper(String *this)
691 {
692 FUNCTION_TEST_BEGIN();
693 FUNCTION_TEST_PARAM(STRING, this);
694 FUNCTION_TEST_END();
695
696 ASSERT(this != NULL);
697
698 if (strSize(this) > 0)
699 for (size_t idx = 0; idx <= strSize(this); idx++)
700 this->pub.buffer[idx] = (char)toupper(this->pub.buffer[idx]);
701
702 FUNCTION_TEST_RETURN(this);
703 }
704
705 /**********************************************************************************************************************************/
706 String *
strLower(String * this)707 strLower(String *this)
708 {
709 FUNCTION_TEST_BEGIN();
710 FUNCTION_TEST_PARAM(STRING, this);
711 FUNCTION_TEST_END();
712
713 ASSERT(this != NULL);
714
715 if (strSize(this) > 0)
716 for (size_t idx = 0; idx <= strSize(this); idx++)
717 this->pub.buffer[idx] = (char)tolower(this->pub.buffer[idx]);
718
719 FUNCTION_TEST_RETURN(this);
720 }
721
722 /**********************************************************************************************************************************/
723 String *
strPath(const String * this)724 strPath(const String *this)
725 {
726 FUNCTION_TEST_BEGIN();
727 FUNCTION_TEST_PARAM(STRING, this);
728 FUNCTION_TEST_END();
729
730 ASSERT(this != NULL);
731
732 const char *end = this->pub.buffer + strSize(this);
733
734 while (end > this->pub.buffer && *(end - 1) != '/')
735 end--;
736
737 FUNCTION_TEST_RETURN(
738 strNewN(
739 this->pub.buffer,
740 end - this->pub.buffer <= 1 ? (size_t)(end - this->pub.buffer) : (size_t)(end - this->pub.buffer - 1)));
741 }
742
743 /**********************************************************************************************************************************/
744 String *
strPathAbsolute(const String * this,const String * base)745 strPathAbsolute(const String *this, const String *base)
746 {
747 FUNCTION_TEST_BEGIN();
748 FUNCTION_TEST_PARAM(STRING, this);
749 FUNCTION_TEST_PARAM(STRING, base);
750 FUNCTION_TEST_END();
751
752 ASSERT(this != NULL);
753
754 String *result = NULL;
755
756 // Path is already absolute so just return it
757 if (strBeginsWith(this, FSLASH_STR))
758 {
759 result = strDup(this);
760 }
761 // Else we'll need to construct the absolute path. You would hope we could use realpath() here but it is so broken in the
762 // Posix spec that is seems best avoided.
763 else
764 {
765 ASSERT(base != NULL);
766
767 // Base must be absolute to start
768 if (!strBeginsWith(base, FSLASH_STR))
769 THROW_FMT(AssertError, "base path '%s' is not absolute", strZ(base));
770
771 MEM_CONTEXT_TEMP_BEGIN()
772 {
773 StringList *baseList = strLstNewSplit(base, FSLASH_STR);
774 StringList *pathList = strLstNewSplit(this, FSLASH_STR);
775
776 while (!strLstEmpty(pathList))
777 {
778 const String *pathPart = strLstGet(pathList, 0);
779
780 // If the last part is empty
781 if (strSize(pathPart) == 0)
782 {
783 // Allow when this is the last part since it just means there was a trailing /
784 if (strLstSize(pathList) == 1)
785 {
786 strLstRemoveIdx(pathList, 0);
787 break;
788 }
789
790 THROW_FMT(AssertError, "'%s' is not a valid relative path", strZ(this));
791 }
792
793 if (strEq(pathPart, DOTDOT_STR))
794 {
795 const String *basePart = strLstGet(baseList, strLstSize(baseList) - 1);
796
797 if (strSize(basePart) == 0)
798 THROW_FMT(AssertError, "relative path '%s' goes back too far in base path '%s'", strZ(this), strZ(base));
799
800 strLstRemoveIdx(baseList, strLstSize(baseList) - 1);
801 }
802 else if (!strEq(pathPart, DOT_STR))
803 strLstAdd(baseList, pathPart);
804
805 strLstRemoveIdx(pathList, 0);
806 }
807
808 MEM_CONTEXT_PRIOR_BEGIN()
809 {
810 if (strLstSize(baseList) == 1)
811 result = strDup(FSLASH_STR);
812 else
813 result = strLstJoin(baseList, "/");
814 }
815 MEM_CONTEXT_PRIOR_END();
816 }
817 MEM_CONTEXT_TEMP_END();
818 }
819
820 // There should not be any stray .. or // in the final result
821 if (strstr(strZ(result), "/..") != NULL || strstr(strZ(result), "//") != NULL)
822 THROW_FMT(AssertError, "result path '%s' is not absolute", strZ(result));
823
824 FUNCTION_TEST_RETURN(result);
825 }
826
827 /**********************************************************************************************************************************/
828 const char *
strZNull(const String * this)829 strZNull(const String *this)
830 {
831 FUNCTION_TEST_BEGIN();
832 FUNCTION_TEST_PARAM(STRING, this);
833 FUNCTION_TEST_END();
834
835 FUNCTION_TEST_RETURN(this == NULL ? NULL : strZ(this));
836 }
837
838 /**********************************************************************************************************************************/
839 String *
strQuote(const String * this,const String * quote)840 strQuote(const String *this, const String *quote)
841 {
842 FUNCTION_TEST_BEGIN();
843 FUNCTION_TEST_PARAM(STRING, this);
844 FUNCTION_TEST_PARAM(STRING, quote);
845 FUNCTION_TEST_END();
846
847 ASSERT(this != NULL);
848 ASSERT(quote != NULL);
849
850 FUNCTION_TEST_RETURN(strQuoteZ(this, strZ(quote)));
851 }
852
853 String *
strQuoteZ(const String * this,const char * quote)854 strQuoteZ(const String *this, const char *quote)
855 {
856 FUNCTION_TEST_BEGIN();
857 FUNCTION_TEST_PARAM(STRING, this);
858 FUNCTION_TEST_PARAM(STRINGZ, quote);
859 FUNCTION_TEST_END();
860
861 ASSERT(this != NULL);
862 ASSERT(quote != NULL);
863
864 FUNCTION_TEST_RETURN(strNewFmt("%s%s%s", quote, strZ(this), quote));
865 }
866
867 /**********************************************************************************************************************************/
868 String *
strReplaceChr(String * this,char find,char replace)869 strReplaceChr(String *this, char find, char replace)
870 {
871 FUNCTION_TEST_BEGIN();
872 FUNCTION_TEST_PARAM(STRING, this);
873 FUNCTION_TEST_PARAM(CHAR, find);
874 FUNCTION_TEST_PARAM(CHAR, replace);
875 FUNCTION_TEST_END();
876
877 ASSERT(this != NULL);
878
879 for (size_t stringIdx = 0; stringIdx < strSize(this); stringIdx++)
880 {
881 if (this->pub.buffer[stringIdx] == find)
882 this->pub.buffer[stringIdx] = replace;
883 }
884
885 FUNCTION_TEST_RETURN(this);
886 }
887
888 /**********************************************************************************************************************************/
889 String *
strSub(const String * this,size_t start)890 strSub(const String *this, size_t start)
891 {
892 FUNCTION_TEST_BEGIN();
893 FUNCTION_TEST_PARAM(STRING, this);
894 FUNCTION_TEST_PARAM(SIZE, start);
895 FUNCTION_TEST_END();
896
897 ASSERT(this != NULL);
898 ASSERT(start <= this->pub.size);
899
900 FUNCTION_TEST_RETURN(strSubN(this, start, strSize(this) - start));
901 }
902
903 /**********************************************************************************************************************************/
904 String *
strSubN(const String * this,size_t start,size_t size)905 strSubN(const String *this, size_t start, size_t size)
906 {
907 FUNCTION_TEST_BEGIN();
908 FUNCTION_TEST_PARAM(STRING, this);
909 FUNCTION_TEST_PARAM(SIZE, start);
910 FUNCTION_TEST_PARAM(SIZE, size);
911 FUNCTION_TEST_END();
912
913 ASSERT(this != NULL);
914 ASSERT(start <= strSize(this));
915 ASSERT(start + size <= strSize(this));
916
917 FUNCTION_TEST_RETURN(strNewN(strZ(this) + start, size));
918 }
919
920 /**********************************************************************************************************************************/
921 String *
strTrim(String * this)922 strTrim(String *this)
923 {
924 FUNCTION_TEST_BEGIN();
925 FUNCTION_TEST_PARAM(STRING, this);
926 FUNCTION_TEST_END();
927
928 ASSERT(this != NULL);
929
930 // Nothing to trim if size is zero
931 if (strSize(this) > 0)
932 {
933 // Find the beginning of the string skipping all whitespace
934 char *begin = this->pub.buffer;
935
936 while (*begin != 0 && (*begin == ' ' || *begin == '\t' || *begin == '\r' || *begin == '\n'))
937 begin++;
938
939 // Find the end of the string skipping all whitespace
940 char *end = this->pub.buffer + (strSize(this) - 1);
941
942 while (end > begin && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n'))
943 end--;
944
945 // Is there anything to trim?
946 size_t newSize = (size_t)(end - begin + 1);
947
948 if (begin != this->pub.buffer || newSize < strSize(this))
949 {
950 // Calculate new size
951 this->pub.size = (unsigned int)newSize;
952
953 // Move the substr to the beginning of the buffer
954 memmove(this->pub.buffer, begin, strSize(this));
955 this->pub.buffer[strSize(this)] = 0;
956 this->pub.extra = 0;
957
958 MEM_CONTEXT_BEGIN(this->memContext)
959 {
960 // Resize the buffer
961 this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
962 }
963 MEM_CONTEXT_END();
964 }
965 }
966
967 FUNCTION_TEST_RETURN(this);
968 }
969
970 /**********************************************************************************************************************************/
971 int
strChr(const String * this,char chr)972 strChr(const String *this, char chr)
973 {
974 FUNCTION_TEST_BEGIN();
975 FUNCTION_TEST_PARAM(STRING, this);
976 FUNCTION_TEST_PARAM(CHAR, chr);
977 FUNCTION_TEST_END();
978
979 ASSERT(this != NULL);
980
981 int result = -1;
982
983 if (strSize(this) > 0)
984 {
985 const char *ptr = strchr(this->pub.buffer, chr);
986
987 if (ptr != NULL)
988 result = (int)(ptr - this->pub.buffer);
989 }
990
991 FUNCTION_TEST_RETURN(result);
992 }
993
994 /**********************************************************************************************************************************/
995 String *
strTrunc(String * this,int idx)996 strTrunc(String *this, int idx)
997 {
998 FUNCTION_TEST_BEGIN();
999 FUNCTION_TEST_PARAM(STRING, this);
1000 FUNCTION_TEST_END();
1001
1002 ASSERT(this != NULL);
1003 ASSERT(idx >= 0 && (size_t)idx <= strSize(this));
1004
1005 if (strSize(this) > 0)
1006 {
1007 // Reset the size to end at the index
1008 this->pub.size = (unsigned int)(idx);
1009 this->pub.buffer[strSize(this)] = 0;
1010 this->pub.extra = 0;
1011
1012 MEM_CONTEXT_BEGIN(this->memContext)
1013 {
1014 // Resize the buffer
1015 this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
1016 }
1017 MEM_CONTEXT_END();
1018 }
1019
1020 FUNCTION_TEST_RETURN(this);
1021 }
1022
1023 /***********************************************************************************************************************************
1024 Convert an object to a zero-terminated string for logging
1025 ***********************************************************************************************************************************/
strObjToLog(const void * object,StrObjToLogFormat formatFunc,char * buffer,size_t bufferSize)1026 size_t strObjToLog(const void *object, StrObjToLogFormat formatFunc, char *buffer, size_t bufferSize)
1027 {
1028 size_t result = 0;
1029
1030 MEM_CONTEXT_TEMP_BEGIN()
1031 {
1032 result = (size_t)snprintf(buffer, bufferSize, "%s", object == NULL ? NULL_Z : strZ(formatFunc(object)));
1033 }
1034 MEM_CONTEXT_TEMP_END();
1035
1036 return result;
1037 }
1038
1039 /**********************************************************************************************************************************/
1040 String *
strToLog(const String * this)1041 strToLog(const String *this)
1042 {
1043 return this == NULL ? strDup(NULL_STR) : strNewFmt("{\"%s\"}", strZ(this));
1044 }
1045
1046 /**********************************************************************************************************************************/
1047 String *
strSizeFormat(const uint64_t size)1048 strSizeFormat(const uint64_t size)
1049 {
1050 FUNCTION_TEST_BEGIN();
1051 FUNCTION_TEST_PARAM(UINT64, size);
1052
1053 FUNCTION_TEST_END();
1054
1055 String *result = NULL;
1056
1057 if (size < 1024)
1058 result = strNewFmt("%" PRIu64 "B", size);
1059 else if (size < (1024 * 1024))
1060 {
1061 if ((uint64_t)((double)size / 102.4) % 10 != 0)
1062 result = strNewFmt("%.1lfKB", (double)size / 1024);
1063 else
1064 result = strNewFmt("%" PRIu64 "KB", size / 1024);
1065 }
1066 else if (size < (1024 * 1024 * 1024))
1067 {
1068 if ((uint64_t)((double)size / (1024 * 102.4)) % 10 != 0)
1069 result = strNewFmt("%.1lfMB", (double)size / (1024 * 1024));
1070 else
1071 result = strNewFmt("%" PRIu64 "MB", size / (1024 * 1024));
1072 }
1073 else
1074 {
1075 if ((uint64_t)((double)size / (1024 * 1024 * 102.4)) % 10 != 0)
1076 result = strNewFmt("%.1lfGB", (double)size / (1024 * 1024 * 1024));
1077 else
1078 result = strNewFmt("%" PRIu64 "GB", size / (1024 * 1024 * 1024));
1079 }
1080
1081 FUNCTION_TEST_RETURN(result);
1082 }
1083
1084 /***********************************************************************************************************************************
1085 Free the string
1086 ***********************************************************************************************************************************/
1087 void
strFree(String * this)1088 strFree(String *this)
1089 {
1090 FUNCTION_TEST_BEGIN();
1091 FUNCTION_TEST_PARAM(STRING, this);
1092 FUNCTION_TEST_END();
1093
1094 if (this != NULL)
1095 {
1096 MEM_CONTEXT_BEGIN(this->memContext)
1097 {
1098 memFree(this->pub.buffer);
1099 memFree(this);
1100 }
1101 MEM_CONTEXT_END();
1102 }
1103
1104 FUNCTION_TEST_RETURN_VOID();
1105 }
1106