Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
+1
-1
@@ -92,7 +92,7 @@ func checkColumns(s nodeScope) error {
|
||||
if !ok || !t.record {
|
||||
return nil
|
||||
}
|
||||
for _, name := range recordColumns(t) {
|
||||
for _, name := range sortedNames(t.fields) {
|
||||
if err := check(path, name, t.fields[name]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-3
@@ -88,7 +88,7 @@ func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
||||
return nil
|
||||
}
|
||||
// A record-only template's format renders nothing, so weigh the columns, not the format.
|
||||
columns := recordColumns(t)
|
||||
columns := sortedNames(t.fields)
|
||||
reads := false
|
||||
for _, name := range columns {
|
||||
reads = reads || c.readsPath(t.fields[name])
|
||||
@@ -148,7 +148,7 @@ func readsTable(n node, a arm) bool {
|
||||
if !isTemplate {
|
||||
return false
|
||||
}
|
||||
_, isTable := t.fields[a.key].(*table)
|
||||
_, isTable := t.head(a.key).(*table)
|
||||
return isTable
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ func (w *drawWalk) walk(n node, at drawAt) {
|
||||
// pins entered, so a selected row renders only its own cells.
|
||||
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
|
||||
if a, reads := refRead(from, e.label); reads {
|
||||
tr := tableReadOf(from.(*template).fields[a.key], a, e.to)
|
||||
tr := tableReadOf(from.(*template).head(a.key), a, e.to)
|
||||
if k := (readKey{at.group, a.path, at.alt.key()}); !w.read[k] {
|
||||
w.read[k] = true
|
||||
w.reads = append(w.reads, pathRead{at, a, tr})
|
||||
|
||||
@@ -212,7 +212,7 @@ func checkOwnFamily(t *template) error {
|
||||
seen[n] = true
|
||||
for _, e := range renderEdges(n) {
|
||||
if a, isRef := refRead(n, e.label); isRef {
|
||||
if head, isTable := n.(*template).fields[a.key].(*table); isTable && head.family() == own.family() {
|
||||
if head, isTable := n.(*template).head(a.key).(*table); isTable && head.family() == own.family() {
|
||||
return e, head, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +146,6 @@ func paths(n node) []string {
|
||||
case *template:
|
||||
out := []string{""}
|
||||
for _, name := range sortedNames(n.fields) {
|
||||
if isRef(name) { // a binding, not a path segment
|
||||
continue
|
||||
}
|
||||
for _, p := range paths(n.fields[name]) {
|
||||
out = append(out, join(name, p))
|
||||
}
|
||||
|
||||
@@ -78,14 +78,9 @@ func contained(n node) []namedNode {
|
||||
}
|
||||
}
|
||||
|
||||
// named skips a bound {/path} key: it is a render edge, not containment, so using
|
||||
// it as a path segment would report a node under a path that does not reach it.
|
||||
func named(m map[string]node) []namedNode {
|
||||
out := make([]namedNode, 0, len(m))
|
||||
for _, name := range sortedNames(m) {
|
||||
if isRef(name) {
|
||||
continue
|
||||
}
|
||||
out = append(out, namedNode{name: name, node: m[name]})
|
||||
}
|
||||
return out
|
||||
@@ -134,8 +129,8 @@ func renderEdges(n node) []renderEdge {
|
||||
var es []renderEdge
|
||||
add := func(name, operand string) {
|
||||
a := splitArm(name, n.refs)
|
||||
c, ok := n.fields[a.key]
|
||||
if !ok {
|
||||
c := n.head(a.key)
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
for _, leaf := range pathLeaves(c, a.tail) {
|
||||
|
||||
@@ -21,7 +21,7 @@ type draw struct {
|
||||
}
|
||||
|
||||
// readField renders one arm of a token. An arm's key is a sibling field or a
|
||||
// reference linkRefs bound into fields. A name the expansion holds — a level some
|
||||
// reference linkRefs bound into refHeads. A name the expansion holds — a level some
|
||||
// token addresses by dotted path, or a field an operand reads — is drawn once and
|
||||
// kept, so {place.postal-code} and {place.locality} read one row, either read twice
|
||||
// gives one value, and a shown operand is the operand computed. Every other name is
|
||||
@@ -32,13 +32,13 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
|
||||
if len(a.tail) > 0 {
|
||||
panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key))
|
||||
}
|
||||
return draw{text: render(s, t.fields[a.key], sc)}
|
||||
return draw{text: render(s, t.head(a.key), sc)}
|
||||
}
|
||||
d := readHold(held, sc, a)
|
||||
if r, done := d.value[a.path]; done {
|
||||
return r
|
||||
}
|
||||
leaf, err := walkPath(t.fields[a.key], a.tail, pathWalk{pins: &d.pins, draws: &pathDraws{s: s, held: d, a: &a}})
|
||||
leaf, err := walkPath(t.head(a.key), a.tail, pathWalk{pins: &d.pins, draws: &pathDraws{s: s, held: d, a: &a}})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err))
|
||||
}
|
||||
|
||||
+2
-2
@@ -52,12 +52,12 @@ func heldHeads(t *template) []string {
|
||||
func heldNodes(t *template, head string, readers []reader) map[node]bool {
|
||||
held := map[node]bool{}
|
||||
if _, isPath := t.bound[head]; !isPath {
|
||||
operandDraw(t.fields[head], held)
|
||||
operandDraw(t.head(head), held)
|
||||
return held
|
||||
}
|
||||
for _, r := range readers {
|
||||
if a := splitArm(r.name, t.refs); a.key == head {
|
||||
coverPath(t.fields[head], a.tail, held)
|
||||
coverPath(t.head(head), a.tail, held)
|
||||
}
|
||||
}
|
||||
return held
|
||||
|
||||
@@ -49,7 +49,7 @@ func (*null) isNode() {}
|
||||
type template struct {
|
||||
// Filled by `compileString`, `compileTemplate` and `table.compileWhole`:
|
||||
format string
|
||||
fields map[string]node // `linkTemplateRefs` adds each bound reference here
|
||||
fields map[string]node
|
||||
repeat int
|
||||
separator string
|
||||
datatype DataType
|
||||
@@ -78,19 +78,19 @@ type template struct {
|
||||
|
||||
// Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree:
|
||||
refs map[string]refBinding // each reference the format reads -> what it is bound to
|
||||
refHeads map[string]node // each refBinding.key -> the category it names
|
||||
readsColumn *columnRead // set when the format is one reference alone reading a record's column
|
||||
drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under
|
||||
}
|
||||
|
||||
func (*template) isNode() {}
|
||||
|
||||
// field is the node a path segment names; a binding is a render edge, not a field.
|
||||
func (t *template) field(seg string) (node, bool) {
|
||||
if isRef(seg) {
|
||||
return nil, false
|
||||
// head is the node an arm's key names: a sibling field, or a reference's category.
|
||||
func (t *template) head(key string) node {
|
||||
if isRef(key) {
|
||||
return t.refHeads[key]
|
||||
}
|
||||
n, ok := t.fields[seg]
|
||||
return n, ok
|
||||
return t.fields[key]
|
||||
}
|
||||
|
||||
// compile converts parsed JSON — a category or an inline template — into a node tree,
|
||||
@@ -277,8 +277,6 @@ func compileChoice(items []any, pos position) (node, error) {
|
||||
}
|
||||
c.cum = cum
|
||||
}
|
||||
// Computed before linkRefs binds references into the items: a binding is keyed
|
||||
// by a reference sigil, which paths skips, so the set is the same after.
|
||||
c.shared = sharedPaths(c.items)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
child, ok := n.field(tail[0])
|
||||
child, ok := n.fields[tail[0]]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no field %q", tail[0])
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ func recordOf(n node) (*template, []Column, error) {
|
||||
if !ok {
|
||||
return nil, nil, errors.New("names a choice, not a template; a record is a template whose fields are its columns")
|
||||
}
|
||||
names := recordColumns(t)
|
||||
names := sortedNames(t.fields)
|
||||
if len(names) == 0 {
|
||||
return nil, nil, ErrNoColumns
|
||||
}
|
||||
@@ -274,16 +274,3 @@ func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Re
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// recordColumns is the sorted non-reference field names — the columns a record
|
||||
// projects. A {/path} binding is carried in fields under its root path, so only a
|
||||
// name that is not a reference is a column.
|
||||
func recordColumns(t *template) []string {
|
||||
var names []string
|
||||
for _, name := range sortedNames(t.fields) {
|
||||
if !isRef(name) {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
+4
-6
@@ -68,7 +68,7 @@ func refSegments(name string, folder []string) ([]string, error) {
|
||||
}
|
||||
|
||||
// linkRefs resolves every reference in the assembled tree. The head of the path —
|
||||
// up to the category it names — is bound into the referring template's fields
|
||||
// up to the category it names — is bound into the referring template's refHeads
|
||||
// under its root path, and the rest reads into it the way a sibling path does, so
|
||||
// a reference is held like a sibling and two spellings of one target are one
|
||||
// draw. It runs once, after all data is merged, so a reference sees the final
|
||||
@@ -91,10 +91,8 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
if t.fields == nil {
|
||||
t.fields = map[string]node{}
|
||||
}
|
||||
t.refs = make(map[string]refBinding, len(names))
|
||||
t.refHeads = make(map[string]node, len(names))
|
||||
for _, name := range names {
|
||||
segments, err := refSegments(name, folder)
|
||||
if err != nil {
|
||||
@@ -111,7 +109,7 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
|
||||
if err := checkPath(target, tail, key); err != nil {
|
||||
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||
}
|
||||
t.fields[key] = target
|
||||
t.refHeads[key] = target
|
||||
t.refs[name] = refBinding{key, tail}
|
||||
}
|
||||
if err := t.compileFormat(); err != nil {
|
||||
@@ -134,7 +132,7 @@ func columnReadOf(t *template) *columnRead {
|
||||
return nil
|
||||
}
|
||||
a := splitArm(name, t.refs)
|
||||
target, isTemplate := t.fields[a.key].(*template)
|
||||
target, isTemplate := t.head(a.key).(*template)
|
||||
if !isTemplate || !target.record || len(a.tail) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
|
||||
|
||||
This round, in order:
|
||||
|
||||
- Keep reference bindings out of `template.fields`, so no walk over a template's fields
|
||||
needs the `isRef` filter `named`, `paths`, `recordColumns` and `template.field` carry.
|
||||
- Compile a template's format once: `compileFormat` runs again from `linkTemplateRefs`,
|
||||
so `ops`, `bound` and `held` are provisional until linking and its fences run twice.
|
||||
- Parse a format once and have the fences read `t.ops`: `eachToken` rescans the format
|
||||
|
||||
@@ -158,7 +158,7 @@ func (p *valueProof) template(t *template) proven {
|
||||
case !isFunc:
|
||||
var leaves []node
|
||||
for _, a := range splitArms(body, t.refs) {
|
||||
leaves = append(leaves, pathLeaves(t.fields[a.key], a.tail)...)
|
||||
leaves = append(leaves, pathLeaves(t.head(a.key), a.tail)...)
|
||||
}
|
||||
return p.unite(leaves)
|
||||
case name == "calc":
|
||||
|
||||
Reference in New Issue
Block a user