Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor report package to be more compatible #831

Merged
merged 1 commit into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions pkg/report/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,65 @@ Package report provides helper structs/methods/funcs for formatting output

To format output for an array of structs:

w := report.NewWriterDefault(os.Stdout)
defer w.Flush()

ExamplePodman:
headers := report.Headers(struct {
ID string
}{}, nil)
t, _ := report.NewTemplate("command name").Parse("{{range .}}{{.ID}}{{end}}")
t.Execute(t, headers)
t.Execute(t, map[string]string{

f := report.New(os.Stdout, "Command Name")
f, _ := f.Parse(report.OriginPodman, "{{range .}}{{.ID}}{{end}}")
defer f.Flush()

if f.RenderHeaders {
f.Execute(headers)
}
f.Execute( map[string]string{
"ID":"fa85da03b40141899f3af3de6d27852b",
})
// t.IsTable() == false

or

w := report.NewWriterDefault(os.Stdout)
defer w.Flush()
// Output:
// ID
// fa85da03b40141899f3af3de6d27852b

ExampleUser:
headers := report.Headers(struct {
CID string
}{}, map[string]string{
"CID":"ID"})
t, _ := report.NewTemplate("command name").Parse("table {{.CID}}")
t.Execute(t, headers)
}{}, map[string]string{"CID":"ID"})

f, _ := report.New(os.Stdout, "Command Name").Parse(report.OriginUser, "table {{.CID}}")
defer f.Flush()

if f.RenderHeaders {
t.Execute(t, headers)
}
t.Execute(t,map[string]string{
"CID":"fa85da03b40141899f3af3de6d27852b",
})
// t.IsTable() == true

// Output:
// ID
// fa85da03b40141899f3af3de6d27852b

Helpers:

if report.IsJSON(cmd.Flag("format").Value.String()) {
... process JSON and output
}

if report.HasTable(cmd.Flag("format").Value.String()) {
... "table" keyword prefix in format text
}

Template Functions:

The following template functions are added to the template when parsed:
- join strings.Join, {{join .Field separator}}
- json encode field as JSON {{ json .Field }}
- lower strings.ToLower {{ .Field | lower }}
- pad add spaces as prefix and suffix {{ pad . 2 2 }}
- split strings.Split {{ .Field | split }}
- title strings.Title {{ .Field | title }}
- truncate limit field length {{ truncate . 10 }}
- upper strings.ToUpper {{ .Field | upper }}

report.Funcs() may be used to add additional template functions.
Expand Down
151 changes: 151 additions & 0 deletions pkg/report/formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package report

import (
"io"
"strings"
"text/tabwriter"
"text/template"
)

// Flusher is the interface that wraps the Flush method.
type Flusher interface {
Flush() error
}

// NopFlusher represents a type which flush operation is nop.
type NopFlusher struct{}

// Flush is a nop operation.
func (f *NopFlusher) Flush() (err error) { return }

type Origin int

const (
OriginUnknown Origin = iota
OriginPodman
OriginUser
)

func (o Origin) String() string {
switch o {
case OriginPodman:
return "OriginPodman"
case OriginUser:
return "OriginUser"
default:
return "OriginUnknown"
}
}

// Formatter holds the configured Writer and parsed Template, additional state fields are
// maintained to assist in the podman command report writing.
type Formatter struct {
Origin Origin // Source of go template. OriginUser or OriginPodman
RenderHeaders bool // Hint, default behavior for given template is to include headers
RenderTable bool // Does template have "table" keyword
flusher Flusher // Flush any buffered formatted output
template *template.Template // Go text/template for formatting output
text string // value of canonical template after processing
writer io.Writer // Destination for formatted output
}

// Parse parses golang template returning a formatter
//
// - OriginPodman implies text is a template from podman code. Output will
// be filtered through a tabwriter.
//
// - OriginUser implies text is a template from a user. If template includes
// keyword "table" output will be filtered through a tabwriter.
func (f *Formatter) Parse(origin Origin, text string) (*Formatter, error) {
f.Origin = origin

switch {
case strings.HasPrefix(text, "table "):
f.RenderTable = true
text = "{{range .}}" + NormalizeFormat(text) + "{{end -}}"
case OriginUser == origin:
text = EnforceRange(NormalizeFormat(text))
default:
text = NormalizeFormat(text)
}
f.text = text

if f.RenderTable || origin == OriginPodman {
tw := tabwriter.NewWriter(f.writer, 12, 2, 2, ' ', tabwriter.StripEscape)
f.writer = tw
f.flusher = tw
f.RenderHeaders = true
}

tmpl, err := f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text)
if err != nil {
return f, err
}
f.template = tmpl
return f, nil
}

// Funcs adds the elements of the argument map to the template's function map.
// A default template function will be replaced if there is a key collision.
func (f *Formatter) Funcs(funcMap template.FuncMap) *Formatter {
m := make(template.FuncMap, len(DefaultFuncs)+len(funcMap))
for k, v := range DefaultFuncs {
m[k] = v
}
for k, v := range funcMap {
m[k] = v
}
f.template = f.template.Funcs(funcMap)
return f
}

// Init either resets the given tabwriter with new values or wraps w in tabwriter with given values
func (f *Formatter) Init(w io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Formatter {
flags |= tabwriter.StripEscape

if tw, ok := f.writer.(*tabwriter.Writer); ok {
tw = tw.Init(w, minwidth, tabwidth, padding, padchar, flags)
f.writer = tw
f.flusher = tw
} else {
tw = tabwriter.NewWriter(w, minwidth, tabwidth, padding, padchar, flags)
f.writer = tw
f.flusher = tw
}
return f
}

// Execute applies a parsed template to the specified data object,
// and writes the output to Formatter.Writer.
func (f *Formatter) Execute(data interface{}) error {
return f.template.Execute(f.writer, data)
}

// Flush should be called after the last call to Write to ensure
// that any data buffered in the Formatter is written to output. Any
// incomplete escape sequence at the end is considered
// complete for formatting purposes.
func (f *Formatter) Flush() error {
// Indirection is required here to prevent caller from having to know when
// value of Flusher may be changed.
return f.flusher.Flush()
}

// Writer returns the embedded io.Writer from Formatter
func (f *Formatter) Writer() io.Writer {
return f.writer
}

// New allocates a new, undefined Formatter with the given name and Writer
func New(output io.Writer, name string) *Formatter {
f := new(Formatter)

f.flusher = new(NopFlusher)
if flusher, ok := output.(Flusher); ok {
f.flusher = flusher
}

f.template = template.New(name)
f.writer = output
return f
}
Loading