Refactor formatter.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2016-09-12 16:59:18 -04:00
parent d9cb421d69
commit db0952ad22
14 changed files with 381 additions and 590 deletions

View File

@ -106,27 +106,19 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
return err return err
} }
f := opts.format format := opts.format
if len(f) == 0 { if len(format) == 0 {
if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet { if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet {
f = dockerCli.ConfigFile().PsFormat format = dockerCli.ConfigFile().PsFormat
} else { } else {
f = "table" format = "table"
} }
} }
psCtx := formatter.ContainerContext{ containerCtx := formatter.Context{
Context: formatter.Context{ Output: dockerCli.Out(),
Output: dockerCli.Out(), Format: formatter.NewContainerFormat(format, opts.quiet, opts.size),
Format: f, Trunc: !opts.noTrunc,
Quiet: opts.quiet,
Trunc: !opts.noTrunc,
},
Size: listOptions.Size,
Containers: containers,
} }
return formatter.ContainerWrite(containerCtx, containers)
psCtx.Write()
return nil
} }

View File

@ -1,7 +1,6 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -11,7 +10,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/stringutils"
"github.com/docker/go-units" units "github.com/docker/go-units"
) )
const ( const (
@ -26,67 +25,53 @@ const (
mountsHeader = "MOUNTS" mountsHeader = "MOUNTS"
) )
// ContainerContext contains container specific information required by the formater, encapsulate a Context struct. // NewContainerFormat returns a Format for rendering using a Context
type ContainerContext struct { func NewContainerFormat(source string, quiet bool, size bool) Format {
Context switch source {
// Size when set to true will display the size of the output. case TableFormatKey:
Size bool if quiet {
// Containers return defaultQuietFormat
Containers []types.Container }
format := defaultContainerTableFormat
if size {
format += `\t{{.Size}}`
}
return Format(format)
case RawFormatKey:
if quiet {
return `container_id: {{.ID}}`
}
format := `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
if size {
format += `size: {{.Size}}\n`
}
return Format(format)
}
return Format(source)
} }
func (ctx ContainerContext) Write() { // ContainerWrite renders the context for a list of containers
switch ctx.Format { func ContainerWrite(ctx Context, containers []types.Container) error {
case tableFormatKey: render := func(format func(subContext subContext) error) error {
if ctx.Quiet { for _, container := range containers {
ctx.Format = defaultQuietFormat err := format(&containerContext{trunc: ctx.Trunc, c: container})
} else { if err != nil {
ctx.Format = defaultContainerTableFormat return err
if ctx.Size {
ctx.Format += `\t{{.Size}}`
}
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `container_id: {{.ID}}`
} else {
ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
if ctx.Size {
ctx.Format += `size: {{.Size}}\n`
} }
} }
return nil
} }
return ctx.Write(&containerContext{}, render)
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, container := range ctx.Containers {
containerCtx := &containerContext{
trunc: ctx.Trunc,
c: container,
}
err = ctx.contextFormat(tmpl, containerCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &containerContext{})
} }
type containerContext struct { type containerContext struct {
baseSubContext HeaderContext
trunc bool trunc bool
c types.Container c types.Container
} }
func (c *containerContext) ID() string { func (c *containerContext) ID() string {
c.addHeader(containerIDHeader) c.AddHeader(containerIDHeader)
if c.trunc { if c.trunc {
return stringid.TruncateID(c.c.ID) return stringid.TruncateID(c.c.ID)
} }
@ -94,7 +79,7 @@ func (c *containerContext) ID() string {
} }
func (c *containerContext) Names() string { func (c *containerContext) Names() string {
c.addHeader(namesHeader) c.AddHeader(namesHeader)
names := stripNamePrefix(c.c.Names) names := stripNamePrefix(c.c.Names)
if c.trunc { if c.trunc {
for _, name := range names { for _, name := range names {
@ -108,7 +93,7 @@ func (c *containerContext) Names() string {
} }
func (c *containerContext) Image() string { func (c *containerContext) Image() string {
c.addHeader(imageHeader) c.AddHeader(imageHeader)
if c.c.Image == "" { if c.c.Image == "" {
return "<no image>" return "<no image>"
} }
@ -121,7 +106,7 @@ func (c *containerContext) Image() string {
} }
func (c *containerContext) Command() string { func (c *containerContext) Command() string {
c.addHeader(commandHeader) c.AddHeader(commandHeader)
command := c.c.Command command := c.c.Command
if c.trunc { if c.trunc {
command = stringutils.Ellipsis(command, 20) command = stringutils.Ellipsis(command, 20)
@ -130,28 +115,28 @@ func (c *containerContext) Command() string {
} }
func (c *containerContext) CreatedAt() string { func (c *containerContext) CreatedAt() string {
c.addHeader(createdAtHeader) c.AddHeader(createdAtHeader)
return time.Unix(int64(c.c.Created), 0).String() return time.Unix(int64(c.c.Created), 0).String()
} }
func (c *containerContext) RunningFor() string { func (c *containerContext) RunningFor() string {
c.addHeader(runningForHeader) c.AddHeader(runningForHeader)
createdAt := time.Unix(int64(c.c.Created), 0) createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) return units.HumanDuration(time.Now().UTC().Sub(createdAt))
} }
func (c *containerContext) Ports() string { func (c *containerContext) Ports() string {
c.addHeader(portsHeader) c.AddHeader(portsHeader)
return api.DisplayablePorts(c.c.Ports) return api.DisplayablePorts(c.c.Ports)
} }
func (c *containerContext) Status() string { func (c *containerContext) Status() string {
c.addHeader(statusHeader) c.AddHeader(statusHeader)
return c.c.Status return c.c.Status
} }
func (c *containerContext) Size() string { func (c *containerContext) Size() string {
c.addHeader(sizeHeader) c.AddHeader(sizeHeader)
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3) srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3) sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
@ -163,7 +148,7 @@ func (c *containerContext) Size() string {
} }
func (c *containerContext) Labels() string { func (c *containerContext) Labels() string {
c.addHeader(labelsHeader) c.AddHeader(labelsHeader)
if c.c.Labels == nil { if c.c.Labels == nil {
return "" return ""
} }
@ -180,7 +165,7 @@ func (c *containerContext) Label(name string) string {
r := strings.NewReplacer("-", " ", "_", " ") r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1]) h := r.Replace(n[len(n)-1])
c.addHeader(h) c.AddHeader(h)
if c.c.Labels == nil { if c.c.Labels == nil {
return "" return ""
@ -189,7 +174,7 @@ func (c *containerContext) Label(name string) string {
} }
func (c *containerContext) Mounts() string { func (c *containerContext) Mounts() string {
c.addHeader(mountsHeader) c.AddHeader(mountsHeader)
var name string var name string
var mounts []string var mounts []string

View File

@ -95,7 +95,7 @@ func TestContainerPsContext(t *testing.T) {
t.Fatalf("Expected %s, was %s\n", c.expValue, v) t.Fatalf("Expected %s, was %s\n", c.expValue, v)
} }
h := ctx.fullHeader() h := ctx.FullHeader()
if h != c.expHeader { if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h) t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
} }
@ -114,7 +114,7 @@ func TestContainerPsContext(t *testing.T) {
t.Fatalf("Expected ubuntu, was %s\n", node) t.Fatalf("Expected ubuntu, was %s\n", node)
} }
h := ctx.fullHeader() h := ctx.FullHeader()
if h != "SWARM ID\tNODE NAME" { if h != "SWARM ID\tNODE NAME" {
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h) t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
@ -129,9 +129,9 @@ func TestContainerPsContext(t *testing.T) {
} }
ctx = containerContext{c: c2, trunc: true} ctx = containerContext{c: c2, trunc: true}
fullHeader := ctx.fullHeader() FullHeader := ctx.FullHeader()
if fullHeader != "" { if FullHeader != "" {
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader) t.Fatalf("Expected FullHeader to be empty, was %s", FullHeader)
} }
} }
@ -140,186 +140,127 @@ func TestContainerContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix() unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String() expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct { cases := []struct {
context ContainerContext context Context
expected string expected string
}{ }{
// Errors // Errors
{ {
ContainerContext{ Context{Format: "{{InvalidFunction}}"},
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined `Template parsing error: template: :1: function "InvalidFunction" not defined
`, `,
}, },
{ {
ContainerContext{ Context{Format: "{{nil}}"},
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`, `,
}, },
// Table Format // Table Format
{ {
ContainerContext{ Context{Format: NewContainerFormat("table", false, true)},
Context: Context{
Format: "table",
},
Size: true,
},
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE `CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
containerID1 ubuntu "" 24 hours ago foobar_baz 0 B containerID1 ubuntu "" 24 hours ago foobar_baz 0 B
containerID2 ubuntu "" 24 hours ago foobar_bar 0 B containerID2 ubuntu "" 24 hours ago foobar_bar 0 B
`, `,
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("table", false, false)},
Context: Context{
Format: "table",
},
},
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES `CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
containerID1 ubuntu "" 24 hours ago foobar_baz containerID1 ubuntu "" 24 hours ago foobar_baz
containerID2 ubuntu "" 24 hours ago foobar_bar containerID2 ubuntu "" 24 hours ago foobar_bar
`, `,
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("table {{.Image}}", false, false)},
Context: Context{
Format: "table {{.Image}}",
},
},
"IMAGE\nubuntu\nubuntu\n", "IMAGE\nubuntu\nubuntu\n",
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("table {{.Image}}", false, true)},
Context: Context{
Format: "table {{.Image}}",
},
Size: true,
},
"IMAGE\nubuntu\nubuntu\n", "IMAGE\nubuntu\nubuntu\n",
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("table {{.Image}}", true, false)},
Context: Context{
Format: "table {{.Image}}",
Quiet: true,
},
},
"IMAGE\nubuntu\nubuntu\n", "IMAGE\nubuntu\nubuntu\n",
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("table", true, false)},
Context: Context{
Format: "table",
Quiet: true,
},
},
"containerID1\ncontainerID2\n", "containerID1\ncontainerID2\n",
}, },
// Raw Format // Raw Format
{ {
ContainerContext{ Context{Format: NewContainerFormat("raw", false, false)},
Context: Context{
Format: "raw",
},
},
fmt.Sprintf(`container_id: containerID1 fmt.Sprintf(`container_id: containerID1
image: ubuntu image: ubuntu
command: "" command: ""
created_at: %s created_at: %s
status: status:
names: foobar_baz names: foobar_baz
labels: labels:
ports: ports:
container_id: containerID2 container_id: containerID2
image: ubuntu image: ubuntu
command: "" command: ""
created_at: %s created_at: %s
status: status:
names: foobar_bar names: foobar_bar
labels: labels:
ports: ports:
`, expectedTime, expectedTime), `, expectedTime, expectedTime),
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("raw", false, true)},
Context: Context{
Format: "raw",
},
Size: true,
},
fmt.Sprintf(`container_id: containerID1 fmt.Sprintf(`container_id: containerID1
image: ubuntu image: ubuntu
command: "" command: ""
created_at: %s created_at: %s
status: status:
names: foobar_baz names: foobar_baz
labels: labels:
ports: ports:
size: 0 B size: 0 B
container_id: containerID2 container_id: containerID2
image: ubuntu image: ubuntu
command: "" command: ""
created_at: %s created_at: %s
status: status:
names: foobar_bar names: foobar_bar
labels: labels:
ports: ports:
size: 0 B size: 0 B
`, expectedTime, expectedTime), `, expectedTime, expectedTime),
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("raw", true, false)},
Context: Context{
Format: "raw",
Quiet: true,
},
},
"container_id: containerID1\ncontainer_id: containerID2\n", "container_id: containerID1\ncontainer_id: containerID2\n",
}, },
// Custom Format // Custom Format
{ {
ContainerContext{ Context{Format: "{{.Image}}"},
Context: Context{
Format: "{{.Image}}",
},
},
"ubuntu\nubuntu\n", "ubuntu\nubuntu\n",
}, },
{ {
ContainerContext{ Context{Format: NewContainerFormat("{{.Image}}", false, true)},
Context: Context{
Format: "{{.Image}}",
},
Size: true,
},
"ubuntu\nubuntu\n", "ubuntu\nubuntu\n",
}, },
} }
for _, context := range contexts { for _, testcase := range cases {
containers := []types.Container{ containers := []types.Container{
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime}, {ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime}, {ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
} }
out := bytes.NewBufferString("") out := bytes.NewBufferString("")
context.context.Output = out testcase.context.Output = out
context.context.Containers = containers err := ContainerWrite(testcase.context, containers)
context.context.Write() if err != nil {
actual := out.String() assert.Error(t, err, testcase.expected)
assert.Equal(t, actual, context.expected) } else {
// Clean buffer assert.Equal(t, out.String(), testcase.expected)
out.Reset() }
} }
} }
@ -328,75 +269,56 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
containers := []types.Container{} containers := []types.Container{}
contexts := []struct { contexts := []struct {
context ContainerContext context Context
expected string expected string
}{ }{
{ {
ContainerContext{ Context{
Context: Context{ Format: "{{.Image}}",
Format: "{{.Image}}", Output: out,
Output: out,
},
}, },
"", "",
}, },
{ {
ContainerContext{ Context{
Context: Context{ Format: "table {{.Image}}",
Format: "table {{.Image}}", Output: out,
Output: out,
},
}, },
"IMAGE\n", "IMAGE\n",
}, },
{ {
ContainerContext{ Context{
Context: Context{ Format: NewContainerFormat("{{.Image}}", false, true),
Format: "{{.Image}}", Output: out,
Output: out,
},
Size: true,
}, },
"", "",
}, },
{ {
ContainerContext{ Context{
Context: Context{ Format: NewContainerFormat("table {{.Image}}", false, true),
Format: "table {{.Image}}", Output: out,
Output: out,
},
Size: true,
}, },
"IMAGE\n", "IMAGE\n",
}, },
{ {
ContainerContext{ Context{
Context: Context{ Format: "table {{.Image}}\t{{.Size}}",
Format: "table {{.Image}}\t{{.Size}}", Output: out,
Output: out,
},
}, },
"IMAGE SIZE\n", "IMAGE SIZE\n",
}, },
{ {
ContainerContext{ Context{
Context: Context{ Format: NewContainerFormat("table {{.Image}}\t{{.Size}}", false, true),
Format: "table {{.Image}}\t{{.Size}}", Output: out,
Output: out,
},
Size: true,
}, },
"IMAGE SIZE\n", "IMAGE SIZE\n",
}, },
} }
for _, context := range contexts { for _, context := range contexts {
context.context.Containers = containers ContainerWrite(context.context, containers)
context.context.Write() assert.Equal(t, context.expected, out.String())
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer // Clean buffer
out.Reset() out.Reset()
} }

View File

@ -5,8 +5,6 @@ import (
) )
const ( const (
tableKey = "table"
imageHeader = "IMAGE" imageHeader = "IMAGE"
createdSinceHeader = "CREATED" createdSinceHeader = "CREATED"
createdAtHeader = "CREATED AT" createdAtHeader = "CREATED AT"
@ -18,22 +16,25 @@ const (
) )
type subContext interface { type subContext interface {
fullHeader() string FullHeader() string
addHeader(header string) AddHeader(header string)
} }
type baseSubContext struct { // HeaderContext provides the subContext interface for managing headers
type HeaderContext struct {
header []string header []string
} }
func (c *baseSubContext) fullHeader() string { // FullHeader returns the header as a string
func (c *HeaderContext) FullHeader() string {
if c.header == nil { if c.header == nil {
return "" return ""
} }
return strings.Join(c.header, "\t") return strings.Join(c.header, "\t")
} }
func (c *baseSubContext) addHeader(header string) { // AddHeader adds another column to the header
func (c *HeaderContext) AddHeader(header string) {
if c.header == nil { if c.header == nil {
c.header = []string{} c.header = []string{}
} }

View File

@ -12,36 +12,48 @@ import (
) )
const ( const (
tableFormatKey = "table" // TableFormatKey is the key used to format as a table
rawFormatKey = "raw" TableFormatKey = "table"
// RawFormatKey is the key used to format as raw JSON
RawFormatKey = "raw"
defaultQuietFormat = "{{.ID}}" defaultQuietFormat = "{{.ID}}"
) )
// Format is the format string rendered using the Context
type Format string
// IsTable returns true if the format is a table-type format
func (f Format) IsTable() bool {
return strings.HasPrefix(string(f), TableFormatKey)
}
// Contains returns true if the format contains the substring
func (f Format) Contains(sub string) bool {
return strings.Contains(string(f), sub)
}
// Context contains information required by the formatter to print the output as desired. // Context contains information required by the formatter to print the output as desired.
type Context struct { type Context struct {
// Output is the output stream to which the formatted string is written. // Output is the output stream to which the formatted string is written.
Output io.Writer Output io.Writer
// Format is used to choose raw, table or custom format for the output. // Format is used to choose raw, table or custom format for the output.
Format string Format Format
// Quiet when set to true will simply print minimal information.
Quiet bool
// Trunc when set to true will truncate the output of certain fields such as Container ID. // Trunc when set to true will truncate the output of certain fields such as Container ID.
Trunc bool Trunc bool
// internal element // internal element
table bool
finalFormat string finalFormat string
header string header string
buffer *bytes.Buffer buffer *bytes.Buffer
} }
func (c *Context) preformat() { func (c *Context) preFormat() {
c.finalFormat = c.Format c.finalFormat = string(c.Format)
if strings.HasPrefix(c.Format, tableKey) { // TODO: handle this in the Format type
c.table = true if c.Format.IsTable() {
c.finalFormat = c.finalFormat[len(tableKey):] c.finalFormat = c.finalFormat[len(TableFormatKey):]
} }
c.finalFormat = strings.Trim(c.finalFormat, " ") c.finalFormat = strings.Trim(c.finalFormat, " ")
@ -52,18 +64,17 @@ func (c *Context) preformat() {
func (c *Context) parseFormat() (*template.Template, error) { func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := templates.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)) return tmpl, fmt.Errorf("Template parsing error: %v\n", err)
c.buffer.WriteTo(c.Output)
} }
return tmpl, err return tmpl, err
} }
func (c *Context) postformat(tmpl *template.Template, subContext subContext) { func (c *Context) postFormat(tmpl *template.Template, subContext subContext) {
if c.table { if c.Format.IsTable() {
if len(c.header) == 0 { if len(c.header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template // if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
tmpl.Execute(bytes.NewBufferString(""), subContext) tmpl.Execute(bytes.NewBufferString(""), subContext)
c.header = subContext.fullHeader() c.header = subContext.FullHeader()
} }
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0) t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
@ -78,13 +89,35 @@ func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error { func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
if err := tmpl.Execute(c.buffer, subContext); err != nil { if err := tmpl.Execute(c.buffer, subContext); err != nil {
c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err)) return fmt.Errorf("Template parsing error: %v\n", err)
c.buffer.WriteTo(c.Output)
return err
} }
if c.table && len(c.header) == 0 { if c.Format.IsTable() && len(c.header) == 0 {
c.header = subContext.fullHeader() c.header = subContext.FullHeader()
} }
c.buffer.WriteString("\n") c.buffer.WriteString("\n")
return nil return nil
} }
// SubFormat is a function type accepted by Write()
type SubFormat func(func(subContext) error) error
// Write the template to the buffer using this Context
func (c *Context) Write(sub subContext, f SubFormat) error {
c.buffer = bytes.NewBufferString("")
c.preFormat()
tmpl, err := c.parseFormat()
if err != nil {
return err
}
subFormat := func(subContext subContext) error {
return c.contextFormat(tmpl, subContext)
}
if err := f(subFormat); err != nil {
return err
}
c.postFormat(tmpl, sub)
return nil
}

View File

@ -1,14 +1,12 @@
package formatter package formatter
import ( import (
"bytes"
"strings"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/reference" "github.com/docker/docker/reference"
"github.com/docker/go-units" units "github.com/docker/go-units"
) )
const ( const (
@ -25,59 +23,63 @@ const (
type ImageContext struct { type ImageContext struct {
Context Context
Digest bool Digest bool
// Images
Images []types.Image
} }
func isDangling(image types.Image) bool { func isDangling(image types.Image) bool {
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>" return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
} }
func (ctx ImageContext) Write() { // NewImageFormat returns a format for rendering an ImageContext
switch ctx.Format { func NewImageFormat(source string, quiet bool, digest bool) Format {
case tableFormatKey: switch source {
ctx.Format = defaultImageTableFormat case TableFormatKey:
if ctx.Digest { switch {
ctx.Format = defaultImageTableFormatWithDigest case quiet:
return defaultQuietFormat
case digest:
return defaultImageTableFormatWithDigest
default:
return defaultImageTableFormat
} }
if ctx.Quiet { case RawFormatKey:
ctx.Format = defaultQuietFormat switch {
} case quiet:
case rawFormatKey: return `image_id: {{.ID}}`
if ctx.Quiet { case digest:
ctx.Format = `image_id: {{.ID}}` return `repository: {{ .Repository }}
} else {
if ctx.Digest {
ctx.Format = `repository: {{ .Repository }}
tag: {{.Tag}} tag: {{.Tag}}
digest: {{.Digest}} digest: {{.Digest}}
image_id: {{.ID}} image_id: {{.ID}}
created_at: {{.CreatedAt}} created_at: {{.CreatedAt}}
virtual_size: {{.Size}} virtual_size: {{.Size}}
` `
} else { default:
ctx.Format = `repository: {{ .Repository }} return `repository: {{ .Repository }}
tag: {{.Tag}} tag: {{.Tag}}
image_id: {{.ID}} image_id: {{.ID}}
created_at: {{.CreatedAt}} created_at: {{.CreatedAt}}
virtual_size: {{.Size}} virtual_size: {{.Size}}
` `
}
} }
} }
ctx.buffer = bytes.NewBufferString("") format := Format(source)
ctx.preformat() if format.IsTable() && digest && !format.Contains("{{.Digest}}") {
if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") { format += "\t{{.Digest}}"
ctx.finalFormat += "\t{{.Digest}}"
} }
return format
}
tmpl, err := ctx.parseFormat() // ImageWrite writes the formatter images using the ImageContext
if err != nil { func ImageWrite(ctx ImageContext, images []types.Image) error {
return render := func(format func(subContext subContext) error) error {
return imageFormat(ctx, images, format)
} }
return ctx.Write(&imageContext{}, render)
}
for _, image := range ctx.Images { func imageFormat(ctx ImageContext, images []types.Image, format func(subContext subContext) error) error {
for _, image := range images {
images := []*imageContext{} images := []*imageContext{}
if isDangling(image) { if isDangling(image) {
images = append(images, &imageContext{ images = append(images, &imageContext{
@ -170,18 +172,16 @@ virtual_size: {{.Size}}
} }
} }
for _, imageCtx := range images { for _, imageCtx := range images {
err = ctx.contextFormat(tmpl, imageCtx) if err := format(imageCtx); err != nil {
if err != nil { return err
return
} }
} }
} }
return nil
ctx.postformat(tmpl, &imageContext{})
} }
type imageContext struct { type imageContext struct {
baseSubContext HeaderContext
trunc bool trunc bool
i types.Image i types.Image
repo string repo string
@ -190,7 +190,7 @@ type imageContext struct {
} }
func (c *imageContext) ID() string { func (c *imageContext) ID() string {
c.addHeader(imageIDHeader) c.AddHeader(imageIDHeader)
if c.trunc { if c.trunc {
return stringid.TruncateID(c.i.ID) return stringid.TruncateID(c.i.ID)
} }
@ -198,32 +198,32 @@ func (c *imageContext) ID() string {
} }
func (c *imageContext) Repository() string { func (c *imageContext) Repository() string {
c.addHeader(repositoryHeader) c.AddHeader(repositoryHeader)
return c.repo return c.repo
} }
func (c *imageContext) Tag() string { func (c *imageContext) Tag() string {
c.addHeader(tagHeader) c.AddHeader(tagHeader)
return c.tag return c.tag
} }
func (c *imageContext) Digest() string { func (c *imageContext) Digest() string {
c.addHeader(digestHeader) c.AddHeader(digestHeader)
return c.digest return c.digest
} }
func (c *imageContext) CreatedSince() string { func (c *imageContext) CreatedSince() string {
c.addHeader(createdSinceHeader) c.AddHeader(createdSinceHeader)
createdAt := time.Unix(int64(c.i.Created), 0) createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) return units.HumanDuration(time.Now().UTC().Sub(createdAt))
} }
func (c *imageContext) CreatedAt() string { func (c *imageContext) CreatedAt() string {
c.addHeader(createdAtHeader) c.AddHeader(createdAtHeader)
return time.Unix(int64(c.i.Created), 0).String() return time.Unix(int64(c.i.Created), 0).String()
} }
func (c *imageContext) Size() string { func (c *imageContext) Size() string {
c.addHeader(sizeHeader) c.AddHeader(sizeHeader)
return units.HumanSizeWithPrecision(float64(c.i.Size), 3) return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/testutil/assert"
) )
func TestImageContext(t *testing.T) { func TestImageContext(t *testing.T) {
@ -66,7 +67,7 @@ func TestImageContext(t *testing.T) {
t.Fatalf("Expected %s, was %s\n", c.expValue, v) t.Fatalf("Expected %s, was %s\n", c.expValue, v)
} }
h := ctx.fullHeader() h := ctx.FullHeader()
if h != c.expHeader { if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h) t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
} }
@ -77,7 +78,7 @@ func TestImageContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix() unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String() expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct { cases := []struct {
context ImageContext context ImageContext
expected string expected string
}{ }{
@ -104,7 +105,7 @@ func TestImageContextWrite(t *testing.T) {
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table", Format: NewImageFormat("table", false, false),
}, },
}, },
`REPOSITORY TAG IMAGE ID CREATED SIZE `REPOSITORY TAG IMAGE ID CREATED SIZE
@ -116,7 +117,7 @@ image tag2 imageID2 24 hours ago
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table {{.Repository}}", Format: NewImageFormat("table {{.Repository}}", false, false),
}, },
}, },
"REPOSITORY\nimage\nimage\n<none>\n", "REPOSITORY\nimage\nimage\n<none>\n",
@ -124,7 +125,7 @@ image tag2 imageID2 24 hours ago
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table {{.Repository}}", Format: NewImageFormat("table {{.Repository}}", false, true),
}, },
Digest: true, Digest: true,
}, },
@ -137,8 +138,7 @@ image <none>
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table {{.Repository}}", Format: NewImageFormat("table {{.Repository}}", true, false),
Quiet: true,
}, },
}, },
"REPOSITORY\nimage\nimage\n<none>\n", "REPOSITORY\nimage\nimage\n<none>\n",
@ -146,8 +146,7 @@ image <none>
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table", Format: NewImageFormat("table", true, false),
Quiet: true,
}, },
}, },
"imageID1\nimageID2\nimageID3\n", "imageID1\nimageID2\nimageID3\n",
@ -155,8 +154,7 @@ image <none>
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table", Format: NewImageFormat("table", false, true),
Quiet: false,
}, },
Digest: true, Digest: true,
}, },
@ -169,8 +167,7 @@ image tag2 <none>
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table", Format: NewImageFormat("table", true, true),
Quiet: true,
}, },
Digest: true, Digest: true,
}, },
@ -180,7 +177,7 @@ image tag2 <none>
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "raw", Format: NewImageFormat("raw", false, false),
}, },
}, },
fmt.Sprintf(`repository: image fmt.Sprintf(`repository: image
@ -206,7 +203,7 @@ virtual_size: 0 B
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "raw", Format: NewImageFormat("raw", false, true),
}, },
Digest: true, Digest: true,
}, },
@ -236,8 +233,7 @@ virtual_size: 0 B
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "raw", Format: NewImageFormat("raw", true, false),
Quiet: true,
}, },
}, },
`image_id: imageID1 `image_id: imageID1
@ -249,7 +245,7 @@ image_id: imageID3
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "{{.Repository}}", Format: NewImageFormat("{{.Repository}}", false, false),
}, },
}, },
"image\nimage\n<none>\n", "image\nimage\n<none>\n",
@ -257,7 +253,7 @@ image_id: imageID3
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "{{.Repository}}", Format: NewImageFormat("{{.Repository}}", false, true),
}, },
Digest: true, Digest: true,
}, },
@ -265,22 +261,20 @@ image_id: imageID3
}, },
} }
for _, context := range contexts { for _, testcase := range cases {
images := []types.Image{ images := []types.Image{
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime}, {ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime}, {ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime}, {ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
} }
out := bytes.NewBufferString("") out := bytes.NewBufferString("")
context.context.Output = out testcase.context.Output = out
context.context.Images = images err := ImageWrite(testcase.context, images)
context.context.Write() if err != nil {
actual := out.String() assert.Error(t, err, testcase.expected)
if actual != context.expected { } else {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual) assert.Equal(t, out.String(), testcase.expected)
} }
// Clean buffer
out.Reset()
} }
} }
@ -295,7 +289,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "{{.Repository}}", Format: NewImageFormat("{{.Repository}}", false, false),
Output: out, Output: out,
}, },
}, },
@ -304,7 +298,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table {{.Repository}}", Format: NewImageFormat("table {{.Repository}}", false, false),
Output: out, Output: out,
}, },
}, },
@ -313,32 +307,26 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "{{.Repository}}", Format: NewImageFormat("{{.Repository}}", false, true),
Output: out, Output: out,
}, },
Digest: true,
}, },
"", "",
}, },
{ {
ImageContext{ ImageContext{
Context: Context{ Context: Context{
Format: "table {{.Repository}}", Format: NewImageFormat("table {{.Repository}}", false, true),
Output: out, Output: out,
}, },
Digest: true,
}, },
"REPOSITORY DIGEST\n", "REPOSITORY DIGEST\n",
}, },
} }
for _, context := range contexts { for _, context := range contexts {
context.context.Images = images ImageWrite(context.context, images)
context.context.Write() assert.Equal(t, out.String(), context.expected)
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer // Clean buffer
out.Reset() out.Reset()
} }

View File

@ -1,7 +1,6 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
@ -17,60 +16,45 @@ const (
internalHeader = "INTERNAL" internalHeader = "INTERNAL"
) )
// NetworkContext contains network specific information required by the formatter, // NewNetworkFormat returns a Format for rendering using a network Context
// encapsulate a Context struct. func NewNetworkFormat(source string, quiet bool) Format {
type NetworkContext struct { switch source {
Context case TableFormatKey:
// Networks if quiet {
Networks []types.NetworkResource return defaultQuietFormat
}
return defaultNetworkTableFormat
case RawFormatKey:
if quiet {
return `network_id: {{.ID}}`
}
return `network_id: {{.ID}}\nname: {{.Name}}\ndriver: {{.Driver}}\nscope: {{.Scope}}\n`
}
return Format(source)
} }
func (ctx NetworkContext) Write() { // NetworkWrite writes the context
switch ctx.Format { func NetworkWrite(ctx Context, networks []types.NetworkResource) error {
case tableFormatKey: render := func(format func(subContext subContext) error) error {
if ctx.Quiet { for _, network := range networks {
ctx.Format = defaultQuietFormat networkCtx := &networkContext{trunc: ctx.Trunc, n: network}
} else { if err := format(networkCtx); err != nil {
ctx.Format = defaultNetworkTableFormat return err
} }
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `network_id: {{.ID}}`
} else {
ctx.Format = `network_id: {{.ID}}\nname: {{.Name}}\ndriver: {{.Driver}}\nscope: {{.Scope}}\n`
} }
return nil
} }
return ctx.Write(&networkContext{}, render)
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, network := range ctx.Networks {
networkCtx := &networkContext{
trunc: ctx.Trunc,
n: network,
}
err = ctx.contextFormat(tmpl, networkCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &networkContext{})
} }
type networkContext struct { type networkContext struct {
baseSubContext HeaderContext
trunc bool trunc bool
n types.NetworkResource n types.NetworkResource
} }
func (c *networkContext) ID() string { func (c *networkContext) ID() string {
c.addHeader(networkIDHeader) c.AddHeader(networkIDHeader)
if c.trunc { if c.trunc {
return stringid.TruncateID(c.n.ID) return stringid.TruncateID(c.n.ID)
} }
@ -78,32 +62,32 @@ func (c *networkContext) ID() string {
} }
func (c *networkContext) Name() string { func (c *networkContext) Name() string {
c.addHeader(nameHeader) c.AddHeader(nameHeader)
return c.n.Name return c.n.Name
} }
func (c *networkContext) Driver() string { func (c *networkContext) Driver() string {
c.addHeader(driverHeader) c.AddHeader(driverHeader)
return c.n.Driver return c.n.Driver
} }
func (c *networkContext) Scope() string { func (c *networkContext) Scope() string {
c.addHeader(scopeHeader) c.AddHeader(scopeHeader)
return c.n.Scope return c.n.Scope
} }
func (c *networkContext) IPv6() string { func (c *networkContext) IPv6() string {
c.addHeader(ipv6Header) c.AddHeader(ipv6Header)
return fmt.Sprintf("%v", c.n.EnableIPv6) return fmt.Sprintf("%v", c.n.EnableIPv6)
} }
func (c *networkContext) Internal() string { func (c *networkContext) Internal() string {
c.addHeader(internalHeader) c.AddHeader(internalHeader)
return fmt.Sprintf("%v", c.n.Internal) return fmt.Sprintf("%v", c.n.Internal)
} }
func (c *networkContext) Labels() string { func (c *networkContext) Labels() string {
c.addHeader(labelsHeader) c.AddHeader(labelsHeader)
if c.n.Labels == nil { if c.n.Labels == nil {
return "" return ""
} }
@ -120,7 +104,7 @@ func (c *networkContext) Label(name string) string {
r := strings.NewReplacer("-", " ", "_", " ") r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1]) h := r.Replace(n[len(n)-1])
c.addHeader(h) c.AddHeader(h)
if c.n.Labels == nil { if c.n.Labels == nil {
return "" return ""

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/testutil/assert"
) )
func TestNetworkContext(t *testing.T) { func TestNetworkContext(t *testing.T) {
@ -62,7 +63,7 @@ func TestNetworkContext(t *testing.T) {
t.Fatalf("Expected %s, was %s\n", c.expValue, v) t.Fatalf("Expected %s, was %s\n", c.expValue, v)
} }
h := ctx.fullHeader() h := ctx.FullHeader()
if h != c.expHeader { if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h) t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
} }
@ -70,71 +71,45 @@ func TestNetworkContext(t *testing.T) {
} }
func TestNetworkContextWrite(t *testing.T) { func TestNetworkContextWrite(t *testing.T) {
contexts := []struct { cases := []struct {
context NetworkContext context Context
expected string expected string
}{ }{
// Errors // Errors
{ {
NetworkContext{ Context{Format: "{{InvalidFunction}}"},
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined `Template parsing error: template: :1: function "InvalidFunction" not defined
`, `,
}, },
{ {
NetworkContext{ Context{Format: "{{nil}}"},
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`, `,
}, },
// Table format // Table format
{ {
NetworkContext{ Context{Format: NewNetworkFormat("table", false)},
Context: Context{
Format: "table",
},
},
`NETWORK ID NAME DRIVER SCOPE `NETWORK ID NAME DRIVER SCOPE
networkID1 foobar_baz foo local networkID1 foobar_baz foo local
networkID2 foobar_bar bar local networkID2 foobar_bar bar local
`, `,
}, },
{ {
NetworkContext{ Context{Format: NewNetworkFormat("table", true)},
Context: Context{
Format: "table",
Quiet: true,
},
},
`networkID1 `networkID1
networkID2 networkID2
`, `,
}, },
{ {
NetworkContext{ Context{Format: NewNetworkFormat("table {{.Name}}", false)},
Context: Context{
Format: "table {{.Name}}",
},
},
`NAME `NAME
foobar_baz foobar_baz
foobar_bar foobar_bar
`, `,
}, },
{ {
NetworkContext{ Context{Format: NewNetworkFormat("table {{.Name}}", true)},
Context: Context{
Format: "table {{.Name}}",
Quiet: true,
},
},
`NAME `NAME
foobar_baz foobar_baz
foobar_bar foobar_bar
@ -142,11 +117,8 @@ foobar_bar
}, },
// Raw Format // Raw Format
{ {
NetworkContext{ Context{Format: NewNetworkFormat("raw", false)},
Context: Context{ `network_id: networkID1
Format: "raw",
},
}, `network_id: networkID1
name: foobar_baz name: foobar_baz
driver: foo driver: foo
scope: local scope: local
@ -159,43 +131,32 @@ scope: local
`, `,
}, },
{ {
NetworkContext{ Context{Format: NewNetworkFormat("raw", true)},
Context: Context{
Format: "raw",
Quiet: true,
},
},
`network_id: networkID1 `network_id: networkID1
network_id: networkID2 network_id: networkID2
`, `,
}, },
// Custom Format // Custom Format
{ {
NetworkContext{ Context{Format: NewNetworkFormat("{{.Name}}", false)},
Context: Context{
Format: "{{.Name}}",
},
},
`foobar_baz `foobar_baz
foobar_bar foobar_bar
`, `,
}, },
} }
for _, context := range contexts { for _, testcase := range cases {
networks := []types.NetworkResource{ networks := []types.NetworkResource{
{ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local"}, {ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local"},
{ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local"}, {ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local"},
} }
out := bytes.NewBufferString("") out := bytes.NewBufferString("")
context.context.Output = out testcase.context.Output = out
context.context.Networks = networks err := NetworkWrite(testcase.context, networks)
context.context.Write() if err != nil {
actual := out.String() assert.Error(t, err, testcase.expected)
if actual != context.expected { } else {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual) assert.Equal(t, out.String(), testcase.expected)
} }
// Clean buffer
out.Reset()
} }
} }

View File

@ -1,7 +1,6 @@
package formatter package formatter
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
@ -16,78 +15,63 @@ const (
// Status header ? // Status header ?
) )
// VolumeContext contains volume specific information required by the formatter, // NewVolumeFormat returns a format for use with a volume Context
// encapsulate a Context struct. func NewVolumeFormat(source string, quiet bool) Format {
type VolumeContext struct { switch source {
Context case TableFormatKey:
// Volumes if quiet {
Volumes []*types.Volume return defaultVolumeQuietFormat
}
return defaultVolumeTableFormat
case RawFormatKey:
if quiet {
return `name: {{.Name}}`
}
return `name: {{.Name}}\ndriver: {{.Driver}}\n`
}
return Format(source)
} }
func (ctx VolumeContext) Write() { // VolumeWrite writes formatted volumes using the Context
switch ctx.Format { func VolumeWrite(ctx Context, volumes []*types.Volume) error {
case tableFormatKey: render := func(format func(subContext subContext) error) error {
if ctx.Quiet { for _, volume := range volumes {
ctx.Format = defaultVolumeQuietFormat if err := format(&volumeContext{v: volume}); err != nil {
} else { return err
ctx.Format = defaultVolumeTableFormat }
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `name: {{.Name}}`
} else {
ctx.Format = `name: {{.Name}}\ndriver: {{.Driver}}\n`
} }
return nil
} }
return ctx.Write(&volumeContext{}, render)
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, volume := range ctx.Volumes {
volumeCtx := &volumeContext{
v: volume,
}
err = ctx.contextFormat(tmpl, volumeCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &networkContext{})
} }
type volumeContext struct { type volumeContext struct {
baseSubContext HeaderContext
v *types.Volume v *types.Volume
} }
func (c *volumeContext) Name() string { func (c *volumeContext) Name() string {
c.addHeader(nameHeader) c.AddHeader(nameHeader)
return c.v.Name return c.v.Name
} }
func (c *volumeContext) Driver() string { func (c *volumeContext) Driver() string {
c.addHeader(driverHeader) c.AddHeader(driverHeader)
return c.v.Driver return c.v.Driver
} }
func (c *volumeContext) Scope() string { func (c *volumeContext) Scope() string {
c.addHeader(scopeHeader) c.AddHeader(scopeHeader)
return c.v.Scope return c.v.Scope
} }
func (c *volumeContext) Mountpoint() string { func (c *volumeContext) Mountpoint() string {
c.addHeader(mountpointHeader) c.AddHeader(mountpointHeader)
return c.v.Mountpoint return c.v.Mountpoint
} }
func (c *volumeContext) Labels() string { func (c *volumeContext) Labels() string {
c.addHeader(labelsHeader) c.AddHeader(labelsHeader)
if c.v.Labels == nil { if c.v.Labels == nil {
return "" return ""
} }
@ -105,7 +89,7 @@ func (c *volumeContext) Label(name string) string {
r := strings.NewReplacer("-", " ", "_", " ") r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1]) h := r.Replace(n[len(n)-1])
c.addHeader(h) c.AddHeader(h)
if c.v.Labels == nil { if c.v.Labels == nil {
return "" return ""

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/testutil/assert"
) )
func TestVolumeContext(t *testing.T) { func TestVolumeContext(t *testing.T) {
@ -48,7 +49,7 @@ func TestVolumeContext(t *testing.T) {
t.Fatalf("Expected %s, was %s\n", c.expValue, v) t.Fatalf("Expected %s, was %s\n", c.expValue, v)
} }
h := ctx.fullHeader() h := ctx.FullHeader()
if h != c.expHeader { if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h) t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
} }
@ -56,71 +57,45 @@ func TestVolumeContext(t *testing.T) {
} }
func TestVolumeContextWrite(t *testing.T) { func TestVolumeContextWrite(t *testing.T) {
contexts := []struct { cases := []struct {
context VolumeContext context Context
expected string expected string
}{ }{
// Errors // Errors
{ {
VolumeContext{ Context{Format: "{{InvalidFunction}}"},
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined `Template parsing error: template: :1: function "InvalidFunction" not defined
`, `,
}, },
{ {
VolumeContext{ Context{Format: "{{nil}}"},
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`, `,
}, },
// Table format // Table format
{ {
VolumeContext{ Context{Format: NewVolumeFormat("table", false)},
Context: Context{
Format: "table",
},
},
`DRIVER NAME `DRIVER NAME
foo foobar_baz foo foobar_baz
bar foobar_bar bar foobar_bar
`, `,
}, },
{ {
VolumeContext{ Context{Format: NewVolumeFormat("table", true)},
Context: Context{
Format: "table",
Quiet: true,
},
},
`foobar_baz `foobar_baz
foobar_bar foobar_bar
`, `,
}, },
{ {
VolumeContext{ Context{Format: NewVolumeFormat("table {{.Name}}", false)},
Context: Context{
Format: "table {{.Name}}",
},
},
`NAME `NAME
foobar_baz foobar_baz
foobar_bar foobar_bar
`, `,
}, },
{ {
VolumeContext{ Context{Format: NewVolumeFormat("table {{.Name}}", true)},
Context: Context{
Format: "table {{.Name}}",
Quiet: true,
},
},
`NAME `NAME
foobar_baz foobar_baz
foobar_bar foobar_bar
@ -128,11 +103,8 @@ foobar_bar
}, },
// Raw Format // Raw Format
{ {
VolumeContext{ Context{Format: NewVolumeFormat("raw", false)},
Context: Context{ `name: foobar_baz
Format: "raw",
},
}, `name: foobar_baz
driver: foo driver: foo
name: foobar_bar name: foobar_bar
@ -141,43 +113,32 @@ driver: bar
`, `,
}, },
{ {
VolumeContext{ Context{Format: NewVolumeFormat("raw", true)},
Context: Context{
Format: "raw",
Quiet: true,
},
},
`name: foobar_baz `name: foobar_baz
name: foobar_bar name: foobar_bar
`, `,
}, },
// Custom Format // Custom Format
{ {
VolumeContext{ Context{Format: NewVolumeFormat("{{.Name}}", false)},
Context: Context{
Format: "{{.Name}}",
},
},
`foobar_baz `foobar_baz
foobar_bar foobar_bar
`, `,
}, },
} }
for _, context := range contexts { for _, testcase := range cases {
volumes := []*types.Volume{ volumes := []*types.Volume{
{Name: "foobar_baz", Driver: "foo"}, {Name: "foobar_baz", Driver: "foo"},
{Name: "foobar_bar", Driver: "bar"}, {Name: "foobar_bar", Driver: "bar"},
} }
out := bytes.NewBufferString("") out := bytes.NewBufferString("")
context.context.Output = out testcase.context.Output = out
context.context.Volumes = volumes err := VolumeWrite(testcase.context, volumes)
context.context.Write() if err != nil {
actual := out.String() assert.Error(t, err, testcase.expected)
if actual != context.expected { } else {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual) assert.Equal(t, out.String(), testcase.expected)
} }
// Clean buffer
out.Reset()
} }
} }

View File

@ -64,27 +64,22 @@ func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
return err return err
} }
f := opts.format format := opts.format
if len(f) == 0 { if len(format) == 0 {
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet { if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
f = dockerCli.ConfigFile().ImagesFormat format = dockerCli.ConfigFile().ImagesFormat
} else { } else {
f = "table" format = "table"
} }
} }
imagesCtx := formatter.ImageContext{ imageCtx := formatter.ImageContext{
Context: formatter.Context{ Context: formatter.Context{
Output: dockerCli.Out(), Output: dockerCli.Out(),
Format: f, Format: formatter.NewImageFormat(format, opts.quiet, opts.showDigests),
Quiet: opts.quiet,
Trunc: !opts.noTrunc, Trunc: !opts.noTrunc,
}, },
Digest: opts.showDigests, Digest: opts.showDigests,
Images: images,
} }
return formatter.ImageWrite(imageCtx, images)
imagesCtx.Write()
return nil
} }

View File

@ -50,35 +50,27 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
func runList(dockerCli *command.DockerCli, opts listOptions) error { func runList(dockerCli *command.DockerCli, opts listOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
options := types.NetworkListOptions{Filters: opts.filter.Value()} options := types.NetworkListOptions{Filters: opts.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), options) networkResources, err := client.NetworkList(context.Background(), options)
if err != nil { if err != nil {
return err return err
} }
f := opts.format format := opts.format
if len(f) == 0 { if len(format) == 0 {
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet { if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet {
f = dockerCli.ConfigFile().NetworksFormat format = dockerCli.ConfigFile().NetworksFormat
} else { } else {
f = "table" format = "table"
} }
} }
sort.Sort(byNetworkName(networkResources)) sort.Sort(byNetworkName(networkResources))
networksCtx := formatter.NetworkContext{ networksCtx := formatter.Context{
Context: formatter.Context{ Output: dockerCli.Out(),
Output: dockerCli.Out(), Format: formatter.NewNetworkFormat(format, opts.quiet),
Format: f, Trunc: !opts.noTrunc,
Quiet: opts.quiet,
Trunc: !opts.noTrunc,
},
Networks: networkResources,
} }
return formatter.NetworkWrite(networksCtx, networkResources)
networksCtx.Write()
return nil
} }

View File

@ -56,29 +56,22 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
return err return err
} }
f := opts.format format := opts.format
if len(f) == 0 { if len(format) == 0 {
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet { if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet {
f = dockerCli.ConfigFile().VolumesFormat format = dockerCli.ConfigFile().VolumesFormat
} else { } else {
f = "table" format = "table"
} }
} }
sort.Sort(byVolumeName(volumes.Volumes)) sort.Sort(byVolumeName(volumes.Volumes))
volumeCtx := formatter.VolumeContext{ volumeCtx := formatter.Context{
Context: formatter.Context{ Output: dockerCli.Out(),
Output: dockerCli.Out(), Format: formatter.NewVolumeFormat(format, opts.quiet),
Format: f,
Quiet: opts.quiet,
},
Volumes: volumes.Volumes,
} }
return formatter.VolumeWrite(volumeCtx, volumes.Volumes)
volumeCtx.Write()
return nil
} }
var listDescription = ` var listDescription = `