From f66770a9e458a78bfce1536380cb7434a532a44e Mon Sep 17 00:00:00 2001 From: Skip Baney Date: Sat, 10 Jun 2023 15:51:05 -0500 Subject: [PATCH] feat: additional template render methods --- render/render.go | 2 +- render/template.go | 56 +++++++++++++++++++++++++++-- render/template_test.go | 78 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/render/render.go b/render/render.go index c6a8e53..9598160 100644 --- a/render/render.go +++ b/render/render.go @@ -108,7 +108,7 @@ func String(s string, data any) (string, error) { func execute(t *template.Template, data any) (string, error) { if t == nil { - return "", nil + return strEmpty, nil } buf := bytes.Buffer{} err := t.Execute(&buf, data) diff --git a/render/template.go b/render/template.go index a01c925..c9544b2 100644 --- a/render/template.go +++ b/render/template.go @@ -1,7 +1,15 @@ package render import ( + "fmt" "text/template" + + "github.com/spf13/cast" +) + +const ( + strEmpty string = "" + strNoValue string = "" ) // Compile parses a template string and returns, if successful, @@ -45,7 +53,51 @@ func (ts *Template) UnmarshalText(text []byte) error { return err } -// Render renders the template using data. +// Render renders the template as a string. func (ts *Template) Render(data any) (string, error) { - return execute(ts.t, data) + rendered, err := execute(ts.t, data) + if err != nil { + return strEmpty, err + } + if rendered == strEmpty || rendered == strNoValue { + return strEmpty, nil + } + return rendered, nil +} + +// Render renders the template as a bool. +func (ts *Template) RenderBool(data any) (bool, error) { + rendered, err := ts.Render(data) + if err != nil { + return false, err + } + if rendered == strEmpty { + return false, nil + } + return cast.ToBoolE(rendered) +} + +// Render renders the template as an int. +func (ts *Template) RenderInt(data any) (int, error) { + rendered, err := ts.Render(data) + if err != nil { + return 0, err + } + if rendered == strEmpty { + return 0, nil + } + return cast.ToIntE(rendered) +} + +// RenderRequired renders the template as a string, +// but returns an error if the result is empty. +func (ts *Template) RenderRequired(data any) (string, error) { + rendered, err := ts.Render(data) + if err != nil { + return strEmpty, err + } + if rendered == strEmpty { + return strEmpty, fmt.Errorf("evaluated to an empty string") + } + return rendered, nil } diff --git a/render/template_test.go b/render/template_test.go index 632e2e2..09e466c 100644 --- a/render/template_test.go +++ b/render/template_test.go @@ -31,12 +31,90 @@ func TestTemplate_Render(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Hello, World", rendered) + ts, _ = Compile(`{{ .Something }}`) + rendered, err = ts.Render(nil) + assert.NoError(t, err) + assert.Equal(t, "", rendered) + ts, _ = Compile(`Hello, {{ fail "boom" }}`) rendered, err = ts.Render(nil) assert.ErrorContains(t, err, "fail: boom") assert.Equal(t, "", rendered) } +func TestTemplate_RenderBool(t *testing.T) { + ts, _ := Compile(`{{ .Value }}`) + rendered, err := ts.RenderBool(map[string]any{ + "Value": "true", + }) + assert.NoError(t, err) + assert.Equal(t, true, rendered) + + ts, _ = Compile(`{{ .Value }}`) + rendered, err = ts.RenderBool(nil) + assert.NoError(t, err) + assert.Equal(t, false, rendered) + + ts, _ = Compile(`{{ .Value }}`) + rendered, err = ts.RenderBool(map[string]any{ + "Value": "not-a-bool", + }) + assert.ErrorContains(t, err, "invalid syntax") + assert.Equal(t, false, rendered) + + ts, _ = Compile(`{{ fail "boom" }}`) + rendered, err = ts.RenderBool(nil) + assert.ErrorContains(t, err, "fail: boom") + assert.Equal(t, false, rendered) +} + +func TestTemplate_RenderInt(t *testing.T) { + ts, _ := Compile(`{{ .Value }}`) + rendered, err := ts.RenderInt(map[string]any{ + "Value": "12", + }) + assert.NoError(t, err) + assert.Equal(t, 12, rendered) + + ts, _ = Compile(`{{ .Value }}`) + rendered, err = ts.RenderInt(nil) + assert.NoError(t, err) + assert.Equal(t, 0, rendered) + + ts, _ = Compile(`{{ .Value }}`) + rendered, err = ts.RenderInt(map[string]any{ + "Value": "not-a-number", + }) + assert.ErrorContains(t, err, "unable to cast") + assert.Equal(t, 0, rendered) + + ts, _ = Compile(`{{ fail "boom" }}`) + rendered, err = ts.RenderInt(nil) + assert.ErrorContains(t, err, "fail: boom") + assert.Equal(t, 0, rendered) +} + +func TestTemplate_RenderRequired(t *testing.T) { + ts, _ := Compile(`{{ .Value }}`) + rendered, err := ts.RenderRequired(map[string]any{ + "Value": "Howdy", + }) + assert.NoError(t, err) + assert.Equal(t, "Howdy", rendered) + + ts, _ = Compile(`{{ fail "boom" }}`) + rendered, err = ts.RenderRequired(nil) + assert.ErrorContains(t, err, "fail: boom") + assert.Equal(t, "", rendered) + + ts, _ = Compile(`{{ .Value }}`) + rendered, err = ts.RenderRequired(map[string]any{ + "Value": "", + }) + assert.ErrorContains(t, err, "empty string") + assert.Equal(t, "", rendered) +} + func TestTemplate_MarshalText(t *testing.T) { ts, err := Compile(`Hello, {{ .Name }}`) assert.NoError(t, err)