mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
util/slicesx: add HasPrefix, HasSuffix, CutPrefix, and CutSuffix functions
The standard library includes these for strings and byte slices, but it lacks similar functions for generic slices of comparable types. Although they are not as commonly used, these functions are useful in scenarios such as working with field index sequences (i.e., []int) via reflection. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
parent
1f94047475
commit
d500a92926
@ -4,7 +4,10 @@
|
|||||||
// Package slicesx contains some helpful generic slice functions.
|
// Package slicesx contains some helpful generic slice functions.
|
||||||
package slicesx
|
package slicesx
|
||||||
|
|
||||||
import "math/rand/v2"
|
import (
|
||||||
|
"math/rand/v2"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
// Interleave combines two slices of the form [a, b, c] and [x, y, z] into a
|
// Interleave combines two slices of the form [a, b, c] and [x, y, z] into a
|
||||||
// slice with elements interleaved; i.e. [a, x, b, y, c, z].
|
// slice with elements interleaved; i.e. [a, x, b, y, c, z].
|
||||||
@ -101,3 +104,35 @@ func AppendMatching[T any](dst, ps []T, f func(T) bool) []T {
|
|||||||
}
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasPrefix reports whether the byte slice s begins with prefix.
|
||||||
|
func HasPrefix[E comparable](s, prefix []E) bool {
|
||||||
|
return len(s) >= len(prefix) && slices.Equal(s[0:len(prefix)], prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSuffix reports whether the slice s ends with suffix.
|
||||||
|
func HasSuffix[E comparable](s, suffix []E) bool {
|
||||||
|
return len(s) >= len(suffix) && slices.Equal(s[len(s)-len(suffix):], suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CutPrefix returns s without the provided leading prefix slice and reports
|
||||||
|
// whether it found the prefix. If s doesn't start with prefix, CutPrefix
|
||||||
|
// returns s, false. If prefix is the empty slice, CutPrefix returns s, true.
|
||||||
|
// CutPrefix returns slices of the original slice s, not copies.
|
||||||
|
func CutPrefix[E comparable](s, prefix []E) (after []E, found bool) {
|
||||||
|
if !HasPrefix(s, prefix) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
return s[len(prefix):], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CutSuffix returns s without the provided ending suffix slice and reports
|
||||||
|
// whether it found the suffix. If s doesn't end with suffix, CutSuffix returns
|
||||||
|
// s, false. If suffix is the empty slice, CutSuffix returns s, true.
|
||||||
|
// CutSuffix returns slices of the original slice s, not copies.
|
||||||
|
func CutSuffix[E comparable](s, suffix []E) (after []E, found bool) {
|
||||||
|
if !HasSuffix(s, suffix) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
return s[:len(s)-len(suffix)], true
|
||||||
|
}
|
||||||
|
@ -151,3 +151,49 @@ func TestAppendMatching(t *testing.T) {
|
|||||||
t.Errorf("got %v; want %v", v, wantOrigMem)
|
t.Errorf("got %v; want %v", v, wantOrigMem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCutPrefix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s, prefix []int
|
||||||
|
after []int
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{"has-prefix", []int{1, 2, 3}, []int{1}, []int{2, 3}, true},
|
||||||
|
{"exact-prefix", []int{1, 2, 3}, []int{1, 2, 3}, []int{}, true},
|
||||||
|
{"blank-prefix", []int{1, 2, 3}, []int{}, []int{1, 2, 3}, true},
|
||||||
|
{"no-prefix", []int{1, 2, 3}, []int{42}, []int{1, 2, 3}, false},
|
||||||
|
{"blank-slice", []int{}, []int{42}, []int{}, false},
|
||||||
|
{"blank-all", []int{}, []int{}, []int{}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if after, found := CutPrefix(tt.s, tt.prefix); !slices.Equal(after, tt.after) || found != tt.found {
|
||||||
|
t.Errorf("CutPrefix(%v, %v) = %v, %v; want %v, %v", tt.s, tt.prefix, after, found, tt.after, tt.found)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCutSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s, suffix []int
|
||||||
|
before []int
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{"has-suffix", []int{1, 2, 3}, []int{3}, []int{1, 2}, true},
|
||||||
|
{"exact-suffix", []int{1, 2, 3}, []int{1, 2, 3}, []int{}, true},
|
||||||
|
{"blank-suffix", []int{1, 2, 3}, []int{}, []int{1, 2, 3}, true},
|
||||||
|
{"no-suffix", []int{1, 2, 3}, []int{42}, []int{1, 2, 3}, false},
|
||||||
|
{"blank-slice", []int{}, []int{42}, []int{}, false},
|
||||||
|
{"blank-all", []int{}, []int{}, []int{}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if before, found := CutSuffix(tt.s, tt.suffix); !slices.Equal(before, tt.before) || found != tt.found {
|
||||||
|
t.Errorf("CutSuffix(%v, %v) = %v, %v; want %v, %v", tt.s, tt.suffix, before, found, tt.before, tt.found)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user