orionis 0.716.0__py3-none-any.whl → 0.717.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3439 @@
1
+ import re
2
+ import base64
3
+ import json
4
+ import hashlib
5
+ import urllib.parse
6
+ import uuid
7
+ import os
8
+ import html
9
+ import textwrap
10
+ import functools
11
+ from datetime import datetime
12
+ from typing import Any, Callable, Iterable, Optional, Union, List, Dict
13
+ import unicodedata
14
+
15
+ class Stringable(str):
16
+
17
+ def after(self, search: str) -> "Stringable":
18
+ """
19
+ Return the substring after the first occurrence of a given value.
20
+
21
+ Searches for the first occurrence of the specified substring and returns everything that comes after it. If the substring is not found, returns the original string unchanged.
22
+
23
+ Parameters
24
+ ----------
25
+ search : str
26
+ Substring to search for within the current string.
27
+
28
+ Returns
29
+ -------
30
+ Stringable
31
+ New Stringable instance containing the substring after the first occurrence of the search string, or the original string if not found.
32
+ """
33
+
34
+ # Find the index of the first occurrence of the search string
35
+ idx = self.find(search)
36
+
37
+ # If found, return substring after the search string
38
+ # Otherwise, return the original string
39
+ return Stringable(self[idx + len(search):]) if idx != -1 else Stringable(self)
40
+
41
+ def afterLast(self, search: str) -> "Stringable":
42
+ """
43
+ Return the substring after the last occurrence of a given value.
44
+
45
+ Searches for the last occurrence of the specified substring and returns everything that comes after it. If the substring is not found, returns the original string unchanged.
46
+
47
+ Parameters
48
+ ----------
49
+ search : str
50
+ Substring to search for within the current string.
51
+
52
+ Returns
53
+ -------
54
+ Stringable
55
+ New Stringable instance containing the substring after the last occurrence of the search string, or the original string if not found.
56
+ """
57
+
58
+ # Find the index of the last occurrence of the search string
59
+ idx = self.rfind(search)
60
+
61
+ # If found, return substring after the search string
62
+ # Otherwise, return the original string
63
+ return Stringable(self[idx + len(search):]) if idx != -1 else Stringable(self)
64
+
65
+ def append(self, *values: str) -> "Stringable":
66
+ """
67
+ Append one or more values to the end of the string.
68
+
69
+ Concatenates the provided string values to the end of the current string, returning a new Stringable instance.
70
+
71
+ Parameters
72
+ ----------
73
+ *values : str
74
+ One or more string values to append.
75
+
76
+ Returns
77
+ -------
78
+ Stringable
79
+ New Stringable instance with all provided values appended to the end.
80
+ """
81
+
82
+ # Join all provided values and append them to the current string
83
+ return Stringable(self + "".join(values))
84
+
85
+ def newLine(self, count: int = 1) -> "Stringable":
86
+ """
87
+ Append one or more newline characters to the string.
88
+
89
+ Appends the specified number of newline characters (\n) to the end of the current string.
90
+
91
+ Parameters
92
+ ----------
93
+ count : int, optional
94
+ Number of newline characters to append. Default is 1.
95
+
96
+ Returns
97
+ -------
98
+ Stringable
99
+ New Stringable instance with the specified number of newline characters appended.
100
+ """
101
+
102
+ # Append the specified number of newline characters to the current string
103
+ return Stringable(str(self) + "\n" * count)
104
+
105
+ def before(self, search: str) -> "Stringable":
106
+ """
107
+ Return the substring before the first occurrence of a given value.
108
+
109
+ Searches for the first occurrence of the specified substring and returns everything before it. If the substring is not found, returns the original string unchanged.
110
+
111
+ Parameters
112
+ ----------
113
+ search : str
114
+ Substring to search for within the current string.
115
+
116
+ Returns
117
+ -------
118
+ Stringable
119
+ New Stringable instance containing the substring before the first occurrence of the search string, or the original string if not found.
120
+ """
121
+
122
+ # Find the index of the first occurrence of the search string
123
+ idx = self.find(search)
124
+
125
+ # If found, return substring before the search string
126
+ # Otherwise, return the original string
127
+ return Stringable(self[:idx]) if idx != -1 else Stringable(self)
128
+
129
+ def beforeLast(self, search: str) -> "Stringable":
130
+ """
131
+ Return the substring before the last occurrence of a given value.
132
+
133
+ Searches for the last occurrence of the specified substring and returns everything before it. If the substring is not found, returns the original string unchanged.
134
+
135
+ Parameters
136
+ ----------
137
+ search : str
138
+ Substring to search for within the current string.
139
+
140
+ Returns
141
+ -------
142
+ Stringable
143
+ New Stringable instance containing the substring before the last occurrence of the search string, or the original string if not found.
144
+ """
145
+
146
+ # Find the index of the last occurrence of the search string
147
+ idx = self.rfind(search)
148
+
149
+ # If found, return substring before the search string
150
+ # Otherwise, return the original string
151
+ return Stringable(self[:idx]) if idx != -1 else Stringable(self)
152
+
153
+ def contains(self, needles: Union[str, Iterable[str]], ignore_case: bool = False) -> bool:
154
+ """
155
+ Check if the string contains any of the given values.
156
+
157
+ Determines whether the string contains any of the specified needle values. The search can be performed case-sensitively or case-insensitively.
158
+
159
+ Parameters
160
+ ----------
161
+ needles : str or Iterable[str]
162
+ Value or values to search for within the string.
163
+ ignore_case : bool, optional
164
+ If True, perform case-insensitive search. Default is False.
165
+
166
+ Returns
167
+ -------
168
+ bool
169
+ True if the string contains any of the needle values, False otherwise.
170
+ """
171
+
172
+ # Normalize needles to a list for consistent processing
173
+ if isinstance(needles, str):
174
+ needles = [needles]
175
+
176
+ # Convert to lowercase for case-insensitive comparison if requested
177
+ s = str(self).lower() if ignore_case else str(self)
178
+
179
+ # Check if any needle is found in the string
180
+ return any((needle.lower() if ignore_case else needle) in s for needle in needles)
181
+
182
+ def endsWith(self, needles: Union[str, Iterable[str]]) -> bool:
183
+ """
184
+ Check if the string ends with any of the given substrings.
185
+
186
+ Determines whether the string ends with any of the specified needle values.
187
+
188
+ Parameters
189
+ ----------
190
+ needles : str or Iterable[str]
191
+ Substring or substrings to check at the end of the string.
192
+
193
+ Returns
194
+ -------
195
+ bool
196
+ True if the string ends with any of the needle values, False otherwise.
197
+ """
198
+
199
+ # Normalize needles to a list for consistent processing
200
+ if isinstance(needles, str):
201
+ needles = [needles]
202
+
203
+ # Check if string ends with any of the provided needles
204
+ return any(str(self).endswith(needle) for needle in needles)
205
+
206
+ # Alias for backwards compatibility
207
+ def ends_with(self, needles: Union[str, Iterable[str]]) -> bool:
208
+ """
209
+ Alias for endsWith method to maintain backwards compatibility.
210
+
211
+ Checks if the string ends with any of the given substrings.
212
+
213
+ Parameters
214
+ ----------
215
+ needles : str or Iterable[str]
216
+ Substring or substrings to check at the end of the string.
217
+
218
+ Returns
219
+ -------
220
+ bool
221
+ True if the string ends with any of the needle values, False otherwise.
222
+ """
223
+
224
+ # Convert single string to list for uniform processing
225
+ if isinstance(needles, str):
226
+ needles = [needles]
227
+
228
+ # Check if string ends with any of the provided needles
229
+ return any(str(self).endswith(needle) for needle in needles)
230
+
231
+ def exactly(self, value: Any) -> bool:
232
+ """
233
+ Check if the string is exactly equal to a given value.
234
+
235
+ Performs a strict equality comparison between the string and the provided value after converting both to string representations.
236
+
237
+ Parameters
238
+ ----------
239
+ value : Any
240
+ Value to compare against the current string.
241
+
242
+ Returns
243
+ -------
244
+ bool
245
+ True if the string exactly matches the given value, False otherwise.
246
+ """
247
+
248
+ # Convert both values to strings and compare for exact equality
249
+ return str(self) == str(value)
250
+
251
+ def isEmpty(self) -> bool:
252
+ """
253
+ Check if the string is empty.
254
+
255
+ Determines if the string has zero length, meaning it contains no characters.
256
+
257
+ Returns
258
+ -------
259
+ bool
260
+ True if the string is empty, False otherwise.
261
+ """
262
+
263
+ # Return True if the string has zero length
264
+ return len(self) == 0
265
+
266
+ def isNotEmpty(self) -> bool:
267
+ """
268
+ Check if the string is not empty.
269
+
270
+ Determines if the string has one or more characters, meaning it contains some content.
271
+
272
+ Returns
273
+ -------
274
+ bool
275
+ True if the string is not empty, False otherwise.
276
+ """
277
+
278
+ # Return True if the string has one or more characters
279
+ return not self.isEmpty()
280
+
281
+ # Aliases for backwards compatibility
282
+ def is_empty(self) -> bool:
283
+ """
284
+ Alias for isEmpty method to maintain backwards compatibility.
285
+
286
+ Returns
287
+ -------
288
+ bool
289
+ True if the string is empty, False otherwise.
290
+ """
291
+
292
+ # Call the camelCase method for compatibility
293
+ return self.isEmpty()
294
+
295
+ def is_not_empty(self) -> bool:
296
+ """
297
+ Alias for isNotEmpty method to maintain backwards compatibility.
298
+
299
+ Returns
300
+ -------
301
+ bool
302
+ True if the string is not empty, False otherwise.
303
+ """
304
+
305
+ # Call the camelCase method for compatibility
306
+ return self.isNotEmpty()
307
+
308
+ def lower(self) -> "Stringable":
309
+ """
310
+ Convert the string to lowercase.
311
+
312
+ Returns
313
+ -------
314
+ Stringable
315
+ New Stringable instance with all characters converted to lowercase.
316
+ """
317
+
318
+ # Convert all characters to lowercase using the built-in method
319
+ return Stringable(super().lower())
320
+
321
+ def upper(self) -> "Stringable":
322
+ """
323
+ Convert the string to uppercase.
324
+
325
+ Returns
326
+ -------
327
+ Stringable
328
+ New Stringable instance with all characters converted to uppercase.
329
+ """
330
+
331
+ # Convert all characters to uppercase using the built-in method
332
+ return Stringable(super().upper())
333
+
334
+ def reverse(self) -> "Stringable":
335
+ """
336
+ Reverse the string.
337
+
338
+ Returns
339
+ -------
340
+ Stringable
341
+ New Stringable instance with characters in reverse order.
342
+ """
343
+
344
+ # Reverse the string using slicing
345
+ return Stringable(self[::-1])
346
+
347
+ def repeat(self, times: int) -> "Stringable":
348
+ """
349
+ Repeat the string a specified number of times.
350
+
351
+ Parameters
352
+ ----------
353
+ times : int
354
+ Number of times to repeat the string.
355
+
356
+ Returns
357
+ -------
358
+ Stringable
359
+ New Stringable instance with the string repeated the specified number of times.
360
+ """
361
+
362
+ # Repeat the string using multiplication
363
+ return Stringable(self * times)
364
+
365
+ def replace(self, search: Union[str, Iterable[str]], replace: Union[str, Iterable[str]], case_sensitive: bool = True) -> "Stringable":
366
+ """
367
+ Replace occurrences of specified substrings with corresponding replacements.
368
+
369
+ Replaces each substring in `search` with the corresponding value in `replace`. If `search` or `replace` is a single string, it is converted to a list for uniform processing. The replacement can be performed case-sensitively or case-insensitively.
370
+
371
+ Parameters
372
+ ----------
373
+ search : str or Iterable[str]
374
+ Substring(s) to search for in the string.
375
+ replace : str or Iterable[str]
376
+ Replacement string(s) for each search substring.
377
+ case_sensitive : bool, optional
378
+ If True, perform case-sensitive replacement. Default is True.
379
+
380
+ Returns
381
+ -------
382
+ Stringable
383
+ New Stringable instance with the specified replacements applied.
384
+ """
385
+
386
+ # Convert search and replace to lists for consistent processing
387
+ s = self
388
+ if isinstance(search, str):
389
+ search = [search]
390
+ if isinstance(replace, str):
391
+ replace = [replace] * len(search)
392
+
393
+ # Iterate through each search-replace pair and apply replacement
394
+ for src, rep in zip(search, replace):
395
+
396
+ # Case-sensitive replacement using str.replace
397
+ if case_sensitive:
398
+ s = str(s).replace(src, rep)
399
+ # Case-insensitive replacement using re.sub with IGNORECASE
400
+ else:
401
+ s = re.sub(re.escape(src), rep, str(s), flags=re.IGNORECASE)
402
+
403
+ # Return a new Stringable instance with replacements
404
+ return Stringable(s)
405
+
406
+ def stripTags(self, allowed_tags: Optional[str] = None) -> "Stringable":
407
+ """
408
+ Remove HTML and PHP tags from the string.
409
+
410
+ Parameters
411
+ ----------
412
+ allowed_tags : str, optional
413
+ Tags that should not be stripped. Default is None.
414
+
415
+ Returns
416
+ -------
417
+ Stringable
418
+ New Stringable with tags removed.
419
+ """
420
+
421
+ # If allowed_tags is specified, use a simple unescape (not full PHP compatibility)
422
+ if allowed_tags:
423
+ return Stringable(html.unescape(str(self)))
424
+ # Otherwise, remove all tags using regex
425
+ else:
426
+ return Stringable(re.sub(r'<[^>]*>', '', str(self)))
427
+
428
+ # Alias for backwards compatibility
429
+ def strip_tags(self) -> "Stringable":
430
+ """
431
+ Alias for stripTags method to maintain backwards compatibility.
432
+
433
+ Returns
434
+ -------
435
+ Stringable
436
+ New Stringable with HTML/PHP tags removed.
437
+ """
438
+
439
+ # Call the camelCase method for compatibility
440
+ return self.stripTags()
441
+
442
+ def toBase64(self) -> "Stringable":
443
+ """
444
+ Encode the string to Base64.
445
+
446
+ Returns
447
+ -------
448
+ Stringable
449
+ New Stringable with Base64 encoded content.
450
+ """
451
+
452
+ # Encode the string to Base64
453
+ return Stringable(base64.b64encode(str(self).encode()).decode())
454
+
455
+ def fromBase64(self, strict: bool = False) -> "Stringable":
456
+ """
457
+ Decode the string from Base64.
458
+
459
+ Parameters
460
+ ----------
461
+ strict : bool, optional
462
+ If True, raise exception on decode errors. Default is False.
463
+
464
+ Returns
465
+ -------
466
+ Stringable
467
+ New Stringable with Base64 decoded content, or empty string if decoding fails and strict is False.
468
+ """
469
+
470
+ # Try to decode the string from Base64
471
+ try:
472
+ return Stringable(base64.b64decode(str(self).encode()).decode())
473
+ except Exception:
474
+ if strict:
475
+ raise
476
+ # Return empty string if decoding fails and strict is False
477
+ return Stringable("")
478
+
479
+ def md5(self) -> str:
480
+ """
481
+ Generate MD5 hash of the string.
482
+
483
+ Returns
484
+ -------
485
+ str
486
+ MD5 hash of the string.
487
+ """
488
+
489
+ # Generate MD5 hash using hashlib
490
+ return hashlib.md5(str(self).encode()).hexdigest()
491
+
492
+ def sha1(self) -> str:
493
+ """
494
+ Generate SHA1 hash of the string.
495
+
496
+ Returns
497
+ -------
498
+ str
499
+ SHA1 hash of the string.
500
+ """
501
+
502
+ # Generate SHA1 hash using hashlib
503
+ return hashlib.sha1(str(self).encode()).hexdigest()
504
+
505
+ def sha256(self) -> str:
506
+ """
507
+ Generate SHA256 hash of the string.
508
+
509
+ Returns
510
+ -------
511
+ str
512
+ SHA256 hash of the string.
513
+ """
514
+
515
+ # Generate SHA256 hash using hashlib
516
+ return hashlib.sha256(str(self).encode()).hexdigest()
517
+
518
+ def length(self) -> int:
519
+ """
520
+ Get the length of the string.
521
+
522
+ Returns
523
+ -------
524
+ int
525
+ Number of characters in the string.
526
+ """
527
+
528
+ # Return the length of the string
529
+ return len(self)
530
+
531
+ def value(self) -> str:
532
+ """
533
+ Get the string value.
534
+
535
+ Returns
536
+ -------
537
+ str
538
+ String representation of the current instance.
539
+ """
540
+
541
+ # Return the string representation
542
+ return str(self)
543
+
544
+ def toString(self) -> str:
545
+ """
546
+ Convert the Stringable to a string.
547
+
548
+ Returns
549
+ -------
550
+ str
551
+ String representation of the current instance.
552
+ """
553
+
554
+ # Return the string representation
555
+ return str(self)
556
+
557
+ def toInteger(self, base: int = 10) -> int:
558
+ """
559
+ Convert the string to an integer.
560
+
561
+ Parameters
562
+ ----------
563
+ base : int, optional
564
+ Base for conversion. Default is 10.
565
+
566
+ Returns
567
+ -------
568
+ int
569
+ Integer representation of the string.
570
+ """
571
+
572
+ # Convert the string to integer using the specified base
573
+ return int(self, base)
574
+
575
+ def toFloat(self) -> float:
576
+ """
577
+ Convert the string to a float.
578
+
579
+ Returns
580
+ -------
581
+ float
582
+ Float representation of the string.
583
+ """
584
+
585
+ # Convert the string to float
586
+ return float(self)
587
+
588
+ def toBoolean(self) -> bool:
589
+ """
590
+ Convert the string to a boolean.
591
+
592
+ The string is considered True if it matches common truthy values like "1", "true", "on", or "yes" (case-insensitive).
593
+
594
+ Returns
595
+ -------
596
+ bool
597
+ Boolean representation of the string.
598
+ """
599
+
600
+ # Check for common truthy values
601
+ return str(self).strip().lower() in ("1", "true", "on", "yes")
602
+
603
+ # Aliases for backwards compatibility
604
+ def to_base64(self) -> "Stringable":
605
+ """
606
+ Alias for toBase64 method to maintain backwards compatibility.
607
+
608
+ Returns
609
+ -------
610
+ Stringable
611
+ New Stringable with base64 encoded content.
612
+ """
613
+
614
+ # Call the camelCase method for compatibility
615
+ return self.toBase64()
616
+
617
+ def from_base64(self, strict: bool = False) -> "Stringable":
618
+ """
619
+ Alias for fromBase64 method to maintain backwards compatibility.
620
+
621
+ Parameters
622
+ ----------
623
+ strict : bool, optional
624
+ If True, raise exception on decode errors. Default is False.
625
+
626
+ Returns
627
+ -------
628
+ Stringable
629
+ New Stringable with base64 decoded content.
630
+ """
631
+
632
+ # Call the camelCase method for compatibility
633
+ return self.fromBase64(strict)
634
+
635
+ def to_string(self) -> str:
636
+ """
637
+ Alias for toString method to maintain backwards compatibility.
638
+
639
+ Returns
640
+ -------
641
+ str
642
+ String representation.
643
+ """
644
+
645
+ # Call the camelCase method for compatibility
646
+ return self.toString()
647
+
648
+ def to_integer(self, base: int = 10) -> int:
649
+ """
650
+ Alias for toInteger method to maintain backwards compatibility.
651
+
652
+ Parameters
653
+ ----------
654
+ base : int, optional
655
+ Base for conversion. Default is 10.
656
+
657
+ Returns
658
+ -------
659
+ int
660
+ Integer representation.
661
+ """
662
+
663
+ # Call the camelCase method for compatibility
664
+ return self.toInteger(base)
665
+
666
+ def to_float(self) -> float:
667
+ """
668
+ Alias for toFloat method to maintain backwards compatibility.
669
+
670
+ Returns
671
+ -------
672
+ float
673
+ Float representation.
674
+ """
675
+
676
+ # Call the camelCase method for compatibility
677
+ return self.toFloat()
678
+
679
+ def to_boolean(self) -> bool:
680
+ """
681
+ Alias for toBoolean method to maintain backwards compatibility.
682
+
683
+ Returns
684
+ -------
685
+ bool
686
+ Boolean representation.
687
+ """
688
+
689
+ # Call the camelCase method for compatibility
690
+ return self.toBoolean()
691
+
692
+ def __getitem__(self, key):
693
+ """
694
+ Get item by index or slice.
695
+
696
+ Parameters
697
+ ----------
698
+ key : int or slice
699
+ Index or slice to retrieve.
700
+
701
+ Returns
702
+ -------
703
+ Stringable
704
+ New Stringable instance for the selected item(s).
705
+ """
706
+
707
+ # Return a Stringable for the selected item(s)
708
+ return Stringable(super().__getitem__(key))
709
+
710
+ def __str__(self):
711
+ """
712
+ Get the string representation.
713
+
714
+ Returns
715
+ -------
716
+ str
717
+ String representation of the object.
718
+ """
719
+
720
+ # Return the string representation
721
+ return super().__str__()
722
+
723
+ # camelCase wrappers for native Python string methods that use snake_case
724
+ def isAlnum(self) -> bool:
725
+ """
726
+ Check if all characters in the string are alphanumeric.
727
+
728
+ Returns
729
+ -------
730
+ bool
731
+ True if all characters are alphanumeric, False otherwise.
732
+ """
733
+
734
+ # Check if all characters are alphanumeric
735
+ return str(self).isalnum()
736
+
737
+ def isAlpha(self) -> bool:
738
+ """
739
+ Check if all characters in the string are alphabetic.
740
+
741
+ Returns
742
+ -------
743
+ bool
744
+ True if all characters are alphabetic, False otherwise.
745
+ """
746
+
747
+ # Check if all characters are alphabetic
748
+ return str(self).isalpha()
749
+
750
+ def isDecimal(self) -> bool:
751
+ """
752
+ Check if all characters in the string are decimal characters.
753
+
754
+ Returns
755
+ -------
756
+ bool
757
+ True if all characters are decimal, False otherwise.
758
+ """
759
+
760
+ # Check if all characters are decimal
761
+ return str(self).isdecimal()
762
+
763
+ def isDigit(self) -> bool:
764
+ """
765
+ Check if all characters in the string are digits.
766
+
767
+ Returns
768
+ -------
769
+ bool
770
+ True if all characters are digits, False otherwise.
771
+ """
772
+
773
+ # Check if all characters are digits
774
+ return str(self).isdigit()
775
+
776
+ def isIdentifier(self) -> bool:
777
+ """
778
+ Check if the string is a valid identifier according to Python language definition.
779
+
780
+ Returns
781
+ -------
782
+ bool
783
+ True if string is a valid identifier, False otherwise.
784
+ """
785
+
786
+ # Check if the string is a valid identifier
787
+ return str(self).isidentifier()
788
+
789
+ def isLower(self) -> bool:
790
+ """
791
+ Check if all cased characters in the string are lowercase.
792
+
793
+ Returns
794
+ -------
795
+ bool
796
+ True if all cased characters are lowercase, False otherwise.
797
+ """
798
+
799
+ # Check if all cased characters are lowercase
800
+ return str(self).islower()
801
+
802
+ def isNumeric(self) -> bool:
803
+ """
804
+ Check if all characters in the string are numeric characters.
805
+
806
+ Returns
807
+ -------
808
+ bool
809
+ True if all characters are numeric, False otherwise.
810
+ """
811
+
812
+ # Check if all characters are numeric
813
+ return str(self).isnumeric()
814
+
815
+ def isPrintable(self) -> bool:
816
+ """
817
+ Check if all characters in the string are printable.
818
+
819
+ Returns
820
+ -------
821
+ bool
822
+ True if all characters are printable, False otherwise.
823
+ """
824
+
825
+ # Check if all characters are printable
826
+ return str(self).isprintable()
827
+
828
+ def isSpace(self) -> bool:
829
+ """
830
+ Check if there are only whitespace characters in the string.
831
+
832
+ Returns
833
+ -------
834
+ bool
835
+ True if string contains only whitespace, False otherwise.
836
+ """
837
+
838
+ # Check if the string contains only whitespace
839
+ return str(self).isspace()
840
+
841
+ def isTitle(self) -> bool:
842
+ """
843
+ Check if the string is a titlecased string.
844
+
845
+ Returns
846
+ -------
847
+ bool
848
+ True if string is titlecased, False otherwise.
849
+ """
850
+
851
+ # Check if the string is titlecased
852
+ return str(self).istitle()
853
+
854
+ def isUpper(self) -> bool:
855
+ """
856
+ Check if all cased characters in the string are uppercase.
857
+
858
+ Returns
859
+ -------
860
+ bool
861
+ True if all cased characters are uppercase, False otherwise.
862
+ """
863
+
864
+ # Check if all cased characters are uppercase
865
+ return str(self).isupper()
866
+
867
+ def lStrip(self, chars: Optional[str] = None) -> "Stringable":
868
+ """
869
+ Return a copy of the string with leading characters removed.
870
+
871
+ Removes leading characters from the left side of the string. If no characters
872
+ are specified, whitespace characters are removed by default.
873
+
874
+ Parameters
875
+ ----------
876
+ chars : str, optional
877
+ Characters to remove from the beginning, by default None (whitespace).
878
+
879
+ Returns
880
+ -------
881
+ Stringable
882
+ A new Stringable instance with leading characters removed.
883
+ """
884
+
885
+ # Use Python's built-in lstrip to remove leading characters
886
+ return Stringable(str(self).lstrip(chars))
887
+
888
+ def rStrip(self, chars: Optional[str] = None) -> "Stringable":
889
+ """
890
+ Return a copy of the string with trailing characters removed.
891
+
892
+ Removes trailing characters from the right side of the string. If no characters
893
+ are specified, whitespace characters are removed by default.
894
+
895
+ Parameters
896
+ ----------
897
+ chars : str, optional
898
+ Characters to remove from the end, by default None (whitespace).
899
+
900
+ Returns
901
+ -------
902
+ Stringable
903
+ A new Stringable instance with trailing characters removed.
904
+ """
905
+
906
+ # Use Python's built-in rstrip to remove trailing characters
907
+ return Stringable(str(self).rstrip(chars))
908
+
909
+ def swapCase(self) -> "Stringable":
910
+ """
911
+ Return a copy of the string with uppercase characters converted to lowercase and vice versa.
912
+
913
+ Converts each uppercase character to lowercase and each lowercase character
914
+ to uppercase, leaving other characters unchanged.
915
+
916
+ Returns
917
+ -------
918
+ Stringable
919
+ A new Stringable instance with all character cases swapped.
920
+ """
921
+
922
+ # Use Python's built-in swapcase to invert the case of all characters
923
+ return Stringable(str(self).swapcase())
924
+
925
+ def zFill(self, width: int) -> "Stringable":
926
+ """
927
+ Pad a numeric string with zeros on the left.
928
+
929
+ Fills the string with leading zeros to reach the specified width. The sign
930
+ of the number (if any) is handled properly by being placed before the zeros.
931
+
932
+ Parameters
933
+ ----------
934
+ width : int
935
+ Total width of the resulting string.
936
+
937
+ Returns
938
+ -------
939
+ Stringable
940
+ A new Stringable instance padded with leading zeros to the specified width.
941
+ """
942
+
943
+ # Use Python's built-in zfill to pad with zeros while preserving sign
944
+ return Stringable(str(self).zfill(width))
945
+
946
+ # Text conversion methods
947
+ def ascii(self, language: str = 'en') -> "Stringable":
948
+ """
949
+ Transliterate a UTF-8 value to ASCII.
950
+
951
+ Parameters
952
+ ----------
953
+ language : str, optional
954
+ The language for transliteration, by default 'en'
955
+
956
+ Returns
957
+ -------
958
+ Stringable
959
+ A new Stringable with ASCII characters
960
+ """
961
+ # Use unicodedata to normalize and transliterate
962
+ normalized = unicodedata.normalize('NFKD', self)
963
+ ascii_str = ''.join(c for c in normalized if ord(c) < 128)
964
+ return Stringable(ascii_str)
965
+
966
+ def camel(self) -> "Stringable":
967
+ """
968
+ Convert a value to camel case.
969
+
970
+ Returns
971
+ -------
972
+ Stringable
973
+ A new Stringable in camelCase
974
+ """
975
+ # Split by common separators and normalize
976
+ words = re.sub(r'[_\-\s]+', ' ', str(self)).split()
977
+ if not words:
978
+ return Stringable("")
979
+
980
+ # First word lowercase, rest title case
981
+ camel_str = words[0].lower() + ''.join(word.capitalize() for word in words[1:])
982
+ return Stringable(camel_str)
983
+
984
+ def kebab(self) -> "Stringable":
985
+ """
986
+ Convert a string to kebab case.
987
+
988
+ Returns
989
+ -------
990
+ Stringable
991
+ A new Stringable in kebab-case
992
+ """
993
+ # Handle camelCase and PascalCase
994
+ s = re.sub(r'([a-z0-9])([A-Z])', r'\1-\2', str(self))
995
+ # Replace spaces, underscores, and multiple dashes with single dash
996
+ s = re.sub(r'[_\s]+', '-', s)
997
+ s = re.sub(r'-+', '-', s)
998
+ return Stringable(s.lower().strip('-'))
999
+
1000
+ def snake(self, delimiter: str = '_') -> "Stringable":
1001
+ """
1002
+ Convert a string to snake case.
1003
+
1004
+ Parameters
1005
+ ----------
1006
+ delimiter : str, optional
1007
+ The delimiter to use, by default '_'
1008
+
1009
+ Returns
1010
+ -------
1011
+ Stringable
1012
+ A new Stringable in snake_case
1013
+ """
1014
+ # Handle camelCase and PascalCase
1015
+ s = re.sub(r'([a-z0-9])([A-Z])', rf'\1{delimiter}\2', str(self))
1016
+ # Replace spaces and dashes with delimiter
1017
+ s = re.sub(r'[\s\-]+', delimiter, s)
1018
+ # Replace multiple delimiters with single
1019
+ s = re.sub(rf'{re.escape(delimiter)}+', delimiter, s)
1020
+ return Stringable(s.lower().strip(delimiter))
1021
+
1022
+ def studly(self) -> "Stringable":
1023
+ """
1024
+ Convert a value to studly caps case (PascalCase).
1025
+
1026
+ Returns
1027
+ -------
1028
+ Stringable
1029
+ A new Stringable in StudlyCase/PascalCase
1030
+ """
1031
+ words = re.sub(r'[_\-\s]+', ' ', str(self)).split()
1032
+ studly_str = ''.join(word.capitalize() for word in words)
1033
+ return Stringable(studly_str)
1034
+
1035
+ def pascal(self) -> "Stringable":
1036
+ """
1037
+ Convert the string to Pascal case.
1038
+
1039
+ Returns
1040
+ -------
1041
+ Stringable
1042
+ A new Stringable in PascalCase
1043
+ """
1044
+ return self.studly()
1045
+
1046
+ def slug(self, separator: str = '-', language: str = 'en', dictionary: Optional[Dict[str, str]] = None) -> "Stringable":
1047
+ """
1048
+ Generate a URL friendly "slug" from a given string.
1049
+
1050
+ Parameters
1051
+ ----------
1052
+ separator : str, optional
1053
+ The separator to use, by default '-'
1054
+ language : str, optional
1055
+ The language for transliteration, by default 'en'
1056
+ dictionary : dict, optional
1057
+ Dictionary for character replacements, by default {'@': 'at'}
1058
+
1059
+ Returns
1060
+ -------
1061
+ Stringable
1062
+ A new Stringable as a URL-friendly slug
1063
+ """
1064
+ if dictionary is None:
1065
+ dictionary = {'@': 'at'}
1066
+
1067
+ s = str(self)
1068
+
1069
+ # Apply dictionary replacements
1070
+ for key, value in dictionary.items():
1071
+ s = s.replace(key, value)
1072
+
1073
+ # Convert to ASCII
1074
+ s = self.__class__(s).ascii().value()
1075
+
1076
+ # Remove all non-alphanumeric characters except spaces and separators
1077
+ s = re.sub(r'[^\w\s-]', '', s)
1078
+
1079
+ # Replace spaces and underscores with separator
1080
+ s = re.sub(r'[\s_]+', separator, s)
1081
+
1082
+ # Replace multiple separators with single
1083
+ s = re.sub(rf'{re.escape(separator)}+', separator, s)
1084
+
1085
+ return Stringable(s.lower().strip(separator))
1086
+
1087
+ def title(self) -> "Stringable":
1088
+ """
1089
+ Convert the given string to proper case.
1090
+
1091
+ Returns
1092
+ -------
1093
+ Stringable
1094
+ A new Stringable in Title Case
1095
+ """
1096
+ return Stringable(str(self).title())
1097
+
1098
+ def headline(self) -> "Stringable":
1099
+ """
1100
+ Convert the given string to proper case for each word.
1101
+
1102
+ Returns
1103
+ -------
1104
+ Stringable
1105
+ A new Stringable as a headline
1106
+ """
1107
+ # Split by common word boundaries
1108
+ words = re.findall(r'\b\w+\b', str(self))
1109
+ headline_str = ' '.join(word.capitalize() for word in words)
1110
+ return Stringable(headline_str)
1111
+
1112
+ def apa(self) -> "Stringable":
1113
+ """
1114
+ Convert the given string to APA-style title case.
1115
+
1116
+ Returns
1117
+ -------
1118
+ Stringable
1119
+ A new Stringable in APA title case
1120
+ """
1121
+ # Words that should not be capitalized in APA style (except at beginning)
1122
+ lowercase_words = {
1123
+ 'a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'if', 'in',
1124
+ 'nor', 'of', 'on', 'or', 'so', 'the', 'to', 'up', 'yet'
1125
+ }
1126
+
1127
+ words = str(self).split()
1128
+ apa_words = []
1129
+
1130
+ for i, word in enumerate(words):
1131
+ # Always capitalize first and last word
1132
+ if i == 0 or i == len(words) - 1:
1133
+ apa_words.append(word.capitalize())
1134
+ # Capitalize words with 4+ letters or not in lowercase set
1135
+ elif len(word) >= 4 or word.lower() not in lowercase_words:
1136
+ apa_words.append(word.capitalize())
1137
+ else:
1138
+ apa_words.append(word.lower())
1139
+
1140
+ return Stringable(' '.join(apa_words))
1141
+
1142
+ def ucfirst(self) -> "Stringable":
1143
+ """
1144
+ Make a string's first character uppercase.
1145
+
1146
+ Returns
1147
+ -------
1148
+ Stringable
1149
+ A new Stringable with first character uppercase
1150
+ """
1151
+ if not self:
1152
+ return Stringable(self)
1153
+ return Stringable(self[0].upper() + self[1:])
1154
+
1155
+ def lcfirst(self) -> "Stringable":
1156
+ """
1157
+ Make a string's first character lowercase.
1158
+
1159
+ Returns
1160
+ -------
1161
+ Stringable
1162
+ A new Stringable with first character lowercase
1163
+ """
1164
+ if not self:
1165
+ return Stringable(self)
1166
+ return Stringable(self[0].lower() + self[1:])
1167
+
1168
+ # Validation methods
1169
+ def isAscii(self) -> bool:
1170
+ """
1171
+ Determine if a given string is 7 bit ASCII.
1172
+
1173
+ Returns
1174
+ -------
1175
+ bool
1176
+ True if string is ASCII, False otherwise
1177
+ """
1178
+ try:
1179
+ self.encode('ascii')
1180
+ return True
1181
+ except UnicodeEncodeError:
1182
+ return False
1183
+
1184
+ def isJson(self) -> bool:
1185
+ """
1186
+ Determine if a given string is valid JSON.
1187
+
1188
+ Returns
1189
+ -------
1190
+ bool
1191
+ True if string is valid JSON, False otherwise
1192
+ """
1193
+ try:
1194
+ json.loads(str(self))
1195
+ return True
1196
+ except (json.JSONDecodeError, TypeError):
1197
+ return False
1198
+
1199
+ def isUrl(self, protocols: Optional[List[str]] = None) -> bool:
1200
+ """
1201
+ Determine if a given value is a valid URL.
1202
+
1203
+ Parameters
1204
+ ----------
1205
+ protocols : list, optional
1206
+ List of valid protocols, by default ['http', 'https']
1207
+
1208
+ Returns
1209
+ -------
1210
+ bool
1211
+ True if string is a valid URL, False otherwise
1212
+ """
1213
+ if protocols is None:
1214
+ protocols = ['http', 'https']
1215
+
1216
+ try:
1217
+ result = urllib.parse.urlparse(str(self))
1218
+ return (
1219
+ all([result.scheme, result.netloc]) and
1220
+ result.scheme in protocols
1221
+ )
1222
+ except Exception:
1223
+ return False
1224
+
1225
+ def isUuid(self, version: Optional[Union[int, str]] = None) -> bool:
1226
+ """
1227
+ Determine if a given string is a valid UUID.
1228
+
1229
+ Parameters
1230
+ ----------
1231
+ version : int or str, optional
1232
+ UUID version to validate (1-8), by default None (any version)
1233
+
1234
+ Returns
1235
+ -------
1236
+ bool
1237
+ True if string is a valid UUID, False otherwise
1238
+ """
1239
+ try:
1240
+ uuid_obj = uuid.UUID(str(self))
1241
+ if version is not None:
1242
+ if version == 'max':
1243
+ return uuid_obj.version <= 8
1244
+ else:
1245
+ return uuid_obj.version == int(version)
1246
+ return True
1247
+ except (ValueError, TypeError):
1248
+ return False
1249
+
1250
+ def isUlid(self) -> bool:
1251
+ """
1252
+ Determine if a given string is a valid ULID.
1253
+
1254
+ Returns
1255
+ -------
1256
+ bool
1257
+ True if string is a valid ULID, False otherwise
1258
+ """
1259
+ # ULID is 26 characters, base32 encoded
1260
+ ulid_pattern = r'^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$'
1261
+ return bool(re.match(ulid_pattern, str(self).upper()))
1262
+
1263
+ # String manipulation methods
1264
+ def chopStart(self, needle: Union[str, List[str]]) -> "Stringable":
1265
+ """
1266
+ Remove the given string if it exists at the start of the current string.
1267
+
1268
+ Parameters
1269
+ ----------
1270
+ needle : str or list
1271
+ The string(s) to remove from the start
1272
+
1273
+ Returns
1274
+ -------
1275
+ Stringable
1276
+ A new Stringable with the needle removed from start
1277
+ """
1278
+ s = str(self)
1279
+ if isinstance(needle, str):
1280
+ needle = [needle]
1281
+
1282
+ for n in needle:
1283
+ if s.startswith(n):
1284
+ s = s[len(n):]
1285
+ break
1286
+
1287
+ return Stringable(s)
1288
+
1289
+ def chopEnd(self, needle: Union[str, List[str]]) -> "Stringable":
1290
+ """
1291
+ Remove the given string if it exists at the end of the current string.
1292
+
1293
+ Parameters
1294
+ ----------
1295
+ needle : str or list
1296
+ The string(s) to remove from the end
1297
+
1298
+ Returns
1299
+ -------
1300
+ Stringable
1301
+ A new Stringable with the needle removed from end
1302
+ """
1303
+ s = str(self)
1304
+ if isinstance(needle, str):
1305
+ needle = [needle]
1306
+
1307
+ for n in needle:
1308
+ if s.endswith(n):
1309
+ s = s[:-len(n)]
1310
+ break
1311
+
1312
+ return Stringable(s)
1313
+
1314
+ def deduplicate(self, character: str = ' ') -> "Stringable":
1315
+ """
1316
+ Replace consecutive instances of a given character with a single character.
1317
+
1318
+ Parameters
1319
+ ----------
1320
+ character : str, optional
1321
+ The character to deduplicate, by default ' '
1322
+
1323
+ Returns
1324
+ -------
1325
+ Stringable
1326
+ A new Stringable with deduplicated characters
1327
+ """
1328
+ pattern = re.escape(character) + '+'
1329
+ return Stringable(re.sub(pattern, character, str(self)))
1330
+
1331
+ def mask(self, character: str, index: int, length: Optional[int] = None, encoding: str = 'UTF-8') -> "Stringable":
1332
+ """
1333
+ Masks a portion of a string with a repeated character.
1334
+
1335
+ Parameters
1336
+ ----------
1337
+ character : str
1338
+ The character to use for masking
1339
+ index : int
1340
+ Starting index for masking
1341
+ length : int, optional
1342
+ Length to mask, by default None (to end of string)
1343
+ encoding : str, optional
1344
+ String encoding, by default 'UTF-8'
1345
+
1346
+ Returns
1347
+ -------
1348
+ Stringable
1349
+ A new Stringable with masked portion
1350
+ """
1351
+ s = str(self)
1352
+
1353
+ if index < 0:
1354
+ index = max(0, len(s) + index)
1355
+
1356
+ if length is None:
1357
+ length = len(s) - index
1358
+ elif length < 0:
1359
+ length = max(0, len(s) + length - index)
1360
+
1361
+ end_index = min(len(s), index + length)
1362
+ mask_str = character * (end_index - index)
1363
+
1364
+ return Stringable(s[:index] + mask_str + s[end_index:])
1365
+
1366
+ def limit(self, limit: int = 100, end: str = '...', preserve_words: bool = False) -> "Stringable":
1367
+ """
1368
+ Limit the number of characters in a string.
1369
+
1370
+ Parameters
1371
+ ----------
1372
+ limit : int, optional
1373
+ Maximum number of characters, by default 100
1374
+ end : str, optional
1375
+ String to append if truncated, by default '...'
1376
+ preserve_words : bool, optional
1377
+ Whether to preserve word boundaries, by default False
1378
+
1379
+ Returns
1380
+ -------
1381
+ Stringable
1382
+ A new Stringable with limited length
1383
+ """
1384
+ s = str(self)
1385
+
1386
+ if len(s) <= limit:
1387
+ return Stringable(s)
1388
+
1389
+ if preserve_words:
1390
+ # Find the last space before the limit
1391
+ truncated = s[:limit]
1392
+ last_space = truncated.rfind(' ')
1393
+ if last_space > 0:
1394
+ truncated = truncated[:last_space]
1395
+ else:
1396
+ truncated = s[:limit]
1397
+
1398
+ return Stringable(truncated + end)
1399
+
1400
+ def padBoth(self, length: int, pad: str = ' ') -> "Stringable":
1401
+ """
1402
+ Pad both sides of the string with another.
1403
+
1404
+ Parameters
1405
+ ----------
1406
+ length : int
1407
+ Total desired length
1408
+ pad : str, optional
1409
+ Padding character(s), by default ' '
1410
+
1411
+ Returns
1412
+ -------
1413
+ Stringable
1414
+ A new Stringable with padding on both sides
1415
+ """
1416
+ s = str(self)
1417
+ if len(s) >= length:
1418
+ return Stringable(s)
1419
+
1420
+ total_padding = length - len(s)
1421
+ left_padding = total_padding // 2
1422
+ right_padding = total_padding - left_padding
1423
+
1424
+ left_pad = (pad * ((left_padding // len(pad)) + 1))[:left_padding]
1425
+ right_pad = (pad * ((right_padding // len(pad)) + 1))[:right_padding]
1426
+
1427
+ return Stringable(left_pad + s + right_pad)
1428
+
1429
+ def padLeft(self, length: int, pad: str = ' ') -> "Stringable":
1430
+ """
1431
+ Pad the left side of the string with another.
1432
+
1433
+ Parameters
1434
+ ----------
1435
+ length : int
1436
+ Total desired length
1437
+ pad : str, optional
1438
+ Padding character(s), by default ' '
1439
+
1440
+ Returns
1441
+ -------
1442
+ Stringable
1443
+ A new Stringable with left padding
1444
+ """
1445
+ s = str(self)
1446
+ if len(s) >= length:
1447
+ return Stringable(s)
1448
+
1449
+ padding_needed = length - len(s)
1450
+ left_pad = (pad * ((padding_needed // len(pad)) + 1))[:padding_needed]
1451
+
1452
+ return Stringable(left_pad + s)
1453
+
1454
+ def padRight(self, length: int, pad: str = ' ') -> "Stringable":
1455
+ """
1456
+ Pad the right side of the string with another.
1457
+
1458
+ Parameters
1459
+ ----------
1460
+ length : int
1461
+ Total desired length
1462
+ pad : str, optional
1463
+ Padding character(s), by default ' '
1464
+
1465
+ Returns
1466
+ -------
1467
+ Stringable
1468
+ A new Stringable with right padding
1469
+ """
1470
+ s = str(self)
1471
+ if len(s) >= length:
1472
+ return Stringable(s)
1473
+
1474
+ padding_needed = length - len(s)
1475
+ right_pad = (pad * ((padding_needed // len(pad)) + 1))[:padding_needed]
1476
+
1477
+ return Stringable(s + right_pad)
1478
+
1479
+ def trim(self, characters: Optional[str] = None) -> "Stringable":
1480
+ """
1481
+ Trim the string of the given characters.
1482
+
1483
+ Parameters
1484
+ ----------
1485
+ characters : str, optional
1486
+ Characters to trim, by default None (whitespace)
1487
+
1488
+ Returns
1489
+ -------
1490
+ Stringable
1491
+ A new trimmed Stringable
1492
+ """
1493
+ return Stringable(str(self).strip(characters))
1494
+
1495
+ def ltrim(self, characters: Optional[str] = None) -> "Stringable":
1496
+ """
1497
+ Left trim the string of the given characters.
1498
+
1499
+ Parameters
1500
+ ----------
1501
+ characters : str, optional
1502
+ Characters to trim, by default None (whitespace)
1503
+
1504
+ Returns
1505
+ -------
1506
+ Stringable
1507
+ A new left-trimmed Stringable
1508
+ """
1509
+ return Stringable(str(self).lstrip(characters))
1510
+
1511
+ def rtrim(self, characters: Optional[str] = None) -> "Stringable":
1512
+ """
1513
+ Right trim the string of the given characters.
1514
+
1515
+ Parameters
1516
+ ----------
1517
+ characters : str, optional
1518
+ Characters to trim, by default None (whitespace)
1519
+
1520
+ Returns
1521
+ -------
1522
+ Stringable
1523
+ A new right-trimmed Stringable
1524
+ """
1525
+ return Stringable(str(self).rstrip(characters))
1526
+
1527
+ # Search and positioning methods
1528
+ def charAt(self, index: int) -> Union[str, bool]:
1529
+ """
1530
+ Get the character at the specified index.
1531
+
1532
+ Parameters
1533
+ ----------
1534
+ index : int
1535
+ The index of the character to get
1536
+
1537
+ Returns
1538
+ -------
1539
+ str or False
1540
+ The character at the index, or False if index is out of bounds
1541
+ """
1542
+ try:
1543
+ return str(self)[index]
1544
+ except IndexError:
1545
+ return False
1546
+
1547
+ def position(self, needle: str, offset: int = 0, encoding: Optional[str] = None) -> Union[int, bool]:
1548
+ """
1549
+ Find the multi-byte safe position of the first occurrence of the given substring.
1550
+
1551
+ Parameters
1552
+ ----------
1553
+ needle : str
1554
+ The substring to search for
1555
+ offset : int, optional
1556
+ Starting offset for search, by default 0
1557
+ encoding : str, optional
1558
+ String encoding (for compatibility), by default None
1559
+
1560
+ Returns
1561
+ -------
1562
+ int or False
1563
+ Position of the substring, or False if not found
1564
+ """
1565
+ try:
1566
+ pos = str(self).find(needle, offset)
1567
+ return pos if pos != -1 else False
1568
+ except Exception:
1569
+ return False
1570
+
1571
+ def match(self, pattern: str) -> "Stringable":
1572
+ """
1573
+ Get the string matching the given pattern.
1574
+
1575
+ Parameters
1576
+ ----------
1577
+ pattern : str
1578
+ Regular expression pattern
1579
+
1580
+ Returns
1581
+ -------
1582
+ Stringable
1583
+ A new Stringable with the first match, or empty if no match
1584
+ """
1585
+ match = re.search(pattern, str(self))
1586
+ return Stringable(match.group(0) if match else "")
1587
+
1588
+ def matchAll(self, pattern: str) -> List[str]:
1589
+ """
1590
+ Get all strings matching the given pattern.
1591
+
1592
+ Parameters
1593
+ ----------
1594
+ pattern : str
1595
+ Regular expression pattern
1596
+
1597
+ Returns
1598
+ -------
1599
+ list
1600
+ List of all matches
1601
+ """
1602
+ return re.findall(pattern, str(self))
1603
+
1604
+ def isMatch(self, pattern: Union[str, List[str]]) -> bool:
1605
+ """
1606
+ Determine if a given string matches a given pattern.
1607
+
1608
+ Parameters
1609
+ ----------
1610
+ pattern : str or list
1611
+ Regular expression pattern(s)
1612
+
1613
+ Returns
1614
+ -------
1615
+ bool
1616
+ True if string matches pattern, False otherwise
1617
+ """
1618
+ if isinstance(pattern, str):
1619
+ pattern = [pattern]
1620
+
1621
+ s = str(self)
1622
+ return any(re.search(p, s) is not None for p in pattern)
1623
+
1624
+ def test(self, pattern: str) -> bool:
1625
+ """
1626
+ Determine if the string matches the given pattern.
1627
+
1628
+ Parameters
1629
+ ----------
1630
+ pattern : str
1631
+ Regular expression pattern
1632
+
1633
+ Returns
1634
+ -------
1635
+ bool
1636
+ True if string matches pattern, False otherwise
1637
+ """
1638
+ return self.isMatch(pattern)
1639
+
1640
+ def numbers(self) -> "Stringable":
1641
+ """
1642
+ Remove all non-numeric characters from a string.
1643
+
1644
+ Returns
1645
+ -------
1646
+ Stringable
1647
+ A new Stringable with only numeric characters
1648
+ """
1649
+ return Stringable(re.sub(r'\D', '', str(self)))
1650
+
1651
+ def excerpt(self, phrase: str = '', options: Optional[Dict] = None) -> Optional[str]:
1652
+ """
1653
+ Extracts an excerpt from text that matches the first instance of a phrase.
1654
+
1655
+ Parameters
1656
+ ----------
1657
+ phrase : str, optional
1658
+ The phrase to search for, by default ''
1659
+ options : dict, optional
1660
+ Options for excerpt extraction, by default None
1661
+
1662
+ Returns
1663
+ -------
1664
+ str or None
1665
+ The excerpt, or None if phrase not found
1666
+ """
1667
+ if options is None:
1668
+ options = {}
1669
+
1670
+ radius = options.get('radius', 100)
1671
+ omission = options.get('omission', '...')
1672
+
1673
+ s = str(self)
1674
+ if not phrase:
1675
+ return s[:radius * 2] + (omission if len(s) > radius * 2 else '')
1676
+
1677
+ pos = s.lower().find(phrase.lower())
1678
+ if pos == -1:
1679
+ return None
1680
+
1681
+ start = max(0, pos - radius)
1682
+ end = min(len(s), pos + len(phrase) + radius)
1683
+
1684
+ excerpt = s[start:end]
1685
+
1686
+ if start > 0:
1687
+ excerpt = omission + excerpt
1688
+ if end < len(s):
1689
+ excerpt = excerpt + omission
1690
+
1691
+ return excerpt
1692
+
1693
+ # Text and file methods
1694
+ def basename(self, suffix: str = '') -> "Stringable":
1695
+ """
1696
+ Get the trailing name component of the path.
1697
+
1698
+ Parameters
1699
+ ----------
1700
+ suffix : str, optional
1701
+ Suffix to remove, by default ''
1702
+
1703
+ Returns
1704
+ -------
1705
+ Stringable
1706
+ A new Stringable with the basename
1707
+ """
1708
+ return Stringable(os.path.basename(str(self)).removesuffix(suffix))
1709
+
1710
+ def dirname(self, levels: int = 1) -> "Stringable":
1711
+ """
1712
+ Get the parent directory's path.
1713
+
1714
+ Parameters
1715
+ ----------
1716
+ levels : int, optional
1717
+ Number of levels up, by default 1
1718
+
1719
+ Returns
1720
+ -------
1721
+ Stringable
1722
+ A new Stringable with the directory name
1723
+ """
1724
+ path = str(self)
1725
+ for _ in range(levels):
1726
+ path = os.path.dirname(path)
1727
+ return Stringable(path)
1728
+
1729
+ def classBasename(self) -> "Stringable":
1730
+ """
1731
+ Get the basename of the class path.
1732
+
1733
+ Returns
1734
+ -------
1735
+ Stringable
1736
+ A new Stringable with the class basename
1737
+ """
1738
+ # Extract the last part after the last dot (class name)
1739
+ parts = str(self).split('.')
1740
+ return Stringable(parts[-1] if parts else str(self))
1741
+
1742
+ def between(self, from_str: str, to_str: str) -> "Stringable":
1743
+ """
1744
+ Get the portion of a string between two given values.
1745
+
1746
+ Parameters
1747
+ ----------
1748
+ from_str : str
1749
+ Starting delimiter
1750
+ to_str : str
1751
+ Ending delimiter
1752
+
1753
+ Returns
1754
+ -------
1755
+ Stringable
1756
+ A new Stringable with the text between delimiters
1757
+ """
1758
+ s = str(self)
1759
+ start = s.find(from_str)
1760
+ if start == -1:
1761
+ return Stringable("")
1762
+
1763
+ start += len(from_str)
1764
+ end = s.find(to_str, start)
1765
+ if end == -1:
1766
+ return Stringable("")
1767
+
1768
+ return Stringable(s[start:end])
1769
+
1770
+ def betweenFirst(self, from_str: str, to_str: str) -> "Stringable":
1771
+ """
1772
+ Get the smallest possible portion of a string between two given values.
1773
+
1774
+ Parameters
1775
+ ----------
1776
+ from_str : str
1777
+ Starting delimiter
1778
+ to_str : str
1779
+ Ending delimiter
1780
+
1781
+ Returns
1782
+ -------
1783
+ Stringable
1784
+ A new Stringable with the text between first delimiters
1785
+ """
1786
+ s = str(self)
1787
+ start = s.find(from_str)
1788
+ if start == -1:
1789
+ return Stringable("")
1790
+
1791
+ start += len(from_str)
1792
+ end = s.find(to_str, start)
1793
+ if end == -1:
1794
+ return Stringable("")
1795
+
1796
+ return Stringable(s[start:end])
1797
+
1798
+ def finish(self, cap: str) -> "Stringable":
1799
+ """
1800
+ Cap a string with a single instance of a given value.
1801
+
1802
+ Parameters
1803
+ ----------
1804
+ cap : str
1805
+ The string to cap with
1806
+
1807
+ Returns
1808
+ -------
1809
+ Stringable
1810
+ A new Stringable that ends with the cap
1811
+ """
1812
+ s = str(self)
1813
+ if not s.endswith(cap):
1814
+ s += cap
1815
+ return Stringable(s)
1816
+
1817
+ def start(self, prefix: str) -> "Stringable":
1818
+ """
1819
+ Begin a string with a single instance of a given value.
1820
+
1821
+ Parameters
1822
+ ----------
1823
+ prefix : str
1824
+ The string to start with
1825
+
1826
+ Returns
1827
+ -------
1828
+ Stringable
1829
+ A new Stringable that starts with the prefix
1830
+ """
1831
+ s = str(self)
1832
+ if not s.startswith(prefix):
1833
+ s = prefix + s
1834
+ return Stringable(s)
1835
+
1836
+ # Array and split methods
1837
+ def explode(self, delimiter: str, limit: int = -1) -> List[str]:
1838
+ """
1839
+ Explode the string into a list using a delimiter.
1840
+
1841
+ Splits the string by the specified delimiter and returns a list of substrings.
1842
+ If a limit is specified, the string will be split into at most that many parts.
1843
+
1844
+ Parameters
1845
+ ----------
1846
+ delimiter : str
1847
+ The delimiter to split on.
1848
+ limit : int, optional
1849
+ Maximum number of elements to return, by default -1 (no limit).
1850
+
1851
+ Returns
1852
+ -------
1853
+ list
1854
+ List of string parts after splitting by the delimiter.
1855
+ """
1856
+
1857
+ # Check if limit is specified to control the number of splits
1858
+ if limit == -1:
1859
+ # Split without limit - all occurrences of delimiter are used
1860
+ return str(self).split(delimiter)
1861
+ else:
1862
+ # Split with limit - maximum of (limit-1) splits are performed
1863
+ return str(self).split(delimiter, limit - 1)
1864
+
1865
+ def split(self, pattern: Union[str, int], limit: int = -1, flags: int = 0) -> List[str]:
1866
+ """
1867
+ Split a string using a regular expression or by length.
1868
+
1869
+ Parameters
1870
+ ----------
1871
+ pattern : str or int
1872
+ Regular expression pattern or length for splitting
1873
+ limit : int, optional
1874
+ Maximum splits, by default -1
1875
+ flags : int, optional
1876
+ Regex flags, by default 0
1877
+
1878
+ Returns
1879
+ -------
1880
+ list
1881
+ List of string segments
1882
+ """
1883
+ if isinstance(pattern, int):
1884
+ # Split by length
1885
+ s = str(self)
1886
+ return [s[i:i+pattern] for i in range(0, len(s), pattern)]
1887
+ else:
1888
+ # Split by regex
1889
+ segments = re.split(pattern, str(self), maxsplit=limit, flags=flags)
1890
+ return segments if segments else []
1891
+
1892
+ def ucsplit(self) -> List[str]:
1893
+ """
1894
+ Split a string by uppercase characters.
1895
+
1896
+ Returns
1897
+ -------
1898
+ list
1899
+ List of words split by uppercase characters
1900
+ """
1901
+ # Split on uppercase letters, keeping the uppercase letter with the following text
1902
+ parts = re.findall(r'[A-Z][a-z]*|[a-z]+|\d+', str(self))
1903
+ return parts if parts else [str(self)]
1904
+
1905
+ def squish(self) -> "Stringable":
1906
+ """
1907
+ Remove all "extra" blank space from the given string.
1908
+
1909
+ Returns
1910
+ -------
1911
+ Stringable
1912
+ A new Stringable with normalized whitespace
1913
+ """
1914
+ # Replace multiple whitespace characters with single spaces and trim
1915
+ return Stringable(re.sub(r'\s+', ' ', str(self)).strip())
1916
+
1917
+ def words(self, words: int = 100, end: str = '...') -> "Stringable":
1918
+ """
1919
+ Limit the number of words in a string.
1920
+
1921
+ Parameters
1922
+ ----------
1923
+ words : int, optional
1924
+ Maximum number of words, by default 100
1925
+ end : str, optional
1926
+ String to append if truncated, by default '...'
1927
+
1928
+ Returns
1929
+ -------
1930
+ Stringable
1931
+ A new Stringable with limited words
1932
+ """
1933
+ word_list = str(self).split()
1934
+ if len(word_list) <= words:
1935
+ return Stringable(str(self))
1936
+
1937
+ truncated = ' '.join(word_list[:words])
1938
+ return Stringable(truncated + end)
1939
+
1940
+ def wordCount(self, characters: Optional[str] = None) -> int:
1941
+ """
1942
+ Get the number of words a string contains.
1943
+
1944
+ Parameters
1945
+ ----------
1946
+ characters : str, optional
1947
+ Additional characters to consider as word separators, by default None
1948
+
1949
+ Returns
1950
+ -------
1951
+ int
1952
+ Number of words in the string
1953
+ """
1954
+ s = str(self).strip()
1955
+ if not s:
1956
+ return 0
1957
+
1958
+ if characters:
1959
+ # Replace additional characters with spaces
1960
+ for char in characters:
1961
+ s = s.replace(char, ' ')
1962
+
1963
+ # Split by whitespace and count non-empty parts
1964
+ return len([word for word in s.split() if word])
1965
+
1966
+ def wordWrap(self, characters: int = 75, break_str: str = "\n", cut_long_words: bool = False) -> "Stringable":
1967
+ """
1968
+ Wrap a string to a given number of characters.
1969
+
1970
+ Parameters
1971
+ ----------
1972
+ characters : int, optional
1973
+ Line width, by default 75
1974
+ break_str : str, optional
1975
+ Line break string, by default "\\n"
1976
+ cut_long_words : bool, optional
1977
+ Whether to cut long words, by default False
1978
+
1979
+ Returns
1980
+ -------
1981
+ Stringable
1982
+ A new Stringable with wrapped text
1983
+ """
1984
+ import textwrap
1985
+
1986
+ if cut_long_words:
1987
+ wrapped = textwrap.fill(str(self), width=characters, break_long_words=True,
1988
+ break_on_hyphens=True, expand_tabs=False)
1989
+ else:
1990
+ wrapped = textwrap.fill(str(self), width=characters, break_long_words=False,
1991
+ break_on_hyphens=True, expand_tabs=False)
1992
+
1993
+ return Stringable(wrapped.replace('\n', break_str))
1994
+
1995
+ def wrap(self, before: str, after: Optional[str] = None) -> "Stringable":
1996
+ """
1997
+ Wrap the string with the given strings.
1998
+
1999
+ Parameters
2000
+ ----------
2001
+ before : str
2002
+ String to prepend
2003
+ after : str, optional
2004
+ String to append, by default None (uses before)
2005
+
2006
+ Returns
2007
+ -------
2008
+ Stringable
2009
+ A new Stringable wrapped with the given strings
2010
+ """
2011
+ if after is None:
2012
+ after = before
2013
+ return Stringable(before + str(self) + after)
2014
+
2015
+ def unwrap(self, before: str, after: Optional[str] = None) -> "Stringable":
2016
+ """
2017
+ Unwrap the string with the given strings.
2018
+
2019
+ Parameters
2020
+ ----------
2021
+ before : str
2022
+ String to remove from start
2023
+ after : str, optional
2024
+ String to remove from end, by default None (uses before)
2025
+
2026
+ Returns
2027
+ -------
2028
+ Stringable
2029
+ A new Stringable with wrapping removed
2030
+ """
2031
+ if after is None:
2032
+ after = before
2033
+
2034
+ s = str(self)
2035
+ if s.startswith(before):
2036
+ s = s[len(before):]
2037
+ if s.endswith(after):
2038
+ s = s[:-len(after)]
2039
+
2040
+ return Stringable(s)
2041
+
2042
+ # Advanced replacement methods
2043
+ def replaceArray(self, search: str, replace: List[str]) -> "Stringable":
2044
+ """
2045
+ Replace a given value in the string sequentially with an array.
2046
+
2047
+ Parameters
2048
+ ----------
2049
+ search : str
2050
+ The string to search for
2051
+ replace : list
2052
+ List of replacement strings
2053
+
2054
+ Returns
2055
+ -------
2056
+ Stringable
2057
+ A new Stringable with sequential replacements
2058
+ """
2059
+ s = str(self)
2060
+ replace_idx = 0
2061
+
2062
+ while search in s and replace_idx < len(replace):
2063
+ s = s.replace(search, str(replace[replace_idx]), 1)
2064
+ replace_idx += 1
2065
+
2066
+ return Stringable(s)
2067
+
2068
+ def replaceFirst(self, search: str, replace: str) -> "Stringable":
2069
+ """
2070
+ Replace the first occurrence of a given value in the string.
2071
+
2072
+ Parameters
2073
+ ----------
2074
+ search : str
2075
+ The string to search for
2076
+ replace : str
2077
+ The replacement string
2078
+
2079
+ Returns
2080
+ -------
2081
+ Stringable
2082
+ A new Stringable with first occurrence replaced
2083
+ """
2084
+ return Stringable(str(self).replace(search, replace, 1))
2085
+
2086
+ def replaceLast(self, search: str, replace: str) -> "Stringable":
2087
+ """
2088
+ Replace the last occurrence of a given value in the string.
2089
+
2090
+ Parameters
2091
+ ----------
2092
+ search : str
2093
+ The string to search for
2094
+ replace : str
2095
+ The replacement string
2096
+
2097
+ Returns
2098
+ -------
2099
+ Stringable
2100
+ A new Stringable with last occurrence replaced
2101
+ """
2102
+ s = str(self)
2103
+ idx = s.rfind(search)
2104
+ if idx != -1:
2105
+ s = s[:idx] + replace + s[idx + len(search):]
2106
+ return Stringable(s)
2107
+
2108
+ def replaceStart(self, search: str, replace: str) -> "Stringable":
2109
+ """
2110
+ Replace the first occurrence of the given value if it appears at the start of the string.
2111
+
2112
+ Parameters
2113
+ ----------
2114
+ search : str
2115
+ The string to search for at the start
2116
+ replace : str
2117
+ The replacement string
2118
+
2119
+ Returns
2120
+ -------
2121
+ Stringable
2122
+ A new Stringable with start replacement
2123
+ """
2124
+ s = str(self)
2125
+ if s.startswith(search):
2126
+ s = replace + s[len(search):]
2127
+ return Stringable(s)
2128
+
2129
+ def replaceEnd(self, search: str, replace: str) -> "Stringable":
2130
+ """
2131
+ Replace the last occurrence of a given value if it appears at the end of the string.
2132
+
2133
+ Parameters
2134
+ ----------
2135
+ search : str
2136
+ The string to search for at the end
2137
+ replace : str
2138
+ The replacement string
2139
+
2140
+ Returns
2141
+ -------
2142
+ Stringable
2143
+ A new Stringable with end replacement
2144
+ """
2145
+ s = str(self)
2146
+ if s.endswith(search):
2147
+ s = s[:-len(search)] + replace
2148
+ return Stringable(s)
2149
+
2150
+ def replaceMatches(self, pattern: Union[str, List[str]], replace: Union[str, Callable], limit: int = -1) -> "Stringable":
2151
+ """
2152
+ Replace the patterns matching the given regular expression.
2153
+
2154
+ Parameters
2155
+ ----------
2156
+ pattern : str or list
2157
+ Regular expression pattern(s)
2158
+ replace : str or callable
2159
+ Replacement string or callback function
2160
+ limit : int, optional
2161
+ Maximum replacements, by default -1 (no limit)
2162
+
2163
+ Returns
2164
+ -------
2165
+ Stringable
2166
+ A new Stringable with pattern matches replaced
2167
+ """
2168
+ s = str(self)
2169
+
2170
+ if isinstance(pattern, list):
2171
+ patterns = pattern
2172
+ else:
2173
+ patterns = [pattern]
2174
+
2175
+ for pat in patterns:
2176
+ if callable(replace):
2177
+ s = re.sub(pat, replace, s, count=0 if limit == -1 else limit)
2178
+ else:
2179
+ s = re.sub(pat, str(replace), s, count=0 if limit == -1 else limit)
2180
+
2181
+ return Stringable(s)
2182
+
2183
+ def remove(self, search: Union[str, List[str]], case_sensitive: bool = True) -> "Stringable":
2184
+ """
2185
+ Remove any occurrence of the given string in the subject.
2186
+
2187
+ Parameters
2188
+ ----------
2189
+ search : str or list
2190
+ The string(s) to remove
2191
+ case_sensitive : bool, optional
2192
+ Whether the search is case sensitive, by default True
2193
+
2194
+ Returns
2195
+ -------
2196
+ Stringable
2197
+ A new Stringable with occurrences removed
2198
+ """
2199
+ s = str(self)
2200
+
2201
+ if isinstance(search, str):
2202
+ search = [search]
2203
+
2204
+ for needle in search:
2205
+ if case_sensitive:
2206
+ s = s.replace(needle, '')
2207
+ else:
2208
+ s = re.sub(re.escape(needle), '', s, flags=re.IGNORECASE)
2209
+
2210
+ return Stringable(s)
2211
+
2212
+ # Pluralization and singularization methods
2213
+ def plural(self, count: Union[int, List, Any] = 2, prepend_count: bool = False) -> "Stringable":
2214
+ """
2215
+ Get the plural form of an English word.
2216
+
2217
+ Parameters
2218
+ ----------
2219
+ count : int, list or any, optional
2220
+ Count to determine if plural is needed, by default 2
2221
+ prepend_count : bool, optional
2222
+ Whether to prepend the count, by default False
2223
+
2224
+ Returns
2225
+ -------
2226
+ Stringable
2227
+ A new Stringable with plural form
2228
+ """
2229
+ # Simple pluralization rules
2230
+ word = str(self).lower()
2231
+
2232
+ # Determine if we need plural
2233
+ if hasattr(count, '__len__'):
2234
+ actual_count = len(count)
2235
+ elif isinstance(count, (int, float)):
2236
+ actual_count = count
2237
+ else:
2238
+ actual_count = 1
2239
+
2240
+ if actual_count == 1:
2241
+ result = str(self)
2242
+ else:
2243
+ # Simple pluralization rules
2244
+ if word.endswith(('s', 'sh', 'ch', 'x', 'z')):
2245
+ plural_word = str(self) + 'es'
2246
+ elif word.endswith('y') and len(word) > 1 and word[-2] not in 'aeiou':
2247
+ plural_word = str(self)[:-1] + 'ies'
2248
+ elif word.endswith('f'):
2249
+ plural_word = str(self)[:-1] + 'ves'
2250
+ elif word.endswith('fe'):
2251
+ plural_word = str(self)[:-2] + 'ves'
2252
+ else:
2253
+ plural_word = str(self) + 's'
2254
+
2255
+ result = plural_word
2256
+
2257
+ if prepend_count:
2258
+ result = f"{actual_count} {result}"
2259
+
2260
+ return Stringable(result)
2261
+
2262
+ def pluralStudly(self, count: Union[int, List, Any] = 2) -> "Stringable":
2263
+ """
2264
+ Pluralize the last word of an English, studly caps case string.
2265
+
2266
+ Parameters
2267
+ ----------
2268
+ count : int, list or any, optional
2269
+ Count to determine if plural is needed, by default 2
2270
+
2271
+ Returns
2272
+ -------
2273
+ Stringable
2274
+ A new Stringable with pluralized last word in StudlyCase
2275
+ """
2276
+ s = str(self)
2277
+ # Find the last word boundary
2278
+ parts = re.findall(r'[A-Z][a-z]*|[a-z]+', s)
2279
+ if parts:
2280
+ last_word = parts[-1]
2281
+ pluralized_last = Stringable(last_word).plural(count).studly().value()
2282
+ parts[-1] = pluralized_last
2283
+ return Stringable(''.join(parts))
2284
+
2285
+ return self.plural(count).studly()
2286
+
2287
+ def pluralPascal(self, count: Union[int, List, Any] = 2) -> "Stringable":
2288
+ """
2289
+ Pluralize the last word of an English, Pascal caps case string.
2290
+
2291
+ Parameters
2292
+ ----------
2293
+ count : int, list or any, optional
2294
+ Count to determine if plural is needed, by default 2
2295
+
2296
+ Returns
2297
+ -------
2298
+ Stringable
2299
+ A new Stringable with pluralized last word in PascalCase
2300
+ """
2301
+ # PascalCase is the same as StudlyCase
2302
+ s = str(self)
2303
+ if len(s) == 0:
2304
+ return Stringable(s)
2305
+
2306
+ # Split by uppercase letters to find words
2307
+ words = re.findall(r'[A-Z][a-z]*|[a-z]+', s)
2308
+ if not words:
2309
+ return Stringable(s)
2310
+
2311
+ # Determine if we need plural
2312
+ if isinstance(count, (list, tuple)):
2313
+ need_plural = len(count) != 1
2314
+ else:
2315
+ need_plural = count != 1
2316
+
2317
+ if need_plural:
2318
+ # Pluralize the last word
2319
+ last_word = words[-1]
2320
+ pluralized = Stringable(last_word).plural(count)
2321
+ words[-1] = pluralized.studly().value()
2322
+
2323
+ return Stringable(''.join(words))
2324
+
2325
+ def singular(self) -> "Stringable":
2326
+ """
2327
+ Get the singular form of an English word.
2328
+
2329
+ Returns
2330
+ -------
2331
+ Stringable
2332
+ A new Stringable with singular form
2333
+ """
2334
+ word = str(self).lower()
2335
+ s = str(self)
2336
+
2337
+ # Simple singularization rules
2338
+ if word.endswith('ies') and len(word) > 3:
2339
+ result = s[:-3] + 'y'
2340
+ elif word.endswith('ves'):
2341
+ if word.endswith('ives'):
2342
+ result = s[:-3] + 'e'
2343
+ else:
2344
+ result = s[:-3] + 'f'
2345
+ elif word.endswith('es'):
2346
+ if word.endswith(('ches', 'shes', 'xes', 'zes')):
2347
+ result = s[:-2]
2348
+ elif word.endswith('ses'):
2349
+ result = s[:-2]
2350
+ else:
2351
+ result = s[:-1]
2352
+ elif word.endswith('s') and not word.endswith('ss'):
2353
+ result = s[:-1]
2354
+ else:
2355
+ result = s
2356
+
2357
+ return Stringable(result)
2358
+
2359
+ def parseCallback(self, default: Optional[str] = None) -> List[Optional[str]]:
2360
+ """
2361
+ Parse a Class@method style callback into class and method.
2362
+
2363
+ Parameters
2364
+ ----------
2365
+ default : str, optional
2366
+ Default method name if not specified, by default None
2367
+
2368
+ Returns
2369
+ -------
2370
+ list
2371
+ List containing [class_name, method_name]
2372
+ """
2373
+ callback_str = str(self)
2374
+
2375
+ if '@' in callback_str:
2376
+ parts = callback_str.split('@', 1)
2377
+ return [parts[0], parts[1]]
2378
+ else:
2379
+ return [callback_str, default]
2380
+
2381
+ # Conditional methods (when)
2382
+ def when(self, condition: Union[bool, Callable], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2383
+ """
2384
+ Execute the given callback if condition is true.
2385
+
2386
+ Parameters
2387
+ ----------
2388
+ condition : bool or callable
2389
+ The condition to evaluate
2390
+ callback : callable
2391
+ The callback to execute if condition is true
2392
+ default : callable, optional
2393
+ The callback to execute if condition is false, by default None
2394
+
2395
+ Returns
2396
+ -------
2397
+ Stringable
2398
+ Result of callback execution or self
2399
+ """
2400
+ if callable(condition):
2401
+ condition_result = condition(self)
2402
+ else:
2403
+ condition_result = condition
2404
+
2405
+ if condition_result:
2406
+ result = callback(self)
2407
+ return Stringable(result) if not isinstance(result, Stringable) else result
2408
+ elif default:
2409
+ result = default(self)
2410
+ return Stringable(result) if not isinstance(result, Stringable) else result
2411
+ else:
2412
+ return self
2413
+
2414
+ def whenContains(self, needles: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2415
+ """
2416
+ Execute the given callback if the string contains a given substring.
2417
+
2418
+ Parameters
2419
+ ----------
2420
+ needles : str or list
2421
+ The substring(s) to search for
2422
+ callback : callable
2423
+ The callback to execute if condition is true
2424
+ default : callable, optional
2425
+ The callback to execute if condition is false, by default None
2426
+
2427
+ Returns
2428
+ -------
2429
+ Stringable
2430
+ Result of callback execution or self
2431
+ """
2432
+ return self.when(self.contains(needles), callback, default)
2433
+
2434
+ def whenContainsAll(self, needles: List[str], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2435
+ """
2436
+ Execute the given callback if the string contains all array values.
2437
+
2438
+ Parameters
2439
+ ----------
2440
+ needles : list
2441
+ The substrings to search for
2442
+ callback : callable
2443
+ The callback to execute if condition is true
2444
+ default : callable, optional
2445
+ The callback to execute if condition is false, by default None
2446
+
2447
+ Returns
2448
+ -------
2449
+ Stringable
2450
+ Result of callback execution or self
2451
+ """
2452
+ contains_all = all(needle in str(self) for needle in needles)
2453
+ return self.when(contains_all, callback, default)
2454
+
2455
+ def whenEmpty(self, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2456
+ """
2457
+ Execute the given callback if the string is empty.
2458
+
2459
+ Parameters
2460
+ ----------
2461
+ callback : callable
2462
+ The callback to execute if condition is true
2463
+ default : callable, optional
2464
+ The callback to execute if condition is false, by default None
2465
+
2466
+ Returns
2467
+ -------
2468
+ Stringable
2469
+ Result of callback execution or self
2470
+ """
2471
+ return self.when(self.isEmpty(), callback, default)
2472
+
2473
+ def whenNotEmpty(self, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2474
+ """
2475
+ Execute the given callback if the string is not empty.
2476
+
2477
+ Parameters
2478
+ ----------
2479
+ callback : callable
2480
+ The callback to execute if condition is true
2481
+ default : callable, optional
2482
+ The callback to execute if condition is false, by default None
2483
+
2484
+ Returns
2485
+ -------
2486
+ Stringable
2487
+ Result of callback execution or self
2488
+ """
2489
+ return self.when(self.isNotEmpty(), callback, default)
2490
+
2491
+ def whenEndsWith(self, needles: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2492
+ """
2493
+ Execute the given callback if the string ends with a given substring.
2494
+
2495
+ Parameters
2496
+ ----------
2497
+ needles : str or list
2498
+ The substring(s) to check
2499
+ callback : callable
2500
+ The callback to execute if condition is true
2501
+ default : callable, optional
2502
+ The callback to execute if condition is false, by default None
2503
+
2504
+ Returns
2505
+ -------
2506
+ Stringable
2507
+ Result of callback execution or self
2508
+ """
2509
+ return self.when(self.ends_with(needles), callback, default)
2510
+
2511
+ def whenDoesntEndWith(self, needles: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2512
+ """
2513
+ Execute the given callback if the string doesn't end with a given substring.
2514
+
2515
+ Parameters
2516
+ ----------
2517
+ needles : str or list
2518
+ The substring(s) to check
2519
+ callback : callable
2520
+ The callback to execute if condition is true
2521
+ default : callable, optional
2522
+ The callback to execute if condition is false, by default None
2523
+
2524
+ Returns
2525
+ -------
2526
+ Stringable
2527
+ Result of callback execution or self
2528
+ """
2529
+ return self.when(not self.ends_with(needles), callback, default)
2530
+
2531
+ def whenExactly(self, value: str, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2532
+ """
2533
+ Execute the given callback if the string is an exact match with the given value.
2534
+
2535
+ Parameters
2536
+ ----------
2537
+ value : str
2538
+ The value to compare exactly
2539
+ callback : callable
2540
+ The callback to execute if condition is true
2541
+ default : callable, optional
2542
+ The callback to execute if condition is false, by default None
2543
+
2544
+ Returns
2545
+ -------
2546
+ Stringable
2547
+ Result of callback execution or self
2548
+ """
2549
+ return self.when(self.exactly(value), callback, default)
2550
+
2551
+ def whenNotExactly(self, value: str, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2552
+ """
2553
+ Execute the given callback if the string is not an exact match with the given value.
2554
+
2555
+ Parameters
2556
+ ----------
2557
+ value : str
2558
+ The value to compare exactly
2559
+ callback : callable
2560
+ The callback to execute if condition is true
2561
+ default : callable, optional
2562
+ The callback to execute if condition is false, by default None
2563
+
2564
+ Returns
2565
+ -------
2566
+ Stringable
2567
+ Result of callback execution or self
2568
+ """
2569
+ return self.when(not self.exactly(value), callback, default)
2570
+
2571
+ def whenStartsWith(self, needles: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2572
+ """
2573
+ Execute the given callback if the string starts with a given substring.
2574
+
2575
+ Parameters
2576
+ ----------
2577
+ needles : str or list
2578
+ The substring(s) to check
2579
+ callback : callable
2580
+ The callback to execute if condition is true
2581
+ default : callable, optional
2582
+ The callback to execute if condition is false, by default None
2583
+
2584
+ Returns
2585
+ -------
2586
+ Stringable
2587
+ Result of callback execution or self
2588
+ """
2589
+ if isinstance(needles, str):
2590
+ needles = [needles]
2591
+ starts_with = any(str(self).startswith(needle) for needle in needles)
2592
+ return self.when(starts_with, callback, default)
2593
+
2594
+ def whenDoesntStartWith(self, needles: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2595
+ """
2596
+ Execute the given callback if the string doesn't start with a given substring.
2597
+
2598
+ Parameters
2599
+ ----------
2600
+ needles : str or list
2601
+ The substring(s) to check
2602
+ callback : callable
2603
+ The callback to execute if condition is true
2604
+ default : callable, optional
2605
+ The callback to execute if condition is false, by default None
2606
+
2607
+ Returns
2608
+ -------
2609
+ Stringable
2610
+ Result of callback execution or self
2611
+ """
2612
+ if isinstance(needles, str):
2613
+ needles = [needles]
2614
+ starts_with = any(str(self).startswith(needle) for needle in needles)
2615
+ return self.when(not starts_with, callback, default)
2616
+
2617
+ def whenTest(self, pattern: str, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
2618
+ """
2619
+ Execute the given callback if the string matches the given pattern.
2620
+
2621
+ Parameters
2622
+ ----------
2623
+ pattern : str
2624
+ Regular expression pattern
2625
+ callback : callable
2626
+ The callback to execute if condition is true
2627
+ default : callable, optional
2628
+ The callback to execute if condition is false, by default None
2629
+
2630
+ Returns
2631
+ -------
2632
+ Stringable
2633
+ Result of callback execution or self
2634
+ """
2635
+ return self.when(self.test(pattern), callback, default)
2636
+
2637
+ # Additional conversion methods
2638
+ def convertCase(self, mode: int = None, encoding: Optional[str] = 'UTF-8') -> "Stringable":
2639
+ """
2640
+ Convert the case of a string.
2641
+
2642
+ Parameters
2643
+ ----------
2644
+ mode : int, optional
2645
+ Case conversion mode, by default None (fold case)
2646
+ encoding : str, optional
2647
+ String encoding, by default 'UTF-8'
2648
+
2649
+ Returns
2650
+ -------
2651
+ Stringable
2652
+ A new Stringable with converted case
2653
+ """
2654
+ s = str(self)
2655
+
2656
+ # Python doesn't have exact MB_CASE constants, so we'll use simple mappings
2657
+ if mode is None or mode == 0: # MB_CASE_FOLD equivalent
2658
+ return Stringable(s.casefold())
2659
+ elif mode == 1: # MB_CASE_UPPER equivalent
2660
+ return Stringable(s.upper())
2661
+ elif mode == 2: # MB_CASE_LOWER equivalent
2662
+ return Stringable(s.lower())
2663
+ elif mode == 3: # MB_CASE_TITLE equivalent
2664
+ return Stringable(s.title())
2665
+ else:
2666
+ return Stringable(s.casefold())
2667
+
2668
+ def transliterate(self, unknown: str = '?', strict: bool = False) -> "Stringable":
2669
+ """
2670
+ Transliterate a string to its closest ASCII representation.
2671
+
2672
+ Parameters
2673
+ ----------
2674
+ unknown : str, optional
2675
+ Character to use for unknown characters, by default '?'
2676
+ strict : bool, optional
2677
+ Whether to be strict about transliteration, by default False
2678
+
2679
+ Returns
2680
+ -------
2681
+ Stringable
2682
+ A new Stringable with transliterated text
2683
+ """
2684
+ s = str(self)
2685
+
2686
+ # Use unicodedata to normalize and transliterate
2687
+ normalized = unicodedata.normalize('NFKD', s)
2688
+
2689
+ if strict:
2690
+ # Only keep ASCII characters
2691
+ ascii_chars = []
2692
+ for char in normalized:
2693
+ if ord(char) < 128:
2694
+ ascii_chars.append(char)
2695
+ else:
2696
+ ascii_chars.append(unknown)
2697
+ return Stringable(''.join(ascii_chars))
2698
+ else:
2699
+ # More lenient transliteration
2700
+ ascii_str = ''.join(char for char in normalized if ord(char) < 128)
2701
+ return Stringable(ascii_str)
2702
+
2703
+ def hash(self, algorithm: str) -> "Stringable":
2704
+ """
2705
+ Hash the string using the given algorithm.
2706
+
2707
+ Parameters
2708
+ ----------
2709
+ algorithm : str
2710
+ Hash algorithm (md5, sha1, sha256, etc.)
2711
+
2712
+ Returns
2713
+ -------
2714
+ Stringable
2715
+ A new Stringable with the hash
2716
+ """
2717
+ hash_obj = hashlib.new(algorithm)
2718
+ hash_obj.update(str(self).encode('utf-8'))
2719
+ return Stringable(hash_obj.hexdigest())
2720
+
2721
+ def pipe(self, callback: Callable) -> "Stringable":
2722
+ """
2723
+ Call the given callback and return a new string.
2724
+
2725
+ Parameters
2726
+ ----------
2727
+ callback : callable
2728
+ The callback function to apply
2729
+
2730
+ Returns
2731
+ -------
2732
+ Stringable
2733
+ A new Stringable with the result of the callback
2734
+ """
2735
+ result = callback(self)
2736
+ return Stringable(result) if not isinstance(result, Stringable) else result
2737
+
2738
+ def take(self, limit: int) -> "Stringable":
2739
+ """
2740
+ Take the first or last {limit} characters.
2741
+
2742
+ Parameters
2743
+ ----------
2744
+ limit : int
2745
+ Number of characters to take (negative for from end)
2746
+
2747
+ Returns
2748
+ -------
2749
+ Stringable
2750
+ A new Stringable with the taken characters
2751
+ """
2752
+ if limit < 0:
2753
+ return Stringable(str(self)[limit:])
2754
+ else:
2755
+ return Stringable(str(self)[:limit])
2756
+
2757
+ def swap(self, map_dict: Dict[str, str]) -> "Stringable":
2758
+ """
2759
+ Swap multiple keywords in a string with other keywords.
2760
+
2761
+ Parameters
2762
+ ----------
2763
+ map_dict : dict
2764
+ Dictionary mapping old values to new values
2765
+
2766
+ Returns
2767
+ -------
2768
+ Stringable
2769
+ A new Stringable with swapped values
2770
+ """
2771
+ s = str(self)
2772
+ for old, new in map_dict.items():
2773
+ s = s.replace(old, new)
2774
+ return Stringable(s)
2775
+
2776
+ def substrCount(self, needle: str, offset: int = 0, length: Optional[int] = None) -> int:
2777
+ """
2778
+ Returns the number of substring occurrences.
2779
+
2780
+ Parameters
2781
+ ----------
2782
+ needle : str
2783
+ The substring to count
2784
+ offset : int, optional
2785
+ Starting offset, by default 0
2786
+ length : int, optional
2787
+ Length to search within, by default None
2788
+
2789
+ Returns
2790
+ -------
2791
+ int
2792
+ Number of occurrences
2793
+ """
2794
+ s = str(self)
2795
+
2796
+ if length is not None:
2797
+ s = s[offset:offset + length]
2798
+ else:
2799
+ s = s[offset:]
2800
+
2801
+ return s.count(needle)
2802
+
2803
+ def substrReplace(self, replace: Union[str, List[str]], offset: Union[int, List[int]] = 0,
2804
+ length: Optional[Union[int, List[int]]] = None) -> "Stringable":
2805
+ """
2806
+ Replace text within a portion of a string.
2807
+
2808
+ Parameters
2809
+ ----------
2810
+ replace : str or list
2811
+ Replacement string(s)
2812
+ offset : int or list, optional
2813
+ Starting position(s), by default 0
2814
+ length : int, list or None, optional
2815
+ Length(s) to replace, by default None
2816
+
2817
+ Returns
2818
+ -------
2819
+ Stringable
2820
+ A new Stringable with replaced text
2821
+ """
2822
+ s = str(self)
2823
+
2824
+ if isinstance(replace, str):
2825
+ replace = [replace]
2826
+ if isinstance(offset, int):
2827
+ offset = [offset]
2828
+ if length is not None and isinstance(length, int):
2829
+ length = [length]
2830
+
2831
+ # Process replacements
2832
+ result = s
2833
+ for i, repl in enumerate(replace):
2834
+ off = offset[i] if i < len(offset) else offset[-1]
2835
+ if length and i < len(length):
2836
+ leng = length[i]
2837
+ result = result[:off] + repl + result[off + leng:]
2838
+ else:
2839
+ result = result[:off] + repl + result[off:]
2840
+
2841
+ return Stringable(result)
2842
+
2843
+ def scan(self, format_str: str) -> List[str]:
2844
+ """
2845
+ Parse input from a string to a list, according to a format.
2846
+
2847
+ Parameters
2848
+ ----------
2849
+ format_str : str
2850
+ Format string (simplified sscanf-like)
2851
+
2852
+ Returns
2853
+ -------
2854
+ list
2855
+ List of parsed values
2856
+ """
2857
+ # Simplified implementation - convert format to regex
2858
+ # This is a basic implementation, not as full-featured as PHP's sscanf
2859
+ pattern = format_str.replace('%s', r'(\S+)').replace('%d', r'(\d+)').replace('%f', r'([\d.]+)')
2860
+ matches = re.findall(pattern, str(self))
2861
+ return list(matches[0]) if matches else []
2862
+
2863
+ # Additional methods for compatibility
2864
+ def prepend(self, *values: str) -> "Stringable":
2865
+ """
2866
+ Prepend the given values to the string.
2867
+
2868
+ Parameters
2869
+ ----------
2870
+ values : str
2871
+ Values to prepend
2872
+
2873
+ Returns
2874
+ -------
2875
+ Stringable
2876
+ A new Stringable with prepended values
2877
+ """
2878
+ return Stringable(''.join(values) + str(self))
2879
+
2880
+ def substr(self, start: int, length: Optional[int] = None, encoding: str = 'UTF-8') -> "Stringable":
2881
+ """
2882
+ Returns the portion of the string specified by the start and length parameters.
2883
+
2884
+ Parameters
2885
+ ----------
2886
+ start : int
2887
+ Starting position
2888
+ length : int, optional
2889
+ Length to extract, by default None
2890
+ encoding : str, optional
2891
+ String encoding (for compatibility), by default 'UTF-8'
2892
+
2893
+ Returns
2894
+ -------
2895
+ Stringable
2896
+ A new Stringable with the substring
2897
+ """
2898
+ s = str(self)
2899
+ if length is None:
2900
+ return Stringable(s[start:])
2901
+ else:
2902
+ return Stringable(s[start:start + length])
2903
+
2904
+ # Additional compatibility methods
2905
+ def doesntContain(self, needles: Union[str, List[str]], ignore_case: bool = False) -> bool:
2906
+ """
2907
+ Determine if a given string doesn't contain a given substring.
2908
+
2909
+ Parameters
2910
+ ----------
2911
+ needles : str or list
2912
+ The substring(s) to search for
2913
+ ignore_case : bool, optional
2914
+ Whether to ignore case, by default False
2915
+
2916
+ Returns
2917
+ -------
2918
+ bool
2919
+ True if string doesn't contain any needle, False otherwise
2920
+ """
2921
+ return not self.contains(needles, ignore_case)
2922
+
2923
+ def doesntStartWith(self, needles: Union[str, List[str]]) -> bool:
2924
+ """
2925
+ Determine if a given string doesn't start with a given substring.
2926
+
2927
+ Parameters
2928
+ ----------
2929
+ needles : str or list
2930
+ The substring(s) to check
2931
+
2932
+ Returns
2933
+ -------
2934
+ bool
2935
+ True if string doesn't start with any needle, False otherwise
2936
+ """
2937
+ if isinstance(needles, str):
2938
+ needles = [needles]
2939
+ return not any(str(self).startswith(needle) for needle in needles)
2940
+
2941
+ def doesntEndWith(self, needles: Union[str, List[str]]) -> bool:
2942
+ """
2943
+ Determine if a given string doesn't end with a given substring.
2944
+
2945
+ Parameters
2946
+ ----------
2947
+ needles : str or list
2948
+ The substring(s) to check
2949
+
2950
+ Returns
2951
+ -------
2952
+ bool
2953
+ True if string doesn't end with any needle, False otherwise
2954
+ """
2955
+ return not self.ends_with(needles)
2956
+
2957
+ def startsWith(self, needles: Union[str, List[str]]) -> bool:
2958
+ """
2959
+ Determine if a given string starts with a given substring.
2960
+
2961
+ Parameters
2962
+ ----------
2963
+ needles : str or list
2964
+ The substring(s) to check
2965
+
2966
+ Returns
2967
+ -------
2968
+ bool
2969
+ True if string starts with any needle, False otherwise
2970
+ """
2971
+ if isinstance(needles, str):
2972
+ needles = [needles]
2973
+ return any(str(self).startswith(needle) for needle in needles)
2974
+
2975
+ # Methods for array-like behavior and JSON serialization
2976
+ def jsonSerialize(self) -> str:
2977
+ """
2978
+ Convert the object to a string when JSON encoded.
2979
+
2980
+ Returns
2981
+ -------
2982
+ str
2983
+ The string representation for JSON serialization
2984
+ """
2985
+ return str(self)
2986
+
2987
+ def offsetExists(self, offset: int) -> bool:
2988
+ """
2989
+ Determine if the given offset exists.
2990
+
2991
+ Parameters
2992
+ ----------
2993
+ offset : int
2994
+ The offset to check
2995
+
2996
+ Returns
2997
+ -------
2998
+ bool
2999
+ True if offset exists, False otherwise
3000
+ """
3001
+ try:
3002
+ str(self)[offset]
3003
+ return True
3004
+ except IndexError:
3005
+ return False
3006
+
3007
+ def offsetGet(self, offset: int) -> str:
3008
+ """
3009
+ Get the value at the given offset.
3010
+
3011
+ Parameters
3012
+ ----------
3013
+ offset : int
3014
+ The offset to get
3015
+
3016
+ Returns
3017
+ -------
3018
+ str
3019
+ The character at the offset
3020
+ """
3021
+ return str(self)[offset]
3022
+
3023
+ def offsetSet(self, offset: int, value: str) -> None:
3024
+ """
3025
+ Set the value at the given offset.
3026
+
3027
+ Note: Strings are immutable in Python, so this method exists primarily
3028
+ for interface compatibility and cannot actually modify the string in place.
3029
+
3030
+ Parameters
3031
+ ----------
3032
+ offset : int
3033
+ The offset position to set.
3034
+ value : str
3035
+ The value to set at the specified offset.
3036
+
3037
+ Returns
3038
+ -------
3039
+ None
3040
+ This method does not return a value due to string immutability.
3041
+ """
3042
+
3043
+ # Since strings are immutable, we can't actually modify in place
3044
+ # This method exists for interface compatibility but doesn't modify self
3045
+ s = list(str(self))
3046
+ s[offset] = value
3047
+ # Note: This doesn't actually modify self due to immutability
3048
+
3049
+ def offsetUnset(self, offset: int) -> None:
3050
+ """
3051
+ Unset the value at the given offset.
3052
+
3053
+ Note: Strings are immutable in Python, so this method exists primarily
3054
+ for interface compatibility and cannot actually remove characters in place.
3055
+
3056
+ Parameters
3057
+ ----------
3058
+ offset : int
3059
+ The offset position to unset.
3060
+
3061
+ Returns
3062
+ -------
3063
+ None
3064
+ This method does not return a value due to string immutability.
3065
+ """
3066
+
3067
+ # Since strings are immutable, we can't actually modify in place
3068
+ # This method exists for interface compatibility but doesn't modify self
3069
+ pass
3070
+
3071
+ # Additional Laravel methods that might be missing
3072
+ def isPattern(self, pattern: Union[str, List[str]], ignore_case: bool = False) -> bool:
3073
+ """
3074
+ Determine if a given string matches a given pattern.
3075
+
3076
+ This method checks if the string matches any of the given patterns,
3077
+ which can include wildcards (* and ?). The matching can be case-sensitive
3078
+ or case-insensitive based on the ignore_case parameter.
3079
+
3080
+ Parameters
3081
+ ----------
3082
+ pattern : str or List[str]
3083
+ Pattern(s) to match (supports wildcards * and ?).
3084
+ ignore_case : bool, optional
3085
+ Whether to ignore case, by default False.
3086
+
3087
+ Returns
3088
+ -------
3089
+ bool
3090
+ True if string matches any of the patterns, False otherwise.
3091
+ """
3092
+ import fnmatch
3093
+
3094
+ # Normalize pattern to list for consistent processing
3095
+ if isinstance(pattern, str):
3096
+ patterns = [pattern]
3097
+ else:
3098
+ patterns = pattern
3099
+
3100
+ # Get string representation
3101
+ s = str(self)
3102
+
3103
+ # Apply case-insensitive matching if requested
3104
+ if ignore_case:
3105
+ s = s.lower()
3106
+ patterns = [p.lower() for p in patterns]
3107
+
3108
+ # Check if string matches any of the patterns
3109
+ return any(fnmatch.fnmatch(s, p) for p in patterns)
3110
+
3111
+ def containsAll(self, needles: List[str], ignore_case: bool = False) -> bool:
3112
+ """
3113
+ Determine if a given string contains all array values.
3114
+
3115
+ Parameters
3116
+ ----------
3117
+ needles : list
3118
+ List of substrings to search for
3119
+ ignore_case : bool, optional
3120
+ Whether to ignore case, by default False
3121
+
3122
+ Returns
3123
+ -------
3124
+ bool
3125
+ True if string contains all needles, False otherwise
3126
+ """
3127
+ s = str(self)
3128
+ if ignore_case:
3129
+ s = s.lower()
3130
+ needles = [needle.lower() for needle in needles]
3131
+
3132
+ return all(needle in s for needle in needles)
3133
+
3134
+ # Additional string transformation methods
3135
+ def markdown(self, options: Optional[Dict] = None, extensions: Optional[List] = None) -> "Stringable":
3136
+ """
3137
+ Convert GitHub flavored Markdown into HTML.
3138
+ Note: This is a placeholder - would need a markdown library for full implementation.
3139
+
3140
+ Parameters
3141
+ ----------
3142
+ options : dict, optional
3143
+ Markdown processing options, by default None
3144
+ extensions : list, optional
3145
+ Markdown extensions, by default None
3146
+
3147
+ Returns
3148
+ -------
3149
+ Stringable
3150
+ A new Stringable with HTML (placeholder implementation)
3151
+ """
3152
+ # Placeholder implementation - would need python-markdown or similar
3153
+ return Stringable(str(self))
3154
+
3155
+ def inlineMarkdown(self, options: Optional[Dict] = None, extensions: Optional[List] = None) -> "Stringable":
3156
+ """
3157
+ Convert inline Markdown into HTML.
3158
+ Note: This is a placeholder - would need a markdown library for full implementation.
3159
+
3160
+ Parameters
3161
+ ----------
3162
+ options : dict, optional
3163
+ Markdown processing options, by default None
3164
+ extensions : list, optional
3165
+ Markdown extensions, by default None
3166
+
3167
+ Returns
3168
+ -------
3169
+ Stringable
3170
+ A new Stringable with HTML (placeholder implementation)
3171
+ """
3172
+ # Placeholder implementation - would need python-markdown or similar
3173
+ return Stringable(str(self))
3174
+
3175
+ def dump(self, *args) -> "Stringable":
3176
+ """
3177
+ Dump the string to stdout for debugging purposes.
3178
+
3179
+ Outputs the current string value along with any additional arguments to
3180
+ the console, useful for debugging and inspection during development.
3181
+
3182
+ Parameters
3183
+ ----------
3184
+ *args : Any
3185
+ Additional arguments to output alongside the string.
3186
+
3187
+ Returns
3188
+ -------
3189
+ Stringable
3190
+ The same Stringable instance for method chaining.
3191
+ """
3192
+
3193
+ # Print the string value along with any additional arguments
3194
+ print(str(self), *args)
3195
+
3196
+ # Return self to allow method chaining
3197
+ return self
3198
+
3199
+ # Additional Laravel-compatible methods
3200
+ def whenIs(self, pattern: Union[str, List[str]], callback: Callable, default: Optional[Callable] = None) -> "Stringable":
3201
+ """
3202
+ Execute the given callback if the string matches a given pattern.
3203
+
3204
+ Parameters
3205
+ ----------
3206
+ pattern : str or list
3207
+ Pattern(s) to match against
3208
+ callback : callable
3209
+ The callback to execute if condition is true
3210
+ default : callable, optional
3211
+ The callback to execute if condition is false, by default None
3212
+
3213
+ Returns
3214
+ -------
3215
+ Stringable
3216
+ Result of callback execution or self
3217
+ """
3218
+ return self.when(self.isPattern(pattern), callback, default)
3219
+
3220
+ def whenIsAscii(self, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
3221
+ """
3222
+ Execute the given callback if the string is 7 bit ASCII.
3223
+
3224
+ Parameters
3225
+ ----------
3226
+ callback : callable
3227
+ The callback to execute if condition is true
3228
+ default : callable, optional
3229
+ The callback to execute if condition is false, by default None
3230
+
3231
+ Returns
3232
+ -------
3233
+ Stringable
3234
+ Result of callback execution or self
3235
+ """
3236
+ return self.when(self.isAscii(), callback, default)
3237
+
3238
+ def whenIsUuid(self, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
3239
+ """
3240
+ Execute the given callback if the string is a valid UUID.
3241
+
3242
+ Parameters
3243
+ ----------
3244
+ callback : callable
3245
+ The callback to execute if condition is true
3246
+ default : callable, optional
3247
+ The callback to execute if condition is false, by default None
3248
+
3249
+ Returns
3250
+ -------
3251
+ Stringable
3252
+ Result of callback execution or self
3253
+ """
3254
+ return self.when(self.isUuid(), callback, default)
3255
+
3256
+ def whenIsUlid(self, callback: Callable, default: Optional[Callable] = None) -> "Stringable":
3257
+ """
3258
+ Execute the given callback if the string is a valid ULID.
3259
+
3260
+ Parameters
3261
+ ----------
3262
+ callback : callable
3263
+ The callback to execute if condition is true
3264
+ default : callable, optional
3265
+ The callback to execute if condition is false, by default None
3266
+
3267
+ Returns
3268
+ -------
3269
+ Stringable
3270
+ Result of callback execution or self
3271
+ """
3272
+ return self.when(self.isUlid(), callback, default)
3273
+
3274
+ def isPattern(self, pattern: Union[str, List[str]], ignore_case: bool = False) -> bool:
3275
+ """
3276
+ Determine if a given string matches a given pattern.
3277
+
3278
+ This method supports wildcard patterns using * and ? characters.
3279
+
3280
+ Parameters
3281
+ ----------
3282
+ pattern : str or list
3283
+ Pattern(s) to match against
3284
+ ignore_case : bool, optional
3285
+ Whether to ignore case, by default False
3286
+
3287
+ Returns
3288
+ -------
3289
+ bool
3290
+ True if string matches pattern, False otherwise
3291
+ """
3292
+ if isinstance(pattern, str):
3293
+ pattern = [pattern]
3294
+
3295
+ s = str(self)
3296
+ if ignore_case:
3297
+ s = s.lower()
3298
+
3299
+ for p in pattern:
3300
+ if ignore_case:
3301
+ p = p.lower()
3302
+
3303
+ # Convert wildcard pattern to regex
3304
+ regex_pattern = re.escape(p).replace(r'\*', '.*').replace(r'\?', '.')
3305
+ regex_pattern = f'^{regex_pattern}$'
3306
+
3307
+ if re.match(regex_pattern, s):
3308
+ return True
3309
+
3310
+ return False
3311
+
3312
+ def toDate(self, format_str: Optional[str] = None) -> Optional[datetime]:
3313
+ """
3314
+ Convert the string to a datetime object.
3315
+
3316
+ Parameters
3317
+ ----------
3318
+ format_str : str, optional
3319
+ Format string for parsing, by default None (auto-detect)
3320
+
3321
+ Returns
3322
+ -------
3323
+ datetime or None
3324
+ Parsed datetime object or None if parsing fails
3325
+ """
3326
+
3327
+
3328
+ s = str(self)
3329
+
3330
+ if format_str:
3331
+ try:
3332
+ return datetime.strptime(s, format_str)
3333
+ except ValueError:
3334
+ return None
3335
+
3336
+ # Try common date formats
3337
+ common_formats = [
3338
+ '%Y-%m-%d',
3339
+ '%Y-%m-%d %H:%M:%S',
3340
+ '%Y-%m-%dT%H:%M:%S',
3341
+ '%Y-%m-%d %H:%M',
3342
+ '%d/%m/%Y',
3343
+ '%m/%d/%Y',
3344
+ '%d-%m-%Y',
3345
+ '%m-%d-%Y'
3346
+ ]
3347
+
3348
+ for fmt in common_formats:
3349
+ try:
3350
+ return datetime.strptime(s, fmt)
3351
+ except ValueError:
3352
+ continue
3353
+
3354
+ return None
3355
+
3356
+ def encrypt(self, serialize: bool = False) -> "Stringable":
3357
+ """
3358
+ Encrypt the string (placeholder implementation).
3359
+
3360
+ Note: This is a placeholder. In a real implementation, you would use
3361
+ a proper encryption library like cryptography.
3362
+
3363
+ Parameters
3364
+ ----------
3365
+ serialize : bool, optional
3366
+ Whether to serialize the value before encryption, by default False
3367
+
3368
+ Returns
3369
+ -------
3370
+ Stringable
3371
+ Encrypted string (base64 encoded for this placeholder)
3372
+ """
3373
+ # Placeholder implementation - just base64 encode
3374
+ # In a real implementation, use proper encryption
3375
+ return self.toBase64()
3376
+
3377
+ def decrypt(self, serialize: bool = False) -> "Stringable":
3378
+ """
3379
+ Decrypt the string (placeholder implementation).
3380
+
3381
+ Note: This is a placeholder. In a real implementation, you would use
3382
+ a proper decryption library like cryptography.
3383
+
3384
+ Parameters
3385
+ ----------
3386
+ serialize : bool, optional
3387
+ Whether to unserialize the value after decryption, by default False
3388
+
3389
+ Returns
3390
+ -------
3391
+ Stringable
3392
+ Decrypted string
3393
+ """
3394
+ # Placeholder implementation - just base64 decode
3395
+ # In a real implementation, use proper decryption
3396
+ return self.fromBase64()
3397
+
3398
+ def toHtmlString(self) -> "Stringable":
3399
+ """
3400
+ Create an HTML string representation (placeholder).
3401
+
3402
+ Returns
3403
+ -------
3404
+ Stringable
3405
+ HTML-safe string
3406
+ """
3407
+ # Escape HTML entities
3408
+ return Stringable(html.escape(str(self)))
3409
+
3410
+ def dd(self, *args) -> None:
3411
+ """
3412
+ Dump the string and halt execution.
3413
+
3414
+ Parameters
3415
+ ----------
3416
+ *args : Any
3417
+ Additional arguments to dump
3418
+ """
3419
+ print(str(self), *args)
3420
+ import sys
3421
+ sys.exit(1)
3422
+
3423
+ # Additional method aliases and compatibility methods
3424
+ def tap(self, callback: Callable) -> "Stringable":
3425
+ """
3426
+ Call the given callback with the string and return the string.
3427
+
3428
+ Parameters
3429
+ ----------
3430
+ callback : callable
3431
+ The callback to execute with the string
3432
+
3433
+ Returns
3434
+ -------
3435
+ Stringable
3436
+ The same Stringable instance
3437
+ """
3438
+ callback(self)
3439
+ return self