This fix is an attempt to address https://github.com/docker/docker/pull/28213#issuecomment-273840405 Currently when specify table format with table `--format "table {{.ID}}..."`, the delimiter in the header section of the table is always `"\t"`. That is actually different from the content of the table as the delimiter could be anything (or even contatenated with `.`, for example): ``` $ docker service ps web --format 'table {{.Name}}.{{.ID}}' --no-trunc NAME ID web.1.inyhxhvjcijl0hdbu8lgrwwh7 \_ web.1.p9m4kx2srjqmfms4igam0uqlb ``` This fix is an attampt to address the skewness of the table when delimiter is not `"\t"`. The basic idea is that, when header consists of `table` key, the header section will be redendered the same way as content section. A map mapping each placeholder name to the HEADER entry name is used for the context of the header. Unit tests have been updated and added to cover the changes. This fix is related to #28313. Signed-off-by: Yong Tang <yong.tang.github@outlook.com> Upstream-commit: ea61dac9e6d04879445f9c34729055ac1bb15050 Component: engine
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package formatter
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"text/template"
|
|
|
|
"github.com/docker/docker/pkg/templates"
|
|
)
|
|
|
|
// Format keys used to specify certain kinds of output formats
|
|
const (
|
|
TableFormatKey = "table"
|
|
RawFormatKey = "raw"
|
|
PrettyFormatKey = "pretty"
|
|
|
|
defaultQuietFormat = "{{.ID}}"
|
|
)
|
|
|
|
// Format is the format string rendered using the Context
|
|
type Format string
|
|
|
|
// IsTable returns true if the format is a table-type format
|
|
func (f Format) IsTable() bool {
|
|
return strings.HasPrefix(string(f), TableFormatKey)
|
|
}
|
|
|
|
// Contains returns true if the format contains the substring
|
|
func (f Format) Contains(sub string) bool {
|
|
return strings.Contains(string(f), sub)
|
|
}
|
|
|
|
// Context contains information required by the formatter to print the output as desired.
|
|
type Context struct {
|
|
// Output is the output stream to which the formatted string is written.
|
|
Output io.Writer
|
|
// Format is used to choose raw, table or custom format for the output.
|
|
Format Format
|
|
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
|
Trunc bool
|
|
|
|
// internal element
|
|
finalFormat string
|
|
header interface{}
|
|
buffer *bytes.Buffer
|
|
}
|
|
|
|
func (c *Context) preFormat() {
|
|
c.finalFormat = string(c.Format)
|
|
|
|
// TODO: handle this in the Format type
|
|
if c.Format.IsTable() {
|
|
c.finalFormat = c.finalFormat[len(TableFormatKey):]
|
|
}
|
|
|
|
c.finalFormat = strings.Trim(c.finalFormat, " ")
|
|
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
|
c.finalFormat = r.Replace(c.finalFormat)
|
|
}
|
|
|
|
func (c *Context) parseFormat() (*template.Template, error) {
|
|
tmpl, err := templates.Parse(c.finalFormat)
|
|
if err != nil {
|
|
return tmpl, fmt.Errorf("Template parsing error: %v\n", err)
|
|
}
|
|
return tmpl, err
|
|
}
|
|
|
|
func (c *Context) postFormat(tmpl *template.Template, subContext subContext) {
|
|
if c.Format.IsTable() {
|
|
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
|
|
buffer := bytes.NewBufferString("")
|
|
tmpl.Execute(buffer, subContext.FullHeader())
|
|
buffer.WriteTo(t)
|
|
t.Write([]byte("\n"))
|
|
c.buffer.WriteTo(t)
|
|
t.Flush()
|
|
} else {
|
|
c.buffer.WriteTo(c.Output)
|
|
}
|
|
}
|
|
|
|
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
|
|
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
|
return fmt.Errorf("Template parsing error: %v\n", err)
|
|
}
|
|
if c.Format.IsTable() && c.header != nil {
|
|
c.header = subContext.FullHeader()
|
|
}
|
|
c.buffer.WriteString("\n")
|
|
return nil
|
|
}
|
|
|
|
// SubFormat is a function type accepted by Write()
|
|
type SubFormat func(func(subContext) error) error
|
|
|
|
// Write the template to the buffer using this Context
|
|
func (c *Context) Write(sub subContext, f SubFormat) error {
|
|
c.buffer = bytes.NewBufferString("")
|
|
c.preFormat()
|
|
|
|
tmpl, err := c.parseFormat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
subFormat := func(subContext subContext) error {
|
|
return c.contextFormat(tmpl, subContext)
|
|
}
|
|
if err := f(subFormat); err != nil {
|
|
return err
|
|
}
|
|
|
|
c.postFormat(tmpl, sub)
|
|
return nil
|
|
}
|