diff --git a/arrutil/enum.go b/arrutil/enum.go deleted file mode 100644 index a5a70f0ec..000000000 --- a/arrutil/enum.go +++ /dev/null @@ -1,64 +0,0 @@ -package arrutil - -import ( - "strconv" - "strings" -) - -// Ints type -type Ints []int - -// String to string -func (is Ints) String() string { - ss := make([]string, len(is)) - for i, iv := range is { - ss[i] = strconv.Itoa(iv) - } - return strings.Join(ss, ",") -} - -// Has given element -func (is Ints) Has(i int) bool { - for _, iv := range is { - if i == iv { - return true - } - } - return false -} - -// Strings type -type Strings []string - -// String to string -func (ss Strings) String() string { - return strings.Join(ss, ",") -} - -// Join to string -func (ss Strings) Join(sep string) string { - return strings.Join(ss, sep) -} - -// Has given element -func (ss Strings) Has(sub string) bool { - return ss.Contains(sub) -} - -// Contains given element -func (ss Strings) Contains(sub string) bool { - for _, s := range ss { - if s == sub { - return true - } - } - return false -} - -// First element value. -func (ss Strings) First() string { - if len(ss) > 0 { - return ss[0] - } - return "" -} diff --git a/arrutil/list.go b/arrutil/list.go new file mode 100644 index 000000000..d4c290ee3 --- /dev/null +++ b/arrutil/list.go @@ -0,0 +1,154 @@ +package arrutil + +import ( + "sort" + "strconv" + "strings" + + "github.com/gookit/goutil/comdef" +) + +// Ints type +type Ints []int + +// String to string +func (is Ints) String() string { + ss := make([]string, len(is)) + for i, iv := range is { + ss[i] = strconv.Itoa(iv) + } + return strings.Join(ss, ",") +} + +// Has given element +func (is Ints) Has(i int) bool { + for _, iv := range is { + if i == iv { + return true + } + } + return false +} + +// First element value. +func (is Ints) First() int { + if len(is) > 0 { + return is[0] + } + panic("empty int slice") +} + +// Last element value. +func (is Ints) Last() int { + if len(is) > 0 { + return is[len(is)-1] + } + panic("empty int slice") +} + +// Sort the int slice +func (is Ints) Sort() { + sort.Ints(is) +} + +// Strings type +type Strings []string + +// String to string +func (ss Strings) String() string { + return strings.Join(ss, ",") +} + +// Join to string +func (ss Strings) Join(sep string) string { + return strings.Join(ss, sep) +} + +// Has given element +func (ss Strings) Has(sub string) bool { + return ss.Contains(sub) +} + +// Contains given element +func (ss Strings) Contains(sub string) bool { + for _, s := range ss { + if s == sub { + return true + } + } + return false +} + +// First element value. +func (ss Strings) First() string { + if len(ss) > 0 { + return ss[0] + } + return "" +} + +// ScalarList definition for any type +type ScalarList[T comdef.ScalarType] []T + +// IsEmpty check +func (ls ScalarList[T]) IsEmpty() bool { + return len(ls) == 0 +} + +// String to string +func (ls ScalarList[T]) String() string { + return ToString(ls) +} + +// Has given element +func (ls ScalarList[T]) Has(el T) bool { + return ls.Contains(el) +} + +// Contains given element +func (ls ScalarList[T]) Contains(el T) bool { + for _, v := range ls { + if v == el { + return true + } + } + return false +} + +// First element value. +func (ls ScalarList[T]) First() T { + if len(ls) > 0 { + return ls[0] + } + panic("empty list") +} + +// Last element value. +func (ls ScalarList[T]) Last() T { + if ln := len(ls); ln > 0 { + return ls[ln-1] + } + panic("empty list") +} + +// Remove given element +func (ls ScalarList[T]) Remove(el T) ScalarList[T] { + return Filter(ls, func(v T) bool { + return v != el + }) +} + +// Filter the slice, default will filter zero value. +func (ls ScalarList[T]) Filter(filter ...comdef.MatchFunc[T]) ScalarList[T] { + return Filter(ls, filter...) +} + +// Map the slice to new slice. TODO syntax ERROR: Method cannot have type parameters +// func (ls ScalarList[T]) Map[V any](mapFn MapFn[T, V]) ScalarList[V] { +// return Map(ls, mapFn) +// } + +// Sort the slice +// func (ls ScalarList[T]) Sort() { +// sort.Sort(ls) +// } diff --git a/strutil/convert_test.go b/strutil/convert_test.go index 7aab19e9a..6ca420cf0 100644 --- a/strutil/convert_test.go +++ b/strutil/convert_test.go @@ -271,6 +271,7 @@ func TestToByteSize(t *testing.T) { {12341739, "11.77 M"}, {1202590842, "1.12GB"}, {1231453023109, "1.12 TB"}, + {1351079888211148, "1.2PB"}, } for _, tt := range tests { diff --git a/strutil/ext.go b/strutil/ext.go index 31134830d..5d2da7d27 100644 --- a/strutil/ext.go +++ b/strutil/ext.go @@ -143,3 +143,10 @@ func (b *Builder) WriteStrings(ss ...string) { _, _ = b.Builder.WriteString(s) } } + +// ResetGet return current string and reset builder +func (b *Builder) ResetGet() string { + s := b.String() + b.Reset() + return s +} diff --git a/strutil/ext_test.go b/strutil/ext_test.go index 917d1845a..c77d6aef0 100644 --- a/strutil/ext_test.go +++ b/strutil/ext_test.go @@ -12,19 +12,19 @@ func TestBuilder_WriteAny(t *testing.T) { sb.Writef("ab-%s", "c") sb.WriteByteNE('d') - assert.Eq(t, "ab-cd", sb.String()) + sb.WriteMulti('-', 'e', 'f') + assert.Eq(t, "ab-cd-ef", sb.ResetGet()) - sb.Reset() sb.WriteStrings("ab", "-", "cd") sb.WriteString("-ef") - assert.Eq(t, "ab-cd-ef", sb.String()) + assert.Eq(t, "ab-cd-ef", sb.ResetGet()) - sb.Reset() sb.WriteAny("abc") sb.WriteAnys(23, "def") - assert.Eq(t, "abc23def", sb.String()) + assert.Eq(t, "abc23def", sb.ResetGet()) - sb.Reset() + sb.Write([]byte("abc")) + sb.WriteRune('-') sb.Writeln("abc") - assert.Eq(t, "abc\n", sb.String()) + assert.Eq(t, "abc-abc\n", sb.ResetGet()) } diff --git a/strutil/padding.go b/strutil/padding.go index 30cb042cc..de67a361f 100644 --- a/strutil/padding.go +++ b/strutil/padding.go @@ -61,13 +61,12 @@ func Resize(s string, length int, align PosFlag) string { } if align == PosMiddle { - strLn := len(s) - padLn := (length - strLn) / 2 - padStr := string(make([]byte, padLn)) - + padLn := (length - len(s)) / 2 if diff := length - padLn*2; diff > 0 { s += " " } + + padStr := string(RepeatBytes(' ', padLn)) return padStr + s + padStr } @@ -85,15 +84,19 @@ func PadChars[T byte | rune](cs []T, pad T, length int, pos PosFlag) []T { idx := length - ln ns := make([]T, length) - ps := RepeatChars(pad, idx) if pos == PosRight { copy(ns, cs) - copy(ns[idx:], ps) - } else { // to left - copy(ns[:idx], ps) - copy(ns[idx:], cs) + for i := ln; i < length; i++ { + ns[i] = pad + } + return ns } + // to left + for i := 0; i < idx; i++ { + ns[i] = pad + } + copy(ns[idx:], cs) return ns } @@ -131,32 +134,26 @@ func PadRunesRight(rs []rune, pad rune, length int) []rune { * String repeat operation *************************************************************/ -// Repeat a string +// Repeat a string by given times. func Repeat(s string, times int) string { - if times <= 0 { - return "" - } - if times == 1 { + if times <= 1 { return s } - ss := make([]string, 0, times) + var sb strings.Builder + sb.Grow(len(s) * times) + for i := 0; i < times; i++ { - ss = append(ss, s) + sb.WriteString(s) } - - return strings.Join(ss, "") + return sb.String() } // RepeatRune repeat a rune char. -func RepeatRune(char rune, times int) []rune { - return RepeatChars(char, times) -} +func RepeatRune(char rune, times int) []rune { return RepeatChars(char, times) } // RepeatBytes repeat a byte char. -func RepeatBytes(char byte, times int) []byte { - return RepeatChars(char, times) -} +func RepeatBytes(char byte, times int) []byte { return RepeatChars(char, times) } // RepeatChars repeat a byte char. func RepeatChars[T byte | rune](char T, times int) []T { @@ -164,9 +161,9 @@ func RepeatChars[T byte | rune](char T, times int) []T { return make([]T, 0) } - chars := make([]T, 0, times) + chars := make([]T, times) for i := 0; i < times; i++ { - chars = append(chars, char) + chars[i] = char } return chars } diff --git a/strutil/padding_test.go b/strutil/padding_test.go index 3a6a08448..2d2a2af46 100644 --- a/strutil/padding_test.go +++ b/strutil/padding_test.go @@ -31,12 +31,32 @@ func TestPadding(t *testing.T) { } } +func TestResize(t *testing.T) { + tests := []struct { + want, give string + len int + pos strutil.PosFlag + }{ + {"ab ", "ab", 5, strutil.PosRight}, + {" ab", "ab", 5, strutil.PosLeft}, + {"ab012", "ab012", 5, strutil.PosLeft}, + {"ab", "ab", 2, strutil.PosLeft}, + } + + for _, tt := range tests { + assert.Eq(t, tt.want, strutil.Resize(tt.give, tt.len, tt.pos)) + } + + want := " title " + assert.Eq(t, want, strutil.Resize("title", 20, strutil.PosMiddle)) +} + func TestRepeat(t *testing.T) { assert.Eq(t, "aaa", strutil.Repeat("a", 3)) assert.Eq(t, "DD", strutil.Repeat("D", 2)) assert.Eq(t, "D", strutil.Repeat("D", 1)) - assert.Eq(t, "", strutil.Repeat("0", 0)) - assert.Eq(t, "", strutil.Repeat("D", -3)) + assert.Eq(t, "A", strutil.Repeat("A", 0)) + assert.Eq(t, "D", strutil.Repeat("D", -3)) } func TestRepeatRune(t *testing.T) { @@ -104,6 +124,12 @@ func TestPadChars(t *testing.T) { } } +func TestPadRunes(t *testing.T) { + assert.Eq(t, []rune("hi123aaa"), strutil.PadRunesRight([]rune("hi123"), 'a', 8)) + assert.Eq(t, []rune("aaahi123"), strutil.PadRunesLeft([]rune("hi123"), 'a', 8)) + assert.Eq(t, []rune("hi123aaa"), strutil.PadRunes([]rune("hi123"), 'a', 8, strutil.PosRight)) +} + // runtime error: make slice: cap out of range #76 // https://github.com/gookit/goutil/issues/76 func TestIssues_76(t *testing.T) { diff --git a/strutil/parse_test.go b/strutil/parse_test.go index 422c17ba2..44eeab7d7 100644 --- a/strutil/parse_test.go +++ b/strutil/parse_test.go @@ -100,15 +100,21 @@ func TestParseSizeRange(t *testing.T) { {"1kb~invalid", 1024, 0, false}, {"invalid1~invalid2", 0, 0, false}, {"invalid", 0, 0, false}, + {"1invalid", 0, 0, false}, } is := assert.New(t) opt := &strutil.ParseSizeOpt{} for _, item := range tests { is.WithMsg(item.expr) - mix, max, err := strutil.ParseSizeRange(item.expr, opt) - is.Equal(item.min, mix, "for "+item.expr) + min, max, err := strutil.ParseSizeRange(item.expr, opt) + is.Equal(item.min, min, "for "+item.expr) is.Equal(item.max, max, "for "+item.expr) is.Equal(item.ok, err == nil, "for "+item.expr) } + + min, max, err := strutil.ParseSizeRange("1kb~1m", nil) + is.Nil(err) + is.Equal(uint64(1024), min) + is.Equal(uint64(1024*1024), max) } diff --git a/strutil/runes_test.go b/strutil/runes_test.go index e94f8614b..e6c4eab7c 100644 --- a/strutil/runes_test.go +++ b/strutil/runes_test.go @@ -34,7 +34,7 @@ func TestUtf8Width(t *testing.T) { func TestUtf8Truncate(t *testing.T) { s := "hello 你好, world 世界" - assert.Eq(t, "hello 你好", strutil.TextTruncate(s, 10, "")) + assert.Eq(t, "hello 你好", strutil.Truncate(s, 10, "")) assert.Eq(t, "hello ...", strutil.TextTruncate(s, 10, "...")) assert.Eq(t, "hello 你好", strutil.TextTruncate("hello 你好", 20, "...")) } diff --git a/strutil/split_test.go b/strutil/split_test.go index e8d7b7b31..d74a233f4 100644 --- a/strutil/split_test.go +++ b/strutil/split_test.go @@ -59,6 +59,9 @@ func TestSplit(t *testing.T) { ss = strutil.SplitN("a, , b,c", ",", 2) assert.Eq(t, `[]string{"a", "b,c"}`, fmt.Sprintf("%#v", ss)) + ss = strutil.SplitNValid(" ", ",", 2) + assert.Empty(t, ss) + ss = strutil.SplitN("origin https://github.com/gookit/gitw (push)", " ", 3) assert.Len(t, ss, 3) diff --git a/strutil/strutil.go b/strutil/strutil.go index f90229343..84af3142f 100644 --- a/strutil/strutil.go +++ b/strutil/strutil.go @@ -8,6 +8,8 @@ import ( "fmt" "strings" "text/template" + + "github.com/gookit/goutil/comdef" ) // OrCond return s1 on cond is True, OR return s2. @@ -28,7 +30,7 @@ func OrElse(s, orVal string) string { } // OrHandle return fn(s) on s is not empty. -func OrHandle(s string, fn func(s string) string) string { +func OrHandle(s string, fn comdef.StringHandleFunc) string { if s != "" { return fn(s) } diff --git a/strutil/strutil_test.go b/strutil/strutil_test.go index 92e5c2fcc..99141df98 100644 --- a/strutil/strutil_test.go +++ b/strutil/strutil_test.go @@ -20,6 +20,8 @@ func TestValid(t *testing.T) { is.Eq("ab", strutil.Valid("ab", "")) is.Eq("ab", strutil.Valid("ab", "cd")) is.Eq("cd", strutil.Valid("", "cd")) + is.Empty(strutil.Valid("", "")) + is.Eq("cd", strutil.OrElse("", "cd")) is.Eq("ab", strutil.OrElse("ab", "cd")) @@ -27,6 +29,7 @@ func TestValid(t *testing.T) { is.Eq("cd", strutil.OrCond(false, "ab", "cd")) is.Eq("ab", strutil.OrHandle(" ab ", strings.TrimSpace)) + is.Empty(strutil.OrHandle("", strings.TrimSpace)) } func TestRenderTemplate(t *testing.T) {