diff --git a/common/pkg/report/template.go b/common/pkg/report/template.go index 914a26a740..559c1625b8 100644 --- a/common/pkg/report/template.go +++ b/common/pkg/report/template.go @@ -1,6 +1,8 @@ package report import ( + "bytes" + "encoding/json" "reflect" "strings" "text/template" @@ -21,22 +23,30 @@ type FuncMap template.FuncMap var tableReplacer = strings.NewReplacer( "table ", "", `\t`, "\t", - `\n`, "\n", " ", "\t", ) // escapedReplacer will clean up escaped characters from CLI var escapedReplacer = strings.NewReplacer( `\t`, "\t", - `\n`, "\n", ) -var defaultFuncs = FuncMap{ - "join": strings.Join, - "lower": strings.ToLower, - "split": strings.Split, - "title": strings.Title, - "upper": strings.ToUpper, +var DefaultFuncs = FuncMap{ + "join": strings.Join, + "json": func(v interface{}) string { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + enc.Encode(v) + // Remove the trailing new line added by the encoder + return strings.TrimSpace(buf.String()) + }, + "lower": strings.ToLower, + "pad": padWithSpace, + "split": strings.Split, + "title": strings.Title, + "truncate": truncateWithLength, + "upper": strings.ToUpper, } // NormalizeFormat reads given go template format provided by CLI and munges it into what we need @@ -55,6 +65,22 @@ func NormalizeFormat(format string) string { return f } +// padWithSpace adds spaces*prefix and spaces*suffix to the input when it is non-empty +func padWithSpace(source string, prefix, suffix int) string { + if source == "" { + return source + } + return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix) +} + +// truncateWithLength truncates the source string up to the length provided by the input +func truncateWithLength(source string, length int) string { + if len(source) < length { + return source + } + return source[:length] +} + // Headers queries the interface for field names. // Array of map is returned to support range templates // Note: unexported fields can be supported by adding field to overrides @@ -96,7 +122,7 @@ func Headers(object interface{}, overrides map[string]string) []map[string]strin // NewTemplate creates a new template object func NewTemplate(name string) *Template { - return &Template{Template: template.New(name).Funcs(template.FuncMap(defaultFuncs))} + return &Template{Template: template.New(name).Funcs(template.FuncMap(DefaultFuncs))} } // Parse parses text as a template body for t @@ -108,7 +134,7 @@ func (t *Template) Parse(text string) (*Template, error) { text = NormalizeFormat(text) } - tt, err := t.Template.Funcs(template.FuncMap(defaultFuncs)).Parse(text) + tt, err := t.Template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text) return &Template{tt, t.isTable}, err } @@ -116,7 +142,7 @@ func (t *Template) Parse(text string) (*Template, error) { // A default template function will be replace if there is a key collision. func (t *Template) Funcs(funcMap FuncMap) *Template { m := make(FuncMap) - for k, v := range defaultFuncs { + for k, v := range DefaultFuncs { m[k] = v } for k, v := range funcMap { diff --git a/common/pkg/report/template_test.go b/common/pkg/report/template_test.go index f07d7a17d1..e5ff5b4bae 100644 --- a/common/pkg/report/template_test.go +++ b/common/pkg/report/template_test.go @@ -48,9 +48,9 @@ func TestNormalizeFormat(t *testing.T) { input string expected string }{ - {"{{.ID}}\t{{.ID}}\n", "{{.ID}}\t{{.ID}}\n"}, - {`{{.ID}}\t{{.ID}}\n`, "{{.ID}}\t{{.ID}}\n"}, - {`{{.ID}} {{.ID}}\n`, "{{.ID}} {{.ID}}\n"}, + {"{{.ID}}\t{{.ID}}", "{{.ID}}\t{{.ID}}\n"}, + {`{{.ID}}\t{{.ID}}`, "{{.ID}}\t{{.ID}}\n"}, + {`{{.ID}} {{.ID}}`, "{{.ID}} {{.ID}}\n"}, {`table {{.ID}}\t{{.ID}}`, "{{.ID}}\t{{.ID}}\n"}, {`table {{.ID}} {{.ID}}`, "{{.ID}}\t{{.ID}}\n"}, } @@ -68,7 +68,6 @@ func TestTemplate_Parse(t *testing.T) { testCase := []string{ "table {{.ID}}", "table {{ .ID}}", - `table {{ .ID}}\n`, "table {{ .ID}}\n", "{{range .}}{{.ID}}{{end}}", `{{range .}}{{.ID}}{{end}}`, @@ -98,7 +97,7 @@ func TestTemplate_IsTable(t *testing.T) { assert.True(t, tmpl.isTable) } -func TestTemplate_Funcs(t *testing.T) { +func TestTemplate_trim(t *testing.T) { tmpl := NewTemplate("TestTemplate") tmpl, e := tmpl.Funcs(FuncMap{"trim": strings.TrimSpace}).Parse("{{.ID |trim}}") assert.NoError(t, e) @@ -114,7 +113,7 @@ func TestTemplate_Funcs(t *testing.T) { func TestTemplate_DefaultFuncs(t *testing.T) { tmpl := NewTemplate("TestTemplate") // Throw in trim function to ensure default 'join' is still available - tmpl, e := tmpl.Funcs(FuncMap{"trim": strings.TrimSpace}).Parse(`{{join .ID ","}}`) + tmpl, e := tmpl.Funcs(FuncMap{"trim": strings.TrimSpace}).Parse(`{{join .ID "\n"}}`) assert.NoError(t, e) var buf bytes.Buffer @@ -122,7 +121,7 @@ func TestTemplate_DefaultFuncs(t *testing.T) { "ID": {"ident1", "ident2", "ident3"}, }) assert.NoError(t, err) - assert.Equal(t, "ident1,ident2,ident3\n", buf.String()) + assert.Equal(t, "ident1\nident2\nident3\n", buf.String()) } func TestTemplate_ReplaceFuncs(t *testing.T) { @@ -138,3 +137,17 @@ func TestTemplate_ReplaceFuncs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "ident\n", buf.String()) } + +func TestTemplate_json(t *testing.T) { + tmpl := NewTemplate("TestTemplate") + // yes, we're overriding upper with lower :-) + tmpl, e := tmpl.Parse(`{{json .ID}}`) + assert.NoError(t, e) + + var buf bytes.Buffer + err := tmpl.Execute(&buf, map[string][]string{ + "ID": {"ident1", "ident2", "ident3"}, + }) + assert.NoError(t, err) + assert.Equal(t, `["ident1","ident2","ident3"]`+"\n", buf.String()) +}