From 8b17f187f4eb8078798d78a7a71abc36dbd5d805 Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Fri, 11 Oct 2024 11:40:13 +0200 Subject: [PATCH] Add additional template functions Adds templat functions which: - to exec an inline template - indent a template - remove n continuous empty lines from a template An example would be a template like: ``` {{define "my-template"}}new template content with empty lines to remove {{end}} Some other template content and add the rendered from my-template {{$var := execTempl "my-template" . | removeNewLines 1}} {{$var}} ``` Signed-off-by: Martin Schuppert --- .pre-commit-config.yaml | 6 +- modules/common/util/template_util.go | 71 ++++++++++++++-- modules/common/util/template_util_test.go | 99 +++++++++++++++++++++++ 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e3c10c0..4d8b44d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,4 +40,8 @@ repos: exclude: ^vendor - id: no-commit-to-branch - id: trailing-whitespace - exclude: ^vendor + exclude: | + (?x)( + ^vendor| + ^modules/common/util/template_util_test.go + ) diff --git a/modules/common/util/template_util.go b/modules/common/util/template_util.go index a641bb89..abcb6118 100644 --- a/modules/common/util/template_util.go +++ b/modules/common/util/template_util.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "bufio" "bytes" "fmt" "os" @@ -138,6 +139,57 @@ func ExecuteTemplate(templateFile string, data interface{}) (string, error) { return renderedTemplate, nil } +// template functions +var tmpl *template.Template + +// template function which allows to execute a template from within +// a template file. +// name - name of the template as defined with with `{{define "some-template"}}your template{{end}} +// data - data to pass into to render the template for all can use `.` +func execTempl(name string, data interface{}) (string, error) { + buf := &bytes.Buffer{} + err := tmpl.ExecuteTemplate(buf, name, data) + return buf.String(), err +} + +// template function to indent the template with n tabs +func indent(n int, in string) (out string) { + s := bufio.NewScanner(bytes.NewReader([]byte(in))) + for s.Scan() { + line := strings.TrimSpace(s.Text()) + for i := 0; i < n; i++ { + line = "\t" + line + } + out += line + "\n" + } + return out +} + +// template function to remove empty lines if there are > n continuous empty lines +func removeNewLines(n int, in string) (out string) { + s := bufio.NewScanner(bytes.NewReader([]byte(in))) + + // Variable to keep track of consecutive empty lines + emptyLineCount := 0 + for s.Scan() { + line := s.Text() + + if strings.TrimSpace(line) == "" { + emptyLineCount++ + // If we have already seen more then n empty lines, skip this one + if emptyLineCount > n { + continue + } + } else { + // Reset the empty line counter when we encounter a non-empty line + emptyLineCount = 0 + } + + out += line + "\n" + } + return out +} + // template function to increment an int func add(x, y int) int { return x + y @@ -153,11 +205,15 @@ func lower(s string) string { func ExecuteTemplateData(templateData string, data interface{}) (string, error) { var buff bytes.Buffer + var err error funcs := template.FuncMap{ - "add": add, - "lower": lower, + "add": add, + "execTempl": execTempl, + "indent": indent, + "lower": lower, + "removeNewLines": removeNewLines, } - tmpl, err := template.New("tmp").Option("missingkey=error").Funcs(funcs).Parse(templateData) + tmpl, err = template.New("tmp").Option("missingkey=error").Funcs(funcs).Parse(templateData) if err != nil { return "", err } @@ -193,10 +249,13 @@ func ExecuteTemplateFile(filename string, data interface{}) (string, error) { file := string(b) var buff bytes.Buffer funcs := template.FuncMap{ - "add": add, - "lower": lower, + "add": add, + "execTempl": execTempl, + "indent": indent, + "lower": lower, + "removeNewLines": removeNewLines, } - tmpl, err := template.New("tmp").Option("missingkey=error").Funcs(funcs).Parse(file) + tmpl, err = template.New("tmp").Option("missingkey=error").Funcs(funcs).Parse(file) if err != nil { return "", err } diff --git a/modules/common/util/template_util_test.go b/modules/common/util/template_util_test.go index ef0a8989..45911b3f 100644 --- a/modules/common/util/template_util_test.go +++ b/modules/common/util/template_util_test.go @@ -1,11 +1,13 @@ package util import ( + "bytes" "os" "path" "path/filepath" "runtime" "testing" + "text/template" . "github.com/onsi/gomega" ) @@ -37,6 +39,103 @@ func TestLower(t *testing.T) { }) } +func TestIndent(t *testing.T) { + + t.Run("Indent string", func(t *testing.T) { + g := NewWithT(t) + const in = `foo +bar` + // 5 tabs and line break + const expct = ` foo + bar +` + + s := indent(5, in) + + g.Expect(s).To(BeIdenticalTo(expct)) + }) +} + +func TestRemoveNewLines(t *testing.T) { + + t.Run("Remove duplicate new lines", func(t *testing.T) { + g := NewWithT(t) + const in = ` foo + + bar + + +foo + + + + +bar` + + const expct = ` foo + + bar + +foo + +bar +` + + s := removeNewLines(1, in) + + g.Expect(s).To(BeIdenticalTo(expct)) + }) +} + +func TestExecTempl(t *testing.T) { + + t.Run("ExecTempl", func(t *testing.T) { + g := NewWithT(t) + const myTmpl = `{{define "my-template"}}my-template + + +content + + + +with empty lines + + + +to +remove +{{end}} +See result: +{{$var := execTempl "my-template" . | removeNewLines 1}} +{{$var}}` + + // render template using execTempl and remove more then 1 continuous empty lines + const expct = ` +See result: + +my-template + +content + +with empty lines + +to +remove +` + var buff bytes.Buffer + + tmpl = template.Must(template.New("").Funcs(template.FuncMap{ + "execTempl": execTempl, + "removeNewLines": removeNewLines, + }).Parse(myTmpl)) + if err := tmpl.Execute(&buff, nil); err != nil { + panic(err) + } + + g.Expect(buff.String()).To(BeIdenticalTo(expct)) + }) +} + func TestGetTemplatesPath(t *testing.T) { // set the env var used to specify the template path in the container case os.Setenv("OPERATOR_TEMPLATES", templatePath)