Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
kercylan98 2024-02-04 12:15:01 +08:00
commit f30de13c34
4 changed files with 1269 additions and 0 deletions

321
utils/stream/string.go Normal file
View File

@ -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...)
}

824
utils/stream/string_test.go Normal file
View File

@ -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)
}
}
})
}
}

123
utils/stream/strings.go Normal file
View File

@ -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
}

View File

@ -0,0 +1 @@
package stream_test