forked from gnormal/gnorm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
funcs.go
235 lines (214 loc) · 5.88 KB
/
funcs.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package environ
import (
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/episub/gnorm/run/data"
"github.com/codemodus/kace"
"github.com/jinzhu/inflection"
"github.com/pkg/errors"
)
// FuncMap is the default list of functions available to templates. If you add
// methods here, please keep them alphabetical.
var FuncMap = map[string]interface{}{
"camel": kace.Camel,
"compare": strings.Compare,
"contains": strings.Contains,
"containsAny": strings.ContainsAny,
"count": strings.Count,
"dec": dec,
"equalFold": strings.EqualFold,
"fields": strings.Fields,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"inc": inc,
"strIndex": strings.Index,
"indexAny": strings.IndexAny,
"join": strings.Join,
"kebab": kace.Kebab,
"kebabUpper": kace.KebabUpper,
"lastIndex": strings.LastIndex,
"lastIndexAny": strings.LastIndexAny,
"makeMap": makeMap,
"makeSlice": makeSlice,
"numbers": numbers,
"pascal": kace.Pascal,
"plural": inflection.Plural,
"repeat": strings.Repeat,
"replace": strings.Replace,
"singular": inflection.Singular,
"sliceString": sliceString,
"snake": kace.Snake,
"snakeUpper": kace.SnakeUpper,
"split": strings.Split,
"splitAfter": strings.SplitAfter,
"splitAfterN": strings.SplitAfterN,
"splitN": strings.SplitN,
"sub": sub,
"sum": sum,
"title": strings.Title,
"toLower": strings.ToLower,
"toTitle": strings.ToTitle,
"toUpper": strings.ToUpper,
"trim": strings.Trim,
"trimLeft": strings.TrimLeft,
"trimPrefix": strings.TrimPrefix,
"trimRight": strings.TrimRight,
"trimSpace": strings.TrimSpace,
"trimSuffix": strings.TrimSuffix,
}
// sliceString returns a slice of s from index start to end.
func sliceString(s string, start, end int) string {
return s[start:end]
}
// makeSlice returns the arguments as a single slice. If all the arguments are
// strings, they are returned as a []string, otherwise they're returned as
// []interface{}.
func makeSlice(vals ...interface{}) interface{} {
ss := make([]string, len(vals))
for x := range vals {
if s, ok := vals[x].(string); ok {
ss[x] = s
} else {
// something was not a string, so just return the []interface{}
return vals
}
}
return ss
}
// makeMap expects an even number of parameters, in order to have name:value
// pairs. All even values must be strings as keys. Odd values may be any
// value. This is used to make maps to pass information into sub templates,
// range statements, etc.
func makeMap(vals ...interface{}) (map[string]interface{}, error) {
if len(vals)%2 != 0 {
return nil, errors.New("odd number of arguments passed to makeMap")
}
ret := make(map[string]interface{}, len(vals)/2)
for x := 0; x < len(vals); x += 2 {
s, ok := vals[x].(string)
if !ok {
return nil, fmt.Errorf("expected key values to be string, but got %T", vals[x])
}
ret[s] = vals[x+1]
}
return ret, nil
}
// dec decrements the argument's value by 1.
func dec(x int) int {
return x - 1
}
// inc increments the argument's value by 1.
func inc(x int) int {
return x + 1
}
// sum returns the sum of its arguments.
func sum(vals ...int) int {
x := 0
for _, v := range vals {
x += v
}
return x
}
// sub subtracts the second and following values from the first argument.
func sub(x int, vals ...int) int {
for _, v := range vals {
x -= v
}
return x
}
// numbers returns a slice of strings of the numbers start to end (inclusive).
func numbers(start, end int) data.Strings {
var s data.Strings
for x := start; x <= end; x++ {
s = append(s, strconv.Itoa(x))
}
return s
}
// Plugin returns a function which can be used in templates for executing plugins,
// dirs is the list of directories which are used fo plugin lookup.
func Plugin(dirs []string) func(string, string, interface{}) (interface{}, error) {
return func(name, function string, ctx interface{}) (interface{}, error) {
name, err := lookUpPlugin(dirs, name)
if err != nil {
return nil, err
}
return callPlugin(exec.Command, name, function, ctx)
}
}
func lookUpPlugin(dirs []string, name string) (p string, err error) {
for _, v := range dirs {
p, err = exec.LookPath(filepath.Join(v, name))
if err == nil {
return
}
}
return
}
func convert(v interface{}) interface{} {
if m, ok := v.(map[string]interface{}); ok {
for k, v := range m {
m[k] = convert(v)
}
return m
}
list, ok := v.([]interface{})
if !ok {
return v
}
var str []string
for _, val := range list {
s, ok := val.(string)
if !ok {
var out []interface{}
for _, x := range list {
out = append(out, convert(x))
}
return out
}
str = append(str, s)
}
return str
}
func callPlugin(runner cmdRunner, name, function string, ctx interface{}) (interface{}, error) {
d := make(map[string]interface{})
d["data"] = ctx
b, err := json.Marshal(d)
if err != nil {
return nil, err
}
o, err := execJSON(runner, name, b, function)
if err != nil {
return nil, err
}
return convert(o["data"]), nil
}
type cmdRunner func(name string, args ...string) *exec.Cmd
// execJSON executes the plugin name with arguments args. It creates a io.Pipe
// to stdin of the command and writes the data into the pipe in a separate
// goroutine.
//
// The output is decoded as json data into a map[string]interface{}
func execJSON(runner cmdRunner, name string, data []byte, args ...string) (map[string]interface{}, error) {
cmd := runner(name, args...)
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
go func() {
defer stdin.Close()
stdin.Write(data)
}()
v, err := cmd.CombinedOutput()
if err != nil {
return nil, errors.Wrapf(err, "error running plugin %q: ", string(v))
}
o := make(map[string]interface{})
if err = json.Unmarshal(v, &o); err != nil {
return nil, err
}
return o, nil
}