forked from geofffranks/spruce
-
Notifications
You must be signed in to change notification settings - Fork 1
/
op_join.go
193 lines (165 loc) · 5.77 KB
/
op_join.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package spruce
import (
"fmt"
"strings"
"github.com/starkandwayne/goutils/ansi"
"github.com/starkandwayne/goutils/tree"
. "github.com/geofffranks/spruce/log"
)
// JoinOperator is invoked with (( join <separator> <lists/strings>... )) and
// joins lists and strings into one string, separated by <separator>
type JoinOperator struct{}
// Setup ...
func (JoinOperator) Setup() error {
return nil
}
// Phase ...
func (JoinOperator) Phase() OperatorPhase {
return EvalPhase
}
// Dependencies returns the nodes that (( join ... )) requires to be resolved
// before its evaluation. Returns no dependencies on error, because who cares
// about eval order if Run is going to bomb out anyway.
func (JoinOperator) Dependencies(ev *Evaluator, args []*Expr, _ []*tree.Cursor, auto []*tree.Cursor) []*tree.Cursor {
DEBUG("Calculating dependencies for (( join ... ))")
deps := []*tree.Cursor{}
if len(args) < 2 {
DEBUG("Not enough arguments to (( join ... ))")
return []*tree.Cursor{}
}
//skip the separator arg
for _, arg := range args[1:] {
if arg.Type == Literal {
continue
}
if arg.Type != Reference {
DEBUG("(( join ... )) argument not Literal or Reference type")
return []*tree.Cursor{}
}
//get the real cursor
finalCursor, err := arg.Resolve(ev.Tree)
if err != nil {
DEBUG("Could not resolve to a canonical path '%s'", arg.String())
return []*tree.Cursor{}
}
//get the list at this location
list, err := finalCursor.Reference.Resolve(ev.Tree)
if err != nil {
DEBUG("Could not retrieve object at path '%s'", arg.String())
return []*tree.Cursor{}
}
//must be a list or a string
switch list.(type) {
case []interface{}:
//add .* to the end of the cursor so we can glob all the elements
globCursor, err := tree.ParseCursor(fmt.Sprintf("%s.*", finalCursor.Reference.String()))
if err != nil {
DEBUG("Could not parse cursor with '.*' appended. This is a BUG")
return []*tree.Cursor{}
}
//have the cursor library get all the subelements for us
subElements, err := globCursor.Glob(ev.Tree)
if err != nil {
DEBUG("Could not retrieve subelements at path '%s'. This may be a BUG.", arg.String())
return []*tree.Cursor{}
}
deps = append(deps, subElements...)
case string:
deps = append(deps, finalCursor.Reference)
default:
DEBUG("Unsupported type at object location")
return []*tree.Cursor{}
}
}
//Append on the auto-generated deps (the operator path args)
for _, dep := range auto {
deps = append(deps, dep)
}
DEBUG("Dependencies for (( join ... )):")
for i, dep := range deps {
DEBUG("\t#%d %s", i, dep.String())
}
return deps
}
// Run ...
func (JoinOperator) Run(ev *Evaluator, args []*Expr) (*Response, error) {
DEBUG("running (( join ... )) operation at $.%s", ev.Here)
defer DEBUG("done with (( join ... )) operation at $%s\n", ev.Here)
if len(args) == 0 {
DEBUG(" no arguments supplied to (( join ... )) operation.")
return nil, ansi.Errorf("no arguments specified to @c{(( join ... ))}")
}
if len(args) == 1 {
DEBUG(" too few arguments supplied to (( join ... )) operation.")
return nil, ansi.Errorf("too few arguments supplied to @c{(( join ... ))}")
}
var separator string
var list []string
for i, arg := range args {
if i == 0 { // argument #0: separator
sep, err := arg.Resolve(ev.Tree)
if err != nil {
DEBUG(" [%d]: resolution failed\n error: %s", i, err)
return nil, err
}
if sep.Type != Literal {
DEBUG(" [%d]: unsupported type for join operator separator argument: '%v'", i, sep)
return nil, fmt.Errorf("join operator only accepts literal argument for the separator")
}
DEBUG(" [%d]: list separator will be: %s", i, sep)
separator = sep.Literal.(string)
} else { // argument #1..n: list, or literal
ref, err := arg.Resolve(ev.Tree)
if err != nil {
DEBUG(" [%d]: resolution failed\n error: %s", i, err)
return nil, err
}
switch ref.Type {
case Literal:
DEBUG(" [%d]: adding literal %s to the list", i, ref)
list = append(list, fmt.Sprintf("%v", ref.Literal))
case Reference:
DEBUG(" [%d]: trying to resolve reference $.%s", i, ref.Reference)
s, err := ref.Reference.Resolve(ev.Tree)
if err != nil {
DEBUG(" [%d]: resolution failed with error: %s", i, err)
return nil, fmt.Errorf("Unable to resolve `%s`: %s", ref.Reference, err)
}
switch s.(type) {
case []interface{}:
DEBUG(" [%d]: $.%s is a list", i, ref.Reference)
for idx, entry := range s.([]interface{}) {
switch entry.(type) {
case []interface{}:
DEBUG(" [%d]: entry #%d in list is a list (not a literal)", i, idx)
return nil, ansi.Errorf("entry #%d in list is not compatible for @c{(( join ... ))}", idx)
case map[interface{}]interface{}:
DEBUG(" [%d]: entry #%d in list is a map (not a literal)", i, idx)
return nil, ansi.Errorf("entry #%d in list is not compatible for @c{(( join ... ))}", idx)
default:
list = append(list, fmt.Sprintf("%v", entry))
}
}
case map[interface{}]interface{}:
DEBUG(" [%d]: $.%s is a map (not a list or a literal)", i, ref.Reference)
return nil, ansi.Errorf("referenced entry is not a list or string for @c{(( join ... ))}")
default:
DEBUG(" [%d]: $.%s is a literal", i, ref.Reference)
list = append(list, fmt.Sprintf("%v", s))
}
default:
DEBUG(" [%d]: unsupported type for join operator: '%v'", i, ref)
return nil, fmt.Errorf("join operator only lists with string entries, and literals as data arguments")
}
}
}
// finally, join and return
DEBUG(" joined list: %s", strings.Join(list, separator))
return &Response{
Type: Replace,
Value: strings.Join(list, separator),
}, nil
}
func init() {
RegisterOp("join", JoinOperator{})
}