Skip to content

Commit f66770a

Browse files
committed
feat: additional template render methods
1 parent e7723cd commit f66770a

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

render/render.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func String(s string, data any) (string, error) {
108108

109109
func execute(t *template.Template, data any) (string, error) {
110110
if t == nil {
111-
return "", nil
111+
return strEmpty, nil
112112
}
113113
buf := bytes.Buffer{}
114114
err := t.Execute(&buf, data)

render/template.go

+54-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package render
22

33
import (
4+
"fmt"
45
"text/template"
6+
7+
"github.com/spf13/cast"
8+
)
9+
10+
const (
11+
strEmpty string = ""
12+
strNoValue string = "<no value>"
513
)
614

715
// Compile parses a template string and returns, if successful,
@@ -45,7 +53,51 @@ func (ts *Template) UnmarshalText(text []byte) error {
4553
return err
4654
}
4755

48-
// Render renders the template using data.
56+
// Render renders the template as a string.
4957
func (ts *Template) Render(data any) (string, error) {
50-
return execute(ts.t, data)
58+
rendered, err := execute(ts.t, data)
59+
if err != nil {
60+
return strEmpty, err
61+
}
62+
if rendered == strEmpty || rendered == strNoValue {
63+
return strEmpty, nil
64+
}
65+
return rendered, nil
66+
}
67+
68+
// Render renders the template as a bool.
69+
func (ts *Template) RenderBool(data any) (bool, error) {
70+
rendered, err := ts.Render(data)
71+
if err != nil {
72+
return false, err
73+
}
74+
if rendered == strEmpty {
75+
return false, nil
76+
}
77+
return cast.ToBoolE(rendered)
78+
}
79+
80+
// Render renders the template as an int.
81+
func (ts *Template) RenderInt(data any) (int, error) {
82+
rendered, err := ts.Render(data)
83+
if err != nil {
84+
return 0, err
85+
}
86+
if rendered == strEmpty {
87+
return 0, nil
88+
}
89+
return cast.ToIntE(rendered)
90+
}
91+
92+
// RenderRequired renders the template as a string,
93+
// but returns an error if the result is empty.
94+
func (ts *Template) RenderRequired(data any) (string, error) {
95+
rendered, err := ts.Render(data)
96+
if err != nil {
97+
return strEmpty, err
98+
}
99+
if rendered == strEmpty {
100+
return strEmpty, fmt.Errorf("evaluated to an empty string")
101+
}
102+
return rendered, nil
51103
}

render/template_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,90 @@ func TestTemplate_Render(t *testing.T) {
3131
assert.NoError(t, err)
3232
assert.Equal(t, "Hello, World", rendered)
3333

34+
ts, _ = Compile(`{{ .Something }}`)
35+
rendered, err = ts.Render(nil)
36+
assert.NoError(t, err)
37+
assert.Equal(t, "", rendered)
38+
3439
ts, _ = Compile(`Hello, {{ fail "boom" }}`)
3540
rendered, err = ts.Render(nil)
3641
assert.ErrorContains(t, err, "fail: boom")
3742
assert.Equal(t, "", rendered)
3843
}
3944

45+
func TestTemplate_RenderBool(t *testing.T) {
46+
ts, _ := Compile(`{{ .Value }}`)
47+
rendered, err := ts.RenderBool(map[string]any{
48+
"Value": "true",
49+
})
50+
assert.NoError(t, err)
51+
assert.Equal(t, true, rendered)
52+
53+
ts, _ = Compile(`{{ .Value }}`)
54+
rendered, err = ts.RenderBool(nil)
55+
assert.NoError(t, err)
56+
assert.Equal(t, false, rendered)
57+
58+
ts, _ = Compile(`{{ .Value }}`)
59+
rendered, err = ts.RenderBool(map[string]any{
60+
"Value": "not-a-bool",
61+
})
62+
assert.ErrorContains(t, err, "invalid syntax")
63+
assert.Equal(t, false, rendered)
64+
65+
ts, _ = Compile(`{{ fail "boom" }}`)
66+
rendered, err = ts.RenderBool(nil)
67+
assert.ErrorContains(t, err, "fail: boom")
68+
assert.Equal(t, false, rendered)
69+
}
70+
71+
func TestTemplate_RenderInt(t *testing.T) {
72+
ts, _ := Compile(`{{ .Value }}`)
73+
rendered, err := ts.RenderInt(map[string]any{
74+
"Value": "12",
75+
})
76+
assert.NoError(t, err)
77+
assert.Equal(t, 12, rendered)
78+
79+
ts, _ = Compile(`{{ .Value }}`)
80+
rendered, err = ts.RenderInt(nil)
81+
assert.NoError(t, err)
82+
assert.Equal(t, 0, rendered)
83+
84+
ts, _ = Compile(`{{ .Value }}`)
85+
rendered, err = ts.RenderInt(map[string]any{
86+
"Value": "not-a-number",
87+
})
88+
assert.ErrorContains(t, err, "unable to cast")
89+
assert.Equal(t, 0, rendered)
90+
91+
ts, _ = Compile(`{{ fail "boom" }}`)
92+
rendered, err = ts.RenderInt(nil)
93+
assert.ErrorContains(t, err, "fail: boom")
94+
assert.Equal(t, 0, rendered)
95+
}
96+
97+
func TestTemplate_RenderRequired(t *testing.T) {
98+
ts, _ := Compile(`{{ .Value }}`)
99+
rendered, err := ts.RenderRequired(map[string]any{
100+
"Value": "Howdy",
101+
})
102+
assert.NoError(t, err)
103+
assert.Equal(t, "Howdy", rendered)
104+
105+
ts, _ = Compile(`{{ fail "boom" }}`)
106+
rendered, err = ts.RenderRequired(nil)
107+
assert.ErrorContains(t, err, "fail: boom")
108+
assert.Equal(t, "", rendered)
109+
110+
ts, _ = Compile(`{{ .Value }}`)
111+
rendered, err = ts.RenderRequired(map[string]any{
112+
"Value": "",
113+
})
114+
assert.ErrorContains(t, err, "empty string")
115+
assert.Equal(t, "", rendered)
116+
}
117+
40118
func TestTemplate_MarshalText(t *testing.T) {
41119
ts, err := Compile(`Hello, {{ .Name }}`)
42120
assert.NoError(t, err)

0 commit comments

Comments
 (0)