-
Notifications
You must be signed in to change notification settings - Fork 176
/
formatter.go
171 lines (149 loc) · 5.13 KB
/
formatter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
}
// stringsCutPrefix is equivalent to Go 1.20’s strings.CutPrefix.
// Replace this function with a direct call to the standard library after we update to Go 1.20.
func stringsCutPrefix(s, prefix string) (string, bool) {
if !strings.HasPrefix(s, prefix) {
return s, false
}
return s[len(prefix):], true
}
// 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
// docker tries to be smart and replaces \n with the actual newline character.
// For compat we do the same but this will break formats such as '{{printf "\n"}}'
// To be backwards compatible with the previous behavior we try to replace and
// parse the template. If it fails use the original text and parse again.
var normText string
textWithoutTable, hasTable := stringsCutPrefix(text, "table ")
switch {
case hasTable:
f.RenderTable = true
normText = "{{range .}}" + NormalizeFormat(text) + "{{end -}}"
text = "{{range .}}" + textWithoutTable + "{{end -}}"
case OriginUser == origin:
normText = EnforceRange(NormalizeFormat(text))
text = EnforceRange(text)
default:
normText = NormalizeFormat(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(normText)
if err != nil {
tmpl, err = f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text)
f.template = tmpl
f.text = text
return f, err
}
f.text = normText
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 any) 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
}