cmd: add IntRange parser util
This commit is contained in:
parent
46f6563f6d
commit
be154eae0f
|
@ -0,0 +1,97 @@
|
|||
package util
|
||||
|
||||
import "strconv"
|
||||
|
||||
type Ints []IntRange
|
||||
|
||||
func (i Ints) Iter(fn func(uint64) bool) bool {
|
||||
for _, r := range i {
|
||||
if !r.Iter(fn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type IntRange struct {
|
||||
Start, Stop uint64
|
||||
}
|
||||
|
||||
func (r IntRange) Iter(fn func(uint64) bool) bool {
|
||||
for i := r.Start; i < r.Stop; i++ {
|
||||
if !fn(i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseInts parses a string indicating ranges of integers.
|
||||
//
|
||||
// e.g. `"0:7,234,1000:2333"`
|
||||
func ParseInts(s string) (Ints, bool) {
|
||||
if s == "" {
|
||||
return nil, true
|
||||
}
|
||||
var p intsParser
|
||||
ok := p.parse(s)
|
||||
return p.res, ok
|
||||
}
|
||||
|
||||
type intsParser struct {
|
||||
res []IntRange
|
||||
}
|
||||
|
||||
func (n *intsParser) parse(s string) bool {
|
||||
var ok bool
|
||||
for {
|
||||
s, ok = n.parseRange(s)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
if s[0] != ',' {
|
||||
return false
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func (n *intsParser) parseRange(s string) (string, bool) {
|
||||
// Parse start integer
|
||||
i := 0
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return s, false
|
||||
}
|
||||
start, err := strconv.ParseUint(s[:i], 0, 64)
|
||||
if err != nil {
|
||||
return s, false
|
||||
}
|
||||
s = s[i:]
|
||||
r := IntRange{Start: start, Stop: start + 1}
|
||||
// Parse optional :stop part
|
||||
if len(s) > 0 && s[0] == ':' {
|
||||
i = 1
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i > 1 {
|
||||
r.Stop, err = strconv.ParseUint(s[1:i], 0, 64)
|
||||
if err != nil {
|
||||
return s, false
|
||||
}
|
||||
s = s[i:]
|
||||
// if range is invalid or empty, skip it
|
||||
if r.Stop <= start {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
}
|
||||
n.res = append(n.res, r)
|
||||
return s, true
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseInts(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
out Ints
|
||||
fail bool
|
||||
}{
|
||||
{
|
||||
name: "Empty",
|
||||
input: "",
|
||||
out: nil,
|
||||
},
|
||||
{
|
||||
name: "Invalid",
|
||||
input: "abc",
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
input: "12",
|
||||
out: Ints{
|
||||
{Start: 12, Stop: 13},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SingleRange",
|
||||
input: "12:23",
|
||||
out: Ints{
|
||||
{Start: 12, Stop: 23},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EmptyRange",
|
||||
input: "1:3,12:12",
|
||||
out: Ints{
|
||||
{Start: 1, Stop: 3},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out, ok := ParseInts(tc.input)
|
||||
assert.Equal(t, !tc.fail, ok)
|
||||
assert.Equal(t, tc.out, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseInts(f *testing.F) {
|
||||
f.Add("12")
|
||||
f.Add("56:23")
|
||||
f.Add("23,95:30")
|
||||
f.Add("1,2,3")
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
_, _ = ParseInts(s)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue