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