github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/api/client/formatter/formatter.go (about)

     1  package formatter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"text/tabwriter"
     9  	"text/template"
    10  
    11  	"github.com/docker/docker/reference"
    12  	"github.com/docker/engine-api/types"
    13  )
    14  
    15  const (
    16  	tableFormatKey = "table"
    17  	rawFormatKey   = "raw"
    18  
    19  	defaultContainerTableFormat       = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
    20  	defaultImageTableFormat           = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    21  	defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    22  	defaultQuietFormat                = "{{.ID}}"
    23  )
    24  
    25  // Context contains information required by the formatter to print the output as desired.
    26  type Context struct {
    27  	// Output is the output stream to which the formatted string is written.
    28  	Output io.Writer
    29  	// Format is used to choose raw, table or custom format for the output.
    30  	Format string
    31  	// Quiet when set to true will simply print minimal information.
    32  	Quiet bool
    33  	// Trunc when set to true will truncate the output of certain fields such as Container ID.
    34  	Trunc bool
    35  
    36  	// internal element
    37  	table       bool
    38  	finalFormat string
    39  	header      string
    40  	buffer      *bytes.Buffer
    41  }
    42  
    43  func (c *Context) preformat() {
    44  	c.finalFormat = c.Format
    45  
    46  	if strings.HasPrefix(c.Format, tableKey) {
    47  		c.table = true
    48  		c.finalFormat = c.finalFormat[len(tableKey):]
    49  	}
    50  
    51  	c.finalFormat = strings.Trim(c.finalFormat, " ")
    52  	r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
    53  	c.finalFormat = r.Replace(c.finalFormat)
    54  }
    55  
    56  func (c *Context) parseFormat() (*template.Template, error) {
    57  	tmpl, err := template.New("").Parse(c.finalFormat)
    58  	if err != nil {
    59  		c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
    60  		c.buffer.WriteTo(c.Output)
    61  	}
    62  	return tmpl, err
    63  }
    64  
    65  func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
    66  	if c.table {
    67  		if len(c.header) == 0 {
    68  			// 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
    69  			tmpl.Execute(bytes.NewBufferString(""), subContext)
    70  			c.header = subContext.fullHeader()
    71  		}
    72  
    73  		t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
    74  		t.Write([]byte(c.header))
    75  		t.Write([]byte("\n"))
    76  		c.buffer.WriteTo(t)
    77  		t.Flush()
    78  	} else {
    79  		c.buffer.WriteTo(c.Output)
    80  	}
    81  }
    82  
    83  func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
    84  	if err := tmpl.Execute(c.buffer, subContext); err != nil {
    85  		c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
    86  		c.buffer.WriteTo(c.Output)
    87  		return err
    88  	}
    89  	if c.table && len(c.header) == 0 {
    90  		c.header = subContext.fullHeader()
    91  	}
    92  	c.buffer.WriteString("\n")
    93  	return nil
    94  }
    95  
    96  // ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
    97  type ContainerContext struct {
    98  	Context
    99  	// Size when set to true will display the size of the output.
   100  	Size bool
   101  	// Containers
   102  	Containers []types.Container
   103  }
   104  
   105  // ImageContext contains image specific information required by the formater, encapsulate a Context struct.
   106  type ImageContext struct {
   107  	Context
   108  	Digest bool
   109  	// Images
   110  	Images []types.Image
   111  }
   112  
   113  func (ctx ContainerContext) Write() {
   114  	switch ctx.Format {
   115  	case tableFormatKey:
   116  		ctx.Format = defaultContainerTableFormat
   117  		if ctx.Quiet {
   118  			ctx.Format = defaultQuietFormat
   119  		}
   120  	case rawFormatKey:
   121  		if ctx.Quiet {
   122  			ctx.Format = `container_id: {{.ID}}`
   123  		} else {
   124  			ctx.Format = `container_id: {{.ID}}
   125  image: {{.Image}}
   126  command: {{.Command}}
   127  created_at: {{.CreatedAt}}
   128  status: {{.Status}}
   129  names: {{.Names}}
   130  labels: {{.Labels}}
   131  ports: {{.Ports}}
   132  `
   133  			if ctx.Size {
   134  				ctx.Format += `size: {{.Size}}
   135  `
   136  			}
   137  		}
   138  	}
   139  
   140  	ctx.buffer = bytes.NewBufferString("")
   141  	ctx.preformat()
   142  	if ctx.table && ctx.Size {
   143  		ctx.finalFormat += "\t{{.Size}}"
   144  	}
   145  
   146  	tmpl, err := ctx.parseFormat()
   147  	if err != nil {
   148  		return
   149  	}
   150  
   151  	for _, container := range ctx.Containers {
   152  		containerCtx := &containerContext{
   153  			trunc: ctx.Trunc,
   154  			c:     container,
   155  		}
   156  		err = ctx.contextFormat(tmpl, containerCtx)
   157  		if err != nil {
   158  			return
   159  		}
   160  	}
   161  
   162  	ctx.postformat(tmpl, &containerContext{})
   163  }
   164  
   165  func (ctx ImageContext) Write() {
   166  	switch ctx.Format {
   167  	case tableFormatKey:
   168  		ctx.Format = defaultImageTableFormat
   169  		if ctx.Digest {
   170  			ctx.Format = defaultImageTableFormatWithDigest
   171  		}
   172  		if ctx.Quiet {
   173  			ctx.Format = defaultQuietFormat
   174  		}
   175  	case rawFormatKey:
   176  		if ctx.Quiet {
   177  			ctx.Format = `image_id: {{.ID}}`
   178  		} else {
   179  			if ctx.Digest {
   180  				ctx.Format = `repository: {{ .Repository }}
   181  tag: {{.Tag}}
   182  digest: {{.Digest}}
   183  image_id: {{.ID}}
   184  created_at: {{.CreatedAt}}
   185  virtual_size: {{.Size}}
   186  `
   187  			} else {
   188  				ctx.Format = `repository: {{ .Repository }}
   189  tag: {{.Tag}}
   190  image_id: {{.ID}}
   191  created_at: {{.CreatedAt}}
   192  virtual_size: {{.Size}}
   193  `
   194  			}
   195  		}
   196  	}
   197  
   198  	ctx.buffer = bytes.NewBufferString("")
   199  	ctx.preformat()
   200  	if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
   201  		ctx.finalFormat += "\t{{.Digest}}"
   202  	}
   203  
   204  	tmpl, err := ctx.parseFormat()
   205  	if err != nil {
   206  		return
   207  	}
   208  
   209  	for _, image := range ctx.Images {
   210  
   211  		repoTags := image.RepoTags
   212  		repoDigests := image.RepoDigests
   213  
   214  		if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
   215  			// dangling image - clear out either repoTags or repoDigests so we only show it once below
   216  			repoDigests = []string{}
   217  		}
   218  		// combine the tags and digests lists
   219  		tagsAndDigests := append(repoTags, repoDigests...)
   220  		for _, repoAndRef := range tagsAndDigests {
   221  			repo := "<none>"
   222  			tag := "<none>"
   223  			digest := "<none>"
   224  
   225  			if !strings.HasPrefix(repoAndRef, "<none>") {
   226  				ref, err := reference.ParseNamed(repoAndRef)
   227  				if err != nil {
   228  					continue
   229  				}
   230  				repo = ref.Name()
   231  
   232  				switch x := ref.(type) {
   233  				case reference.Canonical:
   234  					digest = x.Digest().String()
   235  				case reference.NamedTagged:
   236  					tag = x.Tag()
   237  				}
   238  			}
   239  			imageCtx := &imageContext{
   240  				trunc:  ctx.Trunc,
   241  				i:      image,
   242  				repo:   repo,
   243  				tag:    tag,
   244  				digest: digest,
   245  			}
   246  			err = ctx.contextFormat(tmpl, imageCtx)
   247  			if err != nil {
   248  				return
   249  			}
   250  		}
   251  	}
   252  
   253  	ctx.postformat(tmpl, &imageContext{})
   254  }