~bigbes/sr-ht-dolt

ref: 377c616a8a3b385f18a8d6509c7a6be3a31af2bd sr-ht-dolt/beads/raw.go -rw-r--r-- 5.3 KiB
377c616a — Eugene Blikh web: render memory bodies as markdown, and resolve their references 3 days ago
                                                                                
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
package beads

import "sourcecraft.dev/bigbes/sr-ht-dolt/browse"

// --- the stored rows behind one issue ----------------------------------------
//
// Everything else in this package is a reading: the Issue struct names the
// columns bd surfaces and drops the rest, humanizeEvent turns two JSON blobs
// into a sentence, an Edge keeps an id and a type out of a dependency row. Each
// of those is a choice, and a choice can be wrong — a column bd added that no
// field here models, a status this projection mapped to the wrong category, an
// event whose stored old_value says something the humanized line does not — and
// none of it is visible from the pane that made the choice.
//
// So the detail modes also carry the rows they were built from, as read. The
// point is to be checkable against the rendering above it, which is why nothing
// here is normalised, sorted or rewritten: the columns come in the order the
// table was read in, the values are the strings browse returned, and a cell that
// holds no value says so rather than rendering as one.

// RawMax caps how many rows of one related table the raw section carries. The
// issues row is one row by construction; a busy issue's events or comments are
// not, and a section that dumps three hundred audit rows is not a check on the
// rendering, it is a second page nobody reads. RawTable.Matched says how many
// rows actually belong to the issue, so a capped table is visibly capped.
const RawMax = 50

// RawCell is one stored cell: the column it came from, the value browse rendered
// for it, and whether the cell holds a value at all.
//
// Null is the whole reason this type exists rather than a plain string pair.
// browse renders a cell that holds no value as the text "NULL", which is exactly
// what a cell storing those four characters renders as, and cell() resolves that
// collision by flattening a real NULL to "". That is right everywhere else here
// — a missing closed_at is rendered as nothing — and wrong in this one place: a
// view whose purpose is to be compared against the database may not report an
// absent cell and an empty string as the same thing.
//
// Value is "" for a null cell rather than the "NULL" browse printed. Carrying
// that text would put the collision straight back: a consumer that renders Value
// without reading Null would print "NULL" for both readings again, which is the
// state this type exists to leave behind.
type RawCell struct {
	Column string
	Value  string
	Null   bool
}

// RawRow is one stored row: its cells in the order the columns were read in.
// Column order is data here — it is the table's own order, and a raw view that
// sorted it would be hiding one of the things it is meant to show.
type RawRow struct {
	Cells []RawCell
}

// RawTable is the rows one table contributed to one issue.
//
// Matched is how many rows of the table belong to the issue; Rows holds at most
// RawMax of them, in table order. The two differ only when the cap bit, and that
// difference is what the pane says out loud.
type RawTable struct {
	Table   string
	Rows    []RawRow
	Matched int
}

// Clipped reports that the table had more rows for this issue than RawMax, so
// Rows is the first of them and not all of them.
func (t RawTable) Clipped() bool { return t.Matched > len(t.Rows) }

// rawRow renders one row as stored, in the page's own column order.
//
// A cell's nullness is read exactly as cell() reads it — through rowCells.isNull,
// which is the one place in this package that decides — so the raw section and
// the fields above it can never disagree about which cells hold a value.
func rawRow(columns []string, r rowCells) RawRow {
	out := RawRow{Cells: make([]RawCell, 0, len(columns))}
	for i, name := range columns {
		if i >= len(r.values) {
			// A row shorter than its header is a malformed page, not a null cell:
			// there is no cell here to report either way, so it is left out.
			break
		}
		if r.isNull(i) {
			out.Cells = append(out.Cells, RawCell{Column: name, Null: true})
			continue
		}
		out.Cells = append(out.Cells, RawCell{Column: name, Value: r.values[i]})
	}
	return out
}

// rawTableOf collects the rows of a page that belong to one issue, capped at
// RawMax and counted whole. A table with no rows for this issue yields nil — the
// pane lists the tables that had something to say, not every table it read.
func rawTableOf(name string, page *browse.RowPage, match func(map[string]int, rowCells) bool) *RawTable {
	if page == nil {
		return nil
	}
	cols := indexCols(page.Columns)
	tbl := RawTable{Table: name}
	for _, r := range rowsOf(page) {
		if !match(cols, r) {
			continue
		}
		tbl.Matched++
		if len(tbl.Rows) < RawMax {
			tbl.Rows = append(tbl.Rows, rawRow(page.Columns, r))
		}
	}
	if tbl.Matched == 0 {
		return nil
	}
	return &tbl
}

// matchColumn matches the rows whose named column holds the wanted id — the
// shape of every per-issue table here except dependencies, which has two such
// columns and gets its own matcher at the call site.
func matchColumn(column, want string) func(map[string]int, rowCells) bool {
	return func(cols map[string]int, r rowCells) bool {
		return cell(cols, r, column) == want
	}
}

// addRaw appends a table's rows to the raw section, skipping the tables that had
// none.
func (d *Data) addRaw(t *RawTable) {
	if t == nil {
		return
	}
	d.Raw = append(d.Raw, *t)
}