-
Notifications
You must be signed in to change notification settings - Fork 1
/
eval.go
161 lines (142 loc) · 3.05 KB
/
eval.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
package ycat
//go:generate go run gen.go
import (
"io/ioutil"
"os"
"path"
"strings"
jsonnet "github.com/google/go-jsonnet"
)
// Eval is the execution environment for Jsonnet
type Eval struct {
Bind string
MaxStackSize int
Array bool
Vars map[string]Var
vm *jsonnet.VM
}
// VarType is the type of an external variable
type VarType uint
// VarTypes
const (
_ VarType = iota
FileVar
CodeVar
RawVar
)
// Var is an external variable
type Var struct {
Type VarType
Value string
}
// AddVar adds an external variable
func (e *Eval) AddVar(typ VarType, name, value string) {
if e.Vars == nil {
e.Vars = make(map[string]Var)
}
e.Vars[name] = Var{typ, value}
}
// Render renders the Jsonnet snippet to be executed
func (v Var) Render(w *strings.Builder, name string) {
w.WriteString("local ")
w.WriteString(name)
w.WriteString(" = ")
switch v.Type {
case FileVar:
switch path.Ext(v.Value) {
case ".json", ".libsonnet", ".jsonnet":
w.WriteString(`import "`)
case ".yaml", ".yml":
w.WriteString(`importyaml "`)
default:
w.WriteString(`importstr "`)
}
w.WriteString(v.Value)
w.WriteString("\";\n")
default:
w.WriteString(`std.extVar("`)
w.WriteString(name)
w.WriteString("\");\n")
}
}
// Render renders a snippet binding local variables
func (e *Eval) Render(snippet string) string {
w := strings.Builder{}
for name, v := range e.Vars {
v.Render(&w, name)
}
bind := bindVar(e.Bind)
Var{Type: CodeVar}.Render(&w, "_")
Var{Type: CodeVar}.Render(&w, bind)
w.WriteString(snippet)
return w.String()
}
// VM updates or creates a Jsonnet VM
func (e *Eval) VM() (vm *jsonnet.VM) {
if e.vm == nil {
e.vm = jsonnet.MakeVM()
}
vm = e.vm
if e.MaxStackSize > 0 {
vm.MaxStack = e.MaxStackSize
}
for name, v := range e.Vars {
switch v.Type {
case FileVar:
// Handled by import
case CodeVar:
vm.ExtCode(name, v.Value)
default:
vm.ExtVar(name, v.Value)
}
}
vm.ExtCode("_", ycatStdLib)
return vm
}
// DefaultInputVar is the default name for the stream value
const DefaultInputVar = "x"
func bindVar(v string) string {
if v == "" {
return DefaultInputVar
}
return v
}
func (e *Eval) SnippetFromFile(filename string) StreamTask {
return StreamFunc(func(s Stream) error {
snippet, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
return e.Snippet(filename, string(snippet)).Run(s)
})
}
// EvalSnippetTask transforms a stream of input values with Jsonnet
func (e *Eval) Snippet(filename, snippet string) StreamTask {
bind := bindVar(e.Bind)
vm := e.VM()
snippet = e.Render(snippet)
return StreamFunc(func(s Stream) error {
for {
v, ok := s.Next()
if !ok {
return nil
}
vm.ExtCode(bind, v.MarshalJSONString())
result, err := vm.EvaluateSnippet(filename, snippet)
if err != nil {
return err
}
if !s.Push(RawValue(result)) {
return nil
}
}
})
}
// EvalFilename returns a filename on CWD
func EvalFilename() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return path.Join(cwd, "ycat.jsonnet"), nil
}