full diff: https://github.com/docker/cli/compare/v29.1.5...v29.2.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
|
//go:build go1.24
|
|
|
|
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// basicFunctions are the set of initial
|
|
// functions provided to every template.
|
|
var basicFunctions = template.FuncMap{
|
|
"json": formatJSON,
|
|
"split": strings.Split,
|
|
"join": joinElements,
|
|
"title": strings.Title, //nolint:nolintlint,staticcheck // strings.Title is deprecated, but we only use it for ASCII, so replacing with golang.org/x/text is out of scope
|
|
"lower": strings.ToLower,
|
|
"upper": strings.ToUpper,
|
|
"pad": padWithSpace,
|
|
"truncate": truncateWithLength,
|
|
}
|
|
|
|
// HeaderFunctions are used to created headers of a table.
|
|
// This is a replacement of basicFunctions for header generation
|
|
// because we want the header to remain intact.
|
|
// Some functions like `pad` are not overridden (to preserve alignment
|
|
// with the columns).
|
|
var HeaderFunctions = template.FuncMap{
|
|
"json": func(v string) string {
|
|
return v
|
|
},
|
|
"split": func(v string, _ string) string {
|
|
// we want the table header to show the name of the column, and not
|
|
// split the table header itself. Using a different signature
|
|
// here, and return a string instead of []string
|
|
return v
|
|
},
|
|
"join": func(v string, _ string) string {
|
|
// table headers are always a string, so use a different signature
|
|
// for the "join" function (string instead of []string)
|
|
return v
|
|
},
|
|
"title": func(v string) string {
|
|
return v
|
|
},
|
|
"lower": func(v string) string {
|
|
return v
|
|
},
|
|
"upper": func(v string) string {
|
|
return v
|
|
},
|
|
"truncate": func(v string, _ int) string {
|
|
return v
|
|
},
|
|
}
|
|
|
|
// Parse creates a new anonymous template with the basic functions
|
|
// and parses the given format.
|
|
func Parse(format string) (*template.Template, error) {
|
|
return template.New("").Funcs(basicFunctions).Parse(format)
|
|
}
|
|
|
|
// New creates a new empty template with the provided tag and built-in
|
|
// template functions.
|
|
func New(tag string) *template.Template {
|
|
return template.New(tag).Funcs(basicFunctions)
|
|
}
|
|
|
|
// padWithSpace adds whitespace to the input if the input 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]
|
|
}
|
|
|
|
func formatJSON(v any) string {
|
|
buf := &bytes.Buffer{}
|
|
enc := json.NewEncoder(buf)
|
|
enc.SetEscapeHTML(false)
|
|
err := enc.Encode(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Remove the trailing new line added by the encoder
|
|
return strings.TrimSpace(buf.String())
|
|
}
|
|
|
|
// joinElements joins a slice of items with the given separator. It uses
|
|
// [strings.Join] if it's a slice of strings, otherwise uses [fmt.Sprint]
|
|
// to join each item to the output.
|
|
func joinElements(elems any, sep string) (string, error) {
|
|
if elems == nil {
|
|
return "", nil
|
|
}
|
|
|
|
if ss, ok := elems.([]string); ok {
|
|
return strings.Join(ss, sep), nil
|
|
}
|
|
|
|
switch rv := reflect.ValueOf(elems); rv.Kind() { //nolint:exhaustive // ignore: too many options to make exhaustive
|
|
case reflect.Array, reflect.Slice:
|
|
var b strings.Builder
|
|
for i := range rv.Len() {
|
|
if i > 0 {
|
|
b.WriteString(sep)
|
|
}
|
|
_, _ = fmt.Fprint(&b, rv.Index(i).Interface())
|
|
}
|
|
return b.String(), nil
|
|
|
|
case reflect.Map:
|
|
var out []string
|
|
for _, k := range rv.MapKeys() {
|
|
out = append(out, fmt.Sprint(rv.MapIndex(k).Interface()))
|
|
}
|
|
// Not ideal, but trying to keep a consistent order
|
|
sort.Strings(out)
|
|
return strings.Join(out, sep), nil
|
|
|
|
default:
|
|
return "", fmt.Errorf("expected slice, got %T", elems)
|
|
}
|
|
}
|