mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-04 22:46:49 +02:00
1038 lines
32 KiB
Go
1038 lines
32 KiB
Go
package g
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"math/big"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
"unsafe"
|
|
|
|
"golang.org/x/text/unicode/norm"
|
|
|
|
"github.com/enetx/g/cmp"
|
|
)
|
|
|
|
// String is a wrapper around the string type.
|
|
type String string
|
|
|
|
// Named is a map-like type that stores key-value pairs for resolving named
|
|
// placeholders in Format.
|
|
type Named Map[String, any]
|
|
|
|
// NewString creates a new String from the provided string, rune, byte, rune slice, or byte slice.
|
|
func NewString[T ~string | rune | byte | ~[]rune | ~[]byte](str T) String { return String(str) }
|
|
|
|
// Clone returns a copy of the String.
|
|
// It ensures that the returned String does not share underlying memory with the original String,
|
|
// making it safe to modify or store independently.
|
|
func (s String) Clone() String { return String(strings.Clone(s.Std())) }
|
|
|
|
// Transform applies a transformation function to the String and returns the result.
|
|
func (s String) Transform[U any](fn func(String) U) U { return fn(s) }
|
|
|
|
// Builder returns a new Builder initialized with the content of the String.
|
|
func (s String) Builder() *Builder {
|
|
b := new(Builder)
|
|
b.WriteString(s)
|
|
return b
|
|
}
|
|
|
|
// Min returns the minimum of Strings.
|
|
func (s String) Min(b ...String) String { return cmp.Min(append(b, s)...) }
|
|
|
|
// Max returns the maximum of Strings.
|
|
func (s String) Max(b ...String) String { return cmp.Max(append(b, s)...) }
|
|
|
|
// IsASCII checks if all characters in the String are ASCII bytes.
|
|
func (s String) IsASCII() bool {
|
|
for i := range s {
|
|
if s[i] >= 0x80 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsDigit checks if all characters in the String are digits.
|
|
func (s String) IsDigit() bool {
|
|
if s.IsEmpty() {
|
|
return false
|
|
}
|
|
|
|
for _, c := range s {
|
|
if !unicode.IsDigit(c) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// TryInt tries to parse the String as an int and returns an Int.
|
|
func (s String) TryInt() Result[Int] {
|
|
hint, err := strconv.ParseInt(s.Std(), 0, 64)
|
|
if err != nil {
|
|
return Err[Int](Errorf("{:w}: \"{}\"", ErrParseInt, s))
|
|
}
|
|
|
|
return Ok(Int(hint))
|
|
}
|
|
|
|
// TryBigInt attempts to convert the String receiver into a Result containing a *big.Int.
|
|
// This function assumes the string represents a numerical value, which can be in decimal,
|
|
// hexadecimal (prefixed with "0x"), or octal (prefixed with "0") format. The function
|
|
// leverages the SetString method of the math/big package, automatically detecting the
|
|
// numeric base when set to 0.
|
|
//
|
|
// If the string is correctly formatted and represents a valid number, TryBigInt returns
|
|
// an Ok containing the *big.Int parsed from the string. If the string is empty, contains
|
|
// invalid characters, or does not conform to a recognizable numeric format, TryBigInt
|
|
// returns an Err describing the invalid input.
|
|
//
|
|
// Returns:
|
|
// - A Result[*big.Int] encapsulating the conversion result. It returns Ok[*big.Int]
|
|
// with the parsed value if successful, otherwise Err[*big.Int] if the parsing fails.
|
|
func (s String) TryBigInt() Result[*big.Int] {
|
|
if bigInt, ok := new(big.Int).SetString(s.Std(), 0); ok {
|
|
return Ok(bigInt)
|
|
}
|
|
|
|
return Err[*big.Int](Errorf("{:w}: \"{}\"", ErrParseBigInt, s))
|
|
}
|
|
|
|
// TryFloat tries to parse the String as a float64 and returns an Float.
|
|
func (s String) TryFloat() Result[Float] {
|
|
float, err := strconv.ParseFloat(s.Std(), 64)
|
|
if err != nil {
|
|
return Err[Float](Errorf("{:w}: \"{}\"", ErrParseFloat, s))
|
|
}
|
|
|
|
return Ok(Float(float))
|
|
}
|
|
|
|
// TryBool tries to parse the String as a bool and returns the result.
|
|
// It accepts the values understood by strconv.ParseBool: 1, t, T, TRUE, true,
|
|
// True, 0, f, F, FALSE, false, False.
|
|
func (s String) TryBool() Result[bool] {
|
|
b, err := strconv.ParseBool(s.Std())
|
|
if err != nil {
|
|
return Err[bool](Errorf("{:w}: \"{}\"", ErrParseBool, s))
|
|
}
|
|
|
|
return Ok(b)
|
|
}
|
|
|
|
// TryUint tries to parse the String as an unsigned integer and returns a uint.
|
|
// The base is inferred from the prefix (0x, 0o/0, 0b), matching TryInt.
|
|
func (s String) TryUint() Result[uint] {
|
|
u, err := strconv.ParseUint(s.Std(), 0, 64)
|
|
if err != nil {
|
|
return Err[uint](Errorf("{:w}: \"{}\"", ErrParseUint, s))
|
|
}
|
|
|
|
return Ok(uint(u))
|
|
}
|
|
|
|
// TryComplex tries to parse the String as a complex number and returns a complex128.
|
|
func (s String) TryComplex() Result[complex128] {
|
|
c, err := strconv.ParseComplex(s.Std(), 128)
|
|
if err != nil {
|
|
return Err[complex128](Errorf("{:w}: \"{}\"", ErrParseComplex, s))
|
|
}
|
|
|
|
return Ok(c)
|
|
}
|
|
|
|
// Title converts the String to title case.
|
|
func (s String) Title() String { return String(title.String(s.Std())) }
|
|
|
|
// Lower returns the String in lowercase.
|
|
func (s String) Lower() String { return s.BytesUnsafe().Lower().StringUnsafe() }
|
|
|
|
// Upper returns the String in uppercase.
|
|
func (s String) Upper() String { return s.BytesUnsafe().Upper().StringUnsafe() }
|
|
|
|
// IsLower checks if the String consists only of lowercase letters.
|
|
func (s String) IsLower() bool { return s.BytesUnsafe().IsLower() }
|
|
|
|
// IsUpper checks if the String consists only of uppercase letters.
|
|
func (s String) IsUpper() bool { return s.BytesUnsafe().IsUpper() }
|
|
|
|
// IsTitle checks if the String is in title case.
|
|
func (s String) IsTitle() bool { return s.BytesUnsafe().IsTitle() }
|
|
|
|
// Trim removes leading and trailing white space from the String.
|
|
func (s String) Trim() String { return String(strings.TrimSpace(s.Std())) }
|
|
|
|
// TrimStart removes leading white space from the String.
|
|
func (s String) TrimStart() String { return String(strings.TrimLeftFunc(s.Std(), unicode.IsSpace)) }
|
|
|
|
// TrimEnd removes trailing white space from the String.
|
|
func (s String) TrimEnd() String { return String(strings.TrimRightFunc(s.Std(), unicode.IsSpace)) }
|
|
|
|
// TrimSet removes the specified set of characters from both the beginning and end of the String.
|
|
func (s String) TrimSet(cutset String) String { return String(strings.Trim(s.Std(), cutset.Std())) }
|
|
|
|
// TrimStartSet removes the specified set of characters from the beginning of the String.
|
|
func (s String) TrimStartSet(cutset String) String {
|
|
return String(strings.TrimLeft(s.Std(), cutset.Std()))
|
|
}
|
|
|
|
// TrimEndSet removes the specified set of characters from the end of the String.
|
|
func (s String) TrimEndSet(cutset String) String {
|
|
return String(strings.TrimRight(s.Std(), cutset.Std()))
|
|
}
|
|
|
|
// StripPrefix trims the specified prefix from the String.
|
|
func (s String) StripPrefix(prefix String) String {
|
|
return String(strings.TrimPrefix(s.Std(), prefix.Std()))
|
|
}
|
|
|
|
// StripSuffix trims the specified suffix from the String.
|
|
func (s String) StripSuffix(suffix String) String {
|
|
return String(strings.TrimSuffix(s.Std(), suffix.Std()))
|
|
}
|
|
|
|
// Replace replaces the 'oldS' String with the 'newS' String for the specified number of
|
|
// occurrences.
|
|
func (s String) Replace(oldS, newS String, n Int) String {
|
|
return String(strings.Replace(s.Std(), oldS.Std(), newS.Std(), n.Std()))
|
|
}
|
|
|
|
// ReplaceAll replaces all occurrences of the 'oldS' String with the 'newS' String.
|
|
func (s String) ReplaceAll(oldS, newS String) String {
|
|
return String(strings.ReplaceAll(s.Std(), oldS.Std(), newS.Std()))
|
|
}
|
|
|
|
// ReplaceMulti creates a custom replacer to perform multiple string replacements.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// - oldnew ...String: Pairs of strings to be replaced. Specify as many pairs as needed.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - String: A new string with replacements applied using the custom replacer.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// original := g.String("Hello, world! This is a test.")
|
|
// replaced := original.ReplaceMulti(
|
|
// "Hello", "Greetings",
|
|
// "world", "universe",
|
|
// "test", "example",
|
|
// )
|
|
// // replaced contains "Greetings, universe! This is a example."
|
|
func (s String) ReplaceMulti(oldnew ...String) String {
|
|
pairs := make([]string, len(oldnew))
|
|
for i, str := range oldnew {
|
|
pairs[i] = str.Std()
|
|
}
|
|
|
|
return String(strings.NewReplacer(pairs...).Replace(s.Std()))
|
|
}
|
|
|
|
// Remove removes all occurrences of specified substrings from the String.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// - matches ...String: Substrings to be removed from the string. Specify as many substrings as needed.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - String: A new string with all specified substrings removed.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// original := g.String("Hello, world! This is a test.")
|
|
// modified := original.Remove(
|
|
// "Hello",
|
|
// "test",
|
|
// )
|
|
// // modified contains ", world! This is a ."
|
|
func (s String) Remove(matches ...String) String {
|
|
if len(matches) == 0 {
|
|
return s
|
|
}
|
|
|
|
pairs := make([]string, len(matches)*2)
|
|
for i, match := range matches {
|
|
pairs[i*2] = match.Std()
|
|
pairs[i*2+1] = ""
|
|
}
|
|
|
|
return String(strings.NewReplacer(pairs...).Replace(s.Std()))
|
|
}
|
|
|
|
// ReplaceNth returns a new String instance with the nth occurrence of oldS
|
|
// replaced with newS. If there aren't enough occurrences of oldS, the
|
|
// original String is returned. If n is less than -1, the original String
|
|
// is also returned. If n is -1, the last occurrence of oldS is replaced with newS.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - A new String instance with the nth occurrence of oldS replaced with newS.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("The quick brown dog jumped over the lazy dog.")
|
|
// result := s.ReplaceNth("dog", "fox", 2)
|
|
// fmt.Println(result)
|
|
//
|
|
// Output: "The quick brown dog jumped over the lazy fox.".
|
|
func (s String) ReplaceNth(oldS, newS String, n Int) String {
|
|
if n < -1 || len(oldS) == 0 {
|
|
return s
|
|
}
|
|
|
|
count, i := Int(0), Int(0)
|
|
|
|
for {
|
|
pos := s[i:].Index(oldS)
|
|
if pos == -1 {
|
|
break
|
|
}
|
|
|
|
pos += i
|
|
count++
|
|
|
|
if count == n || (n == -1 && s[pos+oldS.Len():].Index(oldS) == -1) {
|
|
var b Builder
|
|
b.WriteString(s[:pos])
|
|
b.WriteString(newS)
|
|
b.WriteString(s[pos+oldS.Len():])
|
|
|
|
return b.String()
|
|
}
|
|
|
|
i = pos + oldS.Len()
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Contains checks if the String contains the specified substring.
|
|
func (s String) Contains(substr String) bool { return strings.Contains(s.Std(), substr.Std()) }
|
|
|
|
// ContainsAny checks if the String contains any of the specified substrings.
|
|
func (s String) ContainsAny(substrs ...String) bool {
|
|
return slices.ContainsFunc(substrs, s.Contains)
|
|
}
|
|
|
|
// ContainsAll checks if the given String contains all the specified substrings.
|
|
func (s String) ContainsAll(substrs ...String) bool {
|
|
for _, substr := range substrs {
|
|
if !s.Contains(substr) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ContainsAnyChars checks if the String contains any characters from the specified String.
|
|
func (s String) ContainsAnyChars(chars String) bool {
|
|
return strings.ContainsAny(s.Std(), chars.Std())
|
|
}
|
|
|
|
// StartsWith checks if the String starts with the specified prefix.
|
|
func (s String) StartsWith(prefix String) bool { return strings.HasPrefix(s.Std(), prefix.Std()) }
|
|
|
|
// StartsWithAny checks if the String starts with any of the provided prefixes.
|
|
// The method accepts a variable number of arguments, allowing for checking against multiple
|
|
// prefixes at once. It iterates over the provided prefixes and uses the HasPrefix function from
|
|
// the strings package to check if the String starts with each prefix.
|
|
// The function returns true if the String starts with any of the prefixes, and false otherwise.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("http://example.com")
|
|
// if s.StartsWithAny("http://", "https://") {
|
|
// // do something
|
|
// }
|
|
func (s String) StartsWithAny(prefixes ...String) bool {
|
|
return slices.ContainsFunc(prefixes, s.StartsWith)
|
|
}
|
|
|
|
// EndsWith checks if the String ends with the specified suffix.
|
|
func (s String) EndsWith(suffix String) bool { return strings.HasSuffix(s.Std(), suffix.Std()) }
|
|
|
|
// EndsWithAny checks if the String ends with any of the provided suffixes.
|
|
// The method accepts a variable number of arguments, allowing for checking against multiple
|
|
// suffixes at once. It iterates over the provided suffixes and uses the HasSuffix function from
|
|
// the strings package to check if the String ends with each suffix.
|
|
// The function returns true if the String ends with any of the suffixes, and false otherwise.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("example.com")
|
|
// if s.EndsWithAny(".com", ".net") {
|
|
// // do something
|
|
// }
|
|
func (s String) EndsWithAny(suffixes ...String) bool {
|
|
return slices.ContainsFunc(suffixes, s.EndsWith)
|
|
}
|
|
|
|
// Lines splits the String by lines, with trailing whitespace trimmed per
|
|
// line. The substrings share the receiver's backing memory.
|
|
//
|
|
// It returns a plain []String — deliberately not a Seq or Slice: a named
|
|
// generic type in the signature would make every package that names g.String
|
|
// compile the whole container machinery. Convert with g.SliceOf(parts...) for
|
|
// chaining; for lazy streaming over files use fs.File.Lines.
|
|
func (s String) Lines() []String {
|
|
var result []String
|
|
|
|
for line := range strings.Lines(s.Std()) {
|
|
result = append(result, String(line).TrimEnd())
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Fields splits the String around whitespace. See [String.Lines] for why the
|
|
// return type is a plain slice.
|
|
func (s String) Fields() []String {
|
|
return castStrings(strings.Fields(s.Std()))
|
|
}
|
|
|
|
// FieldsBy splits the String using a custom function to determine the field
|
|
// boundaries. See [String.Lines] for why the return type is a plain slice.
|
|
func (s String) FieldsBy(fn func(r rune) bool) []String {
|
|
return castStrings(strings.FieldsFunc(s.Std(), fn))
|
|
}
|
|
|
|
// Split splits the String by the specified separator. If sep is empty, the
|
|
// String is split after each UTF-8 rune. See [String.Lines] for why the return
|
|
// type is a plain slice.
|
|
func (s String) Split(sep String) []String {
|
|
return castStrings(strings.Split(s.Std(), sep.Std()))
|
|
}
|
|
|
|
// SplitAfter splits the String after each instance of the specified separator.
|
|
// See [String.Lines] for why the return type is a plain slice.
|
|
func (s String) SplitAfter(sep String) []String {
|
|
return castStrings(strings.SplitAfter(s.Std(), sep.Std()))
|
|
}
|
|
|
|
// SplitN splits the String into substrings using the provided separator and
|
|
// returns a plain []String of the results (convert with Slice[String] for
|
|
// chaining). The n parameter controls the number of substrings to return:
|
|
// - If n is negative, there is no limit on the number of substrings returned.
|
|
// - If n is zero, an empty slice is returned.
|
|
// - If n is positive, at most n substrings are returned.
|
|
func (s String) SplitN(sep String, n Int) []String {
|
|
parts := strings.SplitN(s.Std(), sep.Std(), n.Std())
|
|
|
|
result := make([]String, len(parts))
|
|
for i, p := range parts {
|
|
result[i] = String(p)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Chunks splits the String into chunks of the specified size.
|
|
//
|
|
// This function iterates through the String, creating new String chunks of the specified size.
|
|
// If size is less than or equal to 0 or the String is empty, it returns nil.
|
|
// If size is greater than or equal to the length of the String, it returns the
|
|
// original String as the only chunk.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// - size (Int): The size of the chunks to split the String into.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - []String: the String chunks of the specified size.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// text := g.String("Hello, World!")
|
|
// chunks := text.Chunks(4)
|
|
//
|
|
// chunks contains {"Hell", "o, W", "orld", "!"}.
|
|
func (s String) Chunks(size Int) []String {
|
|
if size.Lte(0) || s.IsEmpty() {
|
|
return nil
|
|
}
|
|
|
|
if s.IsASCII() {
|
|
n := size.Std()
|
|
l := len(s)
|
|
|
|
if n >= l {
|
|
return []String{s}
|
|
}
|
|
|
|
result := make([]String, 0, (l+n-1)/n)
|
|
for i := 0; i < l; i += n {
|
|
result = append(result, s[i:min(i+n, l)])
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
n := size.Std()
|
|
|
|
var result []String
|
|
|
|
rest := s
|
|
for !rest.IsEmpty() {
|
|
i := 0
|
|
str := rest.Std()
|
|
for count := 0; count < n && i < len(rest); count++ {
|
|
_, sz := utf8.DecodeRuneInString(str[i:])
|
|
i += sz
|
|
}
|
|
|
|
result = append(result, rest[:i])
|
|
rest = rest[i:]
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Cut returns two String values. The first String contains the remainder of the
|
|
// original String after the cut. The second String contains the text between the
|
|
// first occurrences of the 'start' and 'end' strings, with tags removed if specified.
|
|
//
|
|
// The function searches for the 'start' and 'end' strings within the String.
|
|
// If both are found, it returns the first String containing the remainder of the
|
|
// original String after the cut, followed by the second String containing the text
|
|
// between the first occurrences of 'start' and 'end' with tags removed if specified.
|
|
//
|
|
// If either 'start' or 'end' is empty or not found in the String, it returns the
|
|
// original String as the first String, and an empty String as the second.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// - start (String): The String marking the beginning of the text to be cut.
|
|
//
|
|
// - end (String): The String marking the end of the text to be cut.
|
|
//
|
|
// - rmtags (bool, optional): An optional boolean parameter indicating whether
|
|
// to remove the matched region (including the 'start' and 'end' tags) from the
|
|
// remainder. Defaults to false, in which case the remainder equals the original
|
|
// String and only the cut content is extracted.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - String: The first String containing the remainder of the original String.
|
|
// When rmtags is true the matched region is removed from it; otherwise it is
|
|
// the original String. Returns the original String if 'start' or 'end' is empty or not found.
|
|
//
|
|
// - String: The second String containing the text between the first occurrences of
|
|
// 'start' and 'end', or an empty String if 'start' or 'end' is empty or not found.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("Hello, [world]! How are you?")
|
|
// remainder, cut := s.Cut("[", "]", true)
|
|
// // remainder: "Hello, ! How are you?"
|
|
// // cut: "world"
|
|
func (s String) Cut(start, end String, rmtags ...bool) (String, String) {
|
|
if start.IsEmpty() || end.IsEmpty() {
|
|
return s, ""
|
|
}
|
|
|
|
startIndex := s.Index(start)
|
|
if startIndex == -1 {
|
|
return s, ""
|
|
}
|
|
|
|
startEnd := startIndex + start.Len()
|
|
endIndex := s[startEnd:].Index(end)
|
|
if endIndex == -1 {
|
|
return s, ""
|
|
}
|
|
|
|
cut := s[startEnd : startEnd+endIndex]
|
|
|
|
if len(rmtags) == 0 || !rmtags[0] {
|
|
return s, cut
|
|
}
|
|
|
|
return s[:startIndex] + s[startEnd+endIndex+end.Len():], cut
|
|
}
|
|
|
|
// Similarity calculates the similarity between two Strings using the
|
|
// Levenshtein distance algorithm and returns the similarity percentage as an Float.
|
|
//
|
|
// The function compares two Strings using the Levenshtein distance,
|
|
// which measures the difference between two sequences by counting the number
|
|
// of single-character edits required to change one sequence into the other.
|
|
// The similarity is then calculated by normalizing the distance by the maximum
|
|
// length of the two input Strings.
|
|
//
|
|
// Parameters:
|
|
//
|
|
// - str (String): The String to compare with s.
|
|
//
|
|
// Returns:
|
|
//
|
|
// - Float: The similarity percentage between the two Strings as a value between 0 and 100.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s1 := g.String("kitten")
|
|
// s2 := g.String("sitting")
|
|
// similarity := s1.Similarity(s2) // 57.14285714285714
|
|
func (s String) Similarity(str String) Float {
|
|
if s.Eq(str) {
|
|
return 100
|
|
}
|
|
|
|
if s.IsEmpty() || str.IsEmpty() {
|
|
return 0
|
|
}
|
|
|
|
s1 := s.Runes()
|
|
s2 := str.Runes()
|
|
|
|
n1, n2 := len(s1), len(s2)
|
|
|
|
if n1 > n2 {
|
|
s1, s2, n1, n2 = s2, s1, n2, n1
|
|
}
|
|
|
|
distance := make([]int, n1+1)
|
|
|
|
for i, r2 := range s2 {
|
|
prev := i + 1
|
|
|
|
for j, r1 := range s1 {
|
|
current := distance[j]
|
|
if r2 != r1 {
|
|
current = min(distance[j]+1, min(prev+1, distance[j+1]+1))
|
|
}
|
|
|
|
distance[j], prev = prev, current
|
|
}
|
|
|
|
distance[n1] = prev
|
|
}
|
|
|
|
return Float(1-float64(distance[n1])/float64(max(n1, n2))) * 100
|
|
}
|
|
|
|
// Cmp compares two Strings and returns an cmp.Ordering indicating their relative order.
|
|
// The result will be cmp.Equal if s==str, cmp.Less if s < str, and cmp.Greater if s > str.
|
|
func (s String) Cmp(str String) cmp.Ordering { return cmp.Cmp(s, str) }
|
|
|
|
// Append appends the specified String to the current String.
|
|
func (s String) Append(str String) String { return s + str }
|
|
|
|
// Prepend prepends the specified String to the current String.
|
|
func (s String) Prepend(str String) String { return str + s }
|
|
|
|
// ContainsRune checks if the String contains the specified rune.
|
|
func (s String) ContainsRune(r rune) bool { return strings.ContainsRune(s.Std(), r) }
|
|
|
|
// Count returns the number of non-overlapping instances of the substring in the String.
|
|
func (s String) Count(substr String) Int { return Int(strings.Count(s.Std(), substr.Std())) }
|
|
|
|
// IsEmpty checks if the String is empty.
|
|
func (s String) IsEmpty() bool { return len(s) == 0 }
|
|
|
|
// Eq checks if two Strings are equal.
|
|
func (s String) Eq(str String) bool { return s == str }
|
|
|
|
// EqFold compares two String strings case-insensitively.
|
|
func (s String) EqFold(str String) bool { return strings.EqualFold(s.Std(), str.Std()) }
|
|
|
|
// Gt checks if the String is greater than the specified String.
|
|
func (s String) Gt(str String) bool { return s > str }
|
|
|
|
// Gte checks if the String is greater than or equal to the specified String.
|
|
func (s String) Gte(str String) bool { return s >= str }
|
|
|
|
// Bytes returns the String as an Bytes.
|
|
func (s String) Bytes() Bytes { return Bytes(s) }
|
|
|
|
// BytesUnsafe converts the String into Bytes without copying memory.
|
|
// Warning: the resulting Bytes shares the same underlying memory as the original String.
|
|
// If the original String is modified through unsafe operations (rare), or if it is garbage collected,
|
|
// the Bytes may become invalid or cause undefined behavior.
|
|
func (s String) BytesUnsafe() Bytes { return unsafe.Slice(unsafe.StringData(s.Std()), len(s)) }
|
|
|
|
// Index returns the index of the first instance of the specified substring in the String, or -1
|
|
// if substr is not present in s.
|
|
func (s String) Index(substr String) Int { return Int(strings.Index(s.Std(), substr.Std())) }
|
|
|
|
// LastIndex returns the index of the last instance of the specified substring in the String, or -1
|
|
// if substr is not present in s.
|
|
func (s String) LastIndex(substr String) Int { return Int(strings.LastIndex(s.Std(), substr.Std())) }
|
|
|
|
// IndexRune returns the index of the first instance of the specified rune in the String.
|
|
func (s String) IndexRune(r rune) Int { return Int(strings.IndexRune(s.Std(), r)) }
|
|
|
|
// IndexByte returns the index of the first instance of the specified byte in the String, or -1
|
|
// if b is not present in s.
|
|
func (s String) IndexByte(b byte) Int { return Int(strings.IndexByte(s.Std(), b)) }
|
|
|
|
// LastIndexByte returns the index of the last instance of the specified byte in the String, or -1
|
|
// if b is not present in s.
|
|
func (s String) LastIndexByte(b byte) Int { return Int(strings.LastIndexByte(s.Std(), b)) }
|
|
|
|
// Len returns the length of the String.
|
|
func (s String) Len() Int { return Int(len(s)) }
|
|
|
|
// LenRunes returns the number of runes in the String.
|
|
func (s String) LenRunes() Int { return Int(utf8.RuneCountInString(s.Std())) }
|
|
|
|
// Lt checks if the String is less than the specified String.
|
|
func (s String) Lt(str String) bool { return s < str }
|
|
|
|
// Lte checks if the String is less than or equal to the specified String.
|
|
func (s String) Lte(str String) bool { return s <= str }
|
|
|
|
// Map applies the provided function to all runes in the String and returns the resulting String.
|
|
func (s String) Map(fn func(rune) rune) String { return String(strings.Map(fn, s.Std())) }
|
|
|
|
// Ne checks if two Strings are not equal.
|
|
func (s String) Ne(str String) bool { return !s.Eq(str) }
|
|
|
|
// Reader returns a *strings.Reader initialized with the content of String.
|
|
func (s String) Reader() *strings.Reader { return strings.NewReader(s.Std()) }
|
|
|
|
// Repeat returns a new String consisting of the specified count of the original String.
|
|
func (s String) Repeat(count Int) String { return String(strings.Repeat(s.Std(), count.Std())) }
|
|
|
|
// Reverse reverses the String.
|
|
func (s String) Reverse() String { return s.BytesUnsafe().Reverse().StringUnsafe() }
|
|
|
|
// Runes returns the String as a plain slice of runes.
|
|
func (s String) Runes() []rune { return []rune(s) }
|
|
|
|
// Chars splits the String into individual UTF-8 characters, equivalent to
|
|
// s.Split(""). Prefer Runes when only code points are needed.
|
|
func (s String) Chars() []String { return s.Split("") }
|
|
|
|
// SubString extracts a substring from the String starting at the 'start' index and ending before the 'end' index.
|
|
// The function also supports an optional 'step' parameter to define the increment between indices in the substring.
|
|
// If 'start' or 'end' index is negative, they represent positions relative to the end of the String:
|
|
// - A negative 'start' index indicates the position from the end of the String, moving backward.
|
|
// - A negative 'end' index indicates the position from the end of the String.
|
|
// The function ensures that indices are adjusted to fall within the valid range of the String's length.
|
|
// Out-of-bounds indices are clamped to the String's bounds instead of panicking;
|
|
// if 'start' exceeds 'end' (for a positive step) the result is an empty String.
|
|
func (s String) SubString(start, end Int, step ...Int) String {
|
|
runes := s.Runes()
|
|
n := Int(len(runes))
|
|
|
|
clamp := func(i Int) Int {
|
|
if i < 0 {
|
|
i += n
|
|
}
|
|
|
|
if i < 0 {
|
|
return 0
|
|
}
|
|
|
|
if i > n {
|
|
return n
|
|
}
|
|
|
|
return i
|
|
}
|
|
|
|
start, end = clamp(start), clamp(end)
|
|
|
|
st := Int(1)
|
|
if len(step) > 0 {
|
|
st = step[0]
|
|
}
|
|
|
|
// For a negative step the iteration starts AT start and moves down,
|
|
// so a start clamped to n must begin at the last element.
|
|
if st < 0 && start == n {
|
|
start--
|
|
}
|
|
|
|
if st == 1 {
|
|
if start >= end {
|
|
return ""
|
|
}
|
|
|
|
return String(runes[start:end])
|
|
}
|
|
|
|
if (start >= end && st > 0) || (start <= end && st < 0) || st == 0 {
|
|
return ""
|
|
}
|
|
|
|
var out []rune
|
|
|
|
if st > 0 {
|
|
for i := start; i < end; i += st {
|
|
out = append(out, runes[i])
|
|
}
|
|
} else {
|
|
for i := start; i > end; i += st {
|
|
out = append(out, runes[i])
|
|
}
|
|
}
|
|
|
|
return String(out)
|
|
}
|
|
|
|
// NormalizeNFC returns a new String with its Unicode characters normalized using the NFC form.
|
|
func (s String) NormalizeNFC() String { return String(norm.NFC.String(s.Std())) }
|
|
|
|
// Std returns the String as a string.
|
|
func (s String) Std() string { return string(s) }
|
|
|
|
// Format applies a specified format to the String object.
|
|
func (s String) Format(template String) String { return Format(template, s) }
|
|
|
|
// Truncate shortens the String to the specified maximum length. If the String exceeds the
|
|
// specified length, it is truncated, and an ellipsis ("...") is appended to indicate the truncation.
|
|
//
|
|
// If the length of the String is less than or equal to the specified maximum length, the
|
|
// original String is returned unchanged.
|
|
//
|
|
// The method respects Unicode characters and truncates based on the number of runes,
|
|
// not bytes.
|
|
//
|
|
// Parameters:
|
|
// - max: The maximum number of runes allowed in the resulting String.
|
|
//
|
|
// Returns:
|
|
// - A new String truncated to the specified maximum length with "..." appended
|
|
// if truncation occurs. Otherwise, returns the original String.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("Hello, World!")
|
|
// result := s.Truncate(5)
|
|
// // result: "Hello..."
|
|
//
|
|
// s2 := g.String("Short")
|
|
// result2 := s2.Truncate(10)
|
|
// // result2: "Short"
|
|
//
|
|
// s3 := g.String("😊😊😊😊😊")
|
|
// result3 := s3.Truncate(3)
|
|
// // result3: "😊😊😊..."
|
|
func (s String) Truncate(max Int) String {
|
|
if max.IsNegative() {
|
|
return s
|
|
}
|
|
|
|
if s.IsASCII() {
|
|
if Int(len(s)) <= max {
|
|
return s
|
|
}
|
|
|
|
return s[:max].Append("...")
|
|
}
|
|
|
|
i := 0
|
|
str := s.Std()
|
|
for count := Int(0); i < len(s); count++ {
|
|
if count == max {
|
|
return s[:i].Append("...")
|
|
}
|
|
|
|
_, sz := utf8.DecodeRuneInString(str[i:])
|
|
i += sz
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// LeftJustify justifies the String to the left by adding padding to the right, up to the
|
|
// specified length. If the length of the String is already greater than or equal to the specified
|
|
// length, or the pad is empty, the original String is returned.
|
|
//
|
|
// The padding String is repeated as necessary to fill the remaining length.
|
|
// The padding is added to the right of the String.
|
|
//
|
|
// Parameters:
|
|
// - length: The desired length of the resulting justified String.
|
|
// - pad: The String used as padding.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("Hello")
|
|
// result := s.LeftJustify(10, "...")
|
|
// // result: "Hello....."
|
|
func (s String) LeftJustify(length Int, pad String) String {
|
|
rlen := s.LenRunes()
|
|
if rlen >= length || pad.IsEmpty() {
|
|
return s
|
|
}
|
|
|
|
var b Builder
|
|
padlen := pad.LenRunes()
|
|
b.Grow(s.Len() + paddingCapacity(pad, padlen, length-rlen))
|
|
|
|
_, _ = b.WriteString(s)
|
|
writePadding(&b, pad, padlen, length-rlen)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// RightJustify justifies the String to the right by adding padding to the left, up to the
|
|
// specified length. If the length of the String is already greater than or equal to the specified
|
|
// length, or the pad is empty, the original String is returned.
|
|
//
|
|
// The padding String is repeated as necessary to fill the remaining length.
|
|
// The padding is added to the left of the String.
|
|
//
|
|
// Parameters:
|
|
// - length: The desired length of the resulting justified String.
|
|
// - pad: The String used as padding.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("Hello")
|
|
// result := s.RightJustify(10, "...")
|
|
// // result: ".....Hello"
|
|
func (s String) RightJustify(length Int, pad String) String {
|
|
rlen := s.LenRunes()
|
|
if rlen >= length || pad.IsEmpty() {
|
|
return s
|
|
}
|
|
|
|
var b Builder
|
|
padlen := pad.LenRunes()
|
|
b.Grow(s.Len() + paddingCapacity(pad, padlen, length-rlen))
|
|
|
|
writePadding(&b, pad, padlen, length-rlen)
|
|
_, _ = b.WriteString(s)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// Center justifies the String by adding padding on both sides, up to the specified length.
|
|
// If the length of the String is already greater than or equal to the specified length, or the
|
|
// pad is empty, the original String is returned.
|
|
//
|
|
// The padding String is repeated as necessary to evenly distribute the remaining length on both
|
|
// sides.
|
|
// The padding is added to the left and right of the String.
|
|
//
|
|
// Parameters:
|
|
// - length: The desired length of the resulting justified String.
|
|
// - pad: The String used as padding.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// s := g.String("Hello")
|
|
// result := s.Center(10, "...")
|
|
// // result: "..Hello..."
|
|
func (s String) Center(length Int, pad String) String {
|
|
slen := s.LenRunes()
|
|
if slen >= length || pad.IsEmpty() {
|
|
return s
|
|
}
|
|
|
|
var b Builder
|
|
|
|
padlen := pad.LenRunes()
|
|
remains := length - slen
|
|
b.Grow(s.Len() + paddingCapacity(pad, padlen, remains))
|
|
|
|
writePadding(&b, pad, padlen, remains/2)
|
|
_, _ = b.WriteString(s)
|
|
writePadding(&b, pad, padlen, (remains+1)/2)
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func paddingCapacity(pad String, padlen, runes Int) Int {
|
|
return ((runes + padlen - 1) / padlen) * pad.Len()
|
|
}
|
|
|
|
// writePadding writes the padding String to the output Builder to fill the remaining length.
|
|
// It repeats the padding String as necessary and appends any remaining runes from the padding
|
|
// String.
|
|
func writePadding(b *Builder, pad String, padlen, remains Int) {
|
|
for range remains / padlen {
|
|
_, _ = b.WriteString(pad)
|
|
}
|
|
|
|
rem := remains % padlen
|
|
if rem == 0 {
|
|
return
|
|
}
|
|
|
|
if pad.IsASCII() {
|
|
for i := range rem {
|
|
b.WriteByte(pad[i])
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
i := 0
|
|
str := pad.Std()
|
|
for range rem {
|
|
r, sz := utf8.DecodeRuneInString(str[i:])
|
|
_, _ = b.WriteRune(r)
|
|
i += sz
|
|
}
|
|
}
|
|
|
|
// Print writes the content of the String to the standard output (console)
|
|
// and returns the String unchanged.
|
|
func (s String) Print() String { fmt.Print(s); return s }
|
|
|
|
// Println writes the content of the String to the standard output (console) with a newline
|
|
// and returns the String unchanged.
|
|
func (s String) Println() String { fmt.Println(s); return s }
|
|
|
|
// Scan implements the database/sql.Scanner interface for g.String.
|
|
//
|
|
// Behavior:
|
|
// - If src is nil, the String is set to an empty string.
|
|
// - If src is a string, it is directly assigned.
|
|
// - If src is a []byte, it is converted to a string.
|
|
// - Otherwise, an error is returned.
|
|
//
|
|
// Supported SQL types (common):
|
|
// - TEXT / VARCHAR → string
|
|
// - BLOB / BYTEA → []byte (converted to string)
|
|
//
|
|
// Notes:
|
|
// - This method allows g.String to be used directly with database/sql and compatible drivers.
|
|
func (s *String) Scan(src any) error {
|
|
if src == nil {
|
|
*s = ""
|
|
return nil
|
|
}
|
|
|
|
switch v := src.(type) {
|
|
case string:
|
|
*s = String(v)
|
|
return nil
|
|
case []byte:
|
|
*s = String(v)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("g.String.Scan: cannot scan %T into g.String", src)
|
|
}
|
|
}
|
|
|
|
// Value implements the database/sql/driver.Valuer interface for g.String.
|
|
//
|
|
// Behavior:
|
|
// - Returns the underlying string value, ready for database insertion.
|
|
// - Always returns a value compatible with SQL TEXT / VARCHAR types.
|
|
func (s String) Value() (driver.Value, error) { return string(s), nil }
|
|
|
|
// castStrings reinterprets a []string as []String without copying: String is
|
|
// defined as `type String string`, so the two slice types share one memory
|
|
// layout.
|
|
func castStrings(ss []string) []String {
|
|
return unsafe.Slice((*String)(unsafe.SliceData(ss)), len(ss))
|
|
}
|