Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/local-app/pkg/prettyprint/prettyprint.go
2500 views
1
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package prettyprint
6
7
import (
8
"fmt"
9
"io"
10
"reflect"
11
"strconv"
12
"strings"
13
"text/tabwriter"
14
15
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
16
)
17
18
func reflectTabular[T any](data []T) (header []string, rows []map[string]string, err error) {
19
type field struct {
20
Name string
21
Field *reflect.StructField
22
}
23
var fields []field
24
25
var dt T
26
t := reflect.TypeOf(dt)
27
if t.Kind() == reflect.Ptr {
28
t = t.Elem()
29
}
30
if t.Kind() != reflect.Struct {
31
return nil, nil, MarkExceptional(fmt.Errorf("can only reflect tabular data from structs"))
32
}
33
for i := 0; i < t.NumField(); i++ {
34
f := t.Field(i)
35
switch f.Type.Kind() {
36
case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
37
default:
38
continue
39
}
40
name := f.Tag.Get("print")
41
if name == "" {
42
name = f.Name
43
}
44
fields = append(fields, field{Name: name, Field: &f})
45
header = append(header, name)
46
}
47
48
rows = make([]map[string]string, 0, len(rows))
49
for _, row := range data {
50
r := make(map[string]string)
51
for _, f := range fields {
52
v := reflect.ValueOf(row)
53
if v.Kind() == reflect.Ptr {
54
v = v.Elem()
55
}
56
v = v.FieldByName(f.Field.Name)
57
switch v.Kind() {
58
case reflect.String:
59
r[f.Name] = v.String()
60
case reflect.Bool:
61
r[f.Name] = FormatBool(v.Bool())
62
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
63
r[f.Name] = strconv.FormatInt(v.Int(), 10)
64
}
65
}
66
rows = append(rows, r)
67
}
68
69
return header, rows, nil
70
}
71
72
type WriterFormat int
73
74
const (
75
// WriterFormatWide makes the writer produce wide formatted output, e.g.
76
// FIELD ONE FIELD TWO FIELD THREE
77
// valueOne valueTwo valueThree
78
// valueOne valueTwo valueThree
79
WriterFormatWide WriterFormat = iota
80
81
// WriterFormatNarrow makes the writer produce narrow formatted output, e.g.
82
// Field: value
83
WriterFormatNarrow
84
)
85
86
type Writer[T any] struct {
87
Out io.Writer
88
Format WriterFormat
89
Field string
90
}
91
92
// Write writes the given tabular data to the writer
93
func (w Writer[T]) Write(data []T) error {
94
header, rows, err := reflectTabular(data)
95
if err != nil {
96
return err
97
}
98
99
tw := tabwriter.NewWriter(w.Out, 0, 4, 1, ' ', 0)
100
defer tw.Flush()
101
102
switch {
103
case w.Field != "":
104
return w.writeField(tw, header, rows)
105
case w.Format == WriterFormatNarrow:
106
return w.writeNarrowFormat(tw, header, rows)
107
default:
108
return w.writeWideFormat(tw, header, rows)
109
}
110
}
111
112
// writeField writes a single field of the given tabular data to the writer
113
func (w Writer[T]) writeField(tw *tabwriter.Writer, header []string, rows []map[string]string) error {
114
var found bool
115
for _, h := range header {
116
if h == w.Field {
117
found = true
118
break
119
}
120
}
121
if !found {
122
return AddResolution(fmt.Errorf("unknown field: %s", w.Field), "use one of the following fields: "+strings.Join(header, ", "))
123
}
124
125
for _, row := range rows {
126
val := row[w.Field]
127
if val == "" {
128
continue
129
}
130
_, err := tw.Write([]byte(fmt.Sprintf("%s\n", val)))
131
if err != nil {
132
return err
133
}
134
}
135
return nil
136
}
137
138
// writeNarrowFormat writes the given tabular data to the writer in a long format
139
func (w Writer[T]) writeNarrowFormat(tw *tabwriter.Writer, header []string, rows []map[string]string) error {
140
for _, row := range rows {
141
for _, h := range header {
142
fieldName := Capitalize(h)
143
fieldName = strings.ReplaceAll(fieldName, "id", "ID")
144
145
_, err := tw.Write([]byte(fmt.Sprintf("%s:\t%s\n", fieldName, row[h])))
146
if err != nil {
147
return err
148
}
149
}
150
}
151
return nil
152
}
153
154
// writeWideFormat writes the given tabular data to the writer in a short format
155
func (w Writer[T]) writeWideFormat(tw *tabwriter.Writer, header []string, rows []map[string]string) error {
156
for _, h := range header {
157
_, err := tw.Write([]byte(fmt.Sprintf("%s\t", strings.ToUpper(h))))
158
if err != nil {
159
return err
160
}
161
}
162
_, _ = tw.Write([]byte("\n"))
163
for _, row := range rows {
164
for _, h := range header {
165
_, err := tw.Write([]byte(fmt.Sprintf("%s\t", row[h])))
166
if err != nil {
167
return err
168
}
169
}
170
_, err := tw.Write([]byte("\n"))
171
if err != nil {
172
return err
173
}
174
}
175
return nil
176
}
177
178
// FormatBool returns "true" or "false" depending on the value of b.
179
func FormatBool(b bool) string {
180
return strconv.FormatBool(b)
181
}
182
183
// FormatWorkspacePhase returns a user-facing representation of the given workspace phase
184
func FormatWorkspacePhase(phase v1.WorkspaceInstanceStatus_Phase) string {
185
return strings.ToLower(strings.TrimPrefix(phase.String(), "PHASE_"))
186
}
187
188
// Capitalize capitalizes the first letter of the given string
189
func Capitalize(s string) string {
190
if s == "" {
191
return ""
192
}
193
if len(s) == 1 {
194
return strings.ToUpper(s)
195
}
196
197
return strings.ToUpper(s[0:1]) + s[1:]
198
}
199
200