summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2018-11-10 11:16:28 -0500
committerDave Henderson <dhenderson@gmail.com>2018-11-10 20:56:24 -0500
commitb8797404d01fc222cf22535ff57ab925aed4d88d (patch)
treec8c6c40f0d803b0e7b852c43340e55b57a15b717
parent40b95a7537904926783bea001c0d45fd3c30a798 (diff)
New tpl function
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
-rw-r--r--docs-src/content/functions/other.yml36
-rw-r--r--docs/content/functions/other.md46
-rw-r--r--gomplate.go1
-rw-r--r--template.go12
-rw-r--r--tests/integration/tpl_test.go25
-rw-r--r--tpl_func.go68
-rw-r--r--tpl_func_test.go68
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)
+}