-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex_builder.go
134 lines (126 loc) · 3.38 KB
/
index_builder.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
package structexplorer
import (
"fmt"
"html/template"
"log/slog"
"runtime/debug"
"strconv"
"strings"
)
type indexDataBuilder struct {
data indexData
seq int
notLive bool
selectID string // id of the added fieldList (select element)
}
func newIndexDataBuilder() *indexDataBuilder {
b := new(indexDataBuilder)
b.data = indexData{
Script: template.JS(scriptJS),
Style: template.CSS(styleCSS),
}
return b
}
type cellInfo struct {
entriesCount int
hasZeros bool
}
func (b *indexDataBuilder) build(row, column int, access objectAccess) cellInfo {
// check room
for len(b.data.Rows) <= row {
b.data.Rows = append(b.data.Rows, tableRow{})
}
for len(b.data.Rows[row].Cells) <= column {
b.data.Rows[row].Cells = append(b.data.Rows[row].Cells, fieldList{})
}
// copy fields into entries
hasZeros := false
entries := []fieldEntry{}
currentValue := access.Value()
for _, each := range newFields(currentValue) {
valString := safeComputeValueString(each)
if isZeroPrintstring(valString) {
hasZeros = true
if access.hideZeros {
continue
}
}
label := each.displayKey()
entryKey := each.key
// if the access is part of a large slice or array
// then offset both the key and label
if access.sliceRange.size() > 1 {
ik, _ := strconv.Atoi(entryKey)
label = strconv.Itoa(ik + access.sliceRange.from)
entryKey = label
}
entries = append(entries, fieldEntry{
Label: label,
Key: entryKey,
Type: each.Type,
ValueString: valString,
})
}
entries = applyFieldNamePadding(entries)
size := computeSizeOfWidestEntry(entries)
// adjust label so that table cell width is used to display select options
fieldListLabel := access.label
if size > len(fieldListLabel) {
fieldListLabel += strings.Repeat(" ", size-len(access.label))
}
newSelectID := fmt.Sprintf("id%d", b.seq)
typ := access.typeName
if typ == "" {
// when using Follow, the type is not set/known
typ = fmt.Sprintf("%T", currentValue)
}
b.data.Rows[row].Cells[column] = fieldList{
Row: row,
Column: column,
Path: strings.Join(access.path, "."),
Label: template.HTML(fieldListLabel),
Fields: entries,
Type: typ,
IsRoot: access.isRoot,
HasZeros: hasZeros,
SelectSize: len(entries),
SelectID: newSelectID,
NotLive: b.notLive,
}
b.selectID = newSelectID
b.seq++
return cellInfo{entriesCount: len(entries), hasZeros: hasZeros}
}
func safeComputeValueString(fa fieldAccess) string {
if s, ok := tryComputeValueString(fa); ok {
return ellipsis(s)
}
return ellipsis(fallbackPrintString(fa.value()))
}
func tryComputeValueString(fa fieldAccess) (string, bool) {
// capture panics
defer func() {
if err := recover(); err != nil {
slog.Error("[structexplorer] failed to get value of entry, fallback display",
"field", fa.key, "field.label", fa.label,
"field.type", fa.Type, "owner.type", fmt.Sprintf("%T", fa.owner),
"err", err)
full := string(debug.Stack())
methodToken := "structexplorer.printString"
idx := strings.Index(full, methodToken)
fmt.Println(full[:idx+len(methodToken)], "... (more stack left out)")
return
}
}()
return ellipsis(printString(fa.value())), true
}
func computeSizeOfWidestEntry(list []fieldEntry) int {
size := 0
for _, each := range list {
s := len(each.Label) + len(": ") + len(each.ValueString)
if s > size {
size = s
}
}
return size
}