From 1750f4560bd222bf8a9883dbe93a7e8c2206ea19 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Mon, 10 Aug 2026 21:03:56 +0200 Subject: [PATCH] test(templater): snapshot the template function surface and behaviour Golden tests capturing the sorted list of template function names and the rendered output of a representative expression per function. Generated against the current slim-sprig implementation so that any change of templating library produces an auditable diff. --- internal/templater/snapshot_test.go | 325 ++++++++++++++++ .../templater/testdata/func_behaviour.golden | 367 ++++++++++++++++++ internal/templater/testdata/func_names.golden | 194 +++++++++ 3 files changed, 886 insertions(+) create mode 100644 internal/templater/snapshot_test.go create mode 100644 internal/templater/testdata/func_behaviour.golden create mode 100644 internal/templater/testdata/func_names.golden diff --git a/internal/templater/snapshot_test.go b/internal/templater/snapshot_test.go new file mode 100644 index 00000000..c282d401 --- /dev/null +++ b/internal/templater/snapshot_test.go @@ -0,0 +1,325 @@ +package templater + +import ( + "fmt" + "maps" + "os" + "slices" + "strings" + "testing" + "time" + + "github.com/sebdah/goldie/v2" + + "github.com/go-task/task/v3/taskfile/ast" +) + +// The two tests in this file snapshot the entire template surface: the set of +// function names, and the rendered output of a representative expression per +// function. They exist to make any change of templating library auditable — +// the golden diff is the exhaustive list of what changed for users. +// +// Expressions must stay deterministic and platform independent, so anything +// depending on the clock, the filesystem, the environment or randomness is +// deliberately absent (`now`, `uuid`, `randInt`, `env`, `os*`, `exeExt`, `OS`, +// `ARCH`, `spew`, `getHostByName`). Those are covered by unit tests instead. + +// TestMain pins the local timezone so that the date expressions below render +// identically on every machine. A consequence is that this golden cannot show +// the local-vs-UTC difference between sprig's `date`/`toDate` and sprout's — +// that one belongs in the migration documentation. +func TestMain(m *testing.M) { + time.Local = time.UTC + os.Exit(m.Run()) +} + +func TestFuncNames(t *testing.T) { + t.Parallel() + + names := slices.Sorted(maps.Keys(templateFuncs)) + + g := goldie.New(t) + g.Assert(t, "func_names", []byte(strings.Join(names, "\n")+"\n")) +} + +func TestFuncBehaviour(t *testing.T) { + t.Parallel() + + var b strings.Builder + for _, group := range funcBehaviourGroups { + fmt.Fprintf(&b, "## %s\n\n", group.name) + for _, expr := range group.exprs { + cache := &Cache{Vars: ast.NewVars()} + got := ReplaceWithExtra(expr, cache, nil) + fmt.Fprintf(&b, "%s\n", expr) + if err := cache.Err(); err != nil { + fmt.Fprintf(&b, "\t! %s\n", normalizeTemplateError(err)) + } else { + fmt.Fprintf(&b, "\t= %s\n", strings.ReplaceAll(got, "\n", "\\n")) + } + } + b.WriteString("\n") + } + + g := goldie.New(t) + g.Assert(t, "func_behaviour", []byte(b.String())) +} + +// normalizeTemplateError strips the position prefix that text/template adds to +// execution errors, which is noise in the golden and shifts whenever an +// expression is added to a group. +func normalizeTemplateError(err error) string { + s := err.Error() + if _, after, found := strings.Cut(s, `at <`); found { + return "at <" + after + } + return s +} + +var funcBehaviourGroups = []struct { + name string + exprs []string +}{ + { + // The ten functions whose argument order differs between sprig and + // sprout. Written here in sprig order, which is what users' Taskfiles + // contain today. + "argument order — sprig order (target first)", + []string{ + `{{ get (dict "a" "b") "a" }}`, + `{{ set (dict "a" "b") "c" "d" | toJson }}`, + `{{ unset (dict "a" "b" "c" "d") "a" | toJson }}`, + `{{ hasKey (dict "a" "b") "a" }}`, + `{{ pick (dict "a" "1" "b" "2") "a" | toJson }}`, + `{{ omit (dict "a" "1" "b" "2") "a" | toJson }}`, + `{{ append (list 1 2) 3 | toJson }}`, + `{{ push (list 1 2) 3 | toJson }}`, + `{{ prepend (list 2 3) 1 | toJson }}`, + `{{ without (list 1 2 3) 2 | toJson }}`, + `{{ slice (list 1 2 3 4) 1 3 | toJson }}`, + }, + }, + { + // Same ten functions in sprout order. Today these mostly fail; after + // the migration both forms must work. + "argument order — sprout order (target last)", + []string{ + `{{ dict "a" "b" | get "a" }}`, + `{{ dict "a" "b" | set "c" "d" | toJson }}`, + `{{ dict "a" "b" "c" "d" | unset "a" | toJson }}`, + `{{ dict "a" "b" | hasKey "a" }}`, + `{{ list 1 2 | append 3 | toJson }}`, + `{{ list 2 3 | prepend 1 | toJson }}`, + }, + }, + { + "maps — unchanged signatures", + []string{ + `{{ dict "a" 1 "b" 2 | toJson }}`, + `{{ keys (dict "b" 1 "a" 2) | sortAlpha | toJson }}`, + `{{ values (dict "a" 1) | toJson }}`, + `{{ pluck "a" (dict "a" 1) (dict "a" 2) | toJson }}`, + `{{ dig "a" "b" "fallback" (dict "a" (dict "b" "found")) }}`, + `{{ dig "a" "missing" "fallback" (dict "a" (dict "b" "found")) }}`, + `{{ dig "a.b" "fallback" (dict "a" (dict "b" "found")) }}`, + `{{ merge (dict "a" 1) (dict "b" 2) | toJson }}`, + `{{ merge (dict "a" 1) (dict "a" 0) | toJson }}`, + }, + }, + { + "lists", + []string{ + `{{ list 1 2 3 | toJson }}`, + `{{ tuple 1 2 3 | toJson }}`, + `{{ first (list 1 2 3) }}`, + `{{ last (list 1 2 3) }}`, + `{{ rest (list 1 2 3) | toJson }}`, + `{{ initial (list 1 2 3) | toJson }}`, + `{{ reverse (list 1 2 3) | toJson }}`, + `{{ uniq (list 1 1 2) | toJson }}`, + `{{ compact (list 1 "" 2) | toJson }}`, + `{{ concat (list 1) (list 2) | toJson }}`, + `{{ chunk 2 (list 1 2 3) | toJson }}`, + `{{ has 2 (list 1 2 3) }}`, + `{{ sortAlpha (list "b" "a") | toJson }}`, + `{{ splitList "," "a,b,c" | toJson }}`, + `{{ toStrings (list 1 2) | toJson }}`, + `{{ until 3 | toJson }}`, + `{{ untilStep 0 6 2 | toJson }}`, + `{{ seq 1 3 }}`, + `{{ join "," (list "a" "b") }}`, + }, + }, + { + "strings", + []string{ + `{{ trim " x " }}`, + `{{ trimAll "-" "-x-" }}`, + `{{ trimall "-" "-x-" }}`, + `{{ trimPrefix "a" "ab" }}`, + `{{ trimSuffix "b" "ab" }}`, + `{{ upper "abc" }}`, + `{{ lower "ABC" }}`, + `{{ title "hello world" }}`, + `{{ title "hello wORLD" }}`, + `{{ trunc 3 "foobar" }}`, + `{{ trunc -3 "foobar" }}`, + `{{ substr 0 3 "foobar" }}`, + `{{ substr 0 -3 "foobar" }}`, + `{{ repeat 3 "x" }}`, + `{{ contains "oo" "foobar" }}`, + `{{ hasPrefix "foo" "foobar" }}`, + `{{ hasSuffix "bar" "foobar" }}`, + `{{ quote "x" }}`, + `{{ squote "x" }}`, + `{{ cat "a" "b" }}`, + `{{ indent 2 "x" }}`, + `{{ nindent 2 "x" }}`, + `{{ replace "a" "b" "aa" }}`, + `{{ plural "one" "many" 2 }}`, + `{{ split "," "a,b" | toJson }}`, + `{{ splitn "," 2 "a,b,c" | toJson }}`, + `{{ toString 42 }}`, + }, + }, + { + "numbers", + []string{ + `{{ add 1 2 }}`, + `{{ add1 1 }}`, + `{{ sub 5 2 }}`, + `{{ mul 2 3 }}`, + `{{ div 6 2 }}`, + `{{ mod 5 3 }}`, + `{{ max 1 5 3 }}`, + `{{ min 1 5 3 }}`, + `{{ biggest 1 5 3 }}`, + `{{ maxf 1.5 2.5 }}`, + `{{ minf 1.5 2.5 }}`, + `{{ ceil 1.1 }}`, + `{{ floor 1.9 }}`, + `{{ round 1.55 1 }}`, + `{{ atoi "42" }}`, + `{{ atoi "abc" }}`, + `{{ int "42" }}`, + `{{ int64 "42" }}`, + `{{ float64 "1.5" }}`, + `{{ toDecimal "0777" }}`, + }, + }, + { + "defaults and flow", + []string{ + `{{ default "d" "" }}`, + `{{ default "d" "x" }}`, + `{{ empty "" }}`, + `{{ empty 0 }}`, + `{{ coalesce "" "x" }}`, + `{{ all 1 1 }}`, + `{{ any 0 1 }}`, + `{{ ternary "y" "n" true }}`, + `{{ fail "boom" }}`, + }, + }, + { + "encoding", + []string{ + `{{ toJson (dict "a" 1) }}`, + `{{ toPrettyJson (dict "a" 1) }}`, + `{{ toRawJson (dict "a" "") }}`, + `{{ fromJson "{\"a\":1}" | toJson }}`, + `{{ fromJson "not json" | toJson }}`, + `{{ mustFromJson "not json" | toJson }}`, + `{{ toYaml (dict "a" 1) }}`, + `{{ fromYaml "a: 1" | toJson }}`, + `{{ mustFromYaml "a: :" | toJson }}`, + `{{ b64enc "hello" }}`, + `{{ b64dec "aGVsbG8=" }}`, + `{{ b32enc "hello" }}`, + `{{ b32dec "NBSWY3DP" }}`, + }, + }, + { + "regex", + []string{ + `{{ regexMatch "^a" "abc" }}`, + `{{ regexFind "[0-9]+" "abc123" }}`, + `{{ regexFindAll "[0-9]" "a1b2" -1 | toJson }}`, + `{{ regexReplaceAll "[0-9]" "abc123" "#" }}`, + `{{ regexReplaceAllLiteral "[0-9]" "abc123" "#" }}`, + `{{ regexSplit "," "a,b" -1 | toJson }}`, + `{{ regexQuoteMeta "a.b" }}`, + `{{ mustRegexFind "[" "abc" }}`, + }, + }, + { + "reflection", + []string{ + `{{ typeOf 1 }}`, + `{{ typeIs "int" 1 }}`, + `{{ typeIsLike "int" 1 }}`, + `{{ kindOf 1 }}`, + `{{ kindOf (list 1) }}`, + `{{ kindIs "int" 1 }}`, + `{{ deepEqual (list 1) (list 1) }}`, + }, + }, + { + "checksums", + []string{ + `{{ sha1sum "x" }}`, + `{{ sha256sum "x" }}`, + `{{ adler32sum "x" }}`, + }, + }, + { + // path.* semantics — slash based, therefore identical on every platform. + "paths", + []string{ + `{{ base "/foo/bar.txt" }}`, + `{{ dir "/foo/bar.txt" }}`, + `{{ ext "/foo/bar.txt" }}`, + `{{ clean "/foo//bar" }}`, + `{{ isAbs "/foo" }}`, + }, + }, + { + "dates — fixed epoch, explicit zone", + []string{ + `{{ dateInZone "2006-01-02T15:04:05" 0 "UTC" }}`, + `{{ date "2006-01-02T15:04:05" 0 }}`, + `{{ dateModify "1h" (toDate "2006-01-02T15:04:05Z07:00" "2020-01-01T00:00:00Z") }}`, + `{{ date_modify "1h" (toDate "2006-01-02T15:04:05Z07:00" "2020-01-01T00:00:00Z") }}`, + `{{ toDate "2006-01-02" "2020-01-01" }}`, + `{{ unixEpoch (toDate "2006-01-02" "2020-01-01") }}`, + `{{ duration 90 }}`, + `{{ durationRound "1h35m30s" }}`, + `{{ htmlDate 0 }}`, + }, + }, + { + "urls", + []string{ + `{{ urlParse "http://example.com/a?b=c" | toJson }}`, + `{{ urlJoin (dict "scheme" "http" "host" "example.com" "path" "/a") }}`, + }, + }, + { + "task's own functions", + []string{ + `{{ numCPU | kindOf }}`, + `{{ catLines "a\nb" }}`, + `{{ splitLines "a\nb" | toJson }}`, + `{{ toSlash "a/b" }}`, + `{{ fromSlash "a/b" | kindOf }}`, + `{{ ToSlash "a/b" }}`, + `{{ shellQuote "a b" }}`, + `{{ q "a b" }}`, + `{{ splitArgs "a b c" | toJson }}`, + `{{ IsSH }}`, + `{{ joinUrl "http://localhost" "a" "b" }}`, + `{{ mustToYaml (dict "a" 1) }}`, + `{{ randIntN 1 }}`, + }, + }, +} diff --git a/internal/templater/testdata/func_behaviour.golden b/internal/templater/testdata/func_behaviour.golden new file mode 100644 index 00000000..6f98702e --- /dev/null +++ b/internal/templater/testdata/func_behaviour.golden @@ -0,0 +1,367 @@ +## argument order — sprig order (target first) + +{{ get (dict "a" "b") "a" }} + = b +{{ set (dict "a" "b") "c" "d" | toJson }} + = {"a":"b","c":"d"} +{{ unset (dict "a" "b" "c" "d") "a" | toJson }} + = {"c":"d"} +{{ hasKey (dict "a" "b") "a" }} + = true +{{ pick (dict "a" "1" "b" "2") "a" | toJson }} + = {"a":"1"} +{{ omit (dict "a" "1" "b" "2") "a" | toJson }} + = {"b":"2"} +{{ append (list 1 2) 3 | toJson }} + = [1,2,3] +{{ push (list 1 2) 3 | toJson }} + = [1,2,3] +{{ prepend (list 2 3) 1 | toJson }} + = [1,2,3] +{{ without (list 1 2 3) 2 | toJson }} + = [1,3] +{{ slice (list 1 2 3 4) 1 3 | toJson }} + = [2,3] + +## argument order — sprout order (target last) + +{{ dict "a" "b" | get "a" }} + ! at <"a">: can't handle "a" for arg of type map[string]interface {} +{{ dict "a" "b" | set "c" "d" | toJson }} + ! at <"c">: can't handle "c" for arg of type map[string]interface {} +{{ dict "a" "b" "c" "d" | unset "a" | toJson }} + ! at <"a">: can't handle "a" for arg of type map[string]interface {} +{{ dict "a" "b" | hasKey "a" }} + ! at <"a">: can't handle "a" for arg of type map[string]interface {} +{{ list 1 2 | append 3 | toJson }} + ! at : error calling append: Cannot push on type int +{{ list 2 3 | prepend 1 | toJson }} + ! at : error calling prepend: Cannot prepend on type int + +## maps — unchanged signatures + +{{ dict "a" 1 "b" 2 | toJson }} + = {"a":1,"b":2} +{{ keys (dict "b" 1 "a" 2) | sortAlpha | toJson }} + = ["a","b"] +{{ values (dict "a" 1) | toJson }} + = [1] +{{ pluck "a" (dict "a" 1) (dict "a" 2) | toJson }} + = [1,2] +{{ dig "a" "b" "fallback" (dict "a" (dict "b" "found")) }} + = found +{{ dig "a" "missing" "fallback" (dict "a" (dict "b" "found")) }} + = fallback +{{ dig "a.b" "fallback" (dict "a" (dict "b" "found")) }} + = fallback +{{ merge (dict "a" 1) (dict "b" 2) | toJson }} + = {"a":1,"b":2} +{{ merge (dict "a" 1) (dict "a" 0) | toJson }} + = {"a":0} + +## lists + +{{ list 1 2 3 | toJson }} + = [1,2,3] +{{ tuple 1 2 3 | toJson }} + = [1,2,3] +{{ first (list 1 2 3) }} + = 1 +{{ last (list 1 2 3) }} + = 3 +{{ rest (list 1 2 3) | toJson }} + = [2,3] +{{ initial (list 1 2 3) | toJson }} + = [1,2] +{{ reverse (list 1 2 3) | toJson }} + = [3,2,1] +{{ uniq (list 1 1 2) | toJson }} + = [1,2] +{{ compact (list 1 "" 2) | toJson }} + = [1,2] +{{ concat (list 1) (list 2) | toJson }} + = [1,2] +{{ chunk 2 (list 1 2 3) | toJson }} + = [[1,2],[3]] +{{ has 2 (list 1 2 3) }} + = true +{{ sortAlpha (list "b" "a") | toJson }} + = ["a","b"] +{{ splitList "," "a,b,c" | toJson }} + = ["a","b","c"] +{{ toStrings (list 1 2) | toJson }} + = ["1","2"] +{{ until 3 | toJson }} + = [0,1,2] +{{ untilStep 0 6 2 | toJson }} + = [0,2,4] +{{ seq 1 3 }} + = 1 2 3 +{{ join "," (list "a" "b") }} + = a,b + +## strings + +{{ trim " x " }} + = x +{{ trimAll "-" "-x-" }} + = x +{{ trimall "-" "-x-" }} + = x +{{ trimPrefix "a" "ab" }} + = b +{{ trimSuffix "b" "ab" }} + = a +{{ upper "abc" }} + = ABC +{{ lower "ABC" }} + = abc +{{ title "hello world" }} + = Hello World +{{ title "hello wORLD" }} + = Hello WORLD +{{ trunc 3 "foobar" }} + = foo +{{ trunc -3 "foobar" }} + = bar +{{ substr 0 3 "foobar" }} + = foo +{{ substr 0 -3 "foobar" }} + = foobar +{{ repeat 3 "x" }} + = xxx +{{ contains "oo" "foobar" }} + = true +{{ hasPrefix "foo" "foobar" }} + = true +{{ hasSuffix "bar" "foobar" }} + = true +{{ quote "x" }} + = "x" +{{ squote "x" }} + = 'x' +{{ cat "a" "b" }} + = a b +{{ indent 2 "x" }} + = x +{{ nindent 2 "x" }} + = \n x +{{ replace "a" "b" "aa" }} + = bb +{{ plural "one" "many" 2 }} + = many +{{ split "," "a,b" | toJson }} + = {"_0":"a","_1":"b"} +{{ splitn "," 2 "a,b,c" | toJson }} + = {"_0":"a","_1":"b,c"} +{{ toString 42 }} + = 42 + +## numbers + +{{ add 1 2 }} + = 3 +{{ add1 1 }} + = 2 +{{ sub 5 2 }} + = 3 +{{ mul 2 3 }} + = 6 +{{ div 6 2 }} + = 3 +{{ mod 5 3 }} + = 2 +{{ max 1 5 3 }} + = 5 +{{ min 1 5 3 }} + = 1 +{{ biggest 1 5 3 }} + = 5 +{{ maxf 1.5 2.5 }} + = 2.5 +{{ minf 1.5 2.5 }} + = 1.5 +{{ ceil 1.1 }} + = 2 +{{ floor 1.9 }} + = 1 +{{ round 1.55 1 }} + = 1.6 +{{ atoi "42" }} + = 42 +{{ atoi "abc" }} + = 0 +{{ int "42" }} + = 42 +{{ int64 "42" }} + = 42 +{{ float64 "1.5" }} + = 1.5 +{{ toDecimal "0777" }} + = 511 + +## defaults and flow + +{{ default "d" "" }} + = d +{{ default "d" "x" }} + = x +{{ empty "" }} + = true +{{ empty 0 }} + = true +{{ coalesce "" "x" }} + = x +{{ all 1 1 }} + = true +{{ any 0 1 }} + = true +{{ ternary "y" "n" true }} + = y +{{ fail "boom" }} + ! at : error calling fail: boom + +## encoding + +{{ toJson (dict "a" 1) }} + = {"a":1} +{{ toPrettyJson (dict "a" 1) }} + = {\n "a": 1\n} +{{ toRawJson (dict "a" "") }} + = {"a":""} +{{ fromJson "{\"a\":1}" | toJson }} + = {"a":1} +{{ fromJson "not json" | toJson }} + = null +{{ mustFromJson "not json" | toJson }} + ! at : error calling mustFromJson: invalid character 'o' in literal null (expecting 'u') +{{ toYaml (dict "a" 1) }} + = a: 1\n +{{ fromYaml "a: 1" | toJson }} + = {"a":1} +{{ mustFromYaml "a: :" | toJson }} + ! at : error calling mustFromYaml: yaml: mapping values are not allowed in this context +{{ b64enc "hello" }} + = aGVsbG8= +{{ b64dec "aGVsbG8=" }} + = hello +{{ b32enc "hello" }} + = NBSWY3DP +{{ b32dec "NBSWY3DP" }} + = hello + +## regex + +{{ regexMatch "^a" "abc" }} + = true +{{ regexFind "[0-9]+" "abc123" }} + = 123 +{{ regexFindAll "[0-9]" "a1b2" -1 | toJson }} + = ["1","2"] +{{ regexReplaceAll "[0-9]" "abc123" "#" }} + = abc### +{{ regexReplaceAllLiteral "[0-9]" "abc123" "#" }} + = abc### +{{ regexSplit "," "a,b" -1 | toJson }} + = ["a","b"] +{{ regexQuoteMeta "a.b" }} + = a\.b +{{ mustRegexFind "[" "abc" }} + ! at : error calling mustRegexFind: error parsing regexp: missing closing ]: `[` + +## reflection + +{{ typeOf 1 }} + = int +{{ typeIs "int" 1 }} + = true +{{ typeIsLike "int" 1 }} + = true +{{ kindOf 1 }} + = int +{{ kindOf (list 1) }} + = slice +{{ kindIs "int" 1 }} + = true +{{ deepEqual (list 1) (list 1) }} + = true + +## checksums + +{{ sha1sum "x" }} + = 11f6ad8ec52a2984abaafd7c3b516503785c2072 +{{ sha256sum "x" }} + = 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881 +{{ adler32sum "x" }} + = 7929977 + +## paths + +{{ base "/foo/bar.txt" }} + = bar.txt +{{ dir "/foo/bar.txt" }} + = /foo +{{ ext "/foo/bar.txt" }} + = .txt +{{ clean "/foo//bar" }} + = /foo/bar +{{ isAbs "/foo" }} + = true + +## dates — fixed epoch, explicit zone + +{{ dateInZone "2006-01-02T15:04:05" 0 "UTC" }} + = 1970-01-01T00:00:00 +{{ date "2006-01-02T15:04:05" 0 }} + = 1970-01-01T00:00:00 +{{ dateModify "1h" (toDate "2006-01-02T15:04:05Z07:00" "2020-01-01T00:00:00Z") }} + = 2020-01-01 01:00:00 +0000 UTC +{{ date_modify "1h" (toDate "2006-01-02T15:04:05Z07:00" "2020-01-01T00:00:00Z") }} + = 2020-01-01 01:00:00 +0000 UTC +{{ toDate "2006-01-02" "2020-01-01" }} + = 2020-01-01 00:00:00 +0000 UTC +{{ unixEpoch (toDate "2006-01-02" "2020-01-01") }} + = 1577836800 +{{ duration 90 }} + = 0s +{{ durationRound "1h35m30s" }} + = 1h +{{ htmlDate 0 }} + = 1970-01-01 + +## urls + +{{ urlParse "http://example.com/a?b=c" | toJson }} + = {"fragment":"","host":"example.com","hostname":"example.com","opaque":"","path":"/a","query":"b=c","scheme":"http","userinfo":""} +{{ urlJoin (dict "scheme" "http" "host" "example.com" "path" "/a") }} + = http://example.com/a + +## task's own functions + +{{ numCPU | kindOf }} + = int +{{ catLines "a\nb" }} + = a b +{{ splitLines "a\nb" | toJson }} + = ["a","b"] +{{ toSlash "a/b" }} + = a/b +{{ fromSlash "a/b" | kindOf }} + = string +{{ ToSlash "a/b" }} + = a/b +{{ shellQuote "a b" }} + = 'a b' +{{ q "a b" }} + = 'a b' +{{ splitArgs "a b c" | toJson }} + = ["a","b","c"] +{{ IsSH }} + = true +{{ joinUrl "http://localhost" "a" "b" }} + = http://localhost/a/b +{{ mustToYaml (dict "a" 1) }} + = a: 1\n +{{ randIntN 1 }} + = 0 + diff --git a/internal/templater/testdata/func_names.golden b/internal/templater/testdata/func_names.golden new file mode 100644 index 00000000..a756e4bd --- /dev/null +++ b/internal/templater/testdata/func_names.golden @@ -0,0 +1,194 @@ +ARCH +ExeExt +FromSlash +IsSH +OS +ToSlash +absPath +add +add1 +adler32sum +ago +all +any +append +atoi +b32dec +b32enc +b64dec +b64enc +base +biggest +cat +catLines +ceil +chunk +clean +coalesce +compact +concat +contains +date +dateInZone +dateModify +date_in_zone +date_modify +deepEqual +default +dict +dig +dir +div +duration +durationRound +empty +env +exeExt +expandenv +ext +fail +first +float64 +floor +fromJson +fromSlash +fromYaml +get +getHostByName +has +hasKey +hasPrefix +hasSuffix +hello +htmlDate +htmlDateInZone +indent +initial +int +int64 +isAbs +join +joinEnv +joinPath +joinUrl +keys +kindIs +kindOf +last +list +lower +max +maxf +merge +min +minf +mod +mul +mustAppend +mustChunk +mustCompact +mustDateModify +mustFirst +mustFromJson +mustFromYaml +mustHas +mustInitial +mustLast +mustPrepend +mustPush +mustRegexFind +mustRegexFindAll +mustRegexMatch +mustRegexReplaceAll +mustRegexReplaceAllLiteral +mustRegexSplit +mustRest +mustReverse +mustSlice +mustToDate +mustToJson +mustToPrettyJson +mustToRawJson +mustToYaml +mustUniq +mustWithout +must_date_modify +nindent +now +numCPU +omit +osBase +osClean +osDir +osExt +osIsAbs +pick +pluck +plural +prepend +push +q +quote +randInt +randIntN +regexFind +regexFindAll +regexMatch +regexQuoteMeta +regexReplaceAll +regexReplaceAllLiteral +regexSplit +relPath +repeat +replace +rest +reverse +round +seq +set +sha1sum +sha256sum +shellQuote +slice +sortAlpha +spew +split +splitArgs +splitLines +splitList +splitn +squote +sub +substr +ternary +title +toDate +toDecimal +toJson +toPrettyJson +toRawJson +toSlash +toString +toStrings +toYaml +trim +trimAll +trimPrefix +trimSuffix +trimall +trunc +tuple +typeIs +typeIsLike +typeOf +uniq +unixEpoch +unset +until +untilStep +upper +urlJoin +urlParse +uuid +values +without