Skip to content

Commit 8e36ab0

Browse files
committed
bytes, strings: add Cut
Using Cut is a clearer way to write the vast majority (>70%) of existing code that calls Index, IndexByte, IndexRune, and SplitN. There is more discussion on https://golang.org/issue/46336. Fixes #46336. Change-Id: Ia418ed7c3706c65bf61e1b2c5baf534cb783e4d3 Reviewed-on: https://go-review.googlesource.com/c/go/+/351710 Trust: Russ Cox <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 810b08b commit 8e36ab0

File tree

6 files changed

+178
-90
lines changed

6 files changed

+178
-90
lines changed

src/bytes/bytes.go

+13
Original file line numberDiff line numberDiff line change
@@ -1192,3 +1192,16 @@ func Index(s, sep []byte) int {
11921192
}
11931193
return -1
11941194
}
1195+
1196+
// Cut slices s around the first instance of sep,
1197+
// returning the text before and after sep.
1198+
// The found result reports whether sep appears in s.
1199+
// If sep does not appear in s, cut returns s, "", false.
1200+
//
1201+
// Cut returns slices of the original slice s, not copies.
1202+
func Cut(s, sep []byte) (before, after []byte, found bool) {
1203+
if i := Index(s, sep); i >= 0 {
1204+
return s[:i], s[i+len(sep):], true
1205+
}
1206+
return s, nil, false
1207+
}

src/bytes/bytes_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,29 @@ func TestEqualFold(t *testing.T) {
15671567
}
15681568
}
15691569

1570+
var cutTests = []struct {
1571+
s, sep string
1572+
before, after string
1573+
found bool
1574+
}{
1575+
{"abc", "b", "a", "c", true},
1576+
{"abc", "a", "", "bc", true},
1577+
{"abc", "c", "ab", "", true},
1578+
{"abc", "abc", "", "", true},
1579+
{"abc", "", "", "abc", true},
1580+
{"abc", "d", "abc", "", false},
1581+
{"", "d", "", "", false},
1582+
{"", "", "", "", true},
1583+
}
1584+
1585+
func TestCut(t *testing.T) {
1586+
for _, tt := range cutTests {
1587+
if before, after, found := Cut([]byte(tt.s), []byte(tt.sep)); string(before) != tt.before || string(after) != tt.after || found != tt.found {
1588+
t.Errorf("Cut(%q, %q) = %q, %q, %v, want %q, %q, %v", tt.s, tt.sep, before, after, found, tt.before, tt.after, tt.found)
1589+
}
1590+
}
1591+
}
1592+
15701593
func TestBufferGrowNegative(t *testing.T) {
15711594
defer func() {
15721595
if err := recover(); err == nil {

src/bytes/example_test.go

+70-68
Original file line numberDiff line numberDiff line change
@@ -105,36 +105,6 @@ func ExampleCompare_search() {
105105
}
106106
}
107107

108-
func ExampleTrimSuffix() {
109-
var b = []byte("Hello, goodbye, etc!")
110-
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
111-
b = bytes.TrimSuffix(b, []byte("gopher"))
112-
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
113-
os.Stdout.Write(b)
114-
// Output: Hello, world!
115-
}
116-
117-
func ExampleTrimPrefix() {
118-
var b = []byte("Goodbye,, world!")
119-
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
120-
b = bytes.TrimPrefix(b, []byte("See ya,"))
121-
fmt.Printf("Hello%s", b)
122-
// Output: Hello, world!
123-
}
124-
125-
func ExampleFields() {
126-
fmt.Printf("Fields are: %q", bytes.Fields([]byte(" foo bar baz ")))
127-
// Output: Fields are: ["foo" "bar" "baz"]
128-
}
129-
130-
func ExampleFieldsFunc() {
131-
f := func(c rune) bool {
132-
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
133-
}
134-
fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte(" foo1;bar2,baz3..."), f))
135-
// Output: Fields are: ["foo1" "bar2" "baz3"]
136-
}
137-
138108
func ExampleContains() {
139109
fmt.Println(bytes.Contains([]byte("seafood"), []byte("foo")))
140110
fmt.Println(bytes.Contains([]byte("seafood"), []byte("bar")))
@@ -181,6 +151,22 @@ func ExampleCount() {
181151
// 5
182152
}
183153

154+
func ExampleCut() {
155+
show := func(s, sep string) {
156+
before, after, found := bytes.Cut([]byte(s), []byte(sep))
157+
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
158+
}
159+
show("Gopher", "Go")
160+
show("Gopher", "ph")
161+
show("Gopher", "er")
162+
show("Gopher", "Badger")
163+
// Output:
164+
// Cut("Gopher", "Go") = "", "pher", true
165+
// Cut("Gopher", "ph") = "Go", "er", true
166+
// Cut("Gopher", "er") = "Goph", "", true
167+
// Cut("Gopher", "Badger") = "Gopher", "", false
168+
}
169+
184170
func ExampleEqual() {
185171
fmt.Println(bytes.Equal([]byte("Go"), []byte("Go")))
186172
fmt.Println(bytes.Equal([]byte("Go"), []byte("C++")))
@@ -194,6 +180,19 @@ func ExampleEqualFold() {
194180
// Output: true
195181
}
196182

183+
func ExampleFields() {
184+
fmt.Printf("Fields are: %q", bytes.Fields([]byte(" foo bar baz ")))
185+
// Output: Fields are: ["foo" "bar" "baz"]
186+
}
187+
188+
func ExampleFieldsFunc() {
189+
f := func(c rune) bool {
190+
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
191+
}
192+
fmt.Printf("Fields are: %q", bytes.FieldsFunc([]byte(" foo1;bar2,baz3..."), f))
193+
// Output: Fields are: ["foo1" "bar2" "baz3"]
194+
}
195+
197196
func ExampleHasPrefix() {
198197
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("Go")))
199198
fmt.Println(bytes.HasPrefix([]byte("Gopher"), []byte("C")))
@@ -259,6 +258,12 @@ func ExampleIndexRune() {
259258
// -1
260259
}
261260

261+
func ExampleJoin() {
262+
s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
263+
fmt.Printf("%s", bytes.Join(s, []byte(", ")))
264+
// Output: foo, bar, baz
265+
}
266+
262267
func ExampleLastIndex() {
263268
fmt.Println(bytes.Index([]byte("go gopher"), []byte("go")))
264269
fmt.Println(bytes.LastIndex([]byte("go gopher"), []byte("go")))
@@ -299,10 +304,12 @@ func ExampleLastIndexFunc() {
299304
// -1
300305
}
301306

302-
func ExampleJoin() {
303-
s := [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}
304-
fmt.Printf("%s", bytes.Join(s, []byte(", ")))
305-
// Output: foo, bar, baz
307+
func ExampleReader_Len() {
308+
fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
309+
fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
310+
// Output:
311+
// 3
312+
// 16
306313
}
307314

308315
func ExampleRepeat() {
@@ -412,20 +419,6 @@ func ExampleTrimFunc() {
412419
// go-gopher!
413420
}
414421

415-
func ExampleMap() {
416-
rot13 := func(r rune) rune {
417-
switch {
418-
case r >= 'A' && r <= 'Z':
419-
return 'A' + (r-'A'+13)%26
420-
case r >= 'a' && r <= 'z':
421-
return 'a' + (r-'a'+13)%26
422-
}
423-
return r
424-
}
425-
fmt.Printf("%s", bytes.Map(rot13, []byte("'Twas brillig and the slithy gopher...")))
426-
// Output: 'Gjnf oevyyvt naq gur fyvgul tbcure...
427-
}
428-
429422
func ExampleTrimLeft() {
430423
fmt.Print(string(bytes.TrimLeft([]byte("453gopher8257"), "0123456789")))
431424
// Output:
@@ -442,11 +435,28 @@ func ExampleTrimLeftFunc() {
442435
// go-gopher!567
443436
}
444437

438+
func ExampleTrimPrefix() {
439+
var b = []byte("Goodbye,, world!")
440+
b = bytes.TrimPrefix(b, []byte("Goodbye,"))
441+
b = bytes.TrimPrefix(b, []byte("See ya,"))
442+
fmt.Printf("Hello%s", b)
443+
// Output: Hello, world!
444+
}
445+
445446
func ExampleTrimSpace() {
446447
fmt.Printf("%s", bytes.TrimSpace([]byte(" \t\n a lone gopher \n\t\r\n")))
447448
// Output: a lone gopher
448449
}
449450

451+
func ExampleTrimSuffix() {
452+
var b = []byte("Hello, goodbye, etc!")
453+
b = bytes.TrimSuffix(b, []byte("goodbye, etc!"))
454+
b = bytes.TrimSuffix(b, []byte("gopher"))
455+
b = append(b, bytes.TrimSuffix([]byte("world!"), []byte("x!"))...)
456+
os.Stdout.Write(b)
457+
// Output: Hello, world!
458+
}
459+
450460
func ExampleTrimRight() {
451461
fmt.Print(string(bytes.TrimRight([]byte("453gopher8257"), "0123456789")))
452462
// Output:
@@ -463,21 +473,6 @@ func ExampleTrimRightFunc() {
463473
// 1234go-gopher!
464474
}
465475

466-
func ExampleToUpper() {
467-
fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
468-
// Output: GOPHER
469-
}
470-
471-
func ExampleToUpperSpecial() {
472-
str := []byte("ahoj vývojári golang")
473-
totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
474-
fmt.Println("Original : " + string(str))
475-
fmt.Println("ToUpper : " + string(totitle))
476-
// Output:
477-
// Original : ahoj vývojári golang
478-
// ToUpper : AHOJ VÝVOJÁRİ GOLANG
479-
}
480-
481476
func ExampleToLower() {
482477
fmt.Printf("%s", bytes.ToLower([]byte("Gopher")))
483478
// Output: gopher
@@ -493,10 +488,17 @@ func ExampleToLowerSpecial() {
493488
// ToLower : ahoj vývojári golang
494489
}
495490

496-
func ExampleReader_Len() {
497-
fmt.Println(bytes.NewReader([]byte("Hi!")).Len())
498-
fmt.Println(bytes.NewReader([]byte("こんにちは!")).Len())
491+
func ExampleToUpper() {
492+
fmt.Printf("%s", bytes.ToUpper([]byte("Gopher")))
493+
// Output: GOPHER
494+
}
495+
496+
func ExampleToUpperSpecial() {
497+
str := []byte("ahoj vývojári golang")
498+
totitle := bytes.ToUpperSpecial(unicode.AzeriCase, str)
499+
fmt.Println("Original : " + string(str))
500+
fmt.Println("ToUpper : " + string(totitle))
499501
// Output:
500-
// 3
501-
// 16
502+
// Original : ahoj vývojári golang
503+
// ToUpper : AHOJ VÝVOJÁRİ GOLANG
502504
}

src/strings/example_test.go

+37-21
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@ import (
1010
"unicode"
1111
)
1212

13-
func ExampleFields() {
14-
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
15-
// Output: Fields are: ["foo" "bar" "baz"]
16-
}
17-
18-
func ExampleFieldsFunc() {
19-
f := func(c rune) bool {
20-
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
13+
func ExampleBuilder() {
14+
var b strings.Builder
15+
for i := 3; i >= 1; i-- {
16+
fmt.Fprintf(&b, "%d...", i)
2117
}
22-
fmt.Printf("Fields are: %q", strings.FieldsFunc(" foo1;bar2,baz3...", f))
23-
// Output: Fields are: ["foo1" "bar2" "baz3"]
18+
b.WriteString("ignition")
19+
fmt.Println(b.String())
20+
21+
// Output: 3...2...1...ignition
2422
}
2523

2624
func ExampleCompare() {
@@ -79,11 +77,40 @@ func ExampleCount() {
7977
// 5
8078
}
8179

80+
func ExampleCut() {
81+
show := func(s, sep string) {
82+
before, after, found := strings.Cut(s, sep)
83+
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
84+
}
85+
show("Gopher", "Go")
86+
show("Gopher", "ph")
87+
show("Gopher", "er")
88+
show("Gopher", "Badger")
89+
// Output:
90+
// Cut("Gopher", "Go") = "", "pher", true
91+
// Cut("Gopher", "ph") = "Go", "er", true
92+
// Cut("Gopher", "er") = "Goph", "", true
93+
// Cut("Gopher", "Badger") = "Gopher", "", false
94+
}
95+
8296
func ExampleEqualFold() {
8397
fmt.Println(strings.EqualFold("Go", "go"))
8498
// Output: true
8599
}
86100

101+
func ExampleFields() {
102+
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
103+
// Output: Fields are: ["foo" "bar" "baz"]
104+
}
105+
106+
func ExampleFieldsFunc() {
107+
f := func(c rune) bool {
108+
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
109+
}
110+
fmt.Printf("Fields are: %q", strings.FieldsFunc(" foo1;bar2,baz3...", f))
111+
// Output: Fields are: ["foo1" "bar2" "baz3"]
112+
}
113+
87114
func ExampleHasPrefix() {
88115
fmt.Println(strings.HasPrefix("Gopher", "Go"))
89116
fmt.Println(strings.HasPrefix("Gopher", "C"))
@@ -370,14 +397,3 @@ func ExampleTrimRightFunc() {
370397
}))
371398
// Output: ¡¡¡Hello, Gophers
372399
}
373-
374-
func ExampleBuilder() {
375-
var b strings.Builder
376-
for i := 3; i >= 1; i-- {
377-
fmt.Fprintf(&b, "%d...", i)
378-
}
379-
b.WriteString("ignition")
380-
fmt.Println(b.String())
381-
382-
// Output: 3...2...1...ignition
383-
}

src/strings/strings.go

+11
Original file line numberDiff line numberDiff line change
@@ -1118,3 +1118,14 @@ func Index(s, substr string) int {
11181118
}
11191119
return -1
11201120
}
1121+
1122+
// Cut slices s around the first instance of sep,
1123+
// returning the text before and after sep.
1124+
// The found result reports whether sep appears in s.
1125+
// If sep does not appear in s, cut returns s, "", false.
1126+
func Cut(s, sep string) (before, after string, found bool) {
1127+
if i := Index(s, sep); i >= 0 {
1128+
return s[:i], s[i+len(sep):], true
1129+
}
1130+
return s, "", false
1131+
}

src/strings/strings_test.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -1579,7 +1579,30 @@ var CountTests = []struct {
15791579
func TestCount(t *testing.T) {
15801580
for _, tt := range CountTests {
15811581
if num := Count(tt.s, tt.sep); num != tt.num {
1582-
t.Errorf("Count(\"%s\", \"%s\") = %d, want %d", tt.s, tt.sep, num, tt.num)
1582+
t.Errorf("Count(%q, %q) = %d, want %d", tt.s, tt.sep, num, tt.num)
1583+
}
1584+
}
1585+
}
1586+
1587+
var cutTests = []struct {
1588+
s, sep string
1589+
before, after string
1590+
found bool
1591+
}{
1592+
{"abc", "b", "a", "c", true},
1593+
{"abc", "a", "", "bc", true},
1594+
{"abc", "c", "ab", "", true},
1595+
{"abc", "abc", "", "", true},
1596+
{"abc", "", "", "abc", true},
1597+
{"abc", "d", "abc", "", false},
1598+
{"", "d", "", "", false},
1599+
{"", "", "", "", true},
1600+
}
1601+
1602+
func TestCut(t *testing.T) {
1603+
for _, tt := range cutTests {
1604+
if before, after, found := Cut(tt.s, tt.sep); before != tt.before || after != tt.after || found != tt.found {
1605+
t.Errorf("Cut(%q, %q) = %q, %q, %v, want %q, %q, %v", tt.s, tt.sep, before, after, found, tt.before, tt.after, tt.found)
15831606
}
15841607
}
15851608
}

0 commit comments

Comments
 (0)