github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/formatter/formatter.go (about) 1 // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: 2 //go:build go1.19 3 4 package formatter 5 6 import ( 7 "bytes" 8 "io" 9 "strings" 10 "text/template" 11 12 "github.com/docker/cli/cli/command/formatter/tabwriter" 13 "github.com/docker/cli/templates" 14 "github.com/pkg/errors" 15 ) 16 17 // Format keys used to specify certain kinds of output formats 18 const ( 19 TableFormatKey = "table" 20 RawFormatKey = "raw" 21 PrettyFormatKey = "pretty" 22 JSONFormatKey = "json" 23 24 DefaultQuietFormat = "{{.ID}}" 25 JSONFormat = "{{json .}}" 26 ) 27 28 // Format is the format string rendered using the Context 29 type Format string 30 31 // IsTable returns true if the format is a table-type format 32 func (f Format) IsTable() bool { 33 return strings.HasPrefix(string(f), TableFormatKey) 34 } 35 36 // IsJSON returns true if the format is the json format 37 func (f Format) IsJSON() bool { 38 return string(f) == JSONFormatKey 39 } 40 41 // Contains returns true if the format contains the substring 42 func (f Format) Contains(sub string) bool { 43 return strings.Contains(string(f), sub) 44 } 45 46 // Context contains information required by the formatter to print the output as desired. 47 type Context struct { 48 // Output is the output stream to which the formatted string is written. 49 Output io.Writer 50 // Format is used to choose raw, table or custom format for the output. 51 Format Format 52 // Trunc when set to true will truncate the output of certain fields such as Container ID. 53 Trunc bool 54 55 // internal element 56 finalFormat string 57 header any 58 buffer *bytes.Buffer 59 } 60 61 func (c *Context) preFormat() { 62 c.finalFormat = string(c.Format) 63 // TODO: handle this in the Format type 64 switch { 65 case c.Format.IsTable(): 66 c.finalFormat = c.finalFormat[len(TableFormatKey):] 67 case c.Format.IsJSON(): 68 c.finalFormat = JSONFormat 69 } 70 71 c.finalFormat = strings.Trim(c.finalFormat, " ") 72 r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") 73 c.finalFormat = r.Replace(c.finalFormat) 74 } 75 76 func (c *Context) parseFormat() (*template.Template, error) { 77 tmpl, err := templates.Parse(c.finalFormat) 78 if err != nil { 79 return tmpl, errors.Wrap(err, "template parsing error") 80 } 81 return tmpl, err 82 } 83 84 func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) { 85 if c.Format.IsTable() { 86 t := tabwriter.NewWriter(c.Output, 10, 1, 3, ' ', 0) 87 buffer := bytes.NewBufferString("") 88 tmpl.Funcs(templates.HeaderFunctions).Execute(buffer, subContext.FullHeader()) 89 buffer.WriteTo(t) 90 t.Write([]byte("\n")) 91 c.buffer.WriteTo(t) 92 t.Flush() 93 } else { 94 c.buffer.WriteTo(c.Output) 95 } 96 } 97 98 func (c *Context) contextFormat(tmpl *template.Template, subContext SubContext) error { 99 if err := tmpl.Execute(c.buffer, subContext); err != nil { 100 return errors.Wrap(err, "template parsing error") 101 } 102 if c.Format.IsTable() && c.header != nil { 103 c.header = subContext.FullHeader() 104 } 105 c.buffer.WriteString("\n") 106 return nil 107 } 108 109 // SubFormat is a function type accepted by Write() 110 type SubFormat func(func(SubContext) error) error 111 112 // Write the template to the buffer using this Context 113 func (c *Context) Write(sub SubContext, f SubFormat) error { 114 c.buffer = bytes.NewBufferString("") 115 c.preFormat() 116 117 tmpl, err := c.parseFormat() 118 if err != nil { 119 return err 120 } 121 122 subFormat := func(subContext SubContext) error { 123 return c.contextFormat(tmpl, subContext) 124 } 125 if err := f(subFormat); err != nil { 126 return err 127 } 128 129 c.postFormat(tmpl, sub) 130 return nil 131 }