3
0
corteza/pkg/expr/expr.go
2021-08-26 10:36:38 +02:00

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
}