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  }