summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2022-04-09 16:36:58 -0400
committerDave Henderson <dhenderson@gmail.com>2022-04-10 15:27:15 -0400
commitae1620810cbd82eadd71abb51ba2fea97cf50a59 (patch)
treea907ef15a3959cf1ac7101ea1bc5becc038b6605
parent6c66d16b7ccb32636688c644b670141b5e5341aa (diff)
New tmpl.Path/tmpl.PathDir functions
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
-rw-r--r--docs-src/content/functions/tmpl.yml39
-rw-r--r--docs/content/functions/tmpl.md55
-rw-r--r--internal/tests/integration/tmpl_test.go33
-rw-r--r--template.go25
-rw-r--r--tmpl/tmpl.go22
-rw-r--r--tmpl/tmpl_test.go33
6 files changed, 196 insertions, 11 deletions
diff --git a/docs-src/content/functions/tmpl.yml b/docs-src/content/functions/tmpl.yml
index 3bf2f9fa..236ceb99 100644
--- a/docs-src/content/functions/tmpl.yml
+++ b/docs-src/content/functions/tmpl.yml
@@ -55,3 +55,42 @@ funcs:
'
hello world
goodbye world
+ - name: tmpl.Path
+ description: |
+ Output the path of the current template, if it came from a file. For
+ inline templates, this will be an empty string.
+
+ Note that if this function is called from a nested template, the path
+ of the main template will be returned instead.
+ pipeline: false
+ rawExamples:
+ - |
+ _`subdir/input.tpl`:_
+ ```
+ this template is in {{ tmpl.Path }}
+ ```
+
+ ```console
+ $ gomplate -f subdir/input.tpl
+ this template is in subdir/input.tpl
+ ```
+ ```
+ - name: tmpl.PathDir
+ description: |
+ Output the current template's directory. For inline templates, this will
+ be an empty string.
+
+ Note that if this function is called from a nested template, the path
+ of the main template will be used instead.
+ pipeline: false
+ rawExamples:
+ - |
+ _`subdir/input.tpl`:_
+ ```
+ this template is in {{ tmpl.Dir }}
+ ```
+
+ ```console
+ $ gomplate -f subdir/input.tpl
+ this template is in subdir
+ ```
diff --git a/docs/content/functions/tmpl.md b/docs/content/functions/tmpl.md
index 527fa2b6..d7ce870e 100644
--- a/docs/content/functions/tmpl.md
+++ b/docs/content/functions/tmpl.md
@@ -80,3 +80,58 @@ $ gomplate -i '
hello world
goodbye world
```
+
+## `tmpl.Path`
+
+Output the path of the current template, if it came from a file. For
+inline templates, this will be an empty string.
+
+Note that if this function is called from a nested template, the path
+of the main template will be returned instead.
+
+### Usage
+
+```go
+tmpl.Path
+```
+
+
+### Examples
+
+_`subdir/input.tpl`:_
+```
+this template is in {{ tmpl.Path }}
+```
+
+```console
+$ gomplate -f subdir/input.tpl
+this template is in subdir/input.tpl
+```
+```
+
+## `tmpl.PathDir`
+
+Output the current template's directory. For inline templates, this will
+be an empty string.
+
+Note that if this function is called from a nested template, the path
+of the main template will be used instead.
+
+### Usage
+
+```go
+tmpl.PathDir
+```
+
+
+### Examples
+
+_`subdir/input.tpl`:_
+```
+this template is in {{ tmpl.Dir }}
+```
+
+```console
+$ gomplate -f subdir/input.tpl
+this template is in subdir
+```
diff --git a/internal/tests/integration/tmpl_test.go b/internal/tests/integration/tmpl_test.go
index 37d20e17..c35f0167 100644
--- a/internal/tests/integration/tmpl_test.go
+++ b/internal/tests/integration/tmpl_test.go
@@ -8,7 +8,7 @@ import (
"gotest.tools/v3/fs"
)
-func setupTestTmplTest(t *testing.T) *fs.Dir {
+func setupTmplTest(t *testing.T) *fs.Dir {
tmpDir := fs.NewDir(t, "gomplate-tmpltests",
fs.WithFiles(map[string]string{
"toyaml.tmpl": `{{ . | data.ToYAML }}{{"\n"}}`,
@@ -21,13 +21,20 @@ func setupTestTmplTest(t *testing.T) *fs.Dir {
replicas: 18
`,
}),
+ fs.WithDir("a",
+ fs.WithFiles(map[string]string{
+ "pathtest.tpl": "{{ tmpl.Path }}\n{{ template `nested` }}",
+ "a.tpl": "{{ tmpl.PathDir }}",
+ "b.tpl": "{{ tmpl.Path }}",
+ }),
+ ),
)
t.Cleanup(tmpDir.Remove)
return tmpDir
}
-func TestTmpl_TestInline(t *testing.T) {
+func TestTmpl_Inline(t *testing.T) {
inOutTest(t, `
{{- $nums := dict "first" 5 "second" 10 }}
{{- tpl "{{ add .first .second }}" $nums }}`,
@@ -41,8 +48,8 @@ func TestTmpl_TestInline(t *testing.T) {
"1510")
}
-func TestTmpl_TestExec(t *testing.T) {
- tmpDir := setupTestTmplTest(t)
+func TestTmpl_Exec(t *testing.T) {
+ tmpDir := setupTmplTest(t)
_, _, err := cmd(t, "-i", `{{ tmpl.Exec "Nope" }}`).run()
assert.ErrorContains(t, err, "Nope")
@@ -89,3 +96,21 @@ func TestTmpl_TestExec(t *testing.T) {
"replicas": 18
}`, string(out))
}
+
+func TestTmpl_Path(t *testing.T) {
+ tmpDir := setupTmplTest(t)
+
+ o, e, err := cmd(t,
+ "-f", "a/pathtest.tpl",
+ "-t", "nested=a/a.tpl",
+ ).withDir(tmpDir.Path()).run()
+ assertSuccess(t, o, e, err, "a/pathtest.tpl\na")
+
+ o, e, err = cmd(t,
+ "-f", "a/a.tpl",
+ "-f", "a/b.tpl",
+ "-o", "-",
+ "-o", "-",
+ ).withDir(tmpDir.Path()).run()
+ assertSuccess(t, o, e, err, "aa/b.tpl")
+}
diff --git a/template.go b/template.go
index 9d66dee0..4f4d6d36 100644
--- a/template.go
+++ b/template.go
@@ -32,13 +32,27 @@ type tplate struct {
modeOverride bool
}
-func addTmplFuncs(f template.FuncMap, root *template.Template, ctx interface{}) {
- t := tmpl.New(root, ctx)
+func addTmplFuncs(f template.FuncMap, root *template.Template, tctx interface{}, path string) {
+ t := tmpl.New(root, tctx, path)
tns := func() *tmpl.Template { return t }
f["tmpl"] = tns
f["tpl"] = t.Inline
}
+// copyFuncMap - copies the template.FuncMap into a new map so we can modify it
+// without affecting the original
+func copyFuncMap(funcMap template.FuncMap) template.FuncMap {
+ if funcMap == nil {
+ return nil
+ }
+
+ newFuncMap := make(template.FuncMap, len(funcMap))
+ for k, v := range funcMap {
+ newFuncMap[k] = v
+ }
+ return newFuncMap
+}
+
func (t *tplate) toGoTemplate(g *gomplate) (tmpl *template.Template, err error) {
if g.rootTemplate != nil {
tmpl = g.rootTemplate.New(t.name)
@@ -47,9 +61,12 @@ func (t *tplate) toGoTemplate(g *gomplate) (tmpl *template.Template, err error)
g.rootTemplate = tmpl
}
tmpl.Option("missingkey=error")
+
+ funcMap := copyFuncMap(g.funcMap)
+
// the "tmpl" funcs get added here because they need access to the root template and context
- addTmplFuncs(g.funcMap, g.rootTemplate, g.tmplctx)
- tmpl.Funcs(g.funcMap)
+ addTmplFuncs(funcMap, g.rootTemplate, g.tmplctx, t.name)
+ tmpl.Funcs(funcMap)
tmpl.Delims(g.leftDelim, g.rightDelim)
_, err = tmpl.Parse(t.contents)
if err != nil {
diff --git a/tmpl/tmpl.go b/tmpl/tmpl.go
index e0c46c20..7021b72b 100644
--- a/tmpl/tmpl.go
+++ b/tmpl/tmpl.go
@@ -3,6 +3,7 @@ package tmpl
import (
"bytes"
+ "path/filepath"
"text/template"
"github.com/pkg/errors"
@@ -12,11 +13,28 @@ import (
type Template struct {
root *template.Template
defaultCtx interface{}
+ path string
}
// New -
-func New(root *template.Template, ctx interface{}) *Template {
- return &Template{root, ctx}
+func New(root *template.Template, tctx interface{}, path string) *Template {
+ return &Template{root, tctx, path}
+}
+
+// Path - returns the path to the current template if it came from a file.
+// An empty string is returned for inline templates.
+func (t *Template) Path() (string, error) {
+ return t.path, nil
+}
+
+// PathDir - returns the directory of the template, if it came from a file. An empty
+// string is returned for inline templates. If the template was loaded from the
+// current working directory, "." is returned.
+func (t *Template) PathDir() (string, error) {
+ if t.path == "" {
+ return "", nil
+ }
+ return filepath.Dir(t.path), nil
}
// Inline - a template function to do inline template processing
diff --git a/tmpl/tmpl_test.go b/tmpl/tmpl_test.go
index 729c99fb..7109a660 100644
--- a/tmpl/tmpl_test.go
+++ b/tmpl/tmpl_test.go
@@ -29,7 +29,7 @@ func TestInline(t *testing.T) {
func TestParseArgs(t *testing.T) {
defaultCtx := map[string]string{"hello": "world"}
- tmpl := New(nil, defaultCtx)
+ tmpl := New(nil, defaultCtx, "")
name, in, ctx, err := tmpl.parseArgs("foo")
assert.NoError(t, err)
assert.Equal(t, "<inline>", name)
@@ -88,3 +88,34 @@ func TestExec(t *testing.T) {
_, err = tmpl.Exec("bogus")
assert.Error(t, err)
}
+
+func TestPath(t *testing.T) {
+ tmpl := New(nil, nil, "")
+
+ p, err := tmpl.Path()
+ assert.NoError(t, err)
+ assert.Equal(t, "", p)
+
+ tmpl = New(nil, nil, "foo")
+ p, err = tmpl.Path()
+ assert.NoError(t, err)
+ assert.Equal(t, "foo", p)
+}
+
+func TestPathDir(t *testing.T) {
+ tmpl := New(nil, nil, "")
+
+ p, err := tmpl.PathDir()
+ assert.NoError(t, err)
+ assert.Equal(t, "", p)
+
+ tmpl = New(nil, nil, "foo")
+ p, err = tmpl.PathDir()
+ assert.NoError(t, err)
+ assert.Equal(t, ".", p)
+
+ tmpl = New(nil, nil, "foo/bar")
+ p, err = tmpl.PathDir()
+ assert.NoError(t, err)
+ assert.Equal(t, "foo", p)
+}