forked from Archives/fx
Add default reducer
parent
2e30f1ec9a
commit
5e8d67a5fb
@ -0,0 +1,178 @@
|
|||||||
|
package reducer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state int
|
||||||
|
|
||||||
|
const (
|
||||||
|
start state = iota
|
||||||
|
unknown
|
||||||
|
propOrIndex
|
||||||
|
prop
|
||||||
|
index
|
||||||
|
indexEnd
|
||||||
|
number
|
||||||
|
doubleQuote
|
||||||
|
doubleQuoteEscape
|
||||||
|
singleQuote
|
||||||
|
singleQuoteEscape
|
||||||
|
)
|
||||||
|
|
||||||
|
func split(args []string) ([]interface{}, bool) {
|
||||||
|
path := make([]interface{}, 0)
|
||||||
|
for _, arg := range args {
|
||||||
|
s := ""
|
||||||
|
state := start
|
||||||
|
for _, ch := range arg {
|
||||||
|
switch state {
|
||||||
|
|
||||||
|
case start:
|
||||||
|
switch {
|
||||||
|
case ch == 'x':
|
||||||
|
state = unknown
|
||||||
|
case ch == '.':
|
||||||
|
state = propOrIndex
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case unknown:
|
||||||
|
switch {
|
||||||
|
case ch == '.':
|
||||||
|
state = prop
|
||||||
|
s = ""
|
||||||
|
case ch == '[':
|
||||||
|
state = index
|
||||||
|
s = ""
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case propOrIndex:
|
||||||
|
switch {
|
||||||
|
case isProp(ch):
|
||||||
|
state = prop
|
||||||
|
s = string(ch)
|
||||||
|
case ch == '[':
|
||||||
|
state = index
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case prop:
|
||||||
|
switch {
|
||||||
|
case isProp(ch):
|
||||||
|
s += string(ch)
|
||||||
|
case ch == '.':
|
||||||
|
state = prop
|
||||||
|
path = append(path, s)
|
||||||
|
s = ""
|
||||||
|
case ch == '[':
|
||||||
|
state = index
|
||||||
|
path = append(path, s)
|
||||||
|
s = ""
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case index:
|
||||||
|
switch {
|
||||||
|
case unicode.IsDigit(ch):
|
||||||
|
state = number
|
||||||
|
s = string(ch)
|
||||||
|
case ch == '"':
|
||||||
|
state = doubleQuote
|
||||||
|
s = ""
|
||||||
|
case ch == '\'':
|
||||||
|
state = singleQuote
|
||||||
|
s = ""
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case indexEnd:
|
||||||
|
switch {
|
||||||
|
case ch == ']':
|
||||||
|
state = unknown
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case number:
|
||||||
|
switch {
|
||||||
|
case unicode.IsDigit(ch):
|
||||||
|
s += string(ch)
|
||||||
|
case ch == ']':
|
||||||
|
state = unknown
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
path = append(path, n)
|
||||||
|
s = ""
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case doubleQuote:
|
||||||
|
switch ch {
|
||||||
|
case '"':
|
||||||
|
state = indexEnd
|
||||||
|
path = append(path, s)
|
||||||
|
s = ""
|
||||||
|
case '\\':
|
||||||
|
state = doubleQuoteEscape
|
||||||
|
default:
|
||||||
|
s += string(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
case doubleQuoteEscape:
|
||||||
|
switch ch {
|
||||||
|
case '"':
|
||||||
|
state = doubleQuote
|
||||||
|
s += string(ch)
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
case singleQuote:
|
||||||
|
switch ch {
|
||||||
|
case '\'':
|
||||||
|
state = indexEnd
|
||||||
|
path = append(path, s)
|
||||||
|
s = ""
|
||||||
|
case '\\':
|
||||||
|
state = singleQuoteEscape
|
||||||
|
s += string(ch)
|
||||||
|
default:
|
||||||
|
s += string(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
case singleQuoteEscape:
|
||||||
|
switch ch {
|
||||||
|
case '\'':
|
||||||
|
state = singleQuote
|
||||||
|
s += string(ch)
|
||||||
|
default:
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) > 0 {
|
||||||
|
if state == prop {
|
||||||
|
path = append(path, s)
|
||||||
|
} else {
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProp(ch rune) bool {
|
||||||
|
return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '$'
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package reducer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_split(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
want []interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: []string{},
|
||||||
|
want: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"."},
|
||||||
|
want: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x"},
|
||||||
|
want: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo"},
|
||||||
|
want: []interface{}{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x.foo"},
|
||||||
|
want: []interface{}{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[42]"},
|
||||||
|
want: []interface{}{42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".[42]"},
|
||||||
|
want: []interface{}{42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".42"},
|
||||||
|
want: []interface{}{"42"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".физ"},
|
||||||
|
want: []interface{}{"физ"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo.bar"},
|
||||||
|
want: []interface{}{"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo", ".bar"},
|
||||||
|
want: []interface{}{"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo[42]"},
|
||||||
|
want: []interface{}{"foo", 42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo[42].bar"},
|
||||||
|
want: []interface{}{"foo", 42, "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo[1][2]"},
|
||||||
|
want: []interface{}{"foo", 1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo[\"bar\"]"},
|
||||||
|
want: []interface{}{"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo[\"bar\\\"\"]"},
|
||||||
|
want: []interface{}{"foo", "bar\""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".foo['bar']['baz\\'']"},
|
||||||
|
want: []interface{}{"foo", "bar", "baz\\'"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
|
||||||
|
path, ok := split(tt.args)
|
||||||
|
require.Equal(t, tt.want, path)
|
||||||
|
require.True(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_split_negative(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: []string{"./"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x/"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"1+1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[42"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{".i % 2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[for x]"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x['y'."},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[0?"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[\"\\u"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x['\\n"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"x[9999999999999999999999999999999999999]"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
|
||||||
|
path, ok := split(tt.args)
|
||||||
|
require.False(t, ok, path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue