Skip to content

Commit

Permalink
Merge pull request #1364 from hairyhenderson/template-path-dir-funcs-…
Browse files Browse the repository at this point in the history
…1363

New tmpl.Path/tmpl.PathDir functions
  • Loading branch information
hairyhenderson authored Apr 10, 2022
2 parents 6c66d16 + ae16208 commit 81ad06d
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 11 deletions.
39 changes: 39 additions & 0 deletions docs-src/content/functions/tmpl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
55 changes: 55 additions & 0 deletions docs/content/functions/tmpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
33 changes: 29 additions & 4 deletions internal/tests/integration/tmpl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}`,
Expand All @@ -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 }}`,
Expand All @@ -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")
Expand Down Expand Up @@ -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")
}
25 changes: 21 additions & 4 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
22 changes: 20 additions & 2 deletions tmpl/tmpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tmpl

import (
"bytes"
"path/filepath"
"text/template"

"github.com/pkg/errors"
Expand All @@ -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
Expand Down
33 changes: 32 additions & 1 deletion tmpl/tmpl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}

0 comments on commit 81ad06d

Please sign in to comment.