From 8d9ab1cffb4d0e5b0a7c70a01ad6c142952362a0 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Fri, 15 Feb 2019 23:38:37 -0500 Subject: New tmpl.Exec function Signed-off-by: Dave Henderson --- docs-src/content/functions/tmpl.yml | 20 +++++++++ docs/content/functions/tmpl.md | 33 ++++++++++++++ tests/integration/tmpl_test.go | 88 ++++++++++++++++++++++++++++++++++++- tmpl/tmpl.go | 23 +++++++++- tmpl/tmpl_test.go | 21 +++++++++ 5 files changed, 183 insertions(+), 2 deletions(-) diff --git a/docs-src/content/functions/tmpl.yml b/docs-src/content/functions/tmpl.yml index 962fefaf..3bf2f9fa 100644 --- a/docs-src/content/functions/tmpl.yml +++ b/docs-src/content/functions/tmpl.yml @@ -3,6 +3,26 @@ title: template functions preamble: | Functions for defining or executing templates. funcs: + - name: tmpl.Exec + description: | + Execute (render) the named template. This is equivalent to using the [`template`](https://golang.org/pkg/text/template/#hdr-Actions) action, except the result is returned as a string. + + This allows for post-processing of templates. + pipeline: true + arguments: + - name: name + required: true + description: The template's name. + - name: context + required: false + description: The context to use. + examples: + - | + $ gomplate -i '{{define "T1"}}hello, world!{{end}}{{ tmpl.Exec "T1" | strings.ToUpper }}' + HELLO, WORLD! + - | + $ gomplate -i '{{define "T1"}}hello, {{.}}{{end}}{{ tmpl.Exec "T1" "world!" | strings.Title }}' + Hello, World! - name: tmpl.Inline alias: tpl description: | diff --git a/docs/content/functions/tmpl.md b/docs/content/functions/tmpl.md index 64a0bf64..f6da5f4d 100644 --- a/docs/content/functions/tmpl.md +++ b/docs/content/functions/tmpl.md @@ -7,6 +7,39 @@ menu: Functions for defining or executing templates. +## `tmpl.Exec` + +Execute (render) the named template. This is equivalent to using the [`template`](https://golang.org/pkg/text/template/#hdr-Actions) action, except the result is returned as a string. + +This allows for post-processing of templates. + +### Usage +```go +tmpl.Exec name [context] +``` + +```go +context | tmpl.Exec name +``` + +### Arguments + +| name | description | +|------|-------------| +| `name` | _(required)_ The template's name. | +| `context` | _(optional)_ The context to use. | + +### Examples + +```console +$ gomplate -i '{{define "T1"}}hello, world!{{end}}{{ tmpl.Exec "T1" | strings.ToUpper }}' +HELLO, WORLD! +``` +```console +$ gomplate -i '{{define "T1"}}hello, {{.}}{{end}}{{ tmpl.Exec "T1" "world!" | strings.Title }}' +Hello, World! +``` + ## `tmpl.Inline` **Alias:** `tpl` diff --git a/tests/integration/tmpl_test.go b/tests/integration/tmpl_test.go index ca6e4ad8..3ac2e689 100644 --- a/tests/integration/tmpl_test.go +++ b/tests/integration/tmpl_test.go @@ -3,13 +3,42 @@ package integration import ( + "bytes" + "io/ioutil" + "os" + + "github.com/gotestyourself/gotestyourself/assert" + "github.com/gotestyourself/gotestyourself/fs" + "github.com/gotestyourself/gotestyourself/icmd" . "gopkg.in/check.v1" ) -type TmplSuite struct{} +type TmplSuite struct { + tmpDir *fs.Dir +} var _ = Suite(&TmplSuite{}) +func (s *TmplSuite) SetUpTest(c *C) { + s.tmpDir = fs.NewDir(c, "gomplate-tmpltests", + fs.WithFiles(map[string]string{ + "toyaml.tmpl": `{{ . | data.ToYAML }}{{"\n"}}`, + "services.yaml": `services: + - name: users + config: + replicas: 2 + - name: products + config: + replicas: 18 +`, + }), + ) +} + +func (s *TmplSuite) TearDownTest(c *C) { + s.tmpDir.Remove() +} + func (s *TmplSuite) TestInline(c *C) { inOutTest(c, ` {{- $nums := dict "first" 5 "second" 10 }} @@ -23,3 +52,60 @@ func (s *TmplSuite) TestInline(c *C) { {{- template "T" $othernums }}`, "1510") } + +func (s *TmplSuite) TestExec(c *C) { + result := icmd.RunCmd(icmd.Command(GomplateBin, + "-i", `{{ tmpl.Exec "Nope" }}`, + )) + result.Assert(c, icmd.Expected{ExitCode: 1, Err: `template "Nope" not defined`}) + + result = icmd.RunCmd(icmd.Command(GomplateBin, + "-i", `{{define "T1"}}hello world{{end}}{{ tmpl.Exec "T1" | strings.ToUpper }}`, + )) + result.Assert(c, icmd.Expected{ExitCode: 0, Out: `HELLO WORLD`}) + + result = icmd.RunCmd(icmd.Command(GomplateBin, + "-c", "in=stdin:///in.json", + "-t", "toyaml="+s.tmpDir.Join("toyaml.tmpl"), + "-i", `foo: +{{ tmpl.Exec "toyaml" .in | strings.Indent 2 }}`, + ), func(cmd *icmd.Cmd) { + in := bytes.NewBufferString(`{"a":{"nested": "object"},"b":true}`) + cmd.Stdin = in + }) + result.Assert(c, icmd.Expected{ExitCode: 0, Out: `foo: + a: + nested: object + b: true +`}) + + outDir := s.tmpDir.Join("out") + err := os.MkdirAll(outDir, 0755) + if err != nil { + assert.NilError(c, err) + } + result = icmd.RunCmd(icmd.Command(GomplateBin, + "-d", "services="+s.tmpDir.Join("services.yaml"), + "-i", `{{- define "config" }}{{ .config | data.ToJSONPretty " " }}{{ end }} +{{- range (ds "services").services -}} +{{- $outPath := path.Join .name "config.json" }} +{{- tmpl.Exec "config" . | file.Write $outPath }} +{{- end -}}`, + ), func(cmd *icmd.Cmd) { + cmd.Dir = outDir + }) + result.Assert(c, icmd.Expected{ExitCode: 0}) + assert.Equal(c, "", result.Stdout()) + assert.Equal(c, "", result.Stderr()) + + out, err := ioutil.ReadFile(s.tmpDir.Join("out", "users", "config.json")) + assert.NilError(c, err) + assert.Equal(c, `{ + "replicas": 2 +}`, string(out)) + out, err = ioutil.ReadFile(s.tmpDir.Join("out", "products", "config.json")) + assert.NilError(c, err) + assert.Equal(c, `{ + "replicas": 18 +}`, string(out)) +} diff --git a/tmpl/tmpl.go b/tmpl/tmpl.go index 59c1a6a0..97ce0fe6 100644 --- a/tmpl/tmpl.go +++ b/tmpl/tmpl.go @@ -30,12 +30,33 @@ func (t *Template) Inline(args ...interface{}) (string, error) { if err != nil { return "", err } + return t.inline(name, in, ctx) +} + +func (t *Template) inline(name, in string, ctx interface{}) (string, error) { tmpl, err := t.root.New(name).Parse(in) if err != nil { return "", err } + return render(tmpl, ctx) +} + +// Exec - execute (render) a template - this is the built-in `template` action, except with output... +func (t *Template) Exec(name string, context ...interface{}) (string, error) { + ctx := t.defaultCtx + if len(context) == 1 { + ctx = context[0] + } + tmpl := t.root.Lookup(name) + if tmpl == nil { + return "", errors.Errorf(`template "%s" not defined`, name) + } + return render(tmpl, ctx) +} + +func render(tmpl *template.Template, ctx interface{}) (string, error) { out := &bytes.Buffer{} - err = tmpl.Execute(out, ctx) + err := tmpl.Execute(out, ctx) if err != nil { return "", err } diff --git a/tmpl/tmpl_test.go b/tmpl/tmpl_test.go index 0ed21111..729c99fb 100644 --- a/tmpl/tmpl_test.go +++ b/tmpl/tmpl_test.go @@ -67,3 +67,24 @@ func TestParseArgs(t *testing.T) { assert.Equal(t, "bar", in) assert.Equal(t, c, ctx) } + +func TestExec(t *testing.T) { + root := template.New("root") + t1 := root.New("T1") + t1.Parse("hello, {{ . }}") + tmpl := &Template{ + defaultCtx: map[string]string{"foo": "bar"}, + root: root, + } + + out, err := tmpl.Exec("T1") + assert.NoError(t, err) + assert.Equal(t, "hello, map[foo:bar]", out) + + out, err = tmpl.Exec("T1", "world") + assert.NoError(t, err) + assert.Equal(t, "hello, world", out) + + _, err = tmpl.Exec("bogus") + assert.Error(t, err) +} -- cgit v1.2.3