Skip to content

Commit

Permalink
feat: add more list funcs
Browse files Browse the repository at this point in the history
  • Loading branch information
bradenhilton committed May 18, 2024
1 parent 9907d6a commit 6241cb0
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 2 deletions.
31 changes: 31 additions & 0 deletions docs/templatefuncs.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Template Functions

## `compact` *list*

`compact` removes all zero value items from *list*.

```text
{{ list "one" "" list "three" | compact }}
[one three]
```

## `contains` *substring* *string*

`contains` returns whether *substring* is in *string*.
Expand Down Expand Up @@ -29,6 +39,16 @@ true
{{ `{ "foo": "bar" }` | fromJSON }}
```

## `has` *item* *list*

`has` returns whether *item* is in *list*.

```text
{{ list 1 2 3 | has 3 }}
true
```

## `hasPrefix` *prefix* *string*

`hasPrefix` returns whether *string* begins with *prefix*.
Expand Down Expand Up @@ -69,6 +89,17 @@ foobar
666f6f626172
```

## `indexOf` *item* *list*

`indexOf` returns the index of *item* in *list*, or -1 if *item* is not
in *list*.

```text
{{ list "a" "b" "c" | indexOf "b" }}
1
```

## `join` *delimiter* *list*

`join` returns a string containing each item in *list* joined with *delimiter*.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/chezmoi/templatefuncs

go 1.19
go 1.21

require github.com/alecthomas/assert/v2 v2.9.0

Expand Down
38 changes: 38 additions & 0 deletions templatefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"io/fs"
"os"
"os/exec"
"reflect"
"regexp"
"slices"
"strconv"
"strings"
"text/template"
Expand All @@ -29,13 +31,16 @@ var fileModeTypeNames = map[fs.FileMode]string{
// functions.
func NewFuncMap() template.FuncMap {
return template.FuncMap{
"compact": compactTemplateFunc,
"contains": reverseArgs2(strings.Contains),
"eqFold": eqFoldTemplateFunc,
"fromJSON": eachByteSliceErr(fromJSONTemplateFunc),
"has": reverseArgs2(slices.Contains[[]any]),
"hasPrefix": reverseArgs2(strings.HasPrefix),
"hasSuffix": reverseArgs2(strings.HasSuffix),
"hexDecode": eachStringErr(hex.DecodeString),
"hexEncode": eachByteSlice(hex.EncodeToString),
"indexOf": reverseArgs2(slices.Index[[]any]),
"join": reverseArgs2(strings.Join),
"list": listTemplateFunc,
"lookPath": eachStringErr(lookPathTemplateFunc),
Expand All @@ -52,6 +57,12 @@ func NewFuncMap() template.FuncMap {
}
}

// compactTemplateFunc is the core implementation of the `compact` template
// function.
func compactTemplateFunc(list []any) []any {
return slices.DeleteFunc(list, isZeroValue)
}

// eqFoldTemplateFunc is the core implementation of the `eqFold` template
// function.
func eqFoldTemplateFunc(first, second string, more ...string) bool {
Expand Down Expand Up @@ -369,6 +380,33 @@ func fileInfoToMap(fileInfo fs.FileInfo) map[string]any {
}
}

// isZeroValue returns whether a value is the zero value for its type.
// An empty array, map or slice is assumed to be a zero value.
func isZeroValue(v any) bool {
vval := reflect.ValueOf(v)
if !vval.IsValid() {
return true
}
switch vval.Kind() { //nolint:exhaustive
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return vval.Len() == 0
case reflect.Bool:
return !vval.Bool()
case reflect.Complex64, reflect.Complex128:
return vval.Complex() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return vval.Int() == 0
case reflect.Float32, reflect.Float64:
return vval.Float() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return vval.Uint() == 0
case reflect.Struct:
return false
default:
return vval.IsNil()
}
}

// reverseArgs2 transforms a function that takes two arguments and returns an
// `R` into a function that takes the arguments in reverse order and returns an
// `R`.
Expand Down
17 changes: 16 additions & 1 deletion templatefuncs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ func TestFuncMap(t *testing.T) {
data any
expected string
}{
{},
{
template: `{{ list "one" "" list "three" | compact }}`,
expected: `[one three]`,
},
{
template: `{{ "abc" | contains "bc" }}`,
expected: "true",
Expand All @@ -75,6 +78,14 @@ func TestFuncMap(t *testing.T) {
template: `{{ fromJSON "0" }}`,
expected: "0",
},
{
template: `{{ list 1 2 3 | has 3 }}`,
expected: "true",
},
{
template: `{{ has 3 (list 1 2 3) }}`,
expected: "true",
},
{
template: `{{ "ab" | hasPrefix "a" }}`,
expected: "true",
Expand All @@ -91,6 +102,10 @@ func TestFuncMap(t *testing.T) {
template: `{{ "ab" | hasSuffix "b" }}`,
expected: "true",
},
{
template: `{{ list "a" "b" "c" | indexOf "b" }}`,
expected: "1",
},
{
template: `{{ list "a" "b" "c" | quote | join "," }}`,
expected: `"a","b","c"`,
Expand Down

0 comments on commit 6241cb0

Please sign in to comment.