-
Notifications
You must be signed in to change notification settings - Fork 1
/
print.go
209 lines (184 loc) · 4.72 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
package cli
import (
"encoding/json"
"fmt"
"io"
"os"
"reflect"
"bytes"
"sort"
"strings"
"text/tabwriter"
"gopkg.in/yaml.v2"
)
var (
// JSONHTMLEscape controls the SetEscapeHTML option in the "json" output
// format. If this is true then problematic HTML characters will be escaped
// inside JSON quoted strings.
//
// By default this is disabled to not interfere with readability of the
// output. If you are rendering output in an HTML context you should enable
// this feature.
JSONHTMLEscape = false
)
// Print encodes the value using the given encoding and then prints it to the
// standard output. Accepted encodings are "json", "yml", "yaml", "table" and
// "raw". If encoding is the empty string this function defaults to "table"
// encoding.
//
// Usually the encoding is controlled via command line flags of your application
// so the user can select in what format the output should be returned.
//
// Accepted encodings
//
// "table": value is printed via a tab writer (see below)
// "json": value is printed as indented JSON
// "yaml": value is printed as YAML
// "raw": value is printed via fmt.Println
//
// Table encoding
//
// If the "table" encoding is used, the reflection API is used to print all
// exported fields of the value via a tab writer. The columns will be the
// UPPERCASE field names or whatever you set in the "table" tag of the
// corresponding field. Field names with a "table" tag set to "-" are omitted.
// When the "table" encoding is used the value must either be a struct, pointer
// to a struct, a slice or an array.
func Print(encoding string, value interface{}) error {
return PrintWriter(encoding, value, os.Stdout)
}
// PrintWriter is like Print but lets the caller inject an io.Writer.
func PrintWriter(encoding string, value interface{}, w io.Writer) error {
switch strings.ToLower(encoding) {
case "json":
return printJSON(value, w)
case "yml", "yaml":
return printYAML(value, w)
case "table", "":
return printTable(value, w)
case "raw":
return printRaw(value, w)
default:
return fmt.Errorf("unknown encoding %q", encoding)
}
}
// MustPrint is exactly like Print but panics if an error occurs.
func MustPrint(encoding string, i interface{}) {
err := Print(encoding, i)
if err != nil {
panic(err)
}
}
func printRaw(i interface{}, w io.Writer) error {
_, err := fmt.Fprintln(w, i)
return err
}
func printJSON(i interface{}, w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.SetEscapeHTML(JSONHTMLEscape)
return enc.Encode(i)
}
func printYAML(i interface{}, w io.Writer) error {
out, err := yaml.Marshal(i)
if err != nil {
return err
}
_, err = fmt.Fprintln(w, string(out))
return err
}
func printTable(v interface{}, w io.Writer) error {
val := reflect.ValueOf(v)
t := val.Type()
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var isArray bool
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
isArray = true
t = t.Elem()
}
if t.Kind() != reflect.Struct {
if isArray {
for i := 0; i < val.Len(); i++ {
_, err := fmt.Fprintln(w, val.Index(i))
if err != nil {
return err
}
}
return nil
}
return fmt.Errorf("cannot print type %T as table (kind %v)", v, t.Kind())
}
type field struct {
Name string
Index int
}
var fields []field
var header string
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
name := strings.ToUpper(f.Name)
if t := f.Tag.Get("table"); t != "" {
if t == "-" {
continue
}
name = t
}
fields = append(fields, field{Name: name, Index: i})
header += name + "\t"
}
records := []map[string]string{}
if isArray {
for i := 0; i < val.Len(); i++ {
rr := map[string]string{}
for _, f := range fields {
elem := val.Index(i).Field(f.Index).Interface()
switch x := elem.(type) {
case map[string]string:
rr[f.Name] = stringMap(x)
default:
rr[f.Name] = fmt.Sprint(elem)
}
}
records = append(records, rr)
}
} else {
rr := map[string]string{}
for _, f := range fields {
rr[f.Name] = fmt.Sprint(val.Field(f.Index).Interface())
}
records = append(records, rr)
}
tw := tabwriter.NewWriter(w, 8, 8, 2, ' ', 0)
header = strings.TrimSpace(header) + "\n"
_, err := fmt.Fprint(tw, header)
if err != nil {
return err
}
for _, record := range records {
for _, f := range fields {
_, err = fmt.Fprint(tw, record[f.Name]+"\t")
if err != nil {
return err
}
}
fmt.Fprint(tw, "\n")
}
return tw.Flush()
}
func stringMap(m map[string]string) string {
buf := new(bytes.Buffer)
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys{
if i != 0 {
buf.WriteString(" ")
}
fmt.Fprintf(buf, "%s:%s", k, m[k])
}
return buf.String()
}