diff options
| author | Dave Henderson <dhenderson@gmail.com> | 2022-07-26 13:29:27 -0400 |
|---|---|---|
| committer | Dave Henderson <dhenderson@gmail.com> | 2022-09-25 10:45:38 -0400 |
| commit | 5184fa4494d93c7dbe016c41ae282d016aa9590e (patch) | |
| tree | 0f2053d5e2eb3b95e1f86e41e90f213c9a6b2364 | |
| parent | ea83b8fac9d2e795f97372266eb9c7693136e9d8 (diff) | |
Add coll.GoSlice and deprecate slice alias
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
| -rw-r--r-- | docs-src/content/functions/coll.yml | 46 | ||||
| -rw-r--r-- | docs-src/content/functions/conv.yml | 10 | ||||
| -rw-r--r-- | docs-src/content/functions/func_doc.md.tmpl | 2 | ||||
| -rw-r--r-- | docs-src/content/functions/math.yml | 10 | ||||
| -rw-r--r-- | docs-src/content/functions/strings.yml | 6 | ||||
| -rw-r--r-- | docs/content/functions/coll.md | 57 | ||||
| -rw-r--r-- | docs/content/functions/conv.md | 10 | ||||
| -rw-r--r-- | docs/content/functions/data.md | 1 | ||||
| -rw-r--r-- | docs/content/functions/math.md | 10 | ||||
| -rw-r--r-- | docs/content/functions/strings.md | 20 | ||||
| -rw-r--r-- | docs/content/syntax.md | 8 | ||||
| -rw-r--r-- | funcs/coll.go | 17 | ||||
| -rw-r--r-- | funcs/coll_test.go | 34 | ||||
| -rw-r--r-- | funcs/conv.go | 2 | ||||
| -rw-r--r-- | gomplate_test.go | 11 | ||||
| -rw-r--r-- | internal/tests/integration/collection_test.go | 6 | ||||
| -rw-r--r-- | internal/texttemplate/exec.go | 24 | ||||
| -rw-r--r-- | internal/texttemplate/exec_test.go | 107 | ||||
| -rw-r--r-- | internal/texttemplate/funcs.go | 79 |
19 files changed, 382 insertions, 78 deletions
diff --git a/docs-src/content/functions/coll.yml b/docs-src/content/functions/coll.yml index cf267fe1..a82e3f59 100644 --- a/docs-src/content/functions/coll.yml +++ b/docs-src/content/functions/coll.yml @@ -43,6 +43,7 @@ funcs: Hello world! Hello everybody! - name: coll.Slice + deprecated: The `slice` alias is deprecated, use the full name `coll.Slice` instead. alias: slice description: | Creates a slice (like an array or list). Useful when needing to `range` over a bunch of variables. @@ -53,10 +54,39 @@ funcs: description: the elements of the slice examples: - | - $ gomplate -i '{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' + $ gomplate -i '{{ range coll.Slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' Hello, Bart Hello, Lisa Hello, Maggie + - name: coll.GoSlice + description: | + This exposes the `slice` function from Go's [`text/template`](https://golang.org/pkg/text/template/#hdr-Functions) + package. Note that using `slice` will use the `coll.Slice` function instead, + which may not be desired. + For some background on this, see [this issue](https://github.com/hairyhenderson/gomplate/issues/1461). + + Here is the upstream documentation: + + ``` + slice returns the result of slicing its first argument by the + remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], + while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" + is x[1:2:3]. The first argument must be a string, slice, or array. + ``` + + See the [Go language spec](https://go.dev/ref/spec#Slice_expressions) for + more details. + pipeline: false + arguments: + - name: item + required: true + description: the string, slice, or array to slice + - name: indexes... + required: false + description: the indexes to slice the item by (0 to 3 arguments) + examples: + - | + $ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}{{ if has $l "bar" }}a{{else}}no{{end}} bar' - name: coll.Has alias: has description: | @@ -71,7 +101,7 @@ funcs: description: The item to search for examples: - | - $ gomplate -i '{{ $l := slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' + $ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' there is a bar - | $ export DATA='{"foo": "bar"}' @@ -163,7 +193,7 @@ funcs: description: the slice or array to append to examples: - | - $ gomplate -i '{{ slice 1 1 2 3 | append 5 }}' + $ gomplate -i '{{ coll.Slice 1 1 2 3 | append 5 }}' [1 1 2 3 5] - name: coll.Prepend alias: prepend @@ -183,7 +213,7 @@ funcs: description: the slice or array to prepend to examples: - | - $ gomplate -i '{{ slice 4 3 2 1 | prepend 5 }}' + $ gomplate -i '{{ coll.Slice 4 3 2 1 | prepend 5 }}' [5 4 3 2 1] - name: coll.Uniq alias: uniq @@ -198,7 +228,7 @@ funcs: description: the input list examples: - | - $ gomplate -i '{{ slice 1 2 3 2 3 4 1 5 | uniq }}' + $ gomplate -i '{{ coll.Slice 1 2 3 2 3 4 1 5 | uniq }}' [1 2 3 4 5] - name: coll.Flatten alias: flatten @@ -235,7 +265,7 @@ funcs: description: the list to reverse examples: - | - $ gomplate -i '{{ slice 4 3 2 1 | reverse }}' + $ gomplate -i '{{ coll.Slice 4 3 2 1 | reverse }}' [1 2 3 4] - name: coll.Sort alias: sort @@ -257,10 +287,10 @@ funcs: description: the slice or array to sort examples: - | - $ gomplate -i '{{ slice "foo" "bar" "baz" | coll.Sort }}' + $ gomplate -i '{{ coll.Slice "foo" "bar" "baz" | coll.Sort }}' [bar baz foo] - | - $ gomplate -i '{{ sort (slice 3 4 1 2 5) }}' + $ gomplate -i '{{ sort (coll.Slice 3 4 1 2 5) }}' [1 2 3 4 5] - | $ cat <<EOF > in.json diff --git a/docs-src/content/functions/conv.yml b/docs-src/content/functions/conv.yml index 10aa2fc3..884ba35b 100644 --- a/docs-src/content/functions/conv.yml +++ b/docs-src/content/functions/conv.yml @@ -65,7 +65,7 @@ funcs: For creating more complex maps, see [`data.JSON`](../data/#data-json) or [`data.YAML`](../data/#data-yaml). - For creating arrays, see [`conv.Slice`](#conv-slice). + For creating arrays, see [`coll.Slice`](#coll-slice). arguments: - name: in... required: true @@ -97,7 +97,7 @@ funcs: description: the elements of the slice examples: - | - $ gomplate -i '{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' + $ gomplate -i '{{ range coll.Slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' Hello, Bart Hello, Lisa Hello, Maggie @@ -116,7 +116,7 @@ funcs: description: The item to search for examples: - | - $ gomplate -i '{{ $l := slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' + $ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' there is a bar - | $ export DATA='{"foo": "bar"}' @@ -141,7 +141,7 @@ funcs: description: the separator examples: - | - $ gomplate -i '{{ $a := slice 1 2 3 }}{{ join $a "-" }}' + $ gomplate -i '{{ $a := coll.Slice 1 2 3 }}{{ join $a "-" }}' 1-2-3 - name: conv.URL alias: urlParse @@ -417,5 +417,5 @@ funcs: description: the inputs to be converted examples: - | - $ gomplate -i '{{ conv.ToStrings nil 42 true 0xF (slice 1 2 3) }}' + $ gomplate -i '{{ conv.ToStrings nil 42 true 0xF (coll.Slice 1 2 3) }}' [nil 42 true 15 [1 2 3]] diff --git a/docs-src/content/functions/func_doc.md.tmpl b/docs-src/content/functions/func_doc.md.tmpl index 0e8668de..36adc712 100644 --- a/docs-src/content/functions/func_doc.md.tmpl +++ b/docs-src/content/functions/func_doc.md.tmpl @@ -1,7 +1,7 @@ {{ define "argName" }}{{ if not .required }}[{{ .name }}]{{else}}{{ .name }}{{end}}{{ end }} {{- define "usage" }}### Usage -{{- $arguments := index . "arguments" | default slice }} +{{- $arguments := index . "arguments" | default coll.Slice }} {{ if has . "rawUsage" }}{{ .rawUsage | strings.TrimSpace }}{{ else }} ```go {{ .name }}{{ range $a := $arguments }} {{template "argName" $a }}{{end}} diff --git a/docs-src/content/functions/math.yml b/docs-src/content/functions/math.yml index 385b53d0..99c2a846 100644 --- a/docs-src/content/functions/math.yml +++ b/docs-src/content/functions/math.yml @@ -58,7 +58,7 @@ funcs: description: The input number. Will be converted to a `float64`, or `0` if not convertible examples: - | - $ gomplate -i '{{ range (slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}ceil {{ printf "%#v" . }} = {{ math.Ceil . }}{{"\n"}}{{ end }}' + $ gomplate -i '{{ range (coll.Slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}ceil {{ printf "%#v" . }} = {{ math.Ceil . }}{{"\n"}}{{ end }}' ceil 5.1 = 6 ceil 42 = 42 ceil "3.14" = 4 @@ -93,7 +93,7 @@ funcs: description: The input number. Will be converted to a `float64`, or `0` if not convertable examples: - | - $ gomplate -i '{{ range (slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}floor {{ printf "%#v" . }} = {{ math.Floor . }}{{"\n"}}{{ end }}' + $ gomplate -i '{{ range (coll.Slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}floor {{ printf "%#v" . }} = {{ math.Floor . }}{{"\n"}}{{ end }}' floor 5.1 = 4 floor 42 = 42 floor "3.14" = 3 @@ -112,7 +112,7 @@ funcs: description: The value to test examples: - | - $ gomplate -i '{{ range (slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsFloat .) }}{{.}} is a float{{"\n"}}{{ end }}{{end}}' + $ gomplate -i '{{ range (coll.Slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsFloat .) }}{{.}} is a float{{"\n"}}{{ end }}{{end}}' 1 is a float -1.0 is a float 5.1 is a float @@ -128,7 +128,7 @@ funcs: description: The value to test examples: - | - $ gomplate -i '{{ range (slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsInt .) }}{{.}} is an integer{{"\n"}}{{ end }}{{end}}' + $ gomplate -i '{{ range (coll.Slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsInt .) }}{{.}} is an integer{{"\n"}}{{ end }}{{end}}' 42 is an integer 0xFF is an integer -0 is an integer @@ -225,7 +225,7 @@ funcs: description: The input number. Will be converted to a `float64`, or `0` if not convertable examples: - | - $ gomplate -i '{{ range (slice -6.5 5.1 42.9 "3.5" 6.5) }}round {{ printf "%#v" . }} = {{ math.Round . }}{{"\n"}}{{ end }}' + $ gomplate -i '{{ range (coll.Slice -6.5 5.1 42.9 "3.5" 6.5) }}round {{ printf "%#v" . }} = {{ math.Round . }}{{"\n"}}{{ end }}' round -6.5 = -7 round 5.1 = 5 round 42.9 = 43 diff --git a/docs-src/content/functions/strings.yml b/docs-src/content/functions/strings.yml index bdc127aa..b72f7f7b 100644 --- a/docs-src/content/functions/strings.yml +++ b/docs-src/content/functions/strings.yml @@ -135,7 +135,7 @@ funcs: description: The list to sort examples: - | - $ gomplate -i '{{ (slice "foo" "bar" "baz") | strings.Sort }}' + $ gomplate -i '{{ (coll.Slice "foo" "bar" "baz") | strings.Sort }}' [bar baz foo] - name: strings.Split description: | @@ -267,7 +267,7 @@ funcs: description: The input to quote examples: - | - $ gomplate -i "{{ slice \"one word\" \"foo='bar baz'\" | shellQuote }}" + $ gomplate -i "{{ coll.Slice \"one word\" \"foo='bar baz'\" | shellQuote }}" 'one word' 'foo='"'"'bar baz'"'"'' - | $ gomplate -i "{{ strings.ShellQuote \"it's a banana\" }}" @@ -518,7 +518,7 @@ funcs: description: the input(s) to measure examples: - | - $ gomplate -i '{{ range (slice "\u03a9" "\u0030" "\u1430") }}{{ printf "%s is %d bytes and %d runes\n" . (len .) (strings.RuneCount .) }}{{ end }}' + $ gomplate -i '{{ range (coll.Slice "\u03a9" "\u0030" "\u1430") }}{{ printf "%s is %d bytes and %d runes\n" . (len .) (strings.RuneCount .) }}{{ end }}' Ω is 2 bytes and 1 runes 0 is 1 bytes and 1 runes ᐰ is 3 bytes and 1 runes diff --git a/docs/content/functions/coll.md b/docs/content/functions/coll.md index 31878333..75446aa0 100644 --- a/docs/content/functions/coll.md +++ b/docs/content/functions/coll.md @@ -60,7 +60,8 @@ Hello world! Hello everybody! ``` -## `coll.Slice` +## `coll.Slice` _(deprecated)_ +**Deprecation Notice:** The `slice` alias is deprecated, use the full name `coll.Slice` instead. **Alias:** `slice` @@ -81,12 +82,50 @@ coll.Slice in... ### Examples ```console -$ gomplate -i '{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' +$ gomplate -i '{{ range coll.Slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' Hello, Bart Hello, Lisa Hello, Maggie ``` +## `coll.GoSlice` + +This exposes the `slice` function from Go's [`text/template`](https://golang.org/pkg/text/template/#hdr-Functions) +package. Note that using `slice` will use the `coll.Slice` function instead, +which may not be desired. +For some background on this, see [this issue](https://github.com/hairyhenderson/gomplate/issues/1461). + +Here is the upstream documentation: + +``` +slice returns the result of slicing its first argument by the +remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], +while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" +is x[1:2:3]. The first argument must be a string, slice, or array. +``` + +See the [Go language spec](https://go.dev/ref/spec#Slice_expressions) for +more details. + +### Usage + +```go +coll.GoSlice item [indexes...] +``` + +### Arguments + +| name | description | +|------|-------------| +| `item` | _(required)_ the string, slice, or array to slice | +| `indexes...` | _(optional)_ the indexes to slice the item by (0 to 3 arguments) | + +### Examples + +```console +$ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}{{ if has $l "bar" }}a{{else}}no{{end}} bar' +``` + ## `coll.Has` **Alias:** `has` @@ -109,7 +148,7 @@ coll.Has in item ### Examples ```console -$ gomplate -i '{{ $l := slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' +$ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' there is a bar ``` ```console @@ -259,7 +298,7 @@ list... | coll.Append value ### Examples ```console -$ gomplate -i '{{ slice 1 1 2 3 | append 5 }}' +$ gomplate -i '{{ coll.Slice 1 1 2 3 | append 5 }}' [1 1 2 3 5] ``` @@ -292,7 +331,7 @@ list... | coll.Prepend value ### Examples ```console -$ gomplate -i '{{ slice 4 3 2 1 | prepend 5 }}' +$ gomplate -i '{{ coll.Slice 4 3 2 1 | prepend 5 }}' [5 4 3 2 1] ``` @@ -322,7 +361,7 @@ list | coll.Uniq ### Examples ```console -$ gomplate -i '{{ slice 1 2 3 2 3 4 1 5 | uniq }}' +$ gomplate -i '{{ coll.Slice 1 2 3 2 3 4 1 5 | uniq }}' [1 2 3 4 5] ``` @@ -388,7 +427,7 @@ list | coll.Reverse ### Examples ```console -$ gomplate -i '{{ slice 4 3 2 1 | reverse }}' +$ gomplate -i '{{ coll.Slice 4 3 2 1 | reverse }}' [1 2 3 4] ``` @@ -423,11 +462,11 @@ list | coll.Sort [key] ### Examples ```console -$ gomplate -i '{{ slice "foo" "bar" "baz" | coll.Sort }}' +$ gomplate -i '{{ coll.Slice "foo" "bar" "baz" | coll.Sort }}' [bar baz foo] ``` ```console -$ gomplate -i '{{ sort (slice 3 4 1 2 5) }}' +$ gomplate -i '{{ sort (coll.Slice 3 4 1 2 5) }}' [1 2 3 4 5] ``` ```console diff --git a/docs/content/functions/conv.md b/docs/content/functions/conv.md index 32045e1d..d150bf15 100644 --- a/docs/content/functions/conv.md +++ b/docs/content/functions/conv.md @@ -96,7 +96,7 @@ function, as used in [Helm templates](https://docs.helm.sh/chart_template_guide# For creating more complex maps, see [`data.JSON`](../data/#data-json) or [`data.YAML`](../data/#data-yaml). -For creating arrays, see [`conv.Slice`](#conv-slice). +For creating arrays, see [`coll.Slice`](#coll-slice). ### Usage @@ -151,7 +151,7 @@ conv.Slice in... ### Examples ```console -$ gomplate -i '{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' +$ gomplate -i '{{ range coll.Slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}' Hello, Bart Hello, Lisa Hello, Maggie @@ -180,7 +180,7 @@ conv.Has in item ### Examples ```console -$ gomplate -i '{{ $l := slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' +$ gomplate -i '{{ $l := coll.Slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar' there is a bar ``` ```console @@ -218,7 +218,7 @@ conv.Join in sep ### Examples ```console -$ gomplate -i '{{ $a := slice 1 2 3 }}{{ join $a "-" }}' +$ gomplate -i '{{ $a := coll.Slice 1 2 3 }}{{ join $a "-" }}' 1-2-3 ``` @@ -668,6 +668,6 @@ conv.ToStrings in... ### Examples ```console -$ gomplate -i '{{ conv.ToStrings nil 42 true 0xF (slice 1 2 3) }}' +$ gomplate -i '{{ conv.ToStrings nil 42 true 0xF (coll.Slice 1 2 3) }}' [nil 42 true 15 [1 2 3]] ``` diff --git a/docs/content/functions/data.md b/docs/content/functions/data.md index 31d2b27c..c1bac8ca 100644 --- a/docs/content/functions/data.md +++ b/docs/content/functions/data.md @@ -107,6 +107,7 @@ Lists all the datasources defined, list returned will be sorted in ascending ord listDatasources ``` + ### Examples ```console diff --git a/docs/content/functions/math.md b/docs/content/functions/math.md index 82a656e1..7250db1a 100644 --- a/docs/content/functions/math.md +++ b/docs/content/functions/math.md @@ -98,7 +98,7 @@ math.Ceil num ### Examples ```console -$ gomplate -i '{{ range (slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}ceil {{ printf "%#v" . }} = {{ math.Ceil . }}{{"\n"}}{{ end }}' +$ gomplate -i '{{ range (coll.Slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}ceil {{ printf "%#v" . }} = {{ math.Ceil . }}{{"\n"}}{{ end }}' ceil 5.1 = 6 ceil 42 = 42 ceil "3.14" = 4 @@ -158,7 +158,7 @@ math.Floor num ### Examples ```console -$ gomplate -i '{{ range (slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}floor {{ printf "%#v" . }} = {{ math.Floor . }}{{"\n"}}{{ end }}' +$ gomplate -i '{{ range (coll.Slice 5.1 42 "3.14" "0xFF" "NaN" "Inf" "-0") }}floor {{ printf "%#v" . }} = {{ math.Floor . }}{{"\n"}}{{ end }}' floor 5.1 = 4 floor 42 = 42 floor "3.14" = 3 @@ -189,7 +189,7 @@ math.IsFloat num ### Examples ```console -$ gomplate -i '{{ range (slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsFloat .) }}{{.}} is a float{{"\n"}}{{ end }}{{end}}' +$ gomplate -i '{{ range (coll.Slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsFloat .) }}{{.}} is a float{{"\n"}}{{ end }}{{end}}' 1 is a float -1.0 is a float 5.1 is a float @@ -217,7 +217,7 @@ math.IsInt num ### Examples ```console -$ gomplate -i '{{ range (slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsInt .) }}{{.}} is an integer{{"\n"}}{{ end }}{{end}}' +$ gomplate -i '{{ range (coll.Slice 1.0 "-1.0" 5.1 42 "3.14" "foo" "0xFF" "NaN" "Inf" "-0") }}{{ if (math.IsInt .) }}{{.}} is an integer{{"\n"}}{{ end }}{{end}}' 42 is an integer 0xFF is an integer -0 is an integer @@ -399,7 +399,7 @@ math.Round num ### Examples ```console -$ gomplate -i '{{ range (slice -6.5 5.1 42.9 "3.5" 6.5) }}round {{ printf "%#v" . }} = {{ math.Round . }}{{"\n"}}{{ end }}' +$ gomplate -i '{{ range (coll.Slice -6.5 5.1 42.9 "3.5" 6.5) }}round {{ printf "%#v" . }} = {{ math.Round . }}{{"\n"}}{{ end }}' round -6.5 = -7 round 5.1 = 5 round 42.9 = 43 diff --git a/docs/content/functions/strings.md b/docs/content/functions/strings.md index 65b4193c..dc00267e 100644 --- a/docs/content/functions/strings.md +++ b/docs/content/functions/strings.md @@ -203,7 +203,7 @@ list | strings.Sort ### Examples ```console -$ gomplate -i '{{ (slice "foo" "bar" "baz") | strings.Sort }}' +$ gomplate -i '{{ (coll.Slice "foo" "bar" "baz") | strings.Sort }}' [bar baz foo] ``` @@ -229,13 +229,6 @@ input | strings.Split separator ### Examples -Use on its own to produce an array: -```console -$ gomplate -i '{{ "Bart,Lisa,Maggie" | strings.Split "," }}' -[Bart Lisa Maggie] -``` - -Use in combination with `range` to iterate over all items: ```console $ gomplate -i '{{range ("Bart,Lisa,Maggie" | strings.Split ",") }}Hello, {{.}} {{end}}' @@ -244,13 +237,6 @@ Hello, Lisa Hello, Maggie ``` -Use in combination with `index` function to pick a specific value from the resulting array -```console -$ gomplate -i '{{index ("Bart,Lisa,Maggie" | strings.Split ",") 0 }}' -Bart -``` - - ## `strings.SplitN` Creates a slice by splitting a string on a given delimiter. The count determines @@ -439,7 +425,7 @@ in | strings.ShellQuote ### Examples ```console -$ gomplate -i "{{ slice \"one word\" \"foo='bar baz'\" | shellQuote }}" +$ gomplate -i "{{ coll.Slice \"one word\" \"foo='bar baz'\" | shellQuote }}" 'one word' 'foo='"'"'bar baz'"'"'' ``` ```console @@ -885,7 +871,7 @@ input | strings.RuneCount ### Examples ```console -$ gomplate -i '{{ range (slice "\u03a9" "\u0030" "\u1430") }}{{ printf "%s is %d bytes and %d runes\n" . (len .) (strings.RuneCount .) }}{{ end }}' +$ gomplate -i '{{ range (coll.Slice "\u03a9" "\u0030" "\u1430") }}{{ printf "%s is %d bytes and %d runes\n" . (len .) (strings.RuneCount .) }}{{ end }}' Ω is 2 bytes and 1 runes 0 is 1 bytes and 1 runes ᐰ is 3 bytes and 1 runes diff --git a/docs/content/syntax.md b/docs/content/syntax.md index 35f4c82e..277503f5 100644 --- a/docs/content/syntax.md +++ b/docs/content/syntax.md @@ -32,7 +32,7 @@ is rendered. By default, every line containing an action will render a newline. For example, the action block below: ``` -{{ range slice "Foo" "bar" "baz" }} +{{ range coll.Slice "Foo" "bar" "baz" }} Hello, {{ . }}! {{ end }} ``` @@ -60,7 +60,7 @@ Here are a few examples. ### Suppressing leading newlines ``` -{{- range slice "Foo" "bar" "baz" }} +{{- range coll.Slice "Foo" "bar" "baz" }} Hello, {{ . }}! {{- end }} ``` @@ -79,7 +79,7 @@ Hello, baz! This code: ``` -{{ range slice "Foo" "bar" "baz" -}} +{{ range coll.Slice "Foo" "bar" "baz" -}} Hello, {{ . }}! {{ end -}} ``` @@ -97,7 +97,7 @@ Hello, baz! This code: ``` -{{- range slice "Foo" "bar" "baz" -}} +{{- range coll.Slice "Foo" "bar" "baz" -}} Hello, {{ . }}! {{- end -}} ``` diff --git a/funcs/coll.go b/funcs/coll.go index 8c6e9f47..9be0bfc2 100644 --- a/funcs/coll.go +++ b/funcs/coll.go @@ -2,8 +2,11 @@ package funcs import ( "context" + "reflect" "github.com/hairyhenderson/gomplate/v3/conv" + "github.com/hairyhenderson/gomplate/v3/internal/deprecated" + "github.com/hairyhenderson/gomplate/v3/internal/texttemplate" "github.com/hairyhenderson/gomplate/v3/coll" "github.com/pkg/errors" @@ -31,7 +34,7 @@ func CreateCollFuncs(ctx context.Context) map[string]interface{} { f["coll"] = func() interface{} { return ns } f["has"] = ns.Has - f["slice"] = ns.Slice + f["slice"] = ns.deprecatedSlice f["dict"] = ns.Dict f["keys"] = ns.Keys f["values"] = ns.Values @@ -56,6 +59,18 @@ func (CollFuncs) Slice(args ...interface{}) []interface{} { return coll.Slice(args...) } +// deprecatedSlice - +// Deprecated: use coll.Slice instead +func (f *CollFuncs) deprecatedSlice(args ...interface{}) []interface{} { + deprecated.WarnDeprecated(f.ctx, "the 'slice' alias for coll.Slice is deprecated - use coll.Slice instead") + return coll.Slice(args...) +} + +// GoSlice - same as text/template's 'slice' function +func (CollFuncs) GoSlice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { + return texttemplate.GoSlice(item, indexes...) +} + // Has - func (CollFuncs) Has(in interface{}, key string) bool { return coll.Has(in, key) diff --git a/funcs/coll_test.go b/funcs/coll_test.go index 9b6a0b9a..96bb9248 100644 --- a/funcs/coll_test.go +++ b/funcs/coll_test.go @@ -2,6 +2,7 @@ package funcs import ( "context" + "reflect" "strconv" "testing" @@ -142,3 +143,36 @@ func TestOmit(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, map[string]interface{}{}, out) } + +func TestGoSlice(t *testing.T) { + t.Parallel() + + c := &CollFuncs{} + + in := reflect.ValueOf(nil) + _, err := c.GoSlice(in) + assert.Error(t, err) + + in = reflect.ValueOf(42) + _, err = c.GoSlice(in) + assert.Error(t, err) + + // invalid index type + in = reflect.ValueOf([]interface{}{1}) + _, err = c.GoSlice(in, reflect.ValueOf([]interface{}{[]int{2}})) + assert.Error(t, err) + + // valid slice, no slicing + in = reflect.ValueOf([]int{1}) + out, err := c.GoSlice(in) + assert.NoError(t, err) + assert.Equal(t, reflect.TypeOf([]int{}), out.Type()) + assert.EqualValues(t, []int{1}, out.Interface()) + + // valid slice, slicing + in = reflect.ValueOf([]string{"foo", "bar", "baz"}) + out, err = c.GoSlice(in, reflect.ValueOf(1), reflect.ValueOf(3)) + assert.NoError(t, err) + assert.Equal(t, reflect.TypeOf([]string{}), out.Type()) + assert.EqualValues(t, []string{"bar", "baz"}, out.Interface()) +} diff --git a/funcs/conv.go b/funcs/conv.go index 823dccdf..9fc16fcc 100644 --- a/funcs/conv.go +++ b/funcs/conv.go @@ -61,7 +61,7 @@ func (ConvFuncs) ToBools(in ...interface{}) []bool { } // Slice - -// Deprecated: use coll.Slice instead +// Deprecated: use [coll.Slice] instead func (f *ConvFuncs) Slice(args ...interface{}) []interface{} { deprecated.WarnDeprecated(f.ctx, "conv.Slice is deprecated - use coll.Slice instead") return coll.Slice(args...) diff --git a/gomplate_test.go b/gomplate_test.go index 19a5a865..9737ad75 100644 --- a/gomplate_test.go +++ b/gomplate_test.go @@ -105,17 +105,6 @@ func TestYAMLTemplates(t *testing.T) { assert.Equal(t, "bar", testTemplate(t, g, `{{ index (yamlArray "[\"foo\",\"bar\"]") 1 }}`)) } -func TestSliceTemplates(t *testing.T) { - g := NewRenderer(Options{ - Funcs: template.FuncMap{ - "slice": conv.Slice, - }, - }) - assert.Equal(t, "foo", testTemplate(t, g, `{{index (slice "foo") 0}}`)) - assert.Equal(t, `[foo bar 42]`, testTemplate(t, g, `{{slice "foo" "bar" 42}}`)) - assert.Equal(t, `helloworld`, testTemplate(t, g, `{{range slice "hello" "world"}}{{.}}{{end}}`)) -} - func TestHasTemplate(t *testing.T) { g := NewRenderer(Options{ Funcs: template.FuncMap{ diff --git a/internal/tests/integration/collection_test.go b/internal/tests/integration/collection_test.go index ccdc7a15..cc83e14d 100644 --- a/internal/tests/integration/collection_test.go +++ b/internal/tests/integration/collection_test.go @@ -61,9 +61,9 @@ func TestColl_Sort(t *testing.T) { `, "foo\nbaz\nbar\n") inOutTest(t, ` -{{- coll.Sort (slice "b" "a" "c" "aa") }} -{{ coll.Sort (slice "b" 14 "c" "aa") }} -{{ coll.Sort (slice 3.14 3.0 4.0) }} +{{- coll.Sort (coll.Slice "b" "a" "c" "aa") }} +{{ coll.Sort (coll.Slice "b" 14 "c" "aa") }} +{{ coll.Sort (coll.Slice 3.14 3.0 4.0) }} {{ coll.Sort "Scheme" (coll.Slice (conv.URL "zzz:///") (conv.URL "https:///") (conv.URL "http:///")) }} `, `[a aa b c] [b 14 c aa] diff --git a/internal/texttemplate/exec.go b/internal/texttemplate/exec.go new file mode 100644 index 00000000..f4906cb3 --- /dev/null +++ b/internal/texttemplate/exec.go @@ -0,0 +1,24 @@ +// Taken and adapted from the stdlib text/template/funcs.go. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package texttemplate + +import ( + "reflect" +) + +// indirectInterface returns the concrete value in an interface value, +// or else the zero reflect.Value. +// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x): +// the fact that x was an interface value is forgotten. +func indirectInterface(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Interface { + return v + } + if v.IsNil() { + return reflect.Value{} + } + return v.Elem() +} diff --git a/internal/texttemplate/exec_test.go b/internal/texttemplate/exec_test.go new file mode 100644 index 00000000..4696b3c4 --- /dev/null +++ b/internal/texttemplate/exec_test.go @@ -0,0 +1,107 @@ +// Taken and adapted from the stdlib text/template/exec_test.go. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package texttemplate + +import ( + "bytes" + "testing" + gotemplate "text/template" +) + +// T has lots of interesting pieces to use to test execution. +type T struct { + Tmpl *gotemplate.Template + Empty3 any + S string + SI []int + SICap []int + AI [3]int +} + +var tVal = &T{ + S: "xyz", + SI: []int{3, 4, 5}, + SICap: make([]int, 5, 10), + AI: [3]int{3, 4, 5}, + Empty3: []int{7, 8}, + Tmpl: gotemplate.Must(gotemplate.New("").Parse("test template")), +} + +//nolint:govet +type execTest struct { + name string + input string + output string + data any + ok bool +} + +var execTests = []execTest{ + // Slicing. + {"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true}, + {"slice[1:]", "{{slice .SI 1}}", "[4 5]", tVal, true}, + {"slice[1:2]", "{{slice .SI 1 2}}", "[4]", tVal, true}, + {"slice[-1:]", "{{slice .SI -1}}", "", tVal, false}, + {"slice[1:-2]", "{{slice .SI 1 -2}}", "", tVal, false}, + {"slice[1:2:-1]", "{{slice .SI 1 2 -1}}", "", tVal, false}, + {"slice[2:1]", "{{slice .SI 2 1}}", "", tVal, false}, + {"slice[2:2:1]", "{{slice .SI 2 2 1}}", "", tVal, false}, + {"out of range", "{{slice .SI 4 5}}", "", tVal, false}, + {"out of range", "{{slice .SI 2 2 5}}", "", tVal, false}, + {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10}}", "[0 0 0 0]", tVal, true}, + {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10 10}}", "[0 0 0 0]", tVal, true}, + {"indexes > cap(s)", "{{slice .SICap 10 11}}", "", tVal, false}, + {"indexes > cap(s)", "{{slice .SICap 6 10 11}}", "", tVal, false}, + {"array[:]", "{{slice .AI}}", "[3 4 5]", tVal, true}, + {"array[1:]", "{{slice .AI 1}}", "[4 5]", tVal, true}, + {"array[1:2]", "{{slice .AI 1 2}}", "[4]", tVal, true}, + {"string[:]", "{{slice .S}}", "xyz", tVal, true}, + {"string[0:1]", "{{slice .S 0 1}}", "x", tVal, true}, + {"string[1:]", "{{slice .S 1}}", "yz", tVal, true}, + {"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true}, + {"out of range", "{{slice .S 1 5}}", "", tVal, false}, + {"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false}, + {"slice of an interface field", "{{slice .Empty3 0 1}}", "[7]", tVal, true}, +} + +func testExecute(execTests []execTest, template *gotemplate.Template, t *testing.T) { + b := new(bytes.Buffer) + funcs := gotemplate.FuncMap{"slice": GoSlice} + + for _, test := range execTests { + var tmpl *gotemplate.Template + var err error + if template == nil { + tmpl, err = gotemplate.New(test.name).Funcs(funcs).Parse(test.input) + } else { + tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input) + } + if err != nil { + t.Errorf("%s: parse error: %s", test.name, err) + continue + } + b.Reset() + err = tmpl.Execute(b, test.data) + switch { + case !test.ok && err == nil: + t.Errorf("%s: expected error; got none", test.name) + continue + case test.ok && err != nil: + t.Errorf("%s: unexpected execute error: %s", test.name, err) + continue + case !test.ok && err != nil: + // expected error, got one + } + result := b.String() + if result != test.output { + t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result) + } + } +} + +func TestExecute(t *testing.T) { + testExecute(execTests, nil, t) +} diff --git a/internal/texttemplate/funcs.go b/internal/texttemplate/funcs.go new file mode 100644 index 00000000..8658af6f --- /dev/null +++ b/internal/texttemplate/funcs.go @@ -0,0 +1,79 @@ +// Taken and adapted from the stdlib text/template/funcs.go. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package texttemplate + +import ( + "fmt" + "reflect" +) + +// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible. +func indexArg(index reflect.Value, cap int) (int, error) { + var x int64 + switch index.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x = index.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + x = int64(index.Uint()) + case reflect.Invalid: + return 0, fmt.Errorf("cannot index slice/array with nil") + default: + return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type()) + } + if x < 0 || int(x) < 0 || int(x) > cap { + return 0, fmt.Errorf("index out of range: %d", x) + } + return int(x), nil +} + +// Slicing. + +// slice returns the result of slicing its first argument by the remaining +// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x" +// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first +// argument must be a string, slice, or array. +func GoSlice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { + item = indirectInterface(item) + if !item.IsValid() { + return reflect.Value{}, fmt.Errorf("slice of untyped nil") + } + if len(indexes) > 3 { + return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes)) + } + var cap int + switch item.Kind() { + case reflect.String: + if len(indexes) == 3 { + return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string") + } + cap = item.Len() + case reflect.Array, reflect.Slice: + cap = item.Cap() + default: + return reflect.Value{}, fmt.Errorf("can't slice item of type %s", item.Type()) + } + // set default values for cases item[:], item[i:]. + idx := [3]int{0, item.Len()} + for i, index := range indexes { + x, err := indexArg(index, cap) + if err != nil { + return reflect.Value{}, err + } + idx[i] = x + } + // given item[i:j], make sure i <= j. + if idx[0] > idx[1] { + return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1]) + } + if len(indexes) < 3 { + return item.Slice(idx[0], idx[1]), nil + } + // given item[i:j:k], make sure i <= j <= k. + if idx[1] > idx[2] { + return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2]) + } + return item.Slice3(idx[0], idx[1], idx[2]), nil +} |
