This change centralizes the template manipulation in a single package
and adds basic string functions to their execution.
Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"text/template" |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/docker/reference" |
| 12 |
+ "github.com/docker/docker/utils/templates" |
|
| 12 | 13 |
"github.com/docker/engine-api/types" |
| 13 | 14 |
) |
| 14 | 15 |
|
| ... | ... |
@@ -54,7 +55,7 @@ func (c *Context) preformat() {
|
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 | 56 |
func (c *Context) parseFormat() (*template.Template, error) {
|
| 57 |
- tmpl, err := template.New("").Parse(c.finalFormat)
|
|
| 57 |
+ tmpl, err := templates.Parse(c.finalFormat) |
|
| 58 | 58 |
if err != nil {
|
| 59 | 59 |
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
| 60 | 60 |
c.buffer.WriteTo(c.Output) |
| ... | ... |
@@ -1,23 +1,15 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "encoding/json" |
|
| 5 | 4 |
"fmt" |
| 6 |
- "text/template" |
|
| 7 | 5 |
|
| 8 | 6 |
"github.com/docker/docker/api/client/inspect" |
| 9 | 7 |
Cli "github.com/docker/docker/cli" |
| 10 | 8 |
flag "github.com/docker/docker/pkg/mflag" |
| 9 |
+ "github.com/docker/docker/utils/templates" |
|
| 11 | 10 |
"github.com/docker/engine-api/client" |
| 12 | 11 |
) |
| 13 | 12 |
|
| 14 |
-var funcMap = template.FuncMap{
|
|
| 15 |
- "json": func(v interface{}) string {
|
|
| 16 |
- a, _ := json.Marshal(v) |
|
| 17 |
- return string(a) |
|
| 18 |
- }, |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 | 13 |
// CmdInspect displays low-level information on one or more containers or images. |
| 22 | 14 |
// |
| 23 | 15 |
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] |
| ... | ... |
@@ -123,7 +115,7 @@ func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
|
| 123 | 123 |
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
|
| 124 | 124 |
elementInspector := inspect.NewIndentedInspector(cli.out) |
| 125 | 125 |
if tmplStr != "" {
|
| 126 |
- tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
|
|
| 126 |
+ tmpl, err := templates.Parse(tmplStr) |
|
| 127 | 127 |
if err != nil {
|
| 128 | 128 |
return nil, fmt.Errorf("Template parsing error: %s", err)
|
| 129 | 129 |
} |
| ... | ... |
@@ -4,7 +4,8 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"strings" |
| 6 | 6 |
"testing" |
| 7 |
- "text/template" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/utils/templates" |
|
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 | 11 |
type testElement struct {
|
| ... | ... |
@@ -13,7 +14,7 @@ type testElement struct {
|
| 13 | 13 |
|
| 14 | 14 |
func TestTemplateInspectorDefault(t *testing.T) {
|
| 15 | 15 |
b := new(bytes.Buffer) |
| 16 |
- tmpl, err := template.New("test").Parse("{{.DNS}}")
|
|
| 16 |
+ tmpl, err := templates.Parse("{{.DNS}}")
|
|
| 17 | 17 |
if err != nil {
|
| 18 | 18 |
t.Fatal(err) |
| 19 | 19 |
} |
| ... | ... |
@@ -32,7 +33,7 @@ func TestTemplateInspectorDefault(t *testing.T) {
|
| 32 | 32 |
|
| 33 | 33 |
func TestTemplateInspectorEmpty(t *testing.T) {
|
| 34 | 34 |
b := new(bytes.Buffer) |
| 35 |
- tmpl, err := template.New("test").Parse("{{.DNS}}")
|
|
| 35 |
+ tmpl, err := templates.Parse("{{.DNS}}")
|
|
| 36 | 36 |
if err != nil {
|
| 37 | 37 |
t.Fatal(err) |
| 38 | 38 |
} |
| ... | ... |
@@ -48,7 +49,7 @@ func TestTemplateInspectorEmpty(t *testing.T) {
|
| 48 | 48 |
|
| 49 | 49 |
func TestTemplateInspectorTemplateError(t *testing.T) {
|
| 50 | 50 |
b := new(bytes.Buffer) |
| 51 |
- tmpl, err := template.New("test").Parse("{{.Foo}}")
|
|
| 51 |
+ tmpl, err := templates.Parse("{{.Foo}}")
|
|
| 52 | 52 |
if err != nil {
|
| 53 | 53 |
t.Fatal(err) |
| 54 | 54 |
} |
| ... | ... |
@@ -66,7 +67,7 @@ func TestTemplateInspectorTemplateError(t *testing.T) {
|
| 66 | 66 |
|
| 67 | 67 |
func TestTemplateInspectorRawFallback(t *testing.T) {
|
| 68 | 68 |
b := new(bytes.Buffer) |
| 69 |
- tmpl, err := template.New("test").Parse("{{.Dns}}")
|
|
| 69 |
+ tmpl, err := templates.Parse("{{.Dns}}")
|
|
| 70 | 70 |
if err != nil {
|
| 71 | 71 |
t.Fatal(err) |
| 72 | 72 |
} |
| ... | ... |
@@ -85,7 +86,7 @@ func TestTemplateInspectorRawFallback(t *testing.T) {
|
| 85 | 85 |
|
| 86 | 86 |
func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
| 87 | 87 |
b := new(bytes.Buffer) |
| 88 |
- tmpl, err := template.New("test").Parse("{{.Dns}}")
|
|
| 88 |
+ tmpl, err := templates.Parse("{{.Dns}}")
|
|
| 89 | 89 |
if err != nil {
|
| 90 | 90 |
t.Fatal(err) |
| 91 | 91 |
} |
| ... | ... |
@@ -102,7 +103,7 @@ func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
| 102 | 102 |
|
| 103 | 103 |
func TestTemplateInspectorMultiple(t *testing.T) {
|
| 104 | 104 |
b := new(bytes.Buffer) |
| 105 |
- tmpl, err := template.New("test").Parse("{{.DNS}}")
|
|
| 105 |
+ tmpl, err := templates.Parse("{{.DNS}}")
|
|
| 106 | 106 |
if err != nil {
|
| 107 | 107 |
t.Fatal(err) |
| 108 | 108 |
} |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/dockerversion" |
| 10 | 10 |
flag "github.com/docker/docker/pkg/mflag" |
| 11 | 11 |
"github.com/docker/docker/utils" |
| 12 |
+ "github.com/docker/docker/utils/templates" |
|
| 12 | 13 |
"github.com/docker/engine-api/types" |
| 13 | 14 |
) |
| 14 | 15 |
|
| ... | ... |
@@ -48,7 +49,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
var tmpl *template.Template |
| 51 |
- if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
|
|
| 51 |
+ if tmpl, err = templates.Parse(templateFormat); err != nil {
|
|
| 52 | 52 |
return Cli.StatusError{StatusCode: 64,
|
| 53 | 53 |
Status: "Template parsing error: " + err.Error()} |
| 54 | 54 |
} |
| ... | ... |
@@ -3,10 +3,10 @@ package loggerutils |
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"fmt" |
| 6 |
- "text/template" |
|
| 7 | 6 |
|
| 8 | 7 |
"github.com/Sirupsen/logrus" |
| 9 | 8 |
"github.com/docker/docker/daemon/logger" |
| 9 |
+ "github.com/docker/docker/utils/templates" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
// ParseLogTag generates a context aware tag for consistency across different |
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
|
| 15 | 15 |
tagTemplate := lookupTagTemplate(ctx, defaultTemplate) |
| 16 | 16 |
|
| 17 |
- tmpl, err := template.New("log-tag").Parse(tagTemplate)
|
|
| 17 |
+ tmpl, err := templates.NewParse("log-tag", tagTemplate)
|
|
| 18 | 18 |
if err != nil {
|
| 19 | 19 |
return "", err |
| 20 | 20 |
} |
| 21 | 21 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,66 @@ |
| 0 |
+<!--[metadata]> |
|
| 1 |
+title = "Format command and log output" |
|
| 2 |
+description = "CLI and log output formatting reference" |
|
| 3 |
+keywords = ["format, formatting, output, templates, log"] |
|
| 4 |
+[menu.main] |
|
| 5 |
+parent = "engine_admin" |
|
| 6 |
+weight=-90 |
|
| 7 |
+<![end-metadata]--> |
|
| 8 |
+ |
|
| 9 |
+# Formatting reference |
|
| 10 |
+ |
|
| 11 |
+Docker uses [Go templates](https://golang.org/pkg/text/template/) to allow users manipulate the output format |
|
| 12 |
+of certain commands and log drivers. Each command a driver provides a detailed |
|
| 13 |
+list of elements they support in their templates: |
|
| 14 |
+ |
|
| 15 |
+- [Docker Images formatting](https://docs.docker.com/engine/reference/commandline/images/#formatting) |
|
| 16 |
+- [Docker Inspect formatting](https://docs.docker.com/engine/reference/commandline/inspect/#examples) |
|
| 17 |
+- [Docker Log Tag formatting](https://docs.docker.com/engine/admin/logging/log_tags/) |
|
| 18 |
+- [Docker Network Inspect formatting](https://docs.docker.com/engine/reference/commandline/network_inspect/) |
|
| 19 |
+- [Docker PS formatting](https://docs.docker.com/engine/reference/commandline/ps/#formatting) |
|
| 20 |
+- [Docker Volume Inspect formatting](https://docs.docker.com/engine/reference/commandline/volume_inspect/) |
|
| 21 |
+- [Docker Version formatting](https://docs.docker.com/engine/reference/commandline/version/#examples) |
|
| 22 |
+ |
|
| 23 |
+## Template functions |
|
| 24 |
+ |
|
| 25 |
+Docker provides a set of basic functions to manipulate template elements. |
|
| 26 |
+This is the complete list of the available functions with examples: |
|
| 27 |
+ |
|
| 28 |
+### Join |
|
| 29 |
+ |
|
| 30 |
+Join concatenates a list of strings to create a single string. |
|
| 31 |
+It puts a separator between each element in the list. |
|
| 32 |
+ |
|
| 33 |
+ $ docker ps --format '{{join .Names " or "}}'
|
|
| 34 |
+ |
|
| 35 |
+### Json |
|
| 36 |
+ |
|
| 37 |
+Json encodes an element as a json string. |
|
| 38 |
+ |
|
| 39 |
+ $ docker inspect --format '{{json .Mounts}}' container
|
|
| 40 |
+ |
|
| 41 |
+### Lower |
|
| 42 |
+ |
|
| 43 |
+Lower turns a string into its lower case representation. |
|
| 44 |
+ |
|
| 45 |
+ $ docker inspect --format "{{lower .Name}}" container
|
|
| 46 |
+ |
|
| 47 |
+### Split |
|
| 48 |
+ |
|
| 49 |
+Split slices a string into a list of strings separated by a separator. |
|
| 50 |
+ |
|
| 51 |
+ # docker inspect --format '{{split (join .Names "/") "/"}}' container
|
|
| 52 |
+ |
|
| 53 |
+### Title |
|
| 54 |
+ |
|
| 55 |
+Title capitalizes a string. |
|
| 56 |
+ |
|
| 57 |
+ $ docker inspect --format "{{title .Name}}" container
|
|
| 58 |
+ |
|
| 59 |
+### Upper |
|
| 60 |
+ |
|
| 61 |
+Upper turms a string into its upper case representation. |
|
| 62 |
+ |
|
| 63 |
+ $ docker inspect --format "{{upper .Name}}" container
|
| ... | ... |
@@ -8,9 +8,9 @@ import ( |
| 8 | 8 |
"os" |
| 9 | 9 |
"path" |
| 10 | 10 |
"strings" |
| 11 |
- "text/template" |
|
| 12 | 11 |
|
| 13 | 12 |
"github.com/docker/docker/pkg/aaparser" |
| 13 |
+ "github.com/docker/docker/utils/templates" |
|
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 | 16 |
var ( |
| ... | ... |
@@ -36,7 +36,7 @@ type profileData struct {
|
| 36 | 36 |
|
| 37 | 37 |
// generateDefault creates an apparmor profile from ProfileData. |
| 38 | 38 |
func (p *profileData) generateDefault(out io.Writer) error {
|
| 39 |
- compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
|
|
| 39 |
+ compiled, err := templates.NewParse("apparmor_profile", baseTemplate)
|
|
| 40 | 40 |
if err != nil {
|
| 41 | 41 |
return err |
| 42 | 42 |
} |
| 43 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 0 |
+package templates |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "strings" |
|
| 5 |
+ "text/template" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// basicFunctions are the set of initial |
|
| 9 |
+// functions provided to every template. |
|
| 10 |
+var basicFunctions = template.FuncMap{
|
|
| 11 |
+ "json": func(v interface{}) string {
|
|
| 12 |
+ a, _ := json.Marshal(v) |
|
| 13 |
+ return string(a) |
|
| 14 |
+ }, |
|
| 15 |
+ "split": strings.Split, |
|
| 16 |
+ "join": strings.Join, |
|
| 17 |
+ "title": strings.Title, |
|
| 18 |
+ "lower": strings.ToLower, |
|
| 19 |
+ "upper": strings.ToUpper, |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// Parse creates a new annonymous template with the basic functions |
|
| 23 |
+// and parses the given format. |
|
| 24 |
+func Parse(format string) (*template.Template, error) {
|
|
| 25 |
+ return NewParse("", format)
|
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// NewParse creates a new tagged template with the basic functions |
|
| 29 |
+// and parses the given format. |
|
| 30 |
+func NewParse(tag, format string) (*template.Template, error) {
|
|
| 31 |
+ return template.New(tag).Funcs(basicFunctions).Parse(format) |
|
| 32 |
+} |
| 0 | 33 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+package templates |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestParseStringFunctions(t *testing.T) {
|
|
| 8 |
+ tm, err := Parse(`{{join (split . ":") "/"}}`)
|
|
| 9 |
+ if err != nil {
|
|
| 10 |
+ t.Fatal(err) |
|
| 11 |
+ } |
|
| 12 |
+ |
|
| 13 |
+ var b bytes.Buffer |
|
| 14 |
+ if err := tm.Execute(&b, "text:with:colon"); err != nil {
|
|
| 15 |
+ t.Fatal(err) |
|
| 16 |
+ } |
|
| 17 |
+ want := "text/with/colon" |
|
| 18 |
+ if b.String() != want {
|
|
| 19 |
+ t.Fatalf("expected %s, got %s", want, b.String())
|
|
| 20 |
+ } |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func TestNewParse(t *testing.T) {
|
|
| 24 |
+ tm, err := NewParse("foo", "this is a {{ . }}")
|
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ t.Fatal(err) |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ var b bytes.Buffer |
|
| 30 |
+ if err := tm.Execute(&b, "string"); err != nil {
|
|
| 31 |
+ t.Fatal(err) |
|
| 32 |
+ } |
|
| 33 |
+ want := "this is a string" |
|
| 34 |
+ if b.String() != want {
|
|
| 35 |
+ t.Fatalf("expected %s, got %s", want, b.String())
|
|
| 36 |
+ } |
|
| 37 |
+} |