From d489e58561b41089a72734860aa4051f3ce75c51 Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 4 Sep 2026 09:10:31 +0200 Subject: [PATCH] Upstream gookit/goutil state of the touched files before 54004efbfe --- strutil/split.go | 200 ++++++++++++++++++++++++++++++++++++++++++ strutil/split_test.go | 191 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 strutil/split.go create mode 100644 strutil/split_test.go diff --git a/strutil/split.go b/strutil/split.go new file mode 100644 index 0000000..4961b9b --- /dev/null +++ b/strutil/split.go @@ -0,0 +1,200 @@ +package strutil + +import ( + "regexp" + "strings" +) + +// BeforeFirst get substring before first sep. +func BeforeFirst(s, sep string) string { + if i := strings.Index(s, sep); i >= 0 { + return s[:i] + } + return s +} + +// AfterFirst get substring after first sep. +func AfterFirst(s, sep string) string { + if i := strings.Index(s, sep); i >= 0 { + return s[i+len(sep):] + } + return "" +} + +// BeforeLast get substring before last sep. +func BeforeLast(s, sep string) string { + if i := strings.LastIndex(s, sep); i >= 0 { + return s[:i] + } + return s +} + +// AfterLast get substring after last sep. +func AfterLast(s, sep string) string { + if i := strings.LastIndex(s, sep); i >= 0 { + return s[i+len(sep):] + } + return "" +} + +/************************************************************* + * String split operation + *************************************************************/ + +// Cut alias of the strings.Cut +func Cut(s, sep string) (before string, after string, found bool) { + if i := strings.Index(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} + +// QuietCut always returns two substring. +func QuietCut(s, sep string) (before string, after string) { + before, after, _ = Cut(s, sep) + return +} + +// MustCut always returns two substring. +func MustCut(s, sep string) (before string, after string) { + var ok bool + before, after, ok = Cut(s, sep) + if !ok { + panic("cannot split input string to two nodes") + } + return +} + +// TrimCut always returns two substring and trim space for items. +func TrimCut(s, sep string) (string, string) { + before, after, _ := Cut(s, sep) + return strings.TrimSpace(before), strings.TrimSpace(after) +} + +// SplitKV split string to key and value. +func SplitKV(s, sep string) (string, string) { return TrimCut(s, sep) } + +// SplitValid string to slice. will trim each item and filter empty string node. +func SplitValid(s, sep string) (ss []string) { return Split(s, sep) } + +// Split string to slice. will trim each item and filter empty string node. +func Split(s, sep string) (ss []string) { + if s = strings.TrimSpace(s); s == "" { + return + } + + for _, val := range strings.Split(s, sep) { + if val = strings.TrimSpace(val); val != "" { + ss = append(ss, val) + } + } + return +} + +// SplitNValid string to slice. will filter empty string node. +func SplitNValid(s, sep string, n int) (ss []string) { return SplitN(s, sep, n) } + +// SplitN string to slice. will filter empty string node. +func SplitN(s, sep string, n int) (ss []string) { + if s = strings.TrimSpace(s); s == "" { + return + } + + rawList := strings.Split(s, sep) + for i, val := range rawList { + if val = strings.TrimSpace(val); val != "" { + if len(ss) == n-1 { + ss = append(ss, strings.TrimSpace(strings.Join(rawList[i:], sep))) + break + } + + ss = append(ss, val) + } + } + return +} + +// SplitTrimmed split string to slice. +// will trim space for each node, but not filter empty +func SplitTrimmed(s, sep string) (ss []string) { + if s = strings.TrimSpace(s); s == "" { + return + } + + for _, val := range strings.Split(s, sep) { + ss = append(ss, strings.TrimSpace(val)) + } + return +} + +// SplitNTrimmed split string to slice. +// will trim space for each node, but not filter empty +func SplitNTrimmed(s, sep string, n int) (ss []string) { + if s = strings.TrimSpace(s); s == "" { + return + } + + for _, val := range strings.SplitN(s, sep, n) { + ss = append(ss, strings.TrimSpace(val)) + } + return +} + +// 根据空白字符(空格,TAB,换行等)分隔字符串 +var whitespaceRegexp = regexp.MustCompile(`\s+`) + +// SplitByWhitespace Separate strings by whitespace characters (space, TAB, newline, etc.) +func SplitByWhitespace(s string) []string { + return whitespaceRegexp.Split(s, -1) +} + +// Substr for a string. NOTE: strLn := len(runes) +// if length <= 0, return pos to end. +func Substr(s string, pos, length int) string { + runes := []rune(s) + strLn := len(runes) + + // pos is too large + if pos >= strLn { + return "" + } + + stopIdx := pos + length + if length == 0 || stopIdx > strLn { + stopIdx = strLn + } else if length < 0 { + stopIdx = strLn + length + } + + return string(runes[pos:stopIdx]) +} + +// SplitInlineComment for an inline text string. default is strict mode. +func SplitInlineComment(val string, strict ...bool) (string, string) { + // strict check: must with a space + if len(strict) == 0 || strict[0] { + if pos := strings.Index(val, " #"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos+1:] + } + if pos := strings.Index(val, " //"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos+1:] + } + return val, "" + } + + if pos := strings.IndexByte(val, '#'); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos:] + } + if pos := strings.Index(val, "//"); pos > -1 { + return strings.TrimRight(val[0:pos], " "), val[pos:] + } + return val, "" +} + +// FirstLine from command output +func FirstLine(output string) string { + if i := strings.IndexByte(output, '\n'); i >= 0 { + return output[0:i] + } + return output +} diff --git a/strutil/split_test.go b/strutil/split_test.go new file mode 100644 index 0000000..2a97caf --- /dev/null +++ b/strutil/split_test.go @@ -0,0 +1,191 @@ +package strutil_test + +import ( + "fmt" + "testing" + + "github.com/gookit/goutil/strutil" + "github.com/gookit/goutil/x/assert" +) + +func TestBeforeAfter(t *testing.T) { + // BeforeFirst + assert.Eq(t, "abc", strutil.BeforeFirst("abc", ":")) + assert.Eq(t, "abc", strutil.BeforeFirst("abc:123", ":")) + assert.Eq(t, "abc", strutil.BeforeFirst("abc:123:456", ":")) + + // AfterFirst + assert.Eq(t, "", strutil.AfterFirst("abc", ":")) + assert.Eq(t, "123", strutil.AfterFirst("abc:123", ":")) + assert.Eq(t, "123:456", strutil.AfterFirst("abc:123:456", ":")) + + // BeforeLast + assert.Eq(t, "abc", strutil.BeforeLast("abc", ":")) + assert.Eq(t, "abc", strutil.BeforeLast("abc:123", ":")) + assert.Eq(t, "abc:123", strutil.BeforeLast("abc:123:456", ":")) + + // AfterLast + assert.Eq(t, "", strutil.AfterLast("abc", ":")) + assert.Eq(t, "123", strutil.AfterLast("abc:123", ":")) + assert.Eq(t, "456", strutil.AfterLast("abc:123:456", ":")) +} + +func TestCut(t *testing.T) { + str := "hi,inhere" + + b, a, ok := strutil.Cut(str, ",") + assert.True(t, ok) + assert.Eq(t, "hi", b) + assert.Eq(t, "inhere", a) + + b, a = strutil.MustCut(str, ",") + assert.Eq(t, "hi", b) + assert.Eq(t, "inhere", a) + + assert.Panics(t, func() { + strutil.MustCut("abc", ",") + }) + + b, a = strutil.QuietCut(str, ",") + assert.Eq(t, "hi", b) + assert.Eq(t, "inhere", a) + + b, a = strutil.TrimCut(str, ",") + assert.Eq(t, "hi", b) + assert.Eq(t, "inhere", a) + + b, a = strutil.TrimCut(" hi , inhere \n", ",") + assert.Eq(t, "hi", b) + assert.Eq(t, "inhere", a) + + b, a = strutil.SplitKV(" name = inhere \n", "=") + assert.Eq(t, "name", b) + assert.Eq(t, "inhere", a) + + b, a, ok = strutil.Cut(str, "-") + assert.False(t, ok) + assert.Eq(t, str, b) + assert.Eq(t, "", a) +} + +func TestSplit(t *testing.T) { + ss := strutil.Split("a, , b,c", ",") + assert.Eq(t, `[]string{"a", "b", "c"}`, fmt.Sprintf("%#v", ss)) + + ss = strutil.SplitValid("a, , b,c", ",") + assert.Eq(t, `[]string{"a", "b", "c"}`, fmt.Sprintf("%#v", ss)) + + ss = strutil.SplitN("a, , b,c", ",", 3) + assert.Eq(t, `[]string{"a", "b", "c"}`, fmt.Sprintf("%#v", ss)) + + 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) + + ss = strutil.Split(" ", ",") + assert.Nil(t, ss) +} + +func TestSplitTrimmed(t *testing.T) { + ss := strutil.SplitTrimmed("a, , b,c", ",") + assert.Eq(t, `[]string{"a", "", "b", "c"}`, fmt.Sprintf("%#v", ss)) + + ss = strutil.SplitTrimmed("", ",") + assert.Empty(t, ss) + + ss = strutil.SplitTrimmed(",", ",") + assert.NotEmpty(t, ss) + + ss = strutil.SplitNTrimmed("a, , b,c", ",", 2) + assert.Eq(t, `[]string{"a", ", b,c"}`, fmt.Sprintf("%#v", ss)) +} + +func TestSplitByWhitespace(t *testing.T) { + ss := strutil.SplitByWhitespace("a b c") + assert.Eq(t, `[]string{"a", "b", "c"}`, fmt.Sprintf("%#v", ss)) + + ss = strutil.SplitByWhitespace("a\r\nb\tc d") + assert.Eq(t, `[]string{"a", "b", "c", "d"}`, fmt.Sprintf("%#v", ss)) +} + +func TestSubstr(t *testing.T) { + assert.Eq(t, "abc", strutil.Substr("abcDef", 0, 3)) + assert.Eq(t, "cD", strutil.Substr("abcDef", 2, 2)) + assert.Eq(t, "cDef", strutil.Substr("abcDef", 2, 0)) + assert.Eq(t, "", strutil.Substr("abcDEF", 23, 5)) + assert.Eq(t, "cDEF12", strutil.Substr("abcDEF123", 2, -1)) + assert.Eq(t, "cDEF", strutil.Substr("abcDEF123", 2, -3)) +} + +func TestSplitInlineComment(t *testing.T) { + tests := []struct { + str string + val string + cmt string + strict bool + }{ + { + str: "value", + val: "value", + }, + { + str: "value // comments at end", + val: "value", + cmt: "// comments at end", + }, + { + str: "value // comments at end", + val: "value", + cmt: "// comments at end", + strict: true, + }, + { + str: "value// comments at end", + val: "value", + cmt: "// comments at end", + }, + { + str: "value// comments at end", + val: "value// comments at end", + strict: true, + }, + { + str: "value # comments at end", + val: "value", + cmt: "# comments at end", + }, + { + str: "value # comments at end", + val: "value", + cmt: "# comments at end", + strict: true, + }, + { + str: "value# comments at end", + val: "value", + cmt: "# comments at end", + }, + { + str: "value# comments at end", + val: "value# comments at end", + strict: true, + }, + } + + for i, tt := range tests { + idx := strutil.QuietString(i) + val, comment := strutil.SplitInlineComment(tt.str+idx, tt.strict) + if comment == "" { + assert.Eq(t, tt.val+idx, val) + assert.Eq(t, tt.cmt, comment) + } else { + assert.Eq(t, tt.val, val) + assert.Eq(t, tt.cmt+idx, comment) + } + } +}