-
Notifications
You must be signed in to change notification settings - Fork 474
/
structpretty.go
138 lines (112 loc) · 2.78 KB
/
structpretty.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
package structpretty
import (
"fmt"
"io"
"reflect"
"strings"
"github.com/spiffe/spire/pkg/common/cliprinter/internal/errorpretty"
)
// Print prints a struct prettily.
// It will print only easily printable types, and only to one
// level of depth. It will print arrays, slices, and maps if
// their keys and elements are also easily printable types.
func Print(msgs []interface{}, stdout, stderr io.Writer) error {
if len(msgs) == 0 {
return nil
}
for _, msg := range msgs {
if msg == nil {
continue
}
err := printStruct(msg, stdout, stderr)
if err != nil {
return err
}
}
return nil
}
func printStruct(msg interface{}, stdout, stderr io.Writer) error {
msgType := reflect.TypeOf(msg)
msgValue := reflect.ValueOf(msg)
// We also want to accept pointers to structs
if msgType.Kind() == reflect.Ptr {
if msgType.Elem().Kind() != reflect.Struct {
err := fmt.Errorf("cannot print unsupported type %q", msgType.Elem().Kind().String())
_ = errorpretty.Print(err, stdout, stderr)
return err
}
msgType = msgType.Elem()
msgValue = msgValue.Elem()
}
if msgType.Kind() != reflect.Struct {
err := fmt.Errorf("cannot print unsupported type %q", msgType.Kind().String())
_ = errorpretty.Print(err, stdout, stderr)
return err
}
builder := new(strings.Builder)
for i := 0; i < msgType.NumField(); i++ {
fieldType := msgType.Field(i)
fieldValue := msgValue.Field(i)
if !fieldType.IsExported() {
continue
}
if !isFieldTypePrintable(fieldType.Type) {
continue
}
n := fieldType.Name
v := fieldValue.Interface()
line := fmt.Sprintf("%s: %v\n", n, v)
builder.WriteString(line)
}
if builder.Len() > 0 {
_, err := fmt.Fprint(stdout, builder.String())
if err != nil {
return err
}
_, err = fmt.Fprintf(stdout, "\n")
if err != nil {
return err
}
}
return nil
}
func isFieldTypePrintable(t reflect.Type) bool {
if isUnprintableType(t) {
return false
}
switch t.Kind() {
case reflect.Array, reflect.Slice:
return isArrayPrintable(t)
case reflect.Map:
return isMapPrintable(t)
}
return true
}
func isArrayPrintable(t reflect.Type) bool {
return isCompositeTypePrintable(t.Elem())
}
func isMapPrintable(t reflect.Type) bool {
keyOk := isCompositeTypePrintable(t.Key())
elemOk := isCompositeTypePrintable(t.Elem())
return keyOk && elemOk
}
func isCompositeTypePrintable(t reflect.Type) bool {
return !isUnprintableType(t) && !isListType(t)
}
func isUnprintableType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Invalid, reflect.Chan, reflect.Func, reflect.Interface,
reflect.Ptr, reflect.Struct, reflect.UnsafePointer:
return true
default:
return false
}
}
func isListType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
return true
default:
return false
}
}