Browse code

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 <david.calavera@gmail.com>

David Calavera authored on 2016/03/05 02:29:44
Showing 9 changed files
... ...
@@ -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
+}