Skip to content

Commit

Permalink
Merge pull request #900 from hairyhenderson/kind-functions-892
Browse files Browse the repository at this point in the history
Add test.Kind/kind and test.IsKind/isKind functions
  • Loading branch information
Dave Henderson authored and GitHub committed Jul 17, 2020
2 parents fa832c3 + 5dd9dc8 commit 7254b97
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 0 deletions.
71 changes: 71 additions & 0 deletions docs-src/content/functions/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,77 @@ funcs:
template: <arg>:1:3: executing "<arg>" at <fail>: error calling fail: template generation failed
$ gomplate -i '{{ test.Fail "something is wrong!" }}'
template: <arg>:1:7: executing "<arg>" at <test.Fail>: error calling Fail: template generation failed: something is wrong!
- name: test.IsKind
alias: isKind
description: |
Report whether the argument is of the given Kind. Can be used to render
different templates depending on the kind of data.
See [the Go `reflect` source code](https://github.com/golang/go/blob/36fcde1676a0d3863cb5f295eed6938cd782fcbb/src/reflect/type.go#L595..L622)
for the complete list, but these are some common values:
- `string`
- `bool`
- `int`, `int64`, `uint64`
- `float64`
- `slice`
- `map`
- `invalid` (a catch-all, usually just `nil` values)
In addition, the special kind `number` is accepted by this function, to
represent _any_ numeric kind (whether `float32`, `uint8`, or whatever).
This is useful when the specific numeric type is unknown.
See also [`test.Kind`](test-kind).
pipeline: true
arguments:
- name: kind
required: true
description: the kind to compare with (see desription for possible values)
- name: value
required: true
description: the value to check
examples:
- |
$ gomplate -i '{{ $data := "hello world" }}
{{- if isKind "string" $data }}{{ $data }} is a string{{ end }}'
hello world is a string
- |
$ gomplate -i '{{ $object := dict "key1" true "key2" "foobar" }}
{{- if test.IsKind "map" $object }}
Got a map:
{{ range $key, $value := $object -}}
- "{{ $key }}": {{ $value }}
{{ end }}
{{ else if test.IsKind "number" $object }}
Got a number: {{ $object }}
{{ end }}'
Got a map:
- "key1": true
- "key2": foobar
- name: test.Kind
alias: kind
description: |
Report the _kind_ of the given argument. This differs from the _type_ of
the argument in specificity; for example, while a slice of strings may
have a type of `[]string`, the _kind_ of that slice will simply be `slice`.
If you need to know the precise type of a value, use `printf "%T" $value`.
See also [`test.IsKind`](test-iskind).
pipeline: true
arguments:
- name: value
required: true
description: the value to check
examples:
- |
$ gomplate -i '{{ kind "hello world" }}'
string
- |
$ gomplate -i '{{ dict "key1" true "key2" "foobar" | test.Kind }}'
map
- name: test.Required
alias: required
description: |
Expand Down
101 changes: 101 additions & 0 deletions docs/content/functions/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,107 @@ $ gomplate -i '{{ test.Fail "something is wrong!" }}'
template: <arg>:1:7: executing "<arg>" at <test.Fail>: error calling Fail: template generation failed: something is wrong!
```

## `test.IsKind`

**Alias:** `isKind`

Report whether the argument is of the given Kind. Can be used to render
different templates depending on the kind of data.

See [the Go `reflect` source code](https://github.com/golang/go/blob/36fcde1676a0d3863cb5f295eed6938cd782fcbb/src/reflect/type.go#L595..L622)
for the complete list, but these are some common values:

- `string`
- `bool`
- `int`, `int64`, `uint64`
- `float64`
- `slice`
- `map`
- `invalid` (a catch-all, usually just `nil` values)

In addition, the special kind `number` is accepted by this function, to
represent _any_ numeric kind (whether `float32`, `uint8`, or whatever).
This is useful when the specific numeric type is unknown.

See also [`test.Kind`](test-kind).

### Usage

```go
test.IsKind kind value
```
```go
value | test.IsKind kind
```

### Arguments

| name | description |
|------|-------------|
| `kind` | _(required)_ the kind to compare with (see desription for possible values) |
| `value` | _(required)_ the value to check |

### Examples

```console
$ gomplate -i '{{ $data := "hello world" }}
{{- if isKind "string" $data }}{{ $data }} is a string{{ end }}'
hello world is a string
```
```console
$ gomplate -i '{{ $object := dict "key1" true "key2" "foobar" }}
{{- if test.IsKind "map" $object }}
Got a map:
{{ range $key, $value := $object -}}
- "{{ $key }}": {{ $value }}
{{ end }}
{{ else if test.IsKind "number" $object }}
Got a number: {{ $object }}
{{ end }}'

Got a map:
- "key1": true
- "key2": foobar
```

## `test.Kind`

**Alias:** `kind`

Report the _kind_ of the given argument. This differs from the _type_ of
the argument in specificity; for example, while a slice of strings may
have a type of `[]string`, the _kind_ of that slice will simply be `slice`.

If you need to know the precise type of a value, use `printf "%T" $value`.

See also [`test.IsKind`](test-iskind).

### Usage

```go
test.Kind value
```
```go
value | test.Kind
```

### Arguments

| name | description |
|------|-------------|
| `value` | _(required)_ the value to check |

### Examples

```console
$ gomplate -i '{{ kind "hello world" }}'
string
```
```console
$ gomplate -i '{{ dict "key1" true "key2" "foobar" | test.Kind }}'
map
```

## `test.Required`

**Alias:** `required`
Expand Down
23 changes: 23 additions & 0 deletions funcs/test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package funcs

import (
"reflect"
"sync"

"github.com/hairyhenderson/gomplate/v3/conv"
Expand Down Expand Up @@ -28,6 +29,8 @@ func AddTestFuncs(f map[string]interface{}) {
f["fail"] = TestNS().Fail
f["required"] = TestNS().Required
f["ternary"] = TestNS().Ternary
f["kind"] = TestNS().Kind
f["isKind"] = TestNS().IsKind
}

// TestFuncs -
Expand Down Expand Up @@ -85,3 +88,23 @@ func (f *TestFuncs) Ternary(tval, fval, b interface{}) interface{} {
}
return fval
}

// Kind - return the kind of the argument
func (f *TestFuncs) Kind(arg interface{}) string {
return reflect.ValueOf(arg).Kind().String()
}

// IsKind - return whether or not the argument is of the given kind
func (f *TestFuncs) IsKind(kind string, arg interface{}) bool {
k := f.Kind(arg)
if kind == "number" {
switch k {
case "int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr",
"float32", "float64",
"complex64", "complex128":
kind = k
}
}
return k == kind
}
64 changes: 64 additions & 0 deletions funcs/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,67 @@ func TestTernary(t *testing.T) {
assert.Equal(t, d.expected, f.Ternary(d.tval, d.fval, d.b))
}
}

func TestKind(t *testing.T) {
f := TestNS()
testdata := []struct {
arg interface{}
expected string
}{
{"foo", "string"},
{nil, "invalid"},
{false, "bool"},
{[]string{"foo", "bar"}, "slice"},
{map[string]string{"foo": "bar"}, "map"},
{42, "int"},
{42.0, "float64"},
{uint(42), "uint"},
{struct{}{}, "struct"},
}
for _, d := range testdata {
assert.Equal(t, d.expected, f.Kind(d.arg))
}
}

func TestIsKind(t *testing.T) {
f := TestNS()
truedata := []struct {
arg interface{}
kind string
}{
{"foo", "string"},
{nil, "invalid"},
{false, "bool"},
{[]string{"foo", "bar"}, "slice"},
{map[string]string{"foo": "bar"}, "map"},
{42, "int"},
{42.0, "float64"},
{uint(42), "uint"},
{struct{}{}, "struct"},
{42.0, "number"},
{42, "number"},
{uint32(64000), "number"},
{complex128(64000), "number"},
}
for _, d := range truedata {
assert.True(t, f.IsKind(d.kind, d.arg))
}

falsedata := []struct {
arg interface{}
kind string
}{
{"foo", "bool"},
{nil, "struct"},
{false, "string"},
{[]string{"foo", "bar"}, "map"},
{map[string]string{"foo": "bar"}, "int"},
{42, "int64"},
{42.0, "float32"},
{uint(42), "int"},
{struct{}{}, "interface"},
}
for _, d := range falsedata {
assert.False(t, f.IsKind(d.kind, d.arg))
}
}

0 comments on commit 7254b97

Please sign in to comment.