1
1
package templatefuncs
2
2
3
3
import (
4
- "encoding/hex"
5
- "encoding/json"
6
4
"errors"
7
- "fmt"
8
5
"io/fs"
6
+ "maps"
9
7
"os"
10
8
"os/exec"
11
- "regexp"
12
- "strconv"
13
- "strings"
14
9
"text/template"
15
- )
16
10
17
- // fileModeTypeNames maps file mode types to human-readable strings.
18
- var fileModeTypeNames = map [fs.FileMode ]string {
19
- 0 : "file" ,
20
- fs .ModeDir : "dir" ,
21
- fs .ModeSymlink : "symlink" ,
22
- fs .ModeNamedPipe : "named pipe" ,
23
- fs .ModeSocket : "socket" ,
24
- fs .ModeDevice : "device" ,
25
- fs .ModeCharDevice : "char device" ,
26
- }
11
+ "github.com/chezmoi/templatefuncs/internal/utils"
12
+ "github.com/chezmoi/templatefuncs/pkg/booleanfuncs"
13
+ "github.com/chezmoi/templatefuncs/pkg/conversionfuncs"
14
+ "github.com/chezmoi/templatefuncs/pkg/listfuncs"
15
+ "github.com/chezmoi/templatefuncs/pkg/stringfuncs"
16
+ )
27
17
28
18
// NewFuncMap returns a new [text/template.FuncMap] containing all template
29
19
// functions.
30
20
func NewFuncMap () template.FuncMap {
31
- return template.FuncMap {
32
- "contains" : reverseArgs2 (strings .Contains ),
33
- "eqFold" : eqFoldTemplateFunc ,
34
- "fromJSON" : eachByteSliceErr (fromJSONTemplateFunc ),
35
- "hasPrefix" : reverseArgs2 (strings .HasPrefix ),
36
- "hasSuffix" : reverseArgs2 (strings .HasSuffix ),
37
- "hexDecode" : eachStringErr (hex .DecodeString ),
38
- "hexEncode" : eachByteSlice (hex .EncodeToString ),
39
- "join" : reverseArgs2 (strings .Join ),
40
- "list" : listTemplateFunc ,
41
- "lookPath" : eachStringErr (lookPathTemplateFunc ),
42
- "lstat" : eachString (lstatTemplateFunc ),
43
- "prefixLines" : prefixLinesTemplateFunc ,
44
- "quote" : eachString (strconv .Quote ),
45
- "regexpReplaceAll" : regexpReplaceAllTemplateFunc ,
46
- "stat" : eachString (statTemplateFunc ),
47
- "toJSON" : toJSONTemplateFunc ,
48
- "toLower" : eachString (strings .ToLower ),
49
- "toString" : toStringTemplateFunc ,
50
- "toUpper" : eachString (strings .ToUpper ),
51
- "trimSpace" : eachString (strings .TrimSpace ),
52
- }
53
- }
21
+ funcMap := template.FuncMap {}
54
22
55
- // prefixLinesTemplateFunc is the core implementation of the `prefixLines`
56
- // template function.
57
- func prefixLinesTemplateFunc (prefix , s string ) string {
58
- type stateType int
59
- const (
60
- startOfLine stateType = iota
61
- inLine
62
- )
23
+ maps .Copy (funcMap , template.FuncMap {
24
+ "lookPath" : utils .EachStringErr (lookPathTemplateFunc ),
25
+ "lstat" : utils .EachString (lstatTemplateFunc ),
26
+ "stat" : utils .EachString (statTemplateFunc ),
27
+ })
63
28
64
- state := startOfLine
65
- var builder strings.Builder
66
- builder .Grow (2 * len (s ))
67
- for _ , r := range s {
68
- switch state {
69
- case startOfLine :
70
- if _ , err := builder .WriteString (prefix ); err != nil {
71
- panic (err )
72
- }
73
- if _ , err := builder .WriteRune (r ); err != nil {
74
- panic (err )
75
- }
76
- if r != '\n' {
77
- state = inLine
78
- }
79
- case inLine :
80
- if _ , err := builder .WriteRune (r ); err != nil {
81
- panic (err )
82
- }
83
- if r == '\n' {
84
- state = startOfLine
85
- }
86
- }
87
- }
88
- return builder .String ()
89
- }
29
+ maps .Copy (funcMap , booleanfuncs .FuncMap )
30
+ maps .Copy (funcMap , conversionfuncs .FuncMap )
31
+ maps .Copy (funcMap , listfuncs .FuncMap )
32
+ maps .Copy (funcMap , stringfuncs .FuncMap )
90
33
91
- // eqFoldTemplateFunc is the core implementation of the `eqFold` template
92
- // function.
93
- func eqFoldTemplateFunc (first , second string , more ... string ) bool {
94
- if strings .EqualFold (first , second ) {
95
- return true
96
- }
97
- for _ , s := range more {
98
- if strings .EqualFold (first , s ) {
99
- return true
100
- }
101
- }
102
- return false
103
- }
104
-
105
- // fromJSONTemplateFunc is the core implementation of the `fromJSON` template
106
- // function.
107
- func fromJSONTemplateFunc (data []byte ) (any , error ) {
108
- var result any
109
- if err := json .Unmarshal (data , & result ); err != nil {
110
- return nil , err
111
- }
112
- return result , nil
113
- }
114
-
115
- // listTemplateFunc is the core implementation of the `list` template function.
116
- func listTemplateFunc (args ... any ) []any {
117
- return args
34
+ return funcMap
118
35
}
119
36
120
37
// lookPathTemplateFunc is the core implementation of the `lookPath` template
@@ -137,243 +54,22 @@ func lookPathTemplateFunc(file string) (string, error) {
137
54
func lstatTemplateFunc (name string ) any {
138
55
switch fileInfo , err := os .Lstat (name ); {
139
56
case err == nil :
140
- return fileInfoToMap (fileInfo )
57
+ return utils . FileInfoToMap (fileInfo )
141
58
case errors .Is (err , fs .ErrNotExist ):
142
59
return nil
143
60
default :
144
61
panic (err )
145
62
}
146
63
}
147
64
148
- // regexpReplaceAllTemplateFunc is the core implementation of the
149
- // `regexpReplaceAll` template function.
150
- func regexpReplaceAllTemplateFunc (expr , repl , s string ) string {
151
- return regexp .MustCompile (expr ).ReplaceAllString (s , repl )
152
- }
153
-
154
65
// statTemplateFunc is the core implementation of the `stat` template function.
155
66
func statTemplateFunc (name string ) any {
156
67
switch fileInfo , err := os .Stat (name ); {
157
68
case err == nil :
158
- return fileInfoToMap (fileInfo )
69
+ return utils . FileInfoToMap (fileInfo )
159
70
case errors .Is (err , fs .ErrNotExist ):
160
71
return nil
161
72
default :
162
73
panic (err )
163
74
}
164
75
}
165
-
166
- // toJSONTemplateFunc is the core implementation of the `toJSON` template
167
- // function.
168
- func toJSONTemplateFunc (arg any ) []byte {
169
- data , err := json .Marshal (arg )
170
- if err != nil {
171
- panic (err )
172
- }
173
- return data
174
- }
175
-
176
- // toStringTemplateFunc is the core implementation of the `toString` template
177
- // function.
178
- func toStringTemplateFunc (arg any ) string {
179
- // FIXME add more types
180
- switch arg := arg .(type ) {
181
- case string :
182
- return arg
183
- case []byte :
184
- return string (arg )
185
- case bool :
186
- return strconv .FormatBool (arg )
187
- case float32 :
188
- return strconv .FormatFloat (float64 (arg ), 'f' , - 1 , 32 )
189
- case float64 :
190
- return strconv .FormatFloat (arg , 'f' , - 1 , 64 )
191
- case int :
192
- return strconv .Itoa (arg )
193
- case int32 :
194
- return strconv .FormatInt (int64 (arg ), 10 )
195
- case int64 :
196
- return strconv .FormatInt (arg , 10 )
197
- default :
198
- panic (fmt .Sprintf ("%T: unsupported type" , arg ))
199
- }
200
- }
201
-
202
- // eachByteSlice transforms a function that takes a single `[]byte` and returns
203
- // a `T` to a function that takes zero or more `[]byte`-like arguments and
204
- // returns zero or more `T`s.
205
- func eachByteSlice [T any ](f func ([]byte ) T ) func (any ) any {
206
- return func (arg any ) any {
207
- switch arg := arg .(type ) {
208
- case []byte :
209
- return f (arg )
210
- case [][]byte :
211
- result := make ([]T , 0 , len (arg ))
212
- for _ , a := range arg {
213
- result = append (result , f (a ))
214
- }
215
- return result
216
- case string :
217
- return f ([]byte (arg ))
218
- case []string :
219
- result := make ([]T , 0 , len (arg ))
220
- for _ , a := range arg {
221
- result = append (result , f ([]byte (a )))
222
- }
223
- return result
224
- default :
225
- panic (fmt .Sprintf ("%T: unsupported argument type" , arg ))
226
- }
227
- }
228
- }
229
-
230
- // eachByteSliceErr transforms a function that takes a single `[]byte` and
231
- // returns a `T` and an `error` into a function that takes zero or more
232
- // `[]byte`-like arguments and returns zero or more `Ts` and an error.
233
- func eachByteSliceErr [T any ](f func ([]byte ) (T , error )) func (any ) any {
234
- return func (arg any ) any {
235
- switch arg := arg .(type ) {
236
- case []byte :
237
- result , err := f (arg )
238
- if err != nil {
239
- panic (err )
240
- }
241
- return result
242
- case [][]byte :
243
- result := make ([]T , 0 , len (arg ))
244
- for _ , a := range arg {
245
- r , err := f (a )
246
- if err != nil {
247
- panic (err )
248
- }
249
- result = append (result , r )
250
- }
251
- return result
252
- case string :
253
- result , err := f ([]byte (arg ))
254
- if err != nil {
255
- panic (err )
256
- }
257
- return result
258
- case []string :
259
- result := make ([]T , 0 , len (arg ))
260
- for _ , a := range arg {
261
- r , err := f ([]byte (a ))
262
- if err != nil {
263
- panic (err )
264
- }
265
- result = append (result , r )
266
- }
267
- return result
268
- default :
269
- panic (fmt .Sprintf ("%T: unsupported argument type" , arg ))
270
- }
271
- }
272
- }
273
-
274
- // eachString transforms a function that takes a single `string`-like argument
275
- // and returns a `T` into a function that takes zero or more `string`-like
276
- // arguments and returns zero or more `T`s.
277
- func eachString [T any ](f func (string ) T ) func (any ) any {
278
- return func (arg any ) any {
279
- switch arg := arg .(type ) {
280
- case string :
281
- return f (arg )
282
- case []string :
283
- result := make ([]T , 0 , len (arg ))
284
- for _ , a := range arg {
285
- result = append (result , f (a ))
286
- }
287
- return result
288
- case []byte :
289
- return f (string (arg ))
290
- case [][]byte :
291
- result := make ([]T , 0 , len (arg ))
292
- for _ , a := range arg {
293
- result = append (result , f (string (a )))
294
- }
295
- return result
296
- case []any :
297
- result := make ([]T , 0 , len (arg ))
298
- for _ , a := range arg {
299
- switch a := a .(type ) {
300
- case string :
301
- result = append (result , f (a ))
302
- case []byte :
303
- result = append (result , f (string (a )))
304
- default :
305
- panic (fmt .Sprintf ("%T: unsupported argument type" , a ))
306
- }
307
- }
308
- return result
309
- default :
310
- panic (fmt .Sprintf ("%T: unsupported argument type" , arg ))
311
- }
312
- }
313
- }
314
-
315
- // eachStringErr transforms a function that takes a single `string`-like argument
316
- // and returns a `T` and an `error` into a function that takes zero or more
317
- // `string`-like arguments and returns zero or more `T`s and an `error`.
318
- func eachStringErr [T any ](f func (string ) (T , error )) func (any ) any {
319
- return func (arg any ) any {
320
- switch arg := arg .(type ) {
321
- case string :
322
- result , err := f (arg )
323
- if err != nil {
324
- panic (err )
325
- }
326
- return result
327
- case []string :
328
- result := make ([]T , 0 , len (arg ))
329
- for _ , a := range arg {
330
- r , err := f (a )
331
- if err != nil {
332
- panic (err )
333
- }
334
- result = append (result , r )
335
- }
336
- return result
337
- case []byte :
338
- result , err := f (string (arg ))
339
- if err != nil {
340
- panic (err )
341
- }
342
- return result
343
- case [][]byte :
344
- result := make ([]T , 0 , len (arg ))
345
- for _ , a := range arg {
346
- r , err := f (string (a ))
347
- if err != nil {
348
- panic (err )
349
- }
350
- result = append (result , r )
351
- }
352
- return result
353
- default :
354
- panic (fmt .Sprintf ("%T: unsupported argument type" , arg ))
355
- }
356
- }
357
- }
358
-
359
- // fileInfoToMap returns a `map[string]any` of `fileInfo`'s fields.
360
- func fileInfoToMap (fileInfo fs.FileInfo ) map [string ]any {
361
- return map [string ]any {
362
- "name" : fileInfo .Name (),
363
- "size" : fileInfo .Size (),
364
- "mode" : int (fileInfo .Mode ()),
365
- "perm" : int (fileInfo .Mode ().Perm ()),
366
- "modTime" : fileInfo .ModTime ().Unix (),
367
- "isDir" : fileInfo .IsDir (),
368
- "type" : fileModeTypeNames [fileInfo .Mode ()& fs .ModeType ],
369
- }
370
- }
371
-
372
- // reverseArgs2 transforms a function that takes two arguments and returns an
373
- // `R` into a function that takes the arguments in reverse order and returns an
374
- // `R`.
375
- func reverseArgs2 [T1 , T2 , R any ](f func (T1 , T2 ) R ) func (T2 , T1 ) R {
376
- return func (arg1 T2 , arg2 T1 ) R {
377
- return f (arg2 , arg1 )
378
- }
379
- }
0 commit comments