Skip to content

Commit 8514880

Browse files
committed
Provide basic string manupilation functions for template executions.
This change centralizes the template manipulation in a single package and adds basic string functions to their execution. Signed-off-by: David Calavera <[email protected]>
1 parent 44b5634 commit 8514880

File tree

9 files changed

+155
-23
lines changed

9 files changed

+155
-23
lines changed

api/client/formatter/formatter.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"text/template"
1010

1111
"github.com/docker/docker/reference"
12+
"github.com/docker/docker/utils/templates"
1213
"github.com/docker/engine-api/types"
1314
)
1415

@@ -54,7 +55,7 @@ func (c *Context) preformat() {
5455
}
5556

5657
func (c *Context) parseFormat() (*template.Template, error) {
57-
tmpl, err := template.New("").Parse(c.finalFormat)
58+
tmpl, err := templates.Parse(c.finalFormat)
5859
if err != nil {
5960
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
6061
c.buffer.WriteTo(c.Output)

api/client/inspect.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
package client
22

33
import (
4-
"encoding/json"
54
"fmt"
6-
"text/template"
75

86
"github.com/docker/docker/api/client/inspect"
97
Cli "github.com/docker/docker/cli"
108
flag "github.com/docker/docker/pkg/mflag"
9+
"github.com/docker/docker/utils/templates"
1110
"github.com/docker/engine-api/client"
1211
)
1312

14-
var funcMap = template.FuncMap{
15-
"json": func(v interface{}) string {
16-
a, _ := json.Marshal(v)
17-
return string(a)
18-
},
19-
}
20-
2113
// CmdInspect displays low-level information on one or more containers or images.
2214
//
2315
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
@@ -123,7 +115,7 @@ func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
123115
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
124116
elementInspector := inspect.NewIndentedInspector(cli.out)
125117
if tmplStr != "" {
126-
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
118+
tmpl, err := templates.Parse(tmplStr)
127119
if err != nil {
128120
return nil, fmt.Errorf("Template parsing error: %s", err)
129121
}

api/client/inspect/inspector_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import (
44
"bytes"
55
"strings"
66
"testing"
7-
"text/template"
7+
8+
"github.com/docker/docker/utils/templates"
89
)
910

1011
type testElement struct {
@@ -13,7 +14,7 @@ type testElement struct {
1314

1415
func TestTemplateInspectorDefault(t *testing.T) {
1516
b := new(bytes.Buffer)
16-
tmpl, err := template.New("test").Parse("{{.DNS}}")
17+
tmpl, err := templates.Parse("{{.DNS}}")
1718
if err != nil {
1819
t.Fatal(err)
1920
}
@@ -32,7 +33,7 @@ func TestTemplateInspectorDefault(t *testing.T) {
3233

3334
func TestTemplateInspectorEmpty(t *testing.T) {
3435
b := new(bytes.Buffer)
35-
tmpl, err := template.New("test").Parse("{{.DNS}}")
36+
tmpl, err := templates.Parse("{{.DNS}}")
3637
if err != nil {
3738
t.Fatal(err)
3839
}
@@ -48,7 +49,7 @@ func TestTemplateInspectorEmpty(t *testing.T) {
4849

4950
func TestTemplateInspectorTemplateError(t *testing.T) {
5051
b := new(bytes.Buffer)
51-
tmpl, err := template.New("test").Parse("{{.Foo}}")
52+
tmpl, err := templates.Parse("{{.Foo}}")
5253
if err != nil {
5354
t.Fatal(err)
5455
}
@@ -66,7 +67,7 @@ func TestTemplateInspectorTemplateError(t *testing.T) {
6667

6768
func TestTemplateInspectorRawFallback(t *testing.T) {
6869
b := new(bytes.Buffer)
69-
tmpl, err := template.New("test").Parse("{{.Dns}}")
70+
tmpl, err := templates.Parse("{{.Dns}}")
7071
if err != nil {
7172
t.Fatal(err)
7273
}
@@ -85,7 +86,7 @@ func TestTemplateInspectorRawFallback(t *testing.T) {
8586

8687
func TestTemplateInspectorRawFallbackError(t *testing.T) {
8788
b := new(bytes.Buffer)
88-
tmpl, err := template.New("test").Parse("{{.Dns}}")
89+
tmpl, err := templates.Parse("{{.Dns}}")
8990
if err != nil {
9091
t.Fatal(err)
9192
}
@@ -102,7 +103,7 @@ func TestTemplateInspectorRawFallbackError(t *testing.T) {
102103

103104
func TestTemplateInspectorMultiple(t *testing.T) {
104105
b := new(bytes.Buffer)
105-
tmpl, err := template.New("test").Parse("{{.DNS}}")
106+
tmpl, err := templates.Parse("{{.DNS}}")
106107
if err != nil {
107108
t.Fatal(err)
108109
}

api/client/version.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/docker/docker/dockerversion"
1010
flag "github.com/docker/docker/pkg/mflag"
1111
"github.com/docker/docker/utils"
12+
"github.com/docker/docker/utils/templates"
1213
"github.com/docker/engine-api/types"
1314
)
1415

@@ -48,7 +49,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
4849
}
4950

5051
var tmpl *template.Template
51-
if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
52+
if tmpl, err = templates.Parse(templateFormat); err != nil {
5253
return Cli.StatusError{StatusCode: 64,
5354
Status: "Template parsing error: " + err.Error()}
5455
}

daemon/logger/loggerutils/log_tag.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ package loggerutils
33
import (
44
"bytes"
55
"fmt"
6-
"text/template"
76

87
"github.com/Sirupsen/logrus"
98
"github.com/docker/docker/daemon/logger"
9+
"github.com/docker/docker/utils/templates"
1010
)
1111

1212
// ParseLogTag generates a context aware tag for consistency across different
1313
// log drivers based on the context of the running container.
1414
func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
1515
tagTemplate := lookupTagTemplate(ctx, defaultTemplate)
1616

17-
tmpl, err := template.New("log-tag").Parse(tagTemplate)
17+
tmpl, err := templates.NewParse("log-tag", tagTemplate)
1818
if err != nil {
1919
return "", err
2020
}

docs/admin/formatting.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!--[metadata]>
2+
+++
3+
title = "Format command and log output"
4+
description = "CLI and log output formatting reference"
5+
keywords = ["format, formatting, output, templates, log"]
6+
[menu.main]
7+
parent = "engine_admin"
8+
weight=-90
9+
+++
10+
<![end-metadata]-->
11+
12+
# Formatting reference
13+
14+
Docker uses [Go templates](https://golang.org/pkg/text/template/) to allow users manipulate the output format
15+
of certain commands and log drivers. Each command a driver provides a detailed
16+
list of elements they support in their templates:
17+
18+
- [Docker Images formatting](https://docs.docker.com/engine/reference/commandline/images/#formatting)
19+
- [Docker Inspect formatting](https://docs.docker.com/engine/reference/commandline/inspect/#examples)
20+
- [Docker Log Tag formatting](https://docs.docker.com/engine/admin/logging/log_tags/)
21+
- [Docker Network Inspect formatting](https://docs.docker.com/engine/reference/commandline/network_inspect/)
22+
- [Docker PS formatting](https://docs.docker.com/engine/reference/commandline/ps/#formatting)
23+
- [Docker Volume Inspect formatting](https://docs.docker.com/engine/reference/commandline/volume_inspect/)
24+
- [Docker Version formatting](https://docs.docker.com/engine/reference/commandline/version/#examples)
25+
26+
## Template functions
27+
28+
Docker provides a set of basic functions to manipulate template elements.
29+
This is the complete list of the available functions with examples:
30+
31+
### Join
32+
33+
Join concatenates a list of strings to create a single string.
34+
It puts a separator between each element in the list.
35+
36+
$ docker ps --format '{{join .Names " or "}}'
37+
38+
### Json
39+
40+
Json encodes an element as a json string.
41+
42+
$ docker inspect --format '{{json .Mounts}}' container
43+
44+
### Lower
45+
46+
Lower turns a string into its lower case representation.
47+
48+
$ docker inspect --format "{{lower .Name}}" container
49+
50+
### Split
51+
52+
Split slices a string into a list of strings separated by a separator.
53+
54+
# docker inspect --format '{{split (join .Names "/") "/"}}' container
55+
56+
### Title
57+
58+
Title capitalizes a string.
59+
60+
$ docker inspect --format "{{title .Name}}" container
61+
62+
### Upper
63+
64+
Upper turms a string into its upper case representation.
65+
66+
$ docker inspect --format "{{upper .Name}}" container

profiles/apparmor/apparmor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import (
88
"os"
99
"path"
1010
"strings"
11-
"text/template"
1211

1312
"github.com/docker/docker/pkg/aaparser"
13+
"github.com/docker/docker/utils/templates"
1414
)
1515

1616
var (
@@ -36,7 +36,7 @@ type profileData struct {
3636

3737
// generateDefault creates an apparmor profile from ProfileData.
3838
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)
4040
if err != nil {
4141
return err
4242
}

utils/templates/templates.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package templates
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"text/template"
7+
)
8+
9+
// basicFunctions are the set of initial
10+
// functions provided to every template.
11+
var basicFunctions = template.FuncMap{
12+
"json": func(v interface{}) string {
13+
a, _ := json.Marshal(v)
14+
return string(a)
15+
},
16+
"split": strings.Split,
17+
"join": strings.Join,
18+
"title": strings.Title,
19+
"lower": strings.ToLower,
20+
"upper": strings.ToUpper,
21+
}
22+
23+
// Parse creates a new annonymous template with the basic functions
24+
// and parses the given format.
25+
func Parse(format string) (*template.Template, error) {
26+
return NewParse("", format)
27+
}
28+
29+
// NewParse creates a new tagged template with the basic functions
30+
// and parses the given format.
31+
func NewParse(tag, format string) (*template.Template, error) {
32+
return template.New(tag).Funcs(basicFunctions).Parse(format)
33+
}

utils/templates/templates_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package templates
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestParseStringFunctions(t *testing.T) {
9+
tm, err := Parse(`{{join (split . ":") "/"}}`)
10+
if err != nil {
11+
t.Fatal(err)
12+
}
13+
14+
var b bytes.Buffer
15+
if err := tm.Execute(&b, "text:with:colon"); err != nil {
16+
t.Fatal(err)
17+
}
18+
want := "text/with/colon"
19+
if b.String() != want {
20+
t.Fatalf("expected %s, got %s", want, b.String())
21+
}
22+
}
23+
24+
func TestNewParse(t *testing.T) {
25+
tm, err := NewParse("foo", "this is a {{ . }}")
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
30+
var b bytes.Buffer
31+
if err := tm.Execute(&b, "string"); err != nil {
32+
t.Fatal(err)
33+
}
34+
want := "this is a string"
35+
if b.String() != want {
36+
t.Fatalf("expected %s, got %s", want, b.String())
37+
}
38+
}

0 commit comments

Comments
 (0)