diff options
| author | Dave Henderson <dhenderson@gmail.com> | 2018-11-10 11:16:28 -0500 |
|---|---|---|
| committer | Dave Henderson <dhenderson@gmail.com> | 2018-11-10 20:56:24 -0500 |
| commit | b8797404d01fc222cf22535ff57ab925aed4d88d (patch) | |
| tree | c8c6c40f0d803b0e7b852c43340e55b57a15b717 | |
| parent | 40b95a7537904926783bea001c0d45fd3c30a798 (diff) | |
New tpl function
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
| -rw-r--r-- | docs-src/content/functions/other.yml | 36 | ||||
| -rw-r--r-- | docs/content/functions/other.md | 46 | ||||
| -rw-r--r-- | gomplate.go | 1 | ||||
| -rw-r--r-- | template.go | 12 | ||||
| -rw-r--r-- | tests/integration/tpl_test.go | 25 | ||||
| -rw-r--r-- | tpl_func.go | 68 | ||||
| -rw-r--r-- | tpl_func_test.go | 68 |
7 files changed, 253 insertions, 3 deletions
diff --git a/docs-src/content/functions/other.yml b/docs-src/content/functions/other.yml new file mode 100644 index 00000000..d26e9243 --- /dev/null +++ b/docs-src/content/functions/other.yml @@ -0,0 +1,36 @@ +ns: _ +title: Other functions +preamble: | + Miscellaneous functions that aren't part of a specific namespace. Most of these are fairly special-purpose. +funcs: + - name: tpl + description: | + Render the given string as a template, just like a nested template. + + If the template is given a name (see `name` argument below), it can be re-used later with the `template` keyword. + + A context can be provided, otherwise the default gomplate context will be used. + pipeline: false + arguments: + - name: name + required: false + description: The template's name. + - name: in + required: true + description: The template to render, as a string + - name: context + required: false + description: The context to use when rendering - this becomes `.` inside the template. + examples: + - | + $ gomplate -i '{{ tpl "{{print `hello world`}}" }}' + hello world + - | + $ gomplate -i ' + {{ $tstring := "{{ print .value ` world` }}" }} + {{ $context := dict "value" "hello" }} + {{ tpl "T1" $tstring $context }} + {{ template "T1" (dict "value" "goodbye") }} + ' + hello world + goodbye world diff --git a/docs/content/functions/other.md b/docs/content/functions/other.md new file mode 100644 index 00000000..791569c5 --- /dev/null +++ b/docs/content/functions/other.md @@ -0,0 +1,46 @@ +--- +title: Other functions +menu: + main: + parent: functions +--- + +Miscellaneous functions that aren't part of a specific namespace. Most of these are fairly special-purpose. + +## `tpl` + +Render the given string as a template, just like a nested template. + +If the template is given a name (see `name` argument below), it can be re-used later with the `template` keyword. + +A context can be provided, otherwise the default gomplate context will be used. + +### Usage +```go +tpl [name] in [context] +``` + +### Arguments + +| name | description | +|------|-------------| +| `name` | _(optional)_ The template's name. | +| `in` | _(required)_ The template to render, as a string | +| `context` | _(optional)_ The context to use when rendering - this becomes `.` inside the template. | + +### Examples + +```console +$ gomplate -i '{{ tpl "{{print `hello world`}}" }}' +hello world +``` +```console +$ gomplate -i ' +{{ $tstring := "{{ print .value ` world` }}" }} +{{ $context := dict "value" "hello" }} +{{ tpl "T1" $tstring $context }} +{{ template "T1" (dict "value" "goodbye") }} +' +hello world +goodbye world +``` diff --git a/gomplate.go b/gomplate.go index c7ecdb8e..85bc8be6 100644 --- a/gomplate.go +++ b/gomplate.go @@ -96,6 +96,7 @@ type gomplate struct { leftDelim string rightDelim string nestedTemplates templateAliases + rootTemplate *template.Template } // runTemplate - diff --git a/template.go b/template.go index 6ec2b239..86fbfb25 100644 --- a/template.go +++ b/template.go @@ -28,12 +28,18 @@ type tplate struct { modeOverride bool } -func (t *tplate) toGoTemplate(g *gomplate) (*template.Template, error) { - tmpl := template.New(t.name) +func (t *tplate) toGoTemplate(g *gomplate) (tmpl *template.Template, err error) { + if g.rootTemplate != nil { + tmpl = g.rootTemplate.New(t.name) + } else { + tmpl = template.New(t.name) + g.rootTemplate = tmpl + } tmpl.Option("missingkey=error") + g.funcMap["tpl"] = g.tpl tmpl.Funcs(g.funcMap) tmpl.Delims(g.leftDelim, g.rightDelim) - _, err := tmpl.Parse(t.contents) + _, err = tmpl.Parse(t.contents) if err != nil { return nil, err } diff --git a/tests/integration/tpl_test.go b/tests/integration/tpl_test.go new file mode 100644 index 00000000..f87980ab --- /dev/null +++ b/tests/integration/tpl_test.go @@ -0,0 +1,25 @@ +//+build integration + +package integration + +import ( + . "gopkg.in/check.v1" +) + +type TplSuite struct{} + +var _ = Suite(&TplSuite{}) + +func (s *TplSuite) TestTime(c *C) { + inOutTest(c, ` + {{- $nums := dict "first" 5 "second" 10 }} + {{- tpl "{{ add .first .second }}" $nums }}`, + "15") + + inOutTest(c, ` + {{- $nums := dict "first" 5 "second" 10 }} + {{- $othernums := dict "first" 18 "second" -8 }} + {{- tpl "T" "{{ add .first .second }}" $nums }} + {{- template "T" $othernums }}`, + "1510") +} diff --git a/tpl_func.go b/tpl_func.go new file mode 100644 index 00000000..1cee7495 --- /dev/null +++ b/tpl_func.go @@ -0,0 +1,68 @@ +package gomplate + +import ( + "bytes" + + "github.com/pkg/errors" +) + +// tpl - a template function to do inline template processing +// Can be called 4 ways: +// {{ tpl "inline template" }} - unnamed (single-use) template with default context +// {{ tpl "name" "inline template" }} - named template with default context +// {{ tpl "inline template" $foo }} - unnamed (single-use) template with given context +// {{ tpl "name" "inline template" $foo }} - named template with given context +func (g *gomplate) tpl(args ...interface{}) (string, error) { + name, in, ctx, err := parseArgs(args...) + if err != nil { + return "", err + } + t, err := g.rootTemplate.New(name).Parse(in) + if err != nil { + return "", err + } + out := &bytes.Buffer{} + err = t.Execute(out, ctx) + if err != nil { + return "", err + } + return out.String(), nil +} + +func parseArgs(args ...interface{}) (name, in string, ctx interface{}, err error) { + name = "<inline>" + ctx = &context{} + + if len(args) == 0 || len(args) > 3 { + return "", "", nil, errors.Errorf("wrong number of args for tpl: want 1, 2, or 3 - got %d", len(args)) + } + first, ok := args[0].(string) + if !ok { + return "", "", nil, errors.Errorf("wrong input: first arg must be string, got %T", args[0]) + } + + switch len(args) { + case 1: + in = first + case 2: + // this can either be (name string, in string) or (in string, ctx interface{}) + switch second := args[1].(type) { + case string: + name = first + in = second + default: + in = first + ctx = second + } + case 3: + name = first + var ok bool + in, ok = args[1].(string) + if !ok { + return "", "", nil, errors.Errorf("wrong input: second arg (in) must be string, got %T", args[0]) + } + ctx = args[2] + } + + return name, in, ctx, nil +} diff --git a/tpl_func_test.go b/tpl_func_test.go new file mode 100644 index 00000000..2c82908e --- /dev/null +++ b/tpl_func_test.go @@ -0,0 +1,68 @@ +package gomplate + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" +) + +func TestTplFunc(t *testing.T) { + g := &gomplate{ + leftDelim: "{{", + rightDelim: "}}", + funcMap: template.FuncMap{}, + } + tmpl := &tplate{name: "root", contents: "foo"} + tmpl.toGoTemplate(g) + + testdata := []string{ + "{{ print `hello world`}}", + "{{ tpl \"{{ print `hello world`}}\"}}", + "{{ tpl \"{{ tpl \\\"{{ print `hello world`}}\\\"}}\"}}", + } + for _, d := range testdata { + out, err := g.tpl(d) + assert.NoError(t, err, d) + assert.Equal(t, "hello world", out) + } +} + +func TestParseArgs(t *testing.T) { + name, in, ctx, err := parseArgs("foo") + assert.NoError(t, err) + assert.Equal(t, "<inline>", name) + assert.Equal(t, "foo", in) + assert.EqualValues(t, &context{}, ctx) + + _, _, _, err = parseArgs(42) + assert.Error(t, err) + + _, _, _, err = parseArgs() + assert.Error(t, err) + + _, _, _, err = parseArgs("", "", 42, "") + assert.Error(t, err) + + _, _, _, err = parseArgs("", 42, 42) + assert.Error(t, err) + + name, in, ctx, err = parseArgs("foo", "bar") + assert.NoError(t, err) + assert.Equal(t, "foo", name) + assert.Equal(t, "bar", in) + assert.EqualValues(t, &context{}, ctx) + + c := map[string]string{"one": "two"} + name, in, ctx, err = parseArgs("foo", c) + assert.NoError(t, err) + assert.Equal(t, "<inline>", name) + assert.Equal(t, "foo", in) + assert.Equal(t, c, ctx) + + name, in, ctx, err = parseArgs("foo", "bar", c) + assert.NoError(t, err) + assert.Equal(t, "foo", name) + assert.Equal(t, "bar", in) + assert.Equal(t, c, ctx) +} |
