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  }