• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

enetx / g / 22200209868

19 Feb 2026 09:08PM UTC coverage: 89.376% (-0.4%) from 89.809%
22200209868

push

github

enetx
perf: reduce allocations across string, collection, and encoding types

string:
- Lower/Upper/IsLower/IsUpper/IsTitle: Bytes() → BytesUnsafe()/StringUnsafe()
- Random: ASCII fast-path with WriteByte, avoid global charset Runes() alloc
- ReplaceNth: Builder instead of triple string concatenation
- Chunks: ASCII fast-path (zero-copy slicing); Unicode path via
  utf8.DecodeRuneInString, eliminating []rune alloc and per-chunk String([]rune) alloc
- Truncate: ASCII fast-path + single-pass Unicode walk; drop LenRunes()+Runes() double scan
- Center: cache s.LenRunes() and pad.LenRunes() (4 scans → 2)
- writePadding: ASCII fast-path with WriteByte; non-ASCII via utf8.DecodeRuneInString,
  eliminating pad.Runes() alloc on every call
- Reverse: BytesUnsafe()/StringUnsafe()
- Similarity: plain []int for DP table, min() builtins

string/hash: BytesUnsafe()/StringUnsafe() for MD5/SHA1/SHA256/SHA512

string/encode:
- Base64/JSON: BytesUnsafe() to avoid copy
- URL: Grow() pre-allocation
- Hex: inline lookup table — zero per-byte allocs (was: strconv.FormatInt per byte)
- Binary: inline bit shift — zero per-byte allocs (was: fmt.Sprintf per byte)
- Octal: strconv.AppendInt into stack buffer — zero per-rune allocs

string/compress: all five writers use BytesUnsafe() instead of io.WriteString
  (io.WriteString on non-StringWriter falls back to []byte(s) copy)

int: Hex/Octal use strconv.FormatInt instead of fmt.Sprintf

set/map/map_ordered: simplify NewX size param (drop Slice[Int] unwrap indirection)

String() methods: add Grow() pre-allocation to Slice, Set, Map, MapOrd,
  MapSafe, Heap, Deque (n*8 or n*16 estimate reduces realloc churn)

slice: Join pre-computes exact size and uses a single strings.Builder,
  eliminating intermediate []string allocation

133 of 164 new or added lines in 12 files covered. (81.1%)

9 existing lines in 4 files now uncovered.

5325 of 5958 relevant lines covered (89.38%)

14237.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.14
/string.go
1
package g
2

3
import (
4
        "database/sql/driver"
5
        "fmt"
6
        "math/big"
7
        "slices"
8
        "strconv"
9
        "strings"
10
        "unicode"
11
        "unicode/utf8"
12
        "unsafe"
13

14
        "github.com/enetx/g/cmp"
15
        "github.com/enetx/g/f"
16
        "golang.org/x/text/unicode/norm"
17
)
18

19
// String is an alias for the string type.
20
type String string
21

22
// Named is a map-like type that stores key-value pairs for resolving named
23
// placeholders in Sprintf.
24
type Named Map[String, any]
25

26
// NewString creates a new String from the provided string.
27
func NewString[T ~string | rune | byte | ~[]rune | ~[]byte](str T) String { return String(str) }
334✔
28

29
// Clone returns a copy of the String.
30
// It ensures that the returned String does not share underlying memory with the original String,
31
// making it safe to modify or store independently.
32
func (s String) Clone() String { return String(strings.Clone(s.Std())) }
1✔
33

34
// Transform applies a transformation function to the String and returns the result.
35
func (s String) Transform(fn func(String) String) String { return fn(s) }
1✔
36

37
// Builder returns a new Builder initialized with the content of the String.
38
func (s String) Builder() *Builder {
1✔
39
        b := new(Builder)
1✔
40
        b.WriteString(s)
1✔
41
        return b
1✔
42
}
1✔
43

44
// Min returns the minimum of Strings.
45
func (s String) Min(b ...String) String { return cmp.Min(append(b, s)...) }
3✔
46

47
// Max returns the maximum of Strings.
48
func (s String) Max(b ...String) String { return cmp.Max(append(b, s)...) }
3✔
49

50
// Random generates a random String of the specified length, selecting characters from predefined sets.
51
// If additional character sets are provided, only those will be used; the default set (ASCII_LETTERS and DIGITS)
52
// is excluded unless explicitly provided.
53
//
54
// Parameters:
55
// - count (Int): Length of the random String to generate.
56
// - letters (...String): Additional character sets to consider for generating the random String (optional).
57
//
58
// Returns:
59
// - String: Randomly generated String with the specified length.
60
//
61
// Example usage:
62
//
63
//        randomString := g.String.Random(10)
64
//        randomString contains a random String with 10 characters.
65
func (String) Random(length Int, letters ...String) String {
300✔
66
        var b Builder
300✔
67
        b.Grow(length)
300✔
68

300✔
69
        if len(letters) != 0 {
300✔
NEW
70
                var buf Builder
×
71

×
72
                for _, set := range letters {
×
NEW
73
                        _, _ = buf.WriteString(set)
×
74
                }
×
75

NEW
76
                chars := buf.String().Runes()
×
NEW
77
                n := Int(len(chars))
×
UNCOV
78

×
NEW
79
                for range length {
×
NEW
80
                        b.WriteRune(chars[n.Random()])
×
NEW
81
                }
×
82
        } else {
300✔
83
                const charset = ASCII_LETTERS + DIGITS
300✔
84
                n := charset.Len()
300✔
85

300✔
86
                for range length {
12,473✔
87
                        b.WriteByte(charset[n.Random().Std()])
12,173✔
88
                }
12,173✔
89
        }
90

91
        return b.String()
300✔
92
}
93

94
// IsASCII checks if all characters in the String are ASCII bytes.
95
func (s String) IsASCII() bool {
25✔
96
        for i := range s {
192✔
97
                if s[i] >= 0x80 {
174✔
98
                        return false
7✔
99
                }
7✔
100
        }
101

102
        return true
18✔
103
}
104

105
// IsDigit checks if all characters in the String are digits.
106
func (s String) IsDigit() bool {
4✔
107
        if s.IsEmpty() {
5✔
108
                return false
1✔
109
        }
1✔
110

111
        for _, c := range s {
14✔
112
                if !unicode.IsDigit(c) {
12✔
113
                        return false
1✔
114
                }
1✔
115
        }
116

117
        return true
2✔
118
}
119

120
// TryInt tries to parse the String as an int and returns an Int.
121
func (s String) TryInt() Result[Int] {
192✔
122
        hint, err := strconv.ParseInt(s.Std(), 0, 64)
192✔
123
        if err != nil {
240✔
124
                return Err[Int](err)
48✔
125
        }
48✔
126

127
        return Ok(Int(hint))
144✔
128
}
129

130
// TryBigInt attempts to convert the String receiver into an Option containing a *big.Int.
131
// This function assumes the string represents a numerical value, which can be in decimal,
132
// hexadecimal (prefixed with "0x"), or octal (prefixed with "0") format. The function
133
// leverages the SetString method of the math/big package, automatically detecting the
134
// numeric base when set to 0.
135
//
136
// If the string is correctly formatted and represents a valid number, TryBigInt returns
137
// a Some containing the *big.Int parsed from the string. If the string is empty, contains
138
// invalid characters, or does not conform to a recognizable numeric format, TryBigInt
139
// returns a None, indicating that the conversion was unsuccessful.
140
//
141
// Returns:
142
//   - An Option[*big.Int] encapsulating the conversion result. It returns Some[*big.Int]
143
//     with the parsed value if successful, otherwise None[*big.Int] if the parsing fails.
144
func (s String) TryBigInt() Option[*big.Int] {
5✔
145
        if bigInt, ok := new(big.Int).SetString(s.Std(), 0); ok {
8✔
146
                return Some(bigInt)
3✔
147
        }
3✔
148

149
        return None[*big.Int]()
2✔
150
}
151

152
// TryFloat tries to parse the String as a float64 and returns an Float.
153
func (s String) TryFloat() Result[Float] {
8✔
154
        float, err := strconv.ParseFloat(s.Std(), 64)
8✔
155
        if err != nil {
13✔
156
                return Err[Float](err)
5✔
157
        }
5✔
158

159
        return Ok(Float(float))
3✔
160
}
161

162
// Title converts the String to title case.
163
func (s String) Title() String { return String(title.String(s.Std())) }
29✔
164

165
// Lower returns the String in lowercase.
166
func (s String) Lower() String { return s.BytesUnsafe().Lower().StringUnsafe() }
7✔
167

168
// Upper returns the String in uppercase.
169
func (s String) Upper() String { return s.BytesUnsafe().Upper().StringUnsafe() }
14✔
170

171
// IsLower checks if the String consists only of lowercase letters.
172
func (s String) IsLower() bool { return s.BytesUnsafe().IsLower() }
23✔
173

174
// IsUpper checks if the String consists only of uppercase letters.
175
func (s String) IsUpper() bool { return s.BytesUnsafe().IsUpper() }
22✔
176

177
// IsTitle checks if the String is in title case.
178
func (s String) IsTitle() bool { return s.BytesUnsafe().IsTitle() }
23✔
179

180
// Trim removes leading and trailing white space from the String.
181
func (s String) Trim() String { return String(strings.TrimSpace(s.Std())) }
200✔
182

183
// TrimStart removes leading white space from the String.
184
func (s String) TrimStart() String { return String(strings.TrimLeftFunc(s.Std(), unicode.IsSpace)) }
5✔
185

186
// TrimEnd removes trailing white space from the String.
187
func (s String) TrimEnd() String { return String(strings.TrimRightFunc(s.Std(), unicode.IsSpace)) }
11✔
188

189
// TrimSet removes the specified set of characters from both the beginning and end of the String.
190
func (s String) TrimSet(cutset String) String { return String(strings.Trim(s.Std(), cutset.Std())) }
9✔
191

192
// TrimStartSet removes the specified set of characters from the beginning of the String.
193
func (s String) TrimStartSet(cutset String) String {
5✔
194
        return String(strings.TrimLeft(s.Std(), cutset.Std()))
5✔
195
}
5✔
196

197
// TrimEndSet removes the specified set of characters from the end of the String.
198
func (s String) TrimEndSet(cutset String) String {
5✔
199
        return String(strings.TrimRight(s.Std(), cutset.Std()))
5✔
200
}
5✔
201

202
// StripPrefix trims the specified prefix from the String.
203
func (s String) StripPrefix(prefix String) String {
4✔
204
        return String(strings.TrimPrefix(s.Std(), prefix.Std()))
4✔
205
}
4✔
206

207
// StripSuffix trims the specified suffix from the String.
208
func (s String) StripSuffix(suffix String) String {
8✔
209
        return String(strings.TrimSuffix(s.Std(), suffix.Std()))
8✔
210
}
8✔
211

212
// Replace replaces the 'oldS' String with the 'newS' String for the specified number of
213
// occurrences.
214
func (s String) Replace(oldS, newS String, n Int) String {
6✔
215
        return String(strings.Replace(s.Std(), oldS.Std(), newS.Std(), n.Std()))
6✔
216
}
6✔
217

218
// ReplaceAll replaces all occurrences of the 'oldS' String with the 'newS' String.
219
func (s String) ReplaceAll(oldS, newS String) String {
7✔
220
        return String(strings.ReplaceAll(s.Std(), oldS.Std(), newS.Std()))
7✔
221
}
7✔
222

223
// ReplaceMulti creates a custom replacer to perform multiple string replacements.
224
//
225
// Parameters:
226
//
227
// - oldnew ...String: Pairs of strings to be replaced. Specify as many pairs as needed.
228
//
229
// Returns:
230
//
231
// - String: A new string with replacements applied using the custom replacer.
232
//
233
// Example usage:
234
//
235
//        original := g.String("Hello, world! This is a test.")
236
//        replaced := original.ReplaceMulti(
237
//            "Hello", "Greetings",
238
//            "world", "universe",
239
//            "test", "example",
240
//        )
241
//        // replaced contains "Greetings, universe! This is an example."
242
func (s String) ReplaceMulti(oldnew ...String) String {
4✔
243
        pairs := make([]string, len(oldnew))
4✔
244
        for i, str := range oldnew {
20✔
245
                pairs[i] = str.Std()
16✔
246
        }
16✔
247

248
        return String(strings.NewReplacer(pairs...).Replace(s.Std()))
4✔
249
}
250

251
// Remove removes all occurrences of specified substrings from the String.
252
//
253
// Parameters:
254
//
255
// - matches ...String: Substrings to be removed from the string. Specify as many substrings as needed.
256
//
257
// Returns:
258
//
259
// - String: A new string with all specified substrings removed.
260
//
261
// Example usage:
262
//
263
//        original := g.String("Hello, world! This is a test.")
264
//        modified := original.Remove(
265
//            "Hello",
266
//            "test",
267
//        )
268
//        // modified contains ", world! This is a ."
269
func (s String) Remove(matches ...String) String {
3✔
270
        if len(matches) == 0 {
3✔
271
                return s
×
272
        }
×
273

274
        pairs := make([]string, len(matches)*2)
3✔
275
        for i, match := range matches {
9✔
276
                pairs[i*2] = match.Std()
6✔
277
                pairs[i*2+1] = ""
6✔
278
        }
6✔
279

280
        return String(strings.NewReplacer(pairs...).Replace(s.Std()))
3✔
281
}
282

283
// ReplaceNth returns a new String instance with the nth occurrence of oldS
284
// replaced with newS. If there aren't enough occurrences of oldS, the
285
// original String is returned. If n is less than -1, the original String
286
// is also returned. If n is -1, the last occurrence of oldS is replaced with newS.
287
//
288
// Returns:
289
//
290
// - A new String instance with the nth occurrence of oldS replaced with newS.
291
//
292
// Example usage:
293
//
294
//        s := g.String("The quick brown dog jumped over the lazy dog.")
295
//        result := s.ReplaceNth("dog", "fox", 2)
296
//        fmt.Println(result)
297
//
298
// Output: "The quick brown dog jumped over the lazy fox.".
299
func (s String) ReplaceNth(oldS, newS String, n Int) String {
28✔
300
        if n < -1 || len(oldS) == 0 {
31✔
301
                return s
3✔
302
        }
3✔
303

304
        count, i := Int(0), Int(0)
25✔
305

25✔
306
        for {
63✔
307
                pos := s[i:].Index(oldS)
38✔
308
                if pos == -1 {
43✔
309
                        break
5✔
310
                }
311

312
                pos += i
33✔
313
                count++
33✔
314

33✔
315
                if count == n || (n == -1 && s[pos+oldS.Len():].Index(oldS) == -1) {
53✔
316
                        var b Builder
20✔
317
                        b.WriteString(s[:pos])
20✔
318
                        b.WriteString(newS)
20✔
319
                        b.WriteString(s[pos+oldS.Len():])
20✔
320

20✔
321
                        return b.String()
20✔
322
                }
20✔
323

324
                i = pos + oldS.Len()
13✔
325
        }
326

327
        return s
5✔
328
}
329

330
// Contains checks if the String contains the specified substring.
331
func (s String) Contains(substr String) bool { return f.Contains(substr)(s) }
10✔
332

333
// ContainsAny checks if the String contains any of the specified substrings.
334
func (s String) ContainsAny(substrs ...String) bool {
4✔
335
        return slices.ContainsFunc(substrs, s.Contains)
4✔
336
}
4✔
337

338
// ContainsAll checks if the given String contains all the specified substrings.
339
func (s String) ContainsAll(substrs ...String) bool {
4✔
340
        for _, substr := range substrs {
9✔
341
                if !s.Contains(substr) {
7✔
342
                        return false
2✔
343
                }
2✔
344
        }
345

346
        return true
2✔
347
}
348

349
// ContainsAnyChars checks if the String contains any characters from the specified String.
350
func (s String) ContainsAnyChars(chars String) bool { return f.ContainsAnyChars(chars)(s) }
5✔
351

352
// StartsWith checks if the String starts with the specified prefix.
353
// It uses a higher-order function to perform the check.
354
func (s String) StartsWith(prefix String) bool { return f.StartsWith(prefix)(s) }
7✔
355

356
// StartsWithAny checks if the String starts with any of the provided prefixes.
357
// The method accepts a variable number of arguments, allowing for checking against multiple
358
// prefixes at once. It iterates over the provided prefixes and uses the HasPrefix function from
359
// the strings package to check if the String starts with each prefix.
360
// The function returns true if the String starts with any of the prefixes, and false otherwise.
361
//
362
// Example usage:
363
//
364
//        s := g.String("http://example.com")
365
//        if s.StartsWithAny("http://", "https://") {
366
//           // do something
367
//        }
368
func (s String) StartsWithAny(prefixes ...String) bool {
5✔
369
        return slices.ContainsFunc(prefixes, s.StartsWith)
5✔
370
}
5✔
371

372
// EndsWith checks if the String ends with the specified suffix.
373
// It uses a higher-order function to perform the check.
374
func (s String) EndsWith(suffix String) bool { return f.EndsWith(suffix)(s) }
5✔
375

376
// EndsWithAny checks if the String ends with any of the provided suffixes.
377
// The method accepts a variable number of arguments, allowing for checking against multiple
378
// suffixes at once. It iterates over the provided suffixes and uses the HasSuffix function from
379
// the strings package to check if the String ends with each suffix.
380
// The function returns true if the String ends with any of the suffixes, and false otherwise.
381
//
382
// Example usage:
383
//
384
//        s := g.String("example.com")
385
//        if s.EndsWithAny(".com", ".net") {
386
//           // do something
387
//        }
388
func (s String) EndsWithAny(suffixes ...String) bool {
3✔
389
        return slices.ContainsFunc(suffixes, s.EndsWith)
3✔
390
}
3✔
391

392
// Lines splits the String by lines and returns the iterator.
393
func (s String) Lines() SeqSlice[String] {
2✔
394
        return transformSeq(strings.Lines(s.Std()), NewString).Map(String.TrimEnd)
2✔
395
}
2✔
396

397
// Fields splits the String into a slice of substrings, removing any whitespace, and returns the iterator.
398
func (s String) Fields() SeqSlice[String] {
4✔
399
        return transformSeq(strings.FieldsSeq(s.Std()), NewString)
4✔
400
}
4✔
401

402
// FieldsBy splits the String into a slice of substrings using a custom function to determine the field boundaries,
403
// and returns the iterator.
404
func (s String) FieldsBy(fn func(r rune) bool) SeqSlice[String] {
3✔
405
        return transformSeq(strings.FieldsFuncSeq(s.Std(), fn), NewString)
3✔
406
}
3✔
407

408
// Split splits the String by the specified separator and returns the iterator.
409
func (s String) Split(sep ...String) SeqSlice[String] {
110✔
410
        var separator String
110✔
411
        if len(sep) != 0 {
212✔
412
                separator = sep[0]
102✔
413
        }
102✔
414

415
        return transformSeq(strings.SplitSeq(s.Std(), separator.Std()), NewString)
110✔
416
}
417

418
// SplitAfter splits the String after each instance of the specified separator and returns the iterator.
419
func (s String) SplitAfter(sep String) SeqSlice[String] {
8✔
420
        return transformSeq(strings.SplitAfterSeq(s.Std(), sep.Std()), NewString)
8✔
421
}
8✔
422

423
// SplitN splits the String into substrings using the provided separator and returns an Slice[String] of the results.
424
// The n parameter controls the number of substrings to return:
425
// - If n is negative, there is no limit on the number of substrings returned.
426
// - If n is zero, an empty Slice[String] is returned.
427
// - If n is positive, at most n substrings are returned.
428
func (s String) SplitN(sep String, n Int) Slice[String] {
3✔
429
        return TransformSlice(strings.SplitN(s.Std(), sep.Std(), n.Std()), NewString)
3✔
430
}
3✔
431

432
// Chunks splits the String into chunks of the specified size.
433
//
434
// This function iterates through the String, creating new String chunks of the specified size.
435
// If size is less than or equal to 0 or the String is empty,
436
// it returns an empty Slice[String].
437
// If size is greater than or equal to the length of the String,
438
// it returns an Slice[String] containing the original String.
439
//
440
// Parameters:
441
//
442
// - size (Int): The size of the chunks to split the String into.
443
//
444
// Returns:
445
//
446
// - Slice[String]: A slice of String chunks of the specified size.
447
//
448
// Example usage:
449
//
450
//        text := g.String("Hello, World!")
451
//        chunks := text.Chunks(4)
452
//
453
// chunks contains {"Hell", "o, W", "orld", "!"}.
454
func (s String) Chunks(size Int) SeqSlice[String] {
6✔
455
        if size.Lte(0) || s.IsEmpty() {
8✔
456
                return func(func(String) bool) {}
4✔
457
        }
458

459
        if s.IsASCII() {
8✔
460
                n := size.Std()
4✔
461
                l := len(s)
4✔
462

4✔
463
                if n >= l {
6✔
464
                        return func(yield func(String) bool) { yield(s) }
4✔
465
                }
466

467
                return func(yield func(String) bool) {
4✔
468
                        for i := 0; i < l; i += n {
9✔
469
                                if !yield(s[i:min(i+n, l)]) {
7✔
NEW
470
                                        return
×
NEW
471
                                }
×
472
                        }
473
                }
474
        }
475

UNCOV
476
        n := size.Std()
×
NEW
477

×
UNCOV
478
        return func(yield func(String) bool) {
×
NEW
479
                rest := s
×
NEW
480
                for !rest.IsEmpty() {
×
NEW
481
                        i := 0
×
NEW
482
                        for count := 0; count < n && i < len(rest); count++ {
×
NEW
483
                                _, sz := utf8.DecodeRuneInString(string(rest)[i:])
×
NEW
484
                                i += sz
×
NEW
485
                        }
×
486

NEW
487
                        if !yield(rest[:i]) {
×
488
                                return
×
489
                        }
×
490

NEW
491
                        rest = rest[i:]
×
492
                }
493
        }
494
}
495

496
// Cut returns two String values. The first String contains the remainder of the
497
// original String after the cut. The second String contains the text between the
498
// first occurrences of the 'start' and 'end' strings, with tags removed if specified.
499
//
500
// The function searches for the 'start' and 'end' strings within the String.
501
// If both are found, it returns the first String containing the remainder of the
502
// original String after the cut, followed by the second String containing the text
503
// between the first occurrences of 'start' and 'end' with tags removed if specified.
504
//
505
// If either 'start' or 'end' is empty or not found in the String, it returns the
506
// original String as the second String, and an empty String as the first.
507
//
508
// Parameters:
509
//
510
// - start (String): The String marking the beginning of the text to be cut.
511
//
512
// - end (String): The String marking the end of the text to be cut.
513
//
514
//   - rmtags (bool, optional): An optional boolean parameter indicating whether
515
//     to remove 'start' and 'end' tags from the cut text. Defaults to false.
516
//
517
// Returns:
518
//
519
//   - String: The first String containing the remainder of the original String
520
//     after the cut, with tags removed if specified,
521
//     or an empty String if 'start' or 'end' is empty or not found.
522
//
523
//   - String: The second String containing the text between the first occurrences of
524
//     'start' and 'end', or the original String if 'start' or 'end' is empty or not found.
525
//
526
// Example usage:
527
//
528
//        s := g.String("Hello, [world]! How are you?")
529
//        remainder, cut := s.Cut("[", "]")
530
//        // remainder: "Hello, ! How are you?"
531
//        // cut: "world"
532
func (s String) Cut(start, end String, rmtags ...bool) (String, String) {
10✔
533
        if start.IsEmpty() || end.IsEmpty() {
11✔
534
                return s, ""
1✔
535
        }
1✔
536

537
        startIndex := s.Index(start)
9✔
538
        if startIndex == -1 {
11✔
539
                return s, ""
2✔
540
        }
2✔
541

542
        startEnd := startIndex + start.Len()
7✔
543
        endIndex := s[startEnd:].Index(end)
7✔
544
        if endIndex == -1 {
8✔
545
                return s, ""
1✔
546
        }
1✔
547

548
        cut := s[startEnd : startEnd+endIndex]
6✔
549

6✔
550
        if len(rmtags) != 0 && !rmtags[0] {
6✔
551
                startEnd += end.Len()
×
552
                return s[:startIndex] + s[startIndex:startEnd+endIndex] + s[startEnd+endIndex:], cut
×
553
        }
×
554

555
        return s[:startIndex] + s[startEnd+endIndex+end.Len():], cut
6✔
556
}
557

558
// Similarity calculates the similarity between two Strings using the
559
// Levenshtein distance algorithm and returns the similarity percentage as an Float.
560
//
561
// The function compares two Strings using the Levenshtein distance,
562
// which measures the difference between two sequences by counting the number
563
// of single-character edits required to change one sequence into the other.
564
// The similarity is then calculated by normalizing the distance by the maximum
565
// length of the two input Strings.
566
//
567
// Parameters:
568
//
569
// - str (String): The String to compare with s.
570
//
571
// Returns:
572
//
573
// - Float: The similarity percentage between the two Strings as a value between 0 and 100.
574
//
575
// Example usage:
576
//
577
//        s1 := g.String("kitten")
578
//        s2 := g.String("sitting")
579
//        similarity := s1.Similarity(s2) // 57.14285714285714
580
func (s String) Similarity(str String) Float {
25✔
581
        if s.Eq(str) {
27✔
582
                return 100
2✔
583
        }
2✔
584

585
        if s.IsEmpty() || str.IsEmpty() {
24✔
586
                return 0
1✔
587
        }
1✔
588

589
        s1 := s.Runes()
22✔
590
        s2 := str.Runes()
22✔
591

22✔
592
        n1, n2 := len(s1), len(s2)
22✔
593

22✔
594
        if n1 > n2 {
28✔
595
                s1, s2, n1, n2 = s2, s1, n2, n1
6✔
596
        }
6✔
597

598
        distance := make([]int, n1+1)
22✔
599

22✔
600
        for i, r2 := range s2 {
178✔
601
                prev := i + 1
156✔
602

156✔
603
                for j, r1 := range s1 {
1,224✔
604
                        current := distance[j]
1,068✔
605
                        if r2 != r1 {
1,981✔
606
                                current = min(distance[j]+1, min(prev+1, distance[j+1]+1))
913✔
607
                        }
913✔
608

609
                        distance[j], prev = prev, current
1,068✔
610
                }
611

612
                distance[n1] = prev
156✔
613
        }
614

615
        return Float(1-float64(distance[n1])/float64(max(n1, n2))) * 100
22✔
616
}
617

618
// Cmp compares two Strings and returns an cmp.Ordering indicating their relative order.
619
// The result will be cmp.Equal if s==str, cmp.Less if s < str, and cmp.Greater if s > str.
620
func (s String) Cmp(str String) cmp.Ordering { return cmp.Cmp(s, str) }
5✔
621

622
// Append appends the specified String to the current String.
623
func (s String) Append(str String) String { return s + str }
12✔
624

625
// Prepend prepends the specified String to the current String.
626
func (s String) Prepend(str String) String { return str + s }
4✔
627

628
// ContainsRune checks if the String contains the specified rune.
629
func (s String) ContainsRune(r rune) bool { return strings.ContainsRune(s.Std(), r) }
231✔
630

631
// Count returns the number of non-overlapping instances of the substring in the String.
632
func (s String) Count(substr String) Int { return Int(strings.Count(s.Std(), substr.Std())) }
3✔
633

634
// Empty checks if the String is empty.
635
func (s String) IsEmpty() bool { return len(s) == 0 }
668✔
636

637
// Eq checks if two Strings are equal.
638
func (s String) Eq(str String) bool { return s == str }
161✔
639

640
// EqFold compares two String strings case-insensitively.
641
func (s String) EqFold(str String) bool { return strings.EqualFold(s.Std(), str.Std()) }
3✔
642

643
// Gt checks if the String is greater than the specified String.
644
func (s String) Gt(str String) bool { return s > str }
5✔
645

646
// Gte checks if the String is greater than or equal to the specified String.
647
func (s String) Gte(str String) bool { return s >= str }
5✔
648

649
// Bytes returns the String as an Bytes.
650
func (s String) Bytes() Bytes { return Bytes(s) }
207✔
651

652
// BytesUnsafe converts the String into Bytes without copying memory.
653
// Warning: the resulting Bytes shares the same underlying memory as the original String.
654
// If the original String is modified through unsafe operations (rare), or if it is garbage collected,
655
// the Bytes may become invalid or cause undefined behavior.
656
func (s String) BytesUnsafe() Bytes { return unsafe.Slice(unsafe.StringData(s.Std()), len(s)) }
157✔
657

658
// Index returns the index of the first instance of the specified substring in the String, or -1
659
// if substr is not present in s.
660
func (s String) Index(substr String) Int { return Int(strings.Index(s.Std(), substr.Std())) }
714✔
661

662
// LastIndex returns the index of the last instance of the specified substring in the String, or -1
663
// if substr is not present in s.
664
func (s String) LastIndex(substr String) Int { return Int(strings.LastIndex(s.Std(), substr.Std())) }
35✔
665

666
// IndexRune returns the index of the first instance of the specified rune in the String.
667
func (s String) IndexRune(r rune) Int { return Int(strings.IndexRune(s.Std(), r)) }
3✔
668

669
// Len returns the length of the String.
670
func (s String) Len() Int { return Int(len(s)) }
652✔
671

672
// LenRunes returns the number of runes in the String.
673
func (s String) LenRunes() Int { return Int(utf8.RuneCountInString(s.Std())) }
15✔
674

675
// Lt checks if the String is less than the specified String.
676
func (s String) Lt(str String) bool { return s < str }
5✔
677

678
// Lte checks if the String is less than or equal to the specified String.
679
func (s String) Lte(str String) bool { return s <= str }
5✔
680

681
// Map applies the provided function to all runes in the String and returns the resulting String.
682
func (s String) Map(fn func(rune) rune) String { return String(strings.Map(fn, s.Std())) }
9✔
683

684
// NormalizeNFC returns a new String with its Unicode characters normalized using the NFC form.
685
func (s String) NormalizeNFC() String { return String(norm.NFC.String(s.Std())) }
13✔
686

687
// Ne checks if two Strings are not equal.
688
func (s String) Ne(str String) bool { return !s.Eq(str) }
80✔
689

690
// Reader returns a *strings.Reader initialized with the content of String.
691
func (s String) Reader() *strings.Reader { return strings.NewReader(s.Std()) }
28✔
692

693
// Repeat returns a new String consisting of the specified count of the original String.
694
func (s String) Repeat(count Int) String { return String(strings.Repeat(s.Std(), count.Std())) }
7✔
695

696
// Reverse reverses the String.
697
func (s String) Reverse() String { return s.BytesUnsafe().Reverse().StringUnsafe() }
35✔
698

699
// Runes returns the String as a slice of runes.
700
func (s String) Runes() Slice[rune] { return []rune(s) }
50✔
701

702
// Chars splits the String into individual characters and returns the iterator.
703
func (s String) Chars() SeqSlice[String] { return s.Split() }
7✔
704

705
// SubString extracts a substring from the String starting at the 'start' index and ending before the 'end' index.
706
// The function also supports an optional 'step' parameter to define the increment between indices in the substring.
707
// If 'start' or 'end' index is negative, they represent positions relative to the end of the String:
708
// - A negative 'start' index indicates the position from the end of the String, moving backward.
709
// - A negative 'end' index indicates the position from the end of the String.
710
// The function ensures that indices are adjusted to fall within the valid range of the String's length.
711
// If indices are out of bounds or if 'start' exceeds 'end', the function returns the original String unmodified.
712
func (s String) SubString(start, end Int, step ...Int) String {
6✔
713
        return String(s.Runes().SubSlice(start, end, step...))
6✔
714
}
6✔
715

716
// Std returns the String as a string.
717
func (s String) Std() string { return string(s) }
4,785✔
718

719
// Format applies a specified format to the String object.
720
func (s String) Format(template String) String { return Format(template, s) }
4✔
721

722
// Truncate shortens the String to the specified maximum length. If the String exceeds the
723
// specified length, it is truncated, and an ellipsis ("...") is appended to indicate the truncation.
724
//
725
// If the length of the String is less than or equal to the specified maximum length, the
726
// original String is returned unchanged.
727
//
728
// The method respects Unicode characters and truncates based on the number of runes,
729
// not bytes.
730
//
731
// Parameters:
732
//   - max: The maximum number of runes allowed in the resulting String.
733
//
734
// Returns:
735
//   - A new String truncated to the specified maximum length with "..." appended
736
//     if truncation occurs. Otherwise, returns the original String.
737
//
738
// Example usage:
739
//
740
//        s := g.String("Hello, World!")
741
//        result := s.Truncate(5)
742
//        // result: "Hello..."
743
//
744
//        s2 := g.String("Short")
745
//        result2 := s2.Truncate(10)
746
//        // result2: "Short"
747
//
748
//        s3 := g.String("😊😊😊😊😊")
749
//        result3 := s3.Truncate(3)
750
//        // result3: "😊😊😊..."
751
func (s String) Truncate(max Int) String {
11✔
752
        if max.IsNegative() {
12✔
753
                return s
1✔
754
        }
1✔
755

756
        if s.IsASCII() {
18✔
757
                if Int(len(s)) <= max {
13✔
758
                        return s
5✔
759
                }
5✔
760

761
                return s[:max].Append("...")
3✔
762
        }
763

764
        i := 0
2✔
765
        for count := Int(0); i < len(s); count++ {
13✔
766
                if count == max {
13✔
767
                        return s[:i].Append("...")
2✔
768
                }
2✔
769

770
                _, sz := utf8.DecodeRuneInString(string(s)[i:])
9✔
771
                i += sz
9✔
772
        }
773

NEW
774
        return s
×
775
}
776

777
// LeftJustify justifies the String to the left by adding padding to the right, up to the
778
// specified length. If the length of the String is already greater than or equal to the specified
779
// length, or the pad is empty, the original String is returned.
780
//
781
// The padding String is repeated as necessary to fill the remaining length.
782
// The padding is added to the right of the String.
783
//
784
// Parameters:
785
//   - length: The desired length of the resulting justified String.
786
//   - pad: The String used as padding.
787
//
788
// Example usage:
789
//
790
//        s := g.String("Hello")
791
//        result := s.LeftJustify(10, "...")
792
//        // result: "Hello....."
793
func (s String) LeftJustify(length Int, pad String) String {
4✔
794
        rlen := s.LenRunes()
4✔
795
        if rlen >= length || pad.IsEmpty() {
7✔
796
                return s
3✔
797
        }
3✔
798

799
        var b Builder
1✔
800

1✔
801
        _, _ = b.WriteString(s)
1✔
802
        writePadding(&b, pad, pad.LenRunes(), length-rlen)
1✔
803

1✔
804
        return b.String()
1✔
805
}
806

807
// RightJustify justifies the String to the right by adding padding to the left, up to the
808
// specified length. If the length of the String is already greater than or equal to the specified
809
// length, or the pad is empty, the original String is returned.
810
//
811
// The padding String is repeated as necessary to fill the remaining length.
812
// The padding is added to the left of the String.
813
//
814
// Parameters:
815
//   - length: The desired length of the resulting justified String.
816
//   - pad: The String used as padding.
817
//
818
// Example usage:
819
//
820
//        s := g.String("Hello")
821
//        result := s.RightJustify(10, "...")
822
//        // result: ".....Hello"
823
func (s String) RightJustify(length Int, pad String) String {
4✔
824
        rlen := s.LenRunes()
4✔
825
        if rlen >= length || pad.IsEmpty() {
7✔
826
                return s
3✔
827
        }
3✔
828

829
        var b Builder
1✔
830

1✔
831
        writePadding(&b, pad, pad.LenRunes(), length-rlen)
1✔
832
        _, _ = b.WriteString(s)
1✔
833

1✔
834
        return b.String()
1✔
835
}
836

837
// Center justifies the String by adding padding on both sides, up to the specified length.
838
// If the length of the String is already greater than or equal to the specified length, or the
839
// pad is empty, the original String is returned.
840
//
841
// The padding String is repeated as necessary to evenly distribute the remaining length on both
842
// sides.
843
// The padding is added to the left and right of the String.
844
//
845
// Parameters:
846
//   - length: The desired length of the resulting justified String.
847
//   - pad: The String used as padding.
848
//
849
// Example usage:
850
//
851
//        s := g.String("Hello")
852
//        result := s.Center(10, "...")
853
//        // result: "..Hello..."
854
func (s String) Center(length Int, pad String) String {
4✔
855
        slen := s.LenRunes()
4✔
856
        if slen >= length || pad.IsEmpty() {
7✔
857
                return s
3✔
858
        }
3✔
859

860
        var b Builder
1✔
861

1✔
862
        padlen := pad.LenRunes()
1✔
863
        remains := length - slen
1✔
864

1✔
865
        writePadding(&b, pad, padlen, remains/2)
1✔
866
        _, _ = b.WriteString(s)
1✔
867
        writePadding(&b, pad, padlen, (remains+1)/2)
1✔
868

1✔
869
        return b.String()
1✔
870
}
871

872
// writePadding writes the padding String to the output Builder to fill the remaining length.
873
// It repeats the padding String as necessary and appends any remaining runes from the padding
874
// String.
875
func writePadding(b *Builder, pad String, padlen, remains Int) {
4✔
876
        if repeats := remains / padlen; repeats > 0 {
8✔
877
                _, _ = b.WriteString(pad.Repeat(repeats))
4✔
878
        }
4✔
879

880
        rem := remains % padlen
4✔
881
        if rem == 0 {
8✔
882
                return
4✔
883
        }
4✔
884

NEW
885
        if pad.IsASCII() {
×
NEW
886
                for i := range rem {
×
NEW
887
                        b.WriteByte(pad[i])
×
NEW
888
                }
×
889

NEW
890
                return
×
891
        }
892

NEW
893
        i := 0
×
NEW
894
        for range rem {
×
NEW
895
                r, sz := utf8.DecodeRuneInString(string(pad)[i:])
×
NEW
896
                _, _ = b.WriteRune(r)
×
NEW
897
                i += sz
×
UNCOV
898
        }
×
899
}
900

901
// Print writes the content of the String to the standard output (console)
902
// and returns the String unchanged.
903
func (s String) Print() String { fmt.Print(s); return s }
1✔
904

905
// Println writes the content of the String to the standard output (console) with a newline
906
// and returns the String unchanged.
907
func (s String) Println() String { fmt.Println(s); return s }
1✔
908

909
// Scan implements the database/sql.Scanner interface for g.String.
910
//
911
// Behavior:
912
//   - If src is nil, the String is set to an empty string.
913
//   - If src is a string, it is directly assigned.
914
//   - If src is a []byte, it is converted to a string.
915
//   - Otherwise, an error is returned.
916
//
917
// Supported SQL types (common):
918
//   - TEXT / VARCHAR → string
919
//   - BLOB / BYTEA  → []byte (converted to string)
920
//
921
// Notes:
922
//   - This method allows g.String to be used directly with database/sql and compatible drivers.
923
func (s *String) Scan(src any) error {
5✔
924
        if src == nil {
6✔
925
                *s = ""
1✔
926
                return nil
1✔
927
        }
1✔
928

929
        switch v := src.(type) {
4✔
930
        case string:
1✔
931
                *s = String(v)
1✔
932
                return nil
1✔
933
        case []byte:
2✔
934
                *s = String(v)
2✔
935
                return nil
2✔
936
        default:
1✔
937
                return fmt.Errorf("g.String.Scan: cannot scan %T into g.String", src)
1✔
938
        }
939
}
940

941
// Value implements the database/sql/driver.Valuer interface for g.String.
942
//
943
// Behavior:
944
//   - Returns the underlying string value, ready for database insertion.
945
//   - Always returns a value compatible with SQL TEXT / VARCHAR types.
946
func (s String) Value() (driver.Value, error) { return string(s), nil }
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc