mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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>
This commit is contained in:
parent
44b56341d9
commit
8514880997
9 changed files with 155 additions and 23 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ func (c *Context) preformat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) parseFormat() (*template.Template, error) {
|
func (c *Context) parseFormat() (*template.Template, error) {
|
||||||
tmpl, err := template.New("").Parse(c.finalFormat)
|
tmpl, err := templates.Parse(c.finalFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
c.buffer.WriteTo(c.Output)
|
c.buffer.WriteTo(c.Output)
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/client/inspect"
|
"github.com/docker/docker/api/client/inspect"
|
||||||
Cli "github.com/docker/docker/cli"
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
"github.com/docker/engine-api/client"
|
"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.
|
// CmdInspect displays low-level information on one or more containers or images.
|
||||||
//
|
//
|
||||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
// 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) {
|
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
|
||||||
elementInspector := inspect.NewIndentedInspector(cli.out)
|
elementInspector := inspect.NewIndentedInspector(cli.out)
|
||||||
if tmplStr != "" {
|
if tmplStr != "" {
|
||||||
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
|
tmpl, err := templates.Parse(tmplStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Template parsing error: %s", err)
|
return nil, fmt.Errorf("Template parsing error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testElement struct {
|
type testElement struct {
|
||||||
|
@ -13,7 +14,7 @@ type testElement struct {
|
||||||
|
|
||||||
func TestTemplateInspectorDefault(t *testing.T) {
|
func TestTemplateInspectorDefault(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.DNS}}")
|
tmpl, err := templates.Parse("{{.DNS}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,7 @@ func TestTemplateInspectorDefault(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateInspectorEmpty(t *testing.T) {
|
func TestTemplateInspectorEmpty(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.DNS}}")
|
tmpl, err := templates.Parse("{{.DNS}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +49,7 @@ func TestTemplateInspectorEmpty(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateInspectorTemplateError(t *testing.T) {
|
func TestTemplateInspectorTemplateError(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.Foo}}")
|
tmpl, err := templates.Parse("{{.Foo}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +67,7 @@ func TestTemplateInspectorTemplateError(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateInspectorRawFallback(t *testing.T) {
|
func TestTemplateInspectorRawFallback(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.Dns}}")
|
tmpl, err := templates.Parse("{{.Dns}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -85,7 +86,7 @@ func TestTemplateInspectorRawFallback(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.Dns}}")
|
tmpl, err := templates.Parse("{{.Dns}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,7 @@ func TestTemplateInspectorRawFallbackError(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateInspectorMultiple(t *testing.T) {
|
func TestTemplateInspectorMultiple(t *testing.T) {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
tmpl, err := template.New("test").Parse("{{.DNS}}")
|
tmpl, err := templates.Parse("{{.DNS}}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmpl *template.Template
|
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,
|
return Cli.StatusError{StatusCode: 64,
|
||||||
Status: "Template parsing error: " + err.Error()}
|
Status: "Template parsing error: " + err.Error()}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ package loggerutils
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/logger"
|
"github.com/docker/docker/daemon/logger"
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseLogTag generates a context aware tag for consistency across different
|
// ParseLogTag generates a context aware tag for consistency across different
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
|
func ParseLogTag(ctx logger.Context, defaultTemplate string) (string, error) {
|
||||||
tagTemplate := lookupTagTemplate(ctx, defaultTemplate)
|
tagTemplate := lookupTagTemplate(ctx, defaultTemplate)
|
||||||
|
|
||||||
tmpl, err := template.New("log-tag").Parse(tagTemplate)
|
tmpl, err := templates.NewParse("log-tag", tagTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
66
docs/admin/formatting.md
Normal file
66
docs/admin/formatting.md
Normal file
|
@ -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
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/aaparser"
|
"github.com/docker/docker/pkg/aaparser"
|
||||||
|
"github.com/docker/docker/utils/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -36,7 +36,7 @@ type profileData struct {
|
||||||
|
|
||||||
// generateDefault creates an apparmor profile from ProfileData.
|
// generateDefault creates an apparmor profile from ProfileData.
|
||||||
func (p *profileData) generateDefault(out io.Writer) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
33
utils/templates/templates.go
Normal file
33
utils/templates/templates.go
Normal file
|
@ -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
utils/templates/templates_test.go
Normal file
38
utils/templates/templates_test.go
Normal file
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue