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 }