/
properly.go
166 lines (146 loc) · 3.41 KB
/
properly.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package properly
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"text/scanner"
)
var (
// ErrInvalidIndex returned if an invalid index is used for an array.
ErrInvalidIndex = errors.New("Invalid index")
// ErrNotSupported returned if an unsupported value is encountered while
// evaluating the expression.
ErrNotSupported = errors.New("Value not supported")
// ErrNotFound returned if a property was not found.
ErrNotFound = errors.New("Property not found")
)
// Value gets the value of the object graph at the specified path.
func Value(object interface{}, expr string) (v interface{}, ok bool, err error) {
v = object
ok = true
if object == nil {
return
}
var keys []string
keys, err = split(expr)
if err != nil {
ok = false
return
}
for _, key := range keys {
v, ok, err = value(v, key)
if err != nil {
ok = false
break
}
}
return
}
// String gets the string value from the object graph at the specified path.
func String(object interface{}, expr string) (s string, ok bool, err error) {
value, ok, err := Value(object, expr)
if ok {
s, ok = value.(string)
}
return
}
func value(value interface{}, key string) (interface{}, bool, error) {
if key == "" {
return value, true, nil
}
ov := reflect.ValueOf(value)
kind := ov.Kind()
if kind == reflect.Ptr {
ov = ov.Elem()
kind = ov.Kind()
} else if kind == reflect.Interface {
kind = ov.Elem().Kind()
}
var nv reflect.Value
ok := true
switch kind {
case reflect.Map:
nv = ov.MapIndex(reflect.ValueOf(key))
if !nv.IsValid() {
nv = reflect.Zero(ov.Type().Elem())
ok = false
}
case reflect.Struct:
nv = ov.FieldByName(key)
ok = nv.IsValid()
case reflect.Slice, reflect.Array, reflect.String:
i, err := strconv.Atoi(key)
if err != nil {
return value, false, err
}
nv = ov.Index(i)
case reflect.Invalid:
return value, false, ErrNotFound
default:
return value, false, ErrNotSupported
}
if nv.IsValid() {
value = nv.Interface()
} else {
value = nil
}
return value, ok, nil
}
func split(expr string) (keys []string, err error) {
var msgs []string
var s scanner.Scanner
s.Init(strings.NewReader(expr))
s.Filename = expr
s.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings
s.Error = func(s *scanner.Scanner, msg string) { msgs = append(msgs, fmt.Sprintf("%s %s", s.Pos(), msg)) }
var key string
keys = []string{}
for err == nil {
t := s.Peek()
// fmt.Printf(">>> %s: %s %s\n", s.Pos(), scanner.TokenString(t), s.TokenText())
switch t {
case '[':
key, err = scanBracketedKey(&s)
case '.':
s.Scan()
continue
case scanner.EOF:
goto end
default:
key, err = scanKey(&s)
}
if len(msgs) > 0 {
err = errors.New(strings.Join(msgs, "\n"))
}
if err == nil {
keys = append(keys, key)
}
}
end:
return
}
func scanKey(s *scanner.Scanner) (key string, err error) {
t := s.Scan()
switch t {
case scanner.Ident, scanner.Int, scanner.Float:
key = s.TokenText()
case scanner.String:
key = strings.Trim(s.TokenText(), "\"")
default:
err = fmt.Errorf("Unexpected token at %s. Expected ident, number or string, had %s", s.Pos(), scanner.TokenString(t))
}
return
}
func scanBracketedKey(s *scanner.Scanner) (key string, err error) {
s.Scan() // scan the '['
key, err = scanKey(s)
if err == nil {
t := s.Scan()
if t != ']' {
err = fmt.Errorf("Unexpected token at %s. Expected ']', had %s", s.Pos(), scanner.TokenString(t))
}
}
return
}