/
print.go
366 lines (294 loc) · 9.96 KB
/
print.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package print
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"github.com/tidwall/pretty"
"github.com/tomlazar/table"
"github.com/alcionai/corso/src/internal/common/color"
"github.com/alcionai/corso/src/internal/observe"
)
var (
outputAsJSON bool
outputAsJSONDebug bool
outputVerbose bool
)
type rootCmdCtx struct{}
// Adds a root cobra command to the context.
// Used to amend output controls like SilenceUsage or to retrieve
// the command's output writer.
func SetRootCmd(ctx context.Context, root *cobra.Command) context.Context {
return context.WithValue(ctx, rootCmdCtx{}, root)
}
// Gets the root cobra command from the context.
// If no command is found, returns a new, blank command.
func getRootCmd(ctx context.Context) *cobra.Command {
if ctx == nil {
return &cobra.Command{}
}
cmdIface := ctx.Value(rootCmdCtx{})
cmd, ok := cmdIface.(*cobra.Command)
if cmd == nil || !ok {
return &cobra.Command{}
}
return cmd
}
// adds the persistent flag --output to the provided command.
func AddOutputFlag(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.BoolVar(&outputAsJSON, "json", false, "output data in JSON format")
fs.BoolVar(&outputAsJSONDebug, "json-debug", false, "output all internal and debugging data in JSON format")
cobra.CheckErr(fs.MarkHidden("json-debug"))
fs.BoolVar(&outputVerbose, "verbose", false, "don't hide additional information")
}
// DisplayJSONFormat returns true if the printer plans to output as json.
func DisplayJSONFormat() bool {
return outputAsJSON || outputAsJSONDebug
}
// DisplayVerbose returns true if verbose output is enabled
func DisplayVerbose() bool {
return outputVerbose
}
// StderrWriter returns the stderr writer used in the root
// cmd. Returns nil if no root command is seeded.
func StderrWriter(ctx context.Context) io.Writer {
return getRootCmd(ctx).ErrOrStderr()
}
// ---------------------------------------------------------------------------------------------------------
// Exported interface
// ---------------------------------------------------------------------------------------------------------
// Only tells the CLI to only display this error, preventing the usage
// (ie, help) menu from displaying as well.
func Only(ctx context.Context, e error) error {
getRootCmd(ctx).SilenceUsage = true
return e
}
// Err prints the params to cobra's error writer (stdErr by default)
// if s is nil, prints nothing.
func Err(ctx context.Context, s ...any) {
cw := color.NewColorableWriter(color.Red, getRootCmd(ctx).ErrOrStderr())
s = append([]any{"Error:"}, s...)
out(ctx, cw, s...)
}
// Errf prints the params to cobra's error writer (stdErr by default)
// if s is nil, prints nothing.
// You should ideally be using SimpleError or OperationError.
func Errf(ctx context.Context, tmpl string, s ...any) {
cw := color.NewColorableWriter(color.Red, getRootCmd(ctx).ErrOrStderr())
tmpl = "Error: " + tmpl
outf(ctx, cw, tmpl, s...)
}
// Out prints the params to cobra's output writer (stdOut by default)
// if s is nil, prints nothing.
func Out(ctx context.Context, s ...any) {
out(ctx, getRootCmd(ctx).OutOrStdout(), s...)
}
// Out prints the formatted strings to cobra's output writer (stdOut by default)
// if t is empty, prints nothing.
func Outf(ctx context.Context, t string, s ...any) {
outf(ctx, getRootCmd(ctx).OutOrStdout(), t, s...)
}
// Info prints the params to cobra's error writer (stdErr by default)
// if s is nil, prints nothing.
func Info(ctx context.Context, s ...any) {
out(ctx, getRootCmd(ctx).ErrOrStderr(), s...)
}
// Info prints the formatted strings to cobra's error writer (stdErr by default)
// if t is empty, prints nothing.
func Infof(ctx context.Context, t string, s ...any) {
outf(ctx, getRootCmd(ctx).ErrOrStderr(), t, s...)
}
// Pretty prettifies and prints the value.
func Pretty(ctx context.Context, a any) {
if a == nil {
Err(ctx, "<nil>")
return
}
printPrettyJSON(ctx, getRootCmd(ctx).ErrOrStderr(), a)
}
// PrettyJSON prettifies and prints the value.
func PrettyJSON(ctx context.Context, p minimumPrintabler) {
if p == nil {
Err(ctx, "<nil>")
return
}
outputJSON(ctx, getRootCmd(ctx).ErrOrStderr(), p, outputAsJSONDebug)
}
// out is the testable core of exported print funcs
func out(ctx context.Context, w io.Writer, s ...any) {
if len(s) == 0 {
return
}
// observe bars needs to be flushed before printing
observe.Flush(ctx)
fmt.Fprint(w, s...)
fmt.Fprintf(w, "\n")
}
// outf is the testable core of exported print funcs
func outf(ctx context.Context, w io.Writer, t string, s ...any) {
if len(t) == 0 {
return
}
// observe bars needs to be flushed before printing
observe.Flush(ctx)
fmt.Fprintf(w, t, s...)
fmt.Fprintf(w, "\n")
}
// ---------------------------------------------------------------------------------------------------------
// Output control for backup list/details
// ---------------------------------------------------------------------------------------------------------
type Printable interface {
minimumPrintabler
// should list the property names of the values surfaced in Values()
Headers(skipID bool) []string
// list of values for tabular or csv formatting
// if the backing data is nil or otherwise missing,
// values should provide an empty string as opposed to skipping entries
Values(skipID bool) []string
}
type minimumPrintabler interface {
// reduces the struct to a minimized format for easier human consumption
MinimumPrintable() any
}
// Item prints the printable, according to the caller's requested format.
func Item(ctx context.Context, p Printable) {
printItem(ctx, getRootCmd(ctx).OutOrStdout(), p)
}
// print prints the printable items,
// according to the caller's requested format.
func printItem(ctx context.Context, w io.Writer, p Printable) {
if outputAsJSON || outputAsJSONDebug {
outputJSON(ctx, w, p, outputAsJSONDebug)
return
}
outputTable(ctx, w, []Printable{p})
}
// ItemProperties prints the printable either as in a single line or a json
// The difference between this and Item is that this one does not print the ID
func ItemProperties(ctx context.Context, p Printable) {
printItemProperties(ctx, getRootCmd(ctx).OutOrStdout(), p)
}
// print prints the printable items,
// according to the caller's requested format.
func printItemProperties(ctx context.Context, w io.Writer, p Printable) {
if outputAsJSON || outputAsJSONDebug {
outputJSON(ctx, w, p, outputAsJSONDebug)
return
}
outputOneLine(ctx, w, []Printable{p})
}
// All prints the slice of printable items,
// according to the caller's requested format.
func All(ctx context.Context, ps ...Printable) {
printAll(ctx, getRootCmd(ctx).OutOrStdout(), ps)
}
// printAll prints the slice of printable items,
// according to the caller's requested format.
func printAll(ctx context.Context, w io.Writer, ps []Printable) {
if len(ps) == 0 {
return
}
if outputAsJSON || outputAsJSONDebug {
outputJSONArr(ctx, w, ps, outputAsJSONDebug)
return
}
outputTable(ctx, w, ps)
}
// ------------------------------------------------------------------------------------------
// Tabular
// ------------------------------------------------------------------------------------------
// Table writes the printables in a tabular format. Takes headers from
// the 0th printable only.
func Table(ctx context.Context, ps []Printable) {
outputTable(ctx, getRootCmd(ctx).OutOrStdout(), ps)
}
// output to stdout the list of printable structs in a table
func outputTable(ctx context.Context, w io.Writer, ps []Printable) {
t := table.Table{
Headers: ps[0].Headers(false),
Rows: [][]string{},
}
for _, p := range ps {
t.Rows = append(t.Rows, p.Values(false))
}
// observe bars needs to be flushed before printing
observe.Flush(ctx)
_ = t.WriteTable(
w,
&table.Config{
ShowIndex: false,
Color: false,
AlternateColors: false,
})
}
// ------------------------------------------------------------------------------------------
// JSON
// ------------------------------------------------------------------------------------------
func outputJSON(ctx context.Context, w io.Writer, p minimumPrintabler, debug bool) {
if debug {
printJSON(ctx, w, p)
return
}
if debug {
printJSON(ctx, w, p)
} else {
printJSON(ctx, w, p.MinimumPrintable())
}
}
func outputJSONArr(ctx context.Context, w io.Writer, ps []Printable, debug bool) {
sl := make([]any, 0, len(ps))
for _, p := range ps {
if debug {
sl = append(sl, p)
} else {
sl = append(sl, p.MinimumPrintable())
}
}
printJSON(ctx, w, sl)
}
// output to stdout the list of printable structs as json.
func printJSON(ctx context.Context, w io.Writer, a any) {
// observe bars needs to be flushed before printing
observe.Flush(ctx)
bs, err := json.Marshal(a)
if err != nil {
fmt.Fprintf(w, "error formatting results to json: %v\n", err)
return
}
fmt.Fprintln(w, string(pretty.Pretty(bs)))
}
// output to stdout the list of printable structs as prettified json.
func printPrettyJSON(ctx context.Context, w io.Writer, a any) {
// observe bars needs to be flushed before printing
observe.Flush(ctx)
bs, err := json.MarshalIndent(a, "", " ")
if err != nil {
fmt.Fprintf(w, "error formatting results to json: %v\n", err)
return
}
fmt.Fprintln(w, string(pretty.Pretty(bs)))
}
// -------------------------------------------------------------------------------------------
// One line
// -------------------------------------------------------------------------------------------
// Output in the following format:
// Bytes Uploaded: 401 kB | Items Uploaded: 59 | Items Skipped: 0 | Errors: 0
func outputOneLine(ctx context.Context, w io.Writer, ps []Printable) {
// observe bars needs to be flushed before printing
observe.Flush(ctx)
headers := ps[0].Headers(true)
rows := [][]string{}
for _, p := range ps {
rows = append(rows, p.Values(true))
}
printables := []string{}
for _, row := range rows {
for i, col := range row {
printables = append(printables, fmt.Sprintf("%s: %s", headers[i], col))
}
}
fmt.Fprintln(w, strings.Join(printables, " | "))
}