mirror of
https://github.com/docker/cli.git
synced 2026-08-24 02:24:17 -05:00
Add a custom join function that allows for non-string slices to be
joined, following the same rules as "fmt.Sprint", it will use the
fmt.Stringer interface if implemented, or "error" if the type has
an "Error()".
For maps, it joins the map-values, for example:
docker image inspect --format '{{join .Config.Labels ", "}}' ubuntu
24.04, ubuntu
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
232 lines
5.0 KiB
Go
232 lines
5.0 KiB
Go
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"text/template"
|
|
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
// GitHub #32120
|
|
func TestParseJSONFunctions(t *testing.T) {
|
|
tm, err := Parse(`{{json .Ports}}`)
|
|
assert.NilError(t, err)
|
|
|
|
var b bytes.Buffer
|
|
assert.NilError(t, tm.Execute(&b, map[string]string{"Ports": "0.0.0.0:2->8/udp"}))
|
|
want := "\"0.0.0.0:2->8/udp\""
|
|
assert.Check(t, is.Equal(want, b.String()))
|
|
}
|
|
|
|
func TestParseStringFunctions(t *testing.T) {
|
|
tm, err := Parse(`{{join (split . ":") "/"}}`)
|
|
assert.NilError(t, err)
|
|
|
|
var b bytes.Buffer
|
|
assert.NilError(t, tm.Execute(&b, "text:with:colon"))
|
|
want := "text/with/colon"
|
|
assert.Check(t, is.Equal(want, b.String()))
|
|
}
|
|
|
|
func TestNewParse(t *testing.T) {
|
|
tm, err := New("foo").Parse("this is a {{ . }}")
|
|
assert.NilError(t, err)
|
|
|
|
var b bytes.Buffer
|
|
assert.NilError(t, tm.Execute(&b, "string"))
|
|
want := "this is a string"
|
|
assert.Check(t, is.Equal(want, b.String()))
|
|
}
|
|
|
|
func TestParseTruncateFunction(t *testing.T) {
|
|
source := "tupx5xzf6hvsrhnruz5cr8gwp"
|
|
|
|
testCases := []struct {
|
|
template string
|
|
expected string
|
|
}{
|
|
{
|
|
template: `{{truncate . 5}}`,
|
|
expected: "tupx5",
|
|
},
|
|
{
|
|
template: `{{truncate . 25}}`,
|
|
expected: "tupx5xzf6hvsrhnruz5cr8gwp",
|
|
},
|
|
{
|
|
template: `{{truncate . 30}}`,
|
|
expected: "tupx5xzf6hvsrhnruz5cr8gwp",
|
|
},
|
|
{
|
|
template: `{{pad . 3 3}}`,
|
|
expected: " tupx5xzf6hvsrhnruz5cr8gwp ",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tm, err := Parse(tc.template)
|
|
assert.NilError(t, err)
|
|
|
|
t.Run("Non Empty Source Test with template: "+tc.template, func(t *testing.T) {
|
|
var b bytes.Buffer
|
|
assert.NilError(t, tm.Execute(&b, source))
|
|
assert.Check(t, is.Equal(tc.expected, b.String()))
|
|
})
|
|
|
|
t.Run("Empty Source Test with template: "+tc.template, func(t *testing.T) {
|
|
var c bytes.Buffer
|
|
assert.NilError(t, tm.Execute(&c, ""))
|
|
assert.Check(t, is.Equal("", c.String()))
|
|
})
|
|
|
|
t.Run("Nil Source Test with template: "+tc.template, func(t *testing.T) {
|
|
var c bytes.Buffer
|
|
assert.Check(t, tm.Execute(&c, nil) != nil)
|
|
assert.Check(t, is.Equal("", c.String()))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHeaderFunctions(t *testing.T) {
|
|
const source = "hello world"
|
|
|
|
tests := []struct {
|
|
doc string
|
|
template string
|
|
}{
|
|
{
|
|
doc: "json",
|
|
template: `{{ json .}}`,
|
|
},
|
|
{
|
|
doc: "split",
|
|
template: `{{ split . ","}}`,
|
|
},
|
|
{
|
|
doc: "join",
|
|
template: `{{ join . ","}}`,
|
|
},
|
|
{
|
|
doc: "title",
|
|
template: `{{ title .}}`,
|
|
},
|
|
{
|
|
doc: "lower",
|
|
template: `{{ lower .}}`,
|
|
},
|
|
{
|
|
doc: "upper",
|
|
template: `{{ upper .}}`,
|
|
},
|
|
{
|
|
doc: "truncate",
|
|
template: `{{ truncate . 2}}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
tmpl, err := New("").Funcs(HeaderFunctions).Parse(tc.template)
|
|
assert.NilError(t, err)
|
|
|
|
var b bytes.Buffer
|
|
assert.NilError(t, tmpl.Execute(&b, source))
|
|
|
|
// All header-functions are currently stubs, and don't modify the input.
|
|
expected := source
|
|
assert.Equal(t, expected, b.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
type stringerString string
|
|
|
|
func (s stringerString) String() string {
|
|
return "stringer" + string(s)
|
|
}
|
|
|
|
type stringerAndError string
|
|
|
|
func (s stringerAndError) String() string {
|
|
return "stringer" + string(s)
|
|
}
|
|
|
|
func (s stringerAndError) Error() string {
|
|
return "error" + string(s)
|
|
}
|
|
|
|
func TestJoinElements(t *testing.T) {
|
|
tests := []struct {
|
|
doc string
|
|
data any
|
|
expOut string
|
|
expErr string
|
|
}{
|
|
{
|
|
doc: "nil",
|
|
data: nil,
|
|
expOut: `output: ""`,
|
|
},
|
|
{
|
|
doc: "non-slice",
|
|
data: "hello",
|
|
expOut: `output: "`,
|
|
expErr: `error calling join: expected slice, got string`,
|
|
},
|
|
{
|
|
doc: "structs",
|
|
data: []struct{ A, B string }{{"1", "2"}, {"3", "4"}},
|
|
expOut: `output: "{1 2}, {3 4}"`,
|
|
},
|
|
{
|
|
doc: "map with strings",
|
|
data: map[string]string{"A": "1", "B": "2", "C": "3"},
|
|
expOut: `output: "1, 2, 3"`,
|
|
},
|
|
{
|
|
doc: "map with stringers",
|
|
data: map[string]stringerString{"A": "1", "B": "2", "C": "3"},
|
|
expOut: `output: "stringer1, stringer2, stringer3"`,
|
|
},
|
|
{
|
|
doc: "map with errors",
|
|
data: []stringerAndError{"1", "2", "3"},
|
|
expOut: `output: "error1, error2, error3"`,
|
|
},
|
|
{
|
|
doc: "stringers",
|
|
data: []stringerString{"1", "2", "3"},
|
|
expOut: `output: "stringer1, stringer2, stringer3"`,
|
|
},
|
|
{
|
|
doc: "stringer with errors",
|
|
data: []stringerAndError{"1", "2", "3"},
|
|
expOut: `output: "error1, error2, error3"`,
|
|
},
|
|
{
|
|
doc: "slice of bools",
|
|
data: []bool{true, false, true},
|
|
expOut: `output: "true, false, true"`,
|
|
},
|
|
}
|
|
|
|
const formatStr = `output: "{{- join . ", " -}}"`
|
|
tmpl, err := New("my-template").Funcs(template.FuncMap{"join": joinElements}).Parse(formatStr)
|
|
assert.NilError(t, err)
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
var b bytes.Buffer
|
|
err := tmpl.Execute(&b, tc.data)
|
|
if tc.expErr != "" {
|
|
assert.ErrorContains(t, err, tc.expErr)
|
|
} else {
|
|
assert.NilError(t, err)
|
|
}
|
|
assert.Equal(t, b.String(), tc.expOut)
|
|
})
|
|
}
|
|
}
|