ソースを参照

Merge pull request #20958 from calavera/basic_function_templates

Provide basic string manipulation functions for template executions.
Alexander Morozov 9 年 前
コミット
943ae26bc0

+ 2 - 1
api/client/formatter/formatter.go

@@ -9,6 +9,7 @@ import (
 	"text/template"
 
 	"github.com/docker/docker/reference"
+	"github.com/docker/docker/utils/templates"
 	"github.com/docker/engine-api/types"
 )
 
@@ -54,7 +55,7 @@ func (c *Context) preformat() {
 }
 
 func (c *Context) parseFormat() (*template.Template, error) {
-	tmpl, err := template.New("").Parse(c.finalFormat)
+	tmpl, err := templates.Parse(c.finalFormat)
 	if err != nil {
 		c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
 		c.buffer.WriteTo(c.Output)

+ 2 - 10
api/client/inspect.go

@@ -1,23 +1,15 @@
 package client
 
 import (
-	"encoding/json"
 	"fmt"
-	"text/template"
 
 	"github.com/docker/docker/api/client/inspect"
 	Cli "github.com/docker/docker/cli"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/utils/templates"
 	"github.com/docker/engine-api/client"
 )
 
-var funcMap = template.FuncMap{
-	"json": func(v interface{}) string {
-		a, _ := json.Marshal(v)
-		return string(a)
-	},
-}
-
 // CmdInspect displays low-level information on one or more containers or images.
 //
 // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
@@ -123,7 +115,7 @@ func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
 func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
 	elementInspector := inspect.NewIndentedInspector(cli.out)
 	if tmplStr != "" {
-		tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
+		tmpl, err := templates.Parse(tmplStr)
 		if err != nil {
 			return nil, fmt.Errorf("Template parsing error: %s", err)
 		}

+ 8 - 7
api/client/inspect/inspector_test.go

@@ -4,7 +4,8 @@ import (
 	"bytes"
 	"strings"
 	"testing"
-	"text/template"
+
+	"github.com/docker/docker/utils/templates"
 )
 
 type testElement struct {
@@ -13,7 +14,7 @@ type testElement struct {
 
 func TestTemplateInspectorDefault(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.DNS}}")
+	tmpl, err := templates.Parse("{{.DNS}}")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -32,7 +33,7 @@ func TestTemplateInspectorDefault(t *testing.T) {
 
 func TestTemplateInspectorEmpty(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.DNS}}")
+	tmpl, err := templates.Parse("{{.DNS}}")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -48,7 +49,7 @@ func TestTemplateInspectorEmpty(t *testing.T) {
 
 func TestTemplateInspectorTemplateError(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.Foo}}")
+	tmpl, err := templates.Parse("{{.Foo}}")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -66,7 +67,7 @@ func TestTemplateInspectorTemplateError(t *testing.T) {
 
 func TestTemplateInspectorRawFallback(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.Dns}}")
+	tmpl, err := templates.Parse("{{.Dns}}")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -85,7 +86,7 @@ func TestTemplateInspectorRawFallback(t *testing.T) {
 
 func TestTemplateInspectorRawFallbackError(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.Dns}}")
+	tmpl, err := templates.Parse("{{.Dns}}")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -102,7 +103,7 @@ func TestTemplateInspectorRawFallbackError(t *testing.T) {
 
 func TestTemplateInspectorMultiple(t *testing.T) {
 	b := new(bytes.Buffer)
-	tmpl, err := template.New("test").Parse("{{.DNS}}")
+	tmpl, err := templates.Parse("{{.DNS}}")
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 1
api/client/version.go

@@ -9,6 +9,7 @@ import (
 	"github.com/docker/docker/dockerversion"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/utils"
+	"github.com/docker/docker/utils/templates"
 	"github.com/docker/engine-api/types"
 )
 
@@ -48,7 +49,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
 	}
 
 	var tmpl *template.Template
-	if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
+	if tmpl, err = templates.Parse(templateFormat); err != nil {
 		return Cli.StatusError{StatusCode: 64,
 			Status: "Template parsing error: " + err.Error()}
 	}

+ 2 - 2
daemon/logger/loggerutils/log_tag.go

@@ -3,10 +3,10 @@ package loggerutils
 import (
 	"bytes"
 	"fmt"
-	"text/template"
 
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/logger"
+	"github.com/docker/docker/utils/templates"
 )
 
 // ParseLogTag generates a context aware tag for consistency across different
@@ -14,7 +14,7 @@ import (
 func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
 	tagTemplate := lookupTagTemplate(ctx, defaultTemplate)
 
-	tmpl, err := template.New("log-tag").Parse(tagTemplate)
+	tmpl, err := templates.NewParse("log-tag", tagTemplate)
 	if err != nil {
 		return "", err
 	}

+ 66 - 0
docs/admin/formatting.md

@@ -0,0 +1,66 @@
+<!--[metadata]>
++++
+title = "Format command and log output"
+description = "CLI and log output formatting reference"
+keywords = ["format, formatting, output, templates, log"]
+[menu.main]
+parent = "engine_admin"
+weight=-90
++++
+<![end-metadata]-->
+
+# Formatting reference
+
+Docker uses [Go templates](https://golang.org/pkg/text/template/) to allow users manipulate the output format
+of certain commands and log drivers. Each command a driver provides a detailed
+list of elements they support in their templates:
+
+- [Docker Images formatting](https://docs.docker.com/engine/reference/commandline/images/#formatting)
+- [Docker Inspect formatting](https://docs.docker.com/engine/reference/commandline/inspect/#examples)
+- [Docker Log Tag formatting](https://docs.docker.com/engine/admin/logging/log_tags/)
+- [Docker Network Inspect formatting](https://docs.docker.com/engine/reference/commandline/network_inspect/)
+- [Docker PS formatting](https://docs.docker.com/engine/reference/commandline/ps/#formatting)
+- [Docker Volume Inspect formatting](https://docs.docker.com/engine/reference/commandline/volume_inspect/)
+- [Docker Version formatting](https://docs.docker.com/engine/reference/commandline/version/#examples)
+
+## Template functions
+
+Docker provides a set of basic functions to manipulate template elements.
+This is the complete list of the available functions with examples:
+
+### Join
+
+Join concatenates a list of strings to create a single string.
+It puts a separator between each element in the list.
+
+	$ docker ps --format '{{join .Names " or "}}'
+
+### Json
+
+Json encodes an element as a json string.
+
+	$ docker inspect --format '{{json .Mounts}}' container
+
+### Lower
+
+Lower turns a string into its lower case representation.
+
+	$ docker inspect --format "{{lower .Name}}" container
+
+### Split
+
+Split slices a string into a list of strings separated by a separator.
+
+	# docker inspect --format '{{split (join .Names "/") "/"}}' container
+
+### Title
+
+Title capitalizes a string.
+
+	$ docker inspect --format "{{title .Name}}" container
+
+### Upper
+
+Upper turms a string into its upper case representation.
+
+	$ docker inspect --format "{{upper .Name}}" container

+ 2 - 2
profiles/apparmor/apparmor.go

@@ -8,9 +8,9 @@ import (
 	"os"
 	"path"
 	"strings"
-	"text/template"
 
 	"github.com/docker/docker/pkg/aaparser"
+	"github.com/docker/docker/utils/templates"
 )
 
 var (
@@ -36,7 +36,7 @@ type profileData struct {
 
 // generateDefault creates an apparmor profile from ProfileData.
 func (p *profileData) generateDefault(out io.Writer) error {
-	compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
+	compiled, err := templates.NewParse("apparmor_profile", baseTemplate)
 	if err != nil {
 		return err
 	}

+ 33 - 0
utils/templates/templates.go

@@ -0,0 +1,33 @@
+package templates
+
+import (
+	"encoding/json"
+	"strings"
+	"text/template"
+)
+
+// basicFunctions are the set of initial
+// functions provided to every template.
+var basicFunctions = template.FuncMap{
+	"json": func(v interface{}) string {
+		a, _ := json.Marshal(v)
+		return string(a)
+	},
+	"split": strings.Split,
+	"join":  strings.Join,
+	"title": strings.Title,
+	"lower": strings.ToLower,
+	"upper": strings.ToUpper,
+}
+
+// Parse creates a new annonymous template with the basic functions
+// and parses the given format.
+func Parse(format string) (*template.Template, error) {
+	return NewParse("", format)
+}
+
+// NewParse creates a new tagged template with the basic functions
+// and parses the given format.
+func NewParse(tag, format string) (*template.Template, error) {
+	return template.New(tag).Funcs(basicFunctions).Parse(format)
+}

+ 38 - 0
utils/templates/templates_test.go

@@ -0,0 +1,38 @@
+package templates
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestParseStringFunctions(t *testing.T) {
+	tm, err := Parse(`{{join (split . ":") "/"}}`)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var b bytes.Buffer
+	if err := tm.Execute(&b, "text:with:colon"); err != nil {
+		t.Fatal(err)
+	}
+	want := "text/with/colon"
+	if b.String() != want {
+		t.Fatalf("expected %s, got %s", want, b.String())
+	}
+}
+
+func TestNewParse(t *testing.T) {
+	tm, err := NewParse("foo", "this is a {{ . }}")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var b bytes.Buffer
+	if err := tm.Execute(&b, "string"); err != nil {
+		t.Fatal(err)
+	}
+	want := "this is a string"
+	if b.String() != want {
+		t.Fatalf("expected %s, got %s", want, b.String())
+	}
+}