diff --git a/utils/str/string.go b/utils/str/string.go deleted file mode 100644 index f68e71c..0000000 --- a/utils/str/string.go +++ /dev/null @@ -1,146 +0,0 @@ -package str - -import ( - "net/url" - "strings" -) - -type String string - -func New(s string) *String { - return (*String)(&s) -} - -// String 返回字符串 -func (s *String) String() string { - return string(*s) -} - -// Append 追加字符串 -func (s *String) Append(str *String) *String { - *s = String(string(*s) + string(*str)) - return s -} - -// Join 以指定字符连接字符串 -func (s *String) Join(sep string) *String { - *s = String(strings.Join([]string{string(*s), sep}, "")) - return s -} - -// QueryEscape 对字符串进行 URL 编码 -func (s *String) QueryEscape() *String { - *s = String(strings.ReplaceAll(url.QueryEscape(string(*s)), "+", "%20")) - return s -} - -// Replace 替换字符串 -func (s *String) Replace(old, new string, n int) *String { - *s = String(strings.Replace(string(*s), old, new, n)) - return s -} - -// ReplaceAll 替换字符串 -func (s *String) ReplaceAll(old, new string) *String { - *s = String(strings.ReplaceAll(string(*s), old, new)) - return s -} - -// Trim 去除字符串两端的指定字符 -func (s *String) Trim(cs string) *String { - *s = String(strings.Trim(string(*s), cs)) - return s -} - -// TrimLeft 去除字符串左端的指定字符 -func (s *String) TrimLeft(cs string) *String { - *s = String(strings.TrimLeft(string(*s), cs)) - return s -} - -// TrimRight 去除字符串右端的指定字符 -func (s *String) TrimRight(cs string) *String { - *s = String(strings.TrimRight(string(*s), cs)) - return s -} - -// Default 返回字符串,如果字符串为空则返回默认值 -func (s *String) Default(def string) *String { - if string(*s) == "" { - *s = String(def) - } - return s -} - -// Format 格式化字符串 -func (s *String) Format(formatter func(s *String) *String) *String { - *s = *formatter(s) - return s -} - -// Index 返回字符串指定位置的字符 -func (s *String) Index(i int) String { - return String(string(*s)[i]) -} - -// Split 以指定字符分割字符串 -func (s *String) Split(sep string) *Strings { - slice := strings.Split(string(*s), sep) - ss := make(Strings, len(slice)) - for i, v := range slice { - ss[i] = New(v) - } - return &ss -} - -// SplitN 以指定字符分割字符串,最多分割 n 次 -func (s *String) SplitN(sep string, n int) *Strings { - slice := strings.SplitN(string(*s), sep, n) - ss := make(Strings, len(slice)) - for i, v := range slice { - ss[i] = New(v) - } - return &ss -} - -// ToUpper 将字符串转为大写 -func (s *String) ToUpper() *String { - *s = String(strings.ToUpper(string(*s))) - return s -} - -// ToLower 将字符串转为小写 -func (s *String) ToLower() *String { - *s = String(strings.ToLower(string(*s))) - return s -} - -// TrimSpace 去除字符串两端的空白字符 -func (s *String) TrimSpace() *String { - *s = String(strings.TrimSpace(string(*s))) - return s -} - -// TrimPrefix 去除字符串前缀 -func (s *String) TrimPrefix(prefix string) *String { - *s = String(strings.TrimPrefix(string(*s), prefix)) - return s -} - -// TrimSuffix 去除字符串后缀 -func (s *String) TrimSuffix(suffix string) *String { - *s = String(strings.TrimSuffix(string(*s), suffix)) - return s -} - -// TrimSpacePrefix 去除字符串两端的空白字符后再去除前缀 -func (s *String) TrimSpacePrefix(prefix string) *String { - *s = String(strings.TrimPrefix(strings.TrimSpace(string(*s)), prefix)) - return s -} - -// TrimSpaceSuffix 去除字符串两端的空白字符后再去除后缀 -func (s *String) TrimSpaceSuffix(suffix string) *String { - *s = String(strings.TrimSuffix(strings.TrimSpace(string(*s)), suffix)) - return s -} diff --git a/utils/str/string_test.go b/utils/str/string_test.go deleted file mode 100644 index 7ae95b8..0000000 --- a/utils/str/string_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package str_test - -import ( - "github.com/kercylan98/minotaur/utils/str" - "testing" -) - -func TestString_ToLower(t *testing.T) { - var s str.String = "HELLO" - s.ToLower() - t.Log(s) -} diff --git a/utils/str/strings.go b/utils/str/strings.go deleted file mode 100644 index 1e32088..0000000 --- a/utils/str/strings.go +++ /dev/null @@ -1,26 +0,0 @@ -package str - -import "strings" - -type Strings []*String - -// Index 返回字符串指定位置的字符 -func (s *Strings) Index(i int) *String { - return (*s)[i] -} - -// Append 追加字符串 -func (s *Strings) Append(str *String) *Strings { - *s = append(*s, str) - return s -} - -// Join 以指定字符连接字符串 -func (s *Strings) Join(sep string) *String { - var ss []string - for _, v := range *s { - ss = append(ss, v.String()) - } - *s = append(*s, New(strings.Join(ss, sep))) - return (*s)[len(*s)-1] -} diff --git a/utils/stream/string.go b/utils/stream/string.go new file mode 100644 index 0000000..cc79616 --- /dev/null +++ b/utils/stream/string.go @@ -0,0 +1,321 @@ +package stream + +import ( + "strconv" + "strings" +) + +// NewString 创建字符串流 +func NewString[S ~string](s S) *String[S] { + return &String[S]{s} +} + +// String 字符串流 +type String[S ~string] struct { + str S +} + +// Elem 返回字符串 +func (s *String[S]) Elem() S { + return s.str +} + +// String 返回字符串 +func (s *String[S]) String() string { + return string(s.str) +} + +// Index 返回字符串指定位置的字符,当索引超出范围时将会触发 panic +func (s *String[S]) Index(i int) *String[S] { + return NewString(S(s.str[i])) +} + +// Range 返回字符串指定范围的字符 +func (s *String[S]) Range(start, end int) *String[S] { + return NewString(s.str[start:end]) +} + +// TrimSpace 返回去除字符串首尾空白字符的字符串 +func (s *String[S]) TrimSpace() *String[S] { + return NewString(S(strings.TrimSpace(string(s.str)))) +} + +// Trim 返回去除字符串首尾指定字符的字符串 +func (s *String[S]) Trim(cs string) *String[S] { + return NewString(S(strings.Trim(string(s.str), cs))) +} + +// TrimPrefix 返回去除字符串前缀的字符串 +func (s *String[S]) TrimPrefix(prefix string) *String[S] { + return NewString(S(strings.TrimPrefix(string(s.str), prefix))) +} + +// TrimSuffix 返回去除字符串后缀的字符串 +func (s *String[S]) TrimSuffix(suffix string) *String[S] { + return NewString(S(strings.TrimSuffix(string(s.str), suffix))) +} + +// ToUpper 返回字符串的大写形式 +func (s *String[S]) ToUpper() *String[S] { + return NewString(S(strings.ToUpper(string(s.str)))) +} + +// ToLower 返回字符串的小写形式 +func (s *String[S]) ToLower() *String[S] { + return NewString(S(strings.ToLower(string(s.str)))) +} + +// Equal 返回字符串是否相等 +func (s *String[S]) Equal(ss S) bool { + return s.str == ss +} + +// HasPrefix 返回字符串是否包含指定前缀 +func (s *String[S]) HasPrefix(prefix S) bool { + return strings.HasPrefix(string(s.str), string(prefix)) +} + +// HasSuffix 返回字符串是否包含指定后缀 +func (s *String[S]) HasSuffix(suffix S) bool { + return strings.HasSuffix(string(s.str), string(suffix)) +} + +// Len 返回字符串长度 +func (s *String[S]) Len() int { + return len(s.str) +} + +// Contains 返回字符串是否包含指定子串 +func (s *String[S]) Contains(sub S) bool { + return strings.Contains(string(s.str), string(sub)) +} + +// Count 返回字符串包含指定子串的次数 +func (s *String[S]) Count(sub S) int { + return strings.Count(string(s.str), string(sub)) +} + +// Repeat 返回重复 count 次的字符串 +func (s *String[S]) Repeat(count int) *String[S] { + return NewString(S(strings.Repeat(string(s.str), count))) +} + +// Replace 返回替换指定子串后的字符串 +func (s *String[S]) Replace(old, new S, n int) *String[S] { + return NewString(S(strings.Replace(string(s.str), string(old), string(new), n))) +} + +// ReplaceAll 返回替换所有指定子串后的字符串 +func (s *String[S]) ReplaceAll(old, new S) *String[S] { + return NewString(S(strings.ReplaceAll(string(s.str), string(old), string(new)))) +} + +// Append 返回追加指定字符串后的字符串 +func (s *String[S]) Append(ss S) *String[S] { + return NewString(S(string(s.str) + string(ss))) +} + +// Prepend 返回追加指定字符串后的字符串,追加的字符串在前 +func (s *String[S]) Prepend(ss S) *String[S] { + return NewString(S(string(ss) + string(s.str))) +} + +// Clear 返回清空字符串后的字符串 +func (s *String[S]) Clear() *String[S] { + return NewString(S("")) +} + +// Reverse 返回反转字符串后的字符串 +func (s *String[S]) Reverse() *String[S] { + var str = []rune(string(s.str)) + for i, j := 0, len(str)-1; i < j; i, j = i+1, j-1 { + str[i], str[j] = str[j], str[i] + } + return NewString(S(string(str))) +} + +// Queto 返回带引号的字符串 +func (s *String[S]) Queto() *String[S] { + return NewString(S(strconv.Quote(string(s.str)))) +} + +// QuetoToASCII 返回带引号的字符串 +func (s *String[S]) QuetoToASCII() *String[S] { + return NewString(S(strconv.QuoteToASCII(string(s.str)))) +} + +// FirstUpper 返回首字母大写的字符串 +func (s *String[S]) FirstUpper() *String[S] { + var upperStr string + vv := []rune(string(s.str)) + for i := 0; i < len(vv); i++ { + if i == 0 { + if vv[i] >= 97 && vv[i] <= 122 { + vv[i] -= 32 + upperStr += string(vv[i]) + } else { + return s + } + } else { + upperStr += string(vv[i]) + } + } + return NewString(S(upperStr)) +} + +// FirstLower 返回首字母小写的字符串 +func (s *String[S]) FirstLower() *String[S] { + var lowerStr string + vv := []rune(string(s.str)) + for i := 0; i < len(vv); i++ { + if i == 0 { + if vv[i] >= 65 && vv[i] <= 90 { + vv[i] += 32 + lowerStr += string(vv[i]) + } else { + return s + } + } else { + lowerStr += string(vv[i]) + } + } + return NewString(S(lowerStr)) +} + +// SnakeCase 返回蛇形命名的字符串 +func (s *String[S]) SnakeCase() *String[S] { + var str = string(s.str) + var result string + for i, v := range str { + if v >= 65 && v <= 90 { + if i != 0 { + result += "_" + } + result += string(v + 32) + } else { + result += string(v) + } + } + return NewString(S(result)) +} + +// CamelCase 返回驼峰命名的字符串 +func (s *String[S]) CamelCase() *String[S] { + var str = string(s.str) + var result string + var upper = false + for _, v := range str { + if v == 95 { + upper = true + } else { + if upper { + result += string(v - 32) + upper = false + } else { + result += string(v) + } + } + } + return NewString(S(result)) +} + +// KebabCase 返回短横线命名的字符串 +func (s *String[S]) KebabCase() *String[S] { + var str = string(s.str) + var result string + for i, v := range str { + if v >= 65 && v <= 90 { + if i != 0 { + result += "-" + } + result += string(v + 32) + } else { + result += string(v) + } + } + return NewString(S(result)) +} + +// TitleCase 返回标题命名的字符串 +func (s *String[S]) TitleCase() *String[S] { + var str = string(s.str) + var result string + var upper = true + for _, v := range str { + if v == 95 || v == 45 || v == 32 { + upper = true + } else { + if upper { + result += string(v - 32) + upper = false + } else { + result += string(v) + } + } + } + return NewString(S(result)) +} + +// Bytes 返回字符串的字节数组 +func (s *String[S]) Bytes() []byte { + return []byte(s.str) +} + +// Runes 返回字符串的字符数组 +func (s *String[S]) Runes() []rune { + return []rune(s.str) +} + +// Default 当字符串为空时设置默认值 +func (s *String[S]) Default(def S) *String[S] { + if s.str == "" { + return NewString(def) + } + return s +} + +// Handle 处理字符串 +func (s *String[S]) Handle(f func(S)) *String[S] { + f(s.str) + return s +} + +// Update 更新字符串 +func (s *String[S]) Update(f func(S) S) *String[S] { + s.str = f(s.str) + return s +} + +// Split 返回字符串切片 +func (s *String[S]) Split(sep string) Strings[S] { + slice := strings.Split(string(s.str), sep) + rep := make([]S, len(slice)) + for i, v := range slice { + rep[i] = S(v) + } + return NewStrings(rep...) +} + +// SplitN 返回字符串切片 +func (s *String[S]) SplitN(sep string, n int) Strings[S] { + slice := strings.SplitN(string(s.str), sep, n) + rep := make([]S, len(slice)) + for i, v := range slice { + rep[i] = S(v) + } + return NewStrings(rep...) +} + +// Batched 将字符串按照指定长度分组,最后一组可能小于指定长度 +func (s *String[S]) Batched(size int) Strings[S] { + var str = string(s.str) + var result = make([]S, 0, len(str)/size+1) + for len(str) >= size { + result = append(result, S(str[:size])) + str = str[size:] + } + if len(str) > 0 { + result = append(result, S(str)) + } + return NewStrings(result...) +} diff --git a/utils/stream/string_test.go b/utils/stream/string_test.go new file mode 100644 index 0000000..331c7ad --- /dev/null +++ b/utils/stream/string_test.go @@ -0,0 +1,824 @@ +package stream_test + +import ( + "github.com/kercylan98/minotaur/utils/stream" + "testing" +) + +func TestNewString(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "hello"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in) + if got.String() != c.want { + t.Fatalf("NewString(%s) = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_String(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "hello"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).String() + if got != c.want { + t.Fatalf("String(%s).String() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Index(t *testing.T) { + var cases = []struct { + name string + in string + i int + want string + shouldPanic bool + }{ + {name: "case1", in: "hello", i: 0, want: "h", shouldPanic: false}, + {name: "case2", in: "world", i: 2, want: "r", shouldPanic: false}, + {name: "case3", in: "world", i: 5, want: "", shouldPanic: true}, + {name: "case4", in: "world", i: -1, want: "", shouldPanic: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); (r != nil) != c.shouldPanic { + t.Fatalf("NewString(%s).Index(%d) should panic", c.in, c.i) + } + }() + got := stream.NewString(c.in).Index(c.i) + if got.String() != c.want { + t.Fatalf("NewString(%s).Index(%d) = %s; want %s", c.in, c.i, got, c.want) + } + }) + } +} + +func TestString_Range(t *testing.T) { + var cases = []struct { + name string + in string + start int + end int + want string + shouldPanic bool + }{ + {name: "case1", in: "hello", start: 0, end: 2, want: "he", shouldPanic: false}, + {name: "case2", in: "world", start: 2, end: 5, want: "rld", shouldPanic: false}, + {name: "case3", in: "world", start: 5, end: 6, want: "", shouldPanic: true}, + {name: "case4", in: "world", start: -1, end: 6, want: "", shouldPanic: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if r := recover(); (r != nil) != c.shouldPanic { + t.Fatalf("NewString(%s).Range(%d, %d) should panic", c.in, c.start, c.end) + } + }() + got := stream.NewString(c.in).Range(c.start, c.end) + if got.String() != c.want { + t.Fatalf("NewString(%s).Range(%d, %d) = %s; want %s", c.in, c.start, c.end, got, c.want) + } + }) + } +} + +func TestString_TrimSpace(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: " hello ", want: "hello"}, + {name: "case2", in: " world ", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimSpace() + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimSpace() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Trim(t *testing.T) { + var cases = []struct { + name string + in string + cs string + want string + }{ + {name: "case1", in: "hello", cs: "h", want: "ello"}, + {name: "case2", in: "world", cs: "d", want: "worl"}, + {name: "none", in: "world", cs: "", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Trim(c.cs) + if got.String() != c.want { + t.Fatalf("NewString(%s).Trim(%s) = %s; want %s", c.in, c.cs, got, c.want) + } + }) + } +} + +func TestString_TrimPrefix(t *testing.T) { + var cases = []struct { + name string + in string + prefix string + want string + isEqual bool + }{ + {name: "case1", in: "hello", prefix: "h", want: "ello", isEqual: false}, + {name: "case2", in: "world", prefix: "w", want: "orld", isEqual: false}, + {name: "none", in: "world", prefix: "x", want: "world", isEqual: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimPrefix(c.prefix) + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimPrefix(%s) = %s; want %s", c.in, c.prefix, got, c.want) + } + }) + } +} + +func TestString_TrimSuffix(t *testing.T) { + var cases = []struct { + name string + in string + suffix string + want string + isEqual bool + }{ + {name: "case1", in: "hello", suffix: "o", want: "hell", isEqual: false}, + {name: "case2", in: "world", suffix: "d", want: "worl", isEqual: false}, + {name: "none", in: "world", suffix: "x", want: "world", isEqual: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TrimSuffix(c.suffix) + if got.String() != c.want { + t.Fatalf("NewString(%s).TrimSuffix(%s) = %s; want %s", c.in, c.suffix, got, c.want) + } + }) + } +} + +func TestString_ToUpper(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "HELLO"}, + {name: "case2", in: "world", want: "WORLD"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ToUpper() + if got.String() != c.want { + t.Fatalf("NewString(%s).ToUpper() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_ToLower(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "HELLO", want: "hello"}, + {name: "case2", in: "WORLD", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ToLower() + if got.String() != c.want { + t.Fatalf("NewString(%s).ToLower() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Equal(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want bool + }{ + {name: "case1", in: "hello", ss: "hello", want: true}, + {name: "case2", in: "world", ss: "world", want: true}, + {name: "case3", in: "world", ss: "worldx", want: false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Equal(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Equal(%s) = %t; want %t", c.in, c.ss, got, c.want) + } + }) + } +} + +func TestString_HasPrefix(t *testing.T) { + var cases = []struct { + name string + in string + prefix string + want bool + }{ + {name: "case1", in: "hello", prefix: "h", want: true}, + {name: "case2", in: "world", prefix: "w", want: true}, + {name: "case3", in: "world", prefix: "x", want: false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).HasPrefix(c.prefix) + if got != c.want { + t.Fatalf("NewString(%s).HasPrefix(%s) = %t; want %t", c.in, c.prefix, got, c.want) + } + }) + } +} + +func TestString_Len(t *testing.T) { + var cases = []struct { + name string + in string + want int + }{ + {name: "case1", in: "hello", want: 5}, + {name: "case2", in: "world", want: 5}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Len() + if got != c.want { + t.Fatalf("NewString(%s).Len() = %d; want %d", c.in, got, c.want) + } + }) + } +} + +func TestString_Contains(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want bool + }{ + {name: "case1", in: "hello", ss: "he", want: true}, + {name: "case2", in: "world", ss: "or", want: true}, + {name: "case3", in: "world", ss: "x", want: false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Contains(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Contains(%s) = %t; want %t", c.in, c.ss, got, c.want) + } + }) + } +} + +func TestString_Count(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want int + }{ + {name: "case1", in: "hello", ss: "l", want: 2}, + {name: "case2", in: "world", ss: "o", want: 1}, + {name: "case3", in: "world", ss: "x", want: 0}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Count(c.ss) + if got != c.want { + t.Fatalf("NewString(%s).Count(%s) = %d; want %d", c.in, c.ss, got, c.want) + } + }) + } +} + +func TestString_Repeat(t *testing.T) { + var cases = []struct { + name string + in string + count int + want string + }{ + {name: "case1", in: "hello", count: 2, want: "hellohello"}, + {name: "case2", in: "world", count: 3, want: "worldworldworld"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Repeat(c.count) + if got.String() != c.want { + t.Fatalf("NewString(%s).Repeat(%d) = %s; want %s", c.in, c.count, got, c.want) + } + }) + } +} + +func TestString_Replace(t *testing.T) { + var cases = []struct { + name string + in string + old string + new string + want string + }{ + {name: "case1", in: "hello", old: "l", new: "x", want: "hexxo"}, + {name: "case2", in: "world", old: "o", new: "x", want: "wxrld"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Replace(c.old, c.new, -1) + if got.String() != c.want { + t.Fatalf("NewString(%s).Replace(%s, %s) = %s; want %s", c.in, c.old, c.new, got, c.want) + } + }) + } +} + +func TestString_ReplaceAll(t *testing.T) { + var cases = []struct { + name string + in string + old string + new string + want string + }{ + {name: "case1", in: "hello", old: "l", new: "x", want: "hexxo"}, + {name: "case2", in: "world", old: "o", new: "x", want: "wxrld"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).ReplaceAll(c.old, c.new) + if got.String() != c.want { + t.Fatalf("NewString(%s).ReplaceAll(%s, %s) = %s; want %s", c.in, c.old, c.new, got, c.want) + } + }) + } +} + +func TestString_Append(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want string + }{ + {name: "case1", in: "hello", ss: " world", want: "hello world"}, + {name: "case2", in: "world", ss: " hello", want: "world hello"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Append(c.ss) + if got.String() != c.want { + t.Fatalf("NewString(%s).Append(%s) = %s; want %s", c.in, c.ss, got, c.want) + } + }) + } +} + +func TestString_Prepend(t *testing.T) { + var cases = []struct { + name string + in string + ss string + want string + }{ + {name: "case1", in: "hello", ss: "world ", want: "world hello"}, + {name: "case2", in: "world", ss: "hello ", want: "hello world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Prepend(c.ss) + if got.String() != c.want { + t.Fatalf("NewString(%s).Prepend(%s) = %s; want %s", c.in, c.ss, got, c.want) + } + }) + } +} + +func TestString_Clear(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: ""}, + {name: "case2", in: "world", want: ""}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Clear() + if got.String() != c.want { + t.Fatalf("NewString(%s).Clear() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Reverse(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "olleh"}, + {name: "case2", in: "world", want: "dlrow"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Reverse() + if got.String() != c.want { + t.Fatalf("NewString(%s).Reverse() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Queto(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "\"hello\""}, + {name: "case2", in: "world", want: "\"world\""}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Queto() + if got.String() != c.want { + t.Fatalf("NewString(%s).Queto() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_QuetoToASCII(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "\"hello\""}, + {name: "case2", in: "world", want: "\"world\""}, + {name: "case3", in: "你好", want: "\"\\u4f60\\u597d\""}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).QuetoToASCII() + if got.String() != c.want { + t.Fatalf("NewString(%s).QuetoToASCII() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_FirstUpper(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "Hello"}, + {name: "case2", in: "world", want: "World"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).FirstUpper() + if got.String() != c.want { + t.Fatalf("NewString(%s).FirstUpper() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_FirstLower(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "Hello", want: "hello"}, + {name: "case2", in: "World", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).FirstLower() + if got.String() != c.want { + t.Fatalf("NewString(%s).FirstLower() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_SnakeCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "HelloWorld", want: "hello_world"}, + {name: "case2", in: "HelloWorldHello", want: "hello_world_hello"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).SnakeCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).SnakeCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_CamelCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello_world", want: "helloWorld"}, + {name: "case2", in: "hello_world_hello", want: "helloWorldHello"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).CamelCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).CamelCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_KebabCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "HelloWorld", want: "hello-world"}, + {name: "case2", in: "HelloWorldHello", want: "hello-world-hello"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).KebabCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).KebabCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_TitleCase(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello_world", want: "HelloWorld"}, + {name: "case2", in: "hello_world_hello", want: "HelloWorldHello"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).TitleCase() + if got.String() != c.want { + t.Fatalf("NewString(%s).TitleCase() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Bytes(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "hello"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Bytes() + if string(got) != c.want { + t.Fatalf("NewString(%s).Bytes() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Runes(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "hello"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Runes() + if string(got) != c.want { + t.Fatalf("NewString(%s).Runes() = %v; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Default(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "", want: "default"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Default("default") + if got.String() != c.want { + t.Fatalf("NewString(%s).Default() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Handle(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "hello"}, + {name: "case2", in: "world", want: "world"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var w string + got := stream.NewString(c.in).Handle(func(s string) { + w = s + }) + if w != c.want { + t.Fatalf("NewString(%s).Handle() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Update(t *testing.T) { + var cases = []struct { + name string + in string + want string + }{ + {name: "case1", in: "hello", want: "HELLO"}, + {name: "case2", in: "world", want: "WORLD"}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Update(func(s string) string { + return stream.NewString(s).ToUpper().String() + }) + if got.String() != c.want { + t.Fatalf("NewString(%s).Update() = %s; want %s", c.in, got, c.want) + } + }) + } +} + +func TestString_Split(t *testing.T) { + var cases = []struct { + name string + in string + sep string + want []string + }{ + {name: "case1", in: "hello world", sep: " ", want: []string{"hello", "world"}}, + {name: "case2", in: "hello,world", sep: ",", want: []string{"hello", "world"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Split(c.sep) + for i, v := range got { + if v != c.want[i] { + t.Fatalf("NewString(%s).Split(%s) = %v; want %v", c.in, c.sep, got, c.want) + } + } + }) + } +} + +func TestString_SplitN(t *testing.T) { + var cases = []struct { + name string + in string + sep string + n int + want []string + }{ + {name: "case1", in: "hello world", sep: " ", n: 2, want: []string{"hello", "world"}}, + {name: "case2", in: "hello,world", sep: ",", n: 2, want: []string{"hello", "world"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).SplitN(c.sep, c.n) + for i, v := range got { + if v != c.want[i] { + t.Fatalf("NewString(%s).SplitN(%s, %d) = %v; want %v", c.in, c.sep, c.n, got, c.want) + } + } + }) + } +} + +func TestString_Batched(t *testing.T) { + var cases = []struct { + name string + in string + size int + want []string + }{ + {name: "case1", in: "hello world", size: 5, want: []string{"hello", " worl", "d"}}, + {name: "case2", in: "hello,world", size: 5, want: []string{"hello", ",worl", "d"}}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := stream.NewString(c.in).Batched(c.size) + for i, v := range got { + if v != c.want[i] { + t.Fatalf("NewString(%s).Batched(%d) = %v; want %v", c.in, c.size, got, c.want) + } + } + }) + } +} diff --git a/utils/stream/strings.go b/utils/stream/strings.go new file mode 100644 index 0000000..981e04f --- /dev/null +++ b/utils/stream/strings.go @@ -0,0 +1,123 @@ +package stream + +import ( + "sort" +) + +// NewStrings 创建字符串切片 +func NewStrings[S ~string](s ...S) Strings[S] { + var slice = make(Strings[S], len(s)) + for i, v := range s { + slice[i] = v + } + return slice +} + +// Strings 字符串切片 +type Strings[S ~string] []S + +// Len 返回切片长度 +func (s *Strings[S]) Len() int { + return len(*s) +} + +// Append 添加字符串 +func (s *Strings[S]) Append(ss ...S) *Strings[S] { + *s = append(*s, NewStrings(ss...)...) + return s +} + +// Clear 清空切片 +func (s *Strings[S]) Clear() *Strings[S] { + *s = make(Strings[S], 0) + return s +} + +// Copy 复制切片 +func (s *Strings[S]) Copy() *Strings[S] { + ss := make(Strings[S], len(*s)) + copy(ss, *s) + return &ss +} + +// Range 返回指定范围的切片 +func (s *Strings[S]) Range(start, end int) *Strings[S] { + *s = (*s)[start:end] + return s +} + +// First 返回第一个元素 +func (s *Strings[S]) First() *String[S] { + return NewString((*s)[0]) +} + +// Last 返回最后一个元素 +func (s *Strings[S]) Last() *String[S] { + return NewString((*s)[len(*s)-1]) +} + +// Index 返回指定的元素 +func (s *Strings[S]) Index(i int) *String[S] { + return NewString((*s)[i]) +} + +// Reverse 反转切片 +func (s *Strings[S]) Reverse() *Strings[S] { + for i, j := 0, len(*s)-1; i < j; i, j = i+1, j-1 { + (*s)[i], (*s)[j] = (*s)[j], (*s)[i] + } + return s +} + +// Desc 降序排序 +func (s *Strings[S]) Desc() *Strings[S] { + sort.Slice(*s, func(i, j int) bool { + return (*s)[i] > (*s)[j] + }) + return s +} + +// Asc 升序排序 +func (s *Strings[S]) Asc() *Strings[S] { + sort.Slice(*s, func(i, j int) bool { + return (*s)[i] < (*s)[j] + }) + return s +} + +// Sort 自定义排序 +func (s *Strings[S]) Sort(f func(int, int) bool) *Strings[S] { + sort.Slice(*s, func(i, j int) bool { + return f(i, j) + }) + return s +} + +// Unique 去重 +func (s *Strings[S]) Unique() *Strings[S] { + m := map[S]struct{}{} + for _, v := range *s { + m[v] = struct{}{} + } + *s = make(Strings[S], 0, len(m)) + for k := range m { + *s = append(*s, k) + } + return s +} + +// Delete 删除指定位置的字符串 +func (s *Strings[S]) Delete(i int) *Strings[S] { + *s = append((*s)[:i], (*s)[i+1:]...) + return s +} + +// Each 遍历切片 +func (s *Strings[S]) Each(f func(int, S) bool) *Strings[S] { + for i, v := range *s { + if !f(i, v) { + break + } + } + return s +} diff --git a/utils/stream/strings_test.go b/utils/stream/strings_test.go new file mode 100644 index 0000000..8d4d002 --- /dev/null +++ b/utils/stream/strings_test.go @@ -0,0 +1 @@ +package stream_test