-
Notifications
You must be signed in to change notification settings - Fork 7
/
table.go
201 lines (189 loc) · 5.01 KB
/
table.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
package output
import (
"encoding/json"
"fmt"
"math"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/UpCloudLtd/upcloud-cli/v3/internal/ui"
"github.com/UpCloudLtd/upcloud-cli/v3/internal/validation"
)
// TableRow represents a single row of data in a table
type TableRow []interface{}
// TableColumn defines how a particular column is rendered
type TableColumn struct {
Header string
Key string
Hidden bool
config *table.ColumnConfig
Colour text.Colors
Format func(val interface{}) (text.Colors, string, error)
}
// Table represents command output rendered as a table
type Table struct {
Columns []TableColumn
Rows []TableRow
EmptyMessage string
HideHeader bool
}
func (s Table) asListOfMaps() []map[string]interface{} {
jmap := []map[string]interface{}{}
for _, row := range s.Rows {
jrow := map[string]interface{}{}
for i := range row {
jrow[s.Columns[i].Key] = row[i]
}
jmap = append(jmap, jrow)
}
return jmap
}
// MarshalJSON implements json.Marshaler
func (s Table) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(s.asListOfMaps(), "", " ")
}
// MarshalHuman returns table output in a human-readable form
func (s Table) MarshalHuman() ([]byte, error) {
if len(s.Rows) == 0 && len(s.EmptyMessage) > 0 {
return []byte(text.FgHiBlack.Sprintf("\n%s\n", s.EmptyMessage)), nil
}
t := &table.Table{}
columnKeyPos := make(map[string]int)
columnConfig := make(map[string]*table.ColumnConfig)
for pos, column := range s.Columns {
columnKeyPos[column.Key] = pos
}
t.ResetHeaders()
t.ResetFooters()
t.ResetRows()
t.SetStyle(defaultTableStyle)
/*
// TODO: reimplement this if/when necessary
if len(s.overrideColumnKeys) > 0 {
columnKeys = s.overrideColumnKeys
}
*/
var header table.Row
for _, column := range s.Columns {
pos, ok := columnKeyPos[column.Key]
if !ok {
continue
}
if column.Header == "" {
header = append(header, column.Key)
} else {
header = append(header, column.Header)
}
cfg := column.config
if cfg == nil {
cfg = &table.ColumnConfig{}
column.config = cfg
}
cfg.Number = pos + 1
if len(s.Rows) > 0 {
// See if the row value can be shows as numeric and if so, align right if not aligned
if err := validation.Numeric(s.Rows[0][pos]); err == nil && cfg.Align == text.AlignDefault {
cfg.Align = text.AlignRight
}
if _, ok := s.Rows[0][pos].(time.Time); ok && cfg.Transformer == nil {
cfg.Transformer = func(val interface{}) string {
tv, ok := val.(time.Time)
if !ok {
return fmt.Sprintf("%s", val)
}
return ui.FormatTime(tv)
}
}
if _, ok := s.Rows[0][pos].(float64); ok && cfg.Transformer == nil {
cfg.Transformer = func(val interface{}) string {
fv, ok := val.(float64)
if !ok {
return fmt.Sprintf("%s", val)
}
if _, frac := math.Modf(fv); frac != 0 {
return fmt.Sprintf("%s", val)
}
return fmt.Sprintf("%.2f", fv)
}
}
}
}
if len(header) > 0 {
t.AppendHeader(header)
}
var columnConfigs []table.ColumnConfig
for _, cfg := range columnConfig {
columnConfigs = append(columnConfigs, *cfg)
}
t.SetColumnConfigs(columnConfigs)
for _, row := range s.Rows {
var arow table.Row
for _, column := range s.Columns {
if _, ok := columnKeyPos[column.Key]; !ok {
continue
}
val := row[columnKeyPos[column.Key]]
if column.Format != nil {
colour, formatted, err := column.Format(val)
if err != nil {
return nil, fmt.Errorf("error formatting column '%v': %w", column.Key, err)
}
val = colour.Sprintf("%v", formatted)
} else if column.Colour != nil {
val = column.Colour.Sprintf("%v", val)
}
arow = append(arow, val)
}
if len(arow) > 0 {
t.AppendRow(arow)
}
}
// print one newline before and after to conform with details view (which is controlled by go-pretty)
return append([]byte{'\n'}, append([]byte(t.Render()), '\n')...), nil
}
// MarshalRawMap implements output.Output
func (s Table) MarshalRawMap() (map[string]interface{}, error) {
// TODO: make this better..
return map[string]interface{}{
"table": s.asListOfMaps(),
}, nil
}
var defaultTableStyle = table.Style{
Name: "DataTable",
Box: table.BoxStyle{
BottomLeft: " ",
BottomRight: " ",
BottomSeparator: " ",
Left: " ",
LeftSeparator: " ",
MiddleHorizontal: "─",
MiddleSeparator: " ",
MiddleVertical: " ",
PaddingLeft: " ",
PaddingRight: " ",
Right: " ",
RightSeparator: " ",
TopLeft: " ",
TopRight: " ",
TopSeparator: " ",
UnfinishedRow: " ",
},
Color: table.ColorOptions{
Footer: ui.DefaultHeaderColours,
Header: ui.DefaultHeaderColours,
Row: nil,
RowAlternate: nil,
},
Format: table.FormatOptions{
Footer: text.FormatDefault,
Header: text.FormatDefault,
Row: text.FormatDefault,
},
Options: table.Options{
DrawBorder: false,
SeparateColumns: true,
SeparateFooter: false,
SeparateHeader: true,
SeparateRows: false,
},
}