159 lines
3.0 KiB
Go
159 lines
3.0 KiB
Go
package expr
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
pathDelimiter = "."
|
|
)
|
|
|
|
var (
|
|
invalidPathErr = fmt.Errorf("invalid path format")
|
|
)
|
|
|
|
func PathSplit(path string) ([]string, error) {
|
|
out := make([]string, 0)
|
|
s := bufio.NewScanner(strings.NewReader(path))
|
|
s.Split(pathSplitter)
|
|
|
|
for s.Scan() {
|
|
// checks if two consecutive path parts are empty
|
|
if len(s.Text()) == 0 && len(out) > 0 && len(out[len(out)-1]) == 0 {
|
|
return nil, invalidPathErr
|
|
}
|
|
out = append(out, s.Text())
|
|
}
|
|
|
|
if s.Err() != nil {
|
|
return nil, s.Err()
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func pathSplitter(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
start := 0
|
|
for i := 0; i < len(data); i += 1 {
|
|
switch data[i] {
|
|
case '.', '[':
|
|
return i + 1, data[start:i], nil
|
|
case ']':
|
|
// When at closing bracket but not at the end, make sure we properly split the token
|
|
if i == len(data)-1 {
|
|
return i + 1, data[start:i], nil
|
|
}
|
|
|
|
if data[i+1] != '.' && data[i+1] != '[' {
|
|
return 0, nil, invalidPathErr
|
|
}
|
|
|
|
return i + 2, data[start:i], nil
|
|
}
|
|
}
|
|
|
|
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
|
if atEOF && len(data) > start {
|
|
return len(data), data[start:], nil
|
|
}
|
|
// Request more data.
|
|
return start, nil, nil
|
|
}
|
|
|
|
func PathBase(path string) string {
|
|
return strings.Split(path, ".")[0]
|
|
}
|
|
|
|
func Assign(base TypedValue, path string, val TypedValue) error {
|
|
pp, err := PathSplit(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(pp) == 0 {
|
|
panic("setting value with empty path")
|
|
}
|
|
|
|
var (
|
|
key = pp[0]
|
|
)
|
|
|
|
// descend lower by the path but
|
|
// stop before the last part of the path
|
|
for len(pp) > 1 {
|
|
switch s := base.(type) {
|
|
case DeepFieldAssigner:
|
|
return s.AssignFieldValue(pp, val)
|
|
|
|
case FieldSelector:
|
|
key, pp = pp[0], pp[1:]
|
|
if base, err = s.Select(key); err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("cannot set value on %s with path '%s'", base.Type(), path)
|
|
|
|
}
|
|
}
|
|
|
|
key = pp[0]
|
|
|
|
// try with field setter first
|
|
// if not a FieldSetter it has to be a Selector
|
|
// that returns TypedValue that we can set
|
|
switch setter := base.(type) {
|
|
case DeepFieldAssigner:
|
|
return setter.AssignFieldValue(pp, val)
|
|
|
|
case FieldAssigner:
|
|
return setter.AssignFieldValue(key, val)
|
|
|
|
case FieldSelector:
|
|
if base, err = setter.Select(key); err != nil {
|
|
return err
|
|
}
|
|
|
|
return base.Assign(val)
|
|
|
|
default:
|
|
return fmt.Errorf("%T does not support value assigning with '%s'", base, path)
|
|
}
|
|
|
|
}
|
|
|
|
func Select(base TypedValue, path string) (TypedValue, error) {
|
|
pp, err := PathSplit(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(pp) == 0 {
|
|
panic("selecting value with empty path")
|
|
}
|
|
|
|
var (
|
|
failure = fmt.Errorf("cannot get value from %s with path '%s'", base.Type(), path)
|
|
key string
|
|
)
|
|
|
|
// descend lower by the path but
|
|
// stop before the last part of the path
|
|
for len(pp) > 0 {
|
|
s, is := base.(FieldSelector)
|
|
if !is {
|
|
return nil, failure
|
|
}
|
|
|
|
key, pp = pp[0], pp[1:]
|
|
if base, err = s.Select(key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
}
|
|
|
|
return base, nil
|
|
}
|