From 54cb52b4831aabeaf48232d2c0e16b33f0e1a8c1 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 5 Oct 2026 21:10:49 +0200 Subject: [PATCH] Answer architecture round 1: one categoryPath, the walk's label named label, checkAncestors split from proveParent, and file items 115 and 116 --- calc.go | 4 ++-- data.go | 13 ++++++++----- datatype.go | 10 +++++----- graph.go | 39 ++++++++++++++++++++------------------- inline.go | 14 +++++++------- name.go | 6 +++--- namefence.go | 8 ++++---- node.go | 2 +- reference.go | 28 ++++++++++++++-------------- shipped.go | 2 +- table.go | 46 +++++++++++++++++++++++++++------------------- todo.md | 12 +++++++++++- valueproof.go | 6 +++--- 13 files changed, 106 insertions(+), 84 deletions(-) diff --git a/calc.go b/calc.go index 69f4b67..cb4f781 100644 --- a/calc.go +++ b/calc.go @@ -23,13 +23,13 @@ func checkCalcFields(args []string, fields map[string]node) error { } // checkCalcNames holds each calc of t reading a name to the checks checkCalcFields makes of a field. -func checkCalcNames(path string, t *template) error { +func checkCalcNames(label string, t *template) error { for _, o := range t.compiled.ops { if o.Fn != builtinfunc.CalcName || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) { continue } if err := checkOperands(o.Args[0], builtinfunc.ParsedCalc(o.Args[0]), operandNodes(o)); err != nil { - return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err) + return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err) } } return nil diff --git a/data.go b/data.go index 522821b..21a6138 100644 --- a/data.go +++ b/data.go @@ -68,7 +68,7 @@ func mergeChildren(dst, src map[string]node) { // nodeScope is the set of nodes one validation pass covers: the categories one load // binds, or a single inline node. -type nodeScope func(fn func(path string, n node) error) error +type nodeScope func(fn func(label string, n node) error) error // categorySite is a loaded category and where it sits: the folder holding it, that // folder's path, and its own dot path. @@ -97,11 +97,14 @@ func categorySites(root *folder) []categorySite { } func siteIn(dir []string, in *folder, name string) categorySite { - return categorySite{dir: dir, path: join(strings.Join(dir, "."), name), in: in, n: in.children[name]} + return categorySite{dir: dir, path: categoryPath(dir, name), in: in, n: in.children[name]} } +// categoryPath is the dot path of the category name in the folder dir. +func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) } + func sitesScope(sites []categorySite) nodeScope { - return func(fn func(path string, n node) error) error { + return func(fn func(label string, n node) error) error { for _, s := range sites { if err := eachNode(s.n, s.path, fn); err != nil { return err @@ -112,7 +115,7 @@ func sitesScope(sites []categorySite) nodeScope { } func inlineScope(n node, label string) nodeScope { - return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) } + return func(fn func(label string, m node) error) error { return eachNode(n, label, fn) } } // binding is one scope's way through bind, the one load pipeline; its fields are where @@ -159,7 +162,7 @@ func (b binding) bind() error { // first. func checkNodeFences(s nodeScope) error { mem := renderCounts{} - if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil { + if err := s(func(label string, n node) error { return repeatCheck(label, n, mem) }); err != nil { return err } return s((&valueProof{}).checkDatatype) diff --git a/datatype.go b/datatype.go index 063fc3e..1e58d36 100644 --- a/datatype.go +++ b/datatype.go @@ -59,8 +59,8 @@ func datatypeOf(m map[string]any, pos position) (DataType, error) { // after the columns its items read, so a column read is named before its readers. func checkColumns(s nodeScope) error { checked := map[node]bool{} - var check func(path, name string, column node) error - check = func(path, name string, column node) error { + var check func(label, name string, column node) error + check = func(label, name string, column node) error { if checked[column] { return nil } @@ -79,18 +79,18 @@ func checkColumns(s nodeScope) error { first := columnDatatype(column) for _, it := range items[1:] { if d := itemDatatype(it); d != first { - return fmt.Errorf("%s: field %q: %w", path, name, disagreement(items[0], first, it, d)) + return fmt.Errorf("%s: field %q: %w", label, name, disagreement(items[0], first, it, d)) } } return nil } - return s(func(path string, n node) error { + return s(func(label string, n node) error { t, ok := n.(*template) if !ok || !t.isRecord { return nil } for _, name := range sortedNames(t.fields) { - if err := check(path, name, t.fields[name]); err != nil { + if err := check(label, name, t.fields[name]); err != nil { return err } } diff --git a/graph.go b/graph.go index 5ce2f48..76f905f 100644 --- a/graph.go +++ b/graph.go @@ -8,28 +8,29 @@ import ( "github.com/larvit/fejkdata/internal/invariant" ) -// eachNode visits n and every node contained within it once, passing the dot path -// that reaches each, followed by a table cell's line. It never crosses a reference edge — a {/path} reference is -// skipped — so a single inline node is walked on its own. -func eachNode(n node, path string, fn func(path string, n node) error) error { +// eachNode visits n and every node contained within it once, passing the label naming +// each in an error: the dot path reaching it, and a table cell's line after it. It never +// crosses a reference edge — a {/path} reference is skipped — so a single inline node is +// walked on its own. +func eachNode(n node, label string, fn func(label string, n node) error) error { seen := map[node]bool{} var visit func(string, node) error - visit = func(path string, m node) error { + visit = func(label string, m node) error { if m == nil || seen[m] { return nil } seen[m] = true - if err := fn(path, m); err != nil { + if err := fn(label, m); err != nil { return err } for _, c := range contained(m) { - if err := visit(c.labelUnder(path), c.node); err != nil { + if err := visit(c.labelIn(label), c.node); err != nil { return err } } return nil } - return visit(path, n) + return visit(label, n) } // namedNode is a contained child and the segment reaching it; a choice's items carry @@ -40,12 +41,12 @@ type namedNode struct { line int // a table cell's line in its rows file, else 0 } -// labelUnder is the path naming c in an error, a cell by its line. -func (c namedNode) labelUnder(path string) string { +// labelIn is c's label inside the node labelled label. +func (c namedNode) labelIn(label string) string { if c.line > 0 { - return fmt.Sprintf("%s, line %d", path, c.line) + return fmt.Sprintf("%s, line %d", label, c.line) } - return join(path, c.name) + return join(label, c.name) } func contained(n node) []namedNode { @@ -164,9 +165,9 @@ func (m renderCounts) renderCount(n node) int { // repeatCheck bounds the renders a repeat multiplies to along any root-to-leaf // path, so nested repeats cannot build what one repeat may not. // docs/decisions.md#the-repeat-cap-bounds-renders-not-bytes -func repeatCheck(path string, n node, mem renderCounts) error { +func repeatCheck(label string, n node, mem renderCounts) error { if t, ok := n.(*template); ok && t.repeat > 1 && mem.renderCount(n) > MaxRepeat { - return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", path, t.repeat, mem.renderCount(n), MaxRepeat) + return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", label, t.repeat, mem.renderCount(n), MaxRepeat) } return nil } @@ -191,25 +192,25 @@ func checkNoCycles(s nodeScope) error { return nil }) color := map[node]int{} - var visit func(n node, path string) error - visit = func(n node, path string) error { + var visit func(n node, label string) error + visit = func(n node, label string) error { if !inScope[n] { return nil // loaded before this scope and proven then, so it never reaches back into it } switch color[n] { case grey: - return fmt.Errorf("reference cycle: %s", path) + return fmt.Errorf("reference cycle: %s", label) case black: return nil } color[n] = grey for _, e := range renderEdges(n) { - if err := visit(e.to, path+" -> "+e.label); err != nil { + if err := visit(e.to, label+" -> "+e.label); err != nil { return err } } color[n] = black return nil } - return s(func(path string, n node) error { return visit(n, path) }) + return s(func(label string, n node) error { return visit(n, label) }) } diff --git a/inline.go b/inline.go index 2d38ed7..9c7203a 100644 --- a/inline.go +++ b/inline.go @@ -104,7 +104,7 @@ func bindInline(root *folder, n node, label string, typedByGo bool) error { // refuseFolderRefs refuses each reference in scope not written from the root, {/x}: an // inline node sits in no folder. func refuseFolderRefs(scope nodeScope) error { - return scope(func(path string, m node) error { + return scope(func(label string, m node) error { t, ok := m.(*template) if !ok { return nil @@ -112,10 +112,10 @@ func refuseFolderRefs(scope nodeScope) error { for _, name := range refTokens(t.tokens) { sigil, rest, err := grammar.RefShape(name) if err != nil { - return fmt.Errorf("%s: reference {%s}: %w", path, name, err) + return fmt.Errorf("%s: reference {%s}: %w", label, name, err) } if sigil != "/" { - return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", path, name, rest) + return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", label, name, rest) } } return nil @@ -125,18 +125,18 @@ func refuseFolderRefs(scope nodeScope) error { // linkNodeRefs binds the references in an inline node's templates against the // loaded tree. func linkNodeRefs(scope nodeScope, root map[string]node) error { - if err := scope(func(path string, m node) error { + if err := scope(func(label string, m node) error { if t, ok := m.(*template); ok { - return linkTemplate(nil, path, "", t, root) + return linkTemplate(nil, label, "", t, root) } return nil }); err != nil { return err } for _, pass := range linkPasses { - if err := scope(func(path string, m node) error { + if err := scope(func(label string, m node) error { if t, ok := m.(*template); ok { - return pass(path, t) + return pass(label, t) } return nil }); err != nil { diff --git a/name.go b/name.go index b268ff8..ce91ad0 100644 --- a/name.go +++ b/name.go @@ -255,14 +255,14 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error { // linkPasses run in order once every template is linked, each over every template before the next // starts: a pass reads what the one before filled in. -var linkPasses = []func(path string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames} +var linkPasses = []func(label string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames} // linkNames compiles t's reads of a name as paths from the head its binding resolved to, once every // template is linked, so each binder has resolved what it binds. -func linkNames(path string, t *template) error { +func linkNames(label string, t *template) error { return namedReads(t, func(o *op, a *arm) error { if err := linkName(t, a); err != nil { - return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err) + return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err) } return nil }) diff --git a/namefence.go b/namefence.go index 732b295..420e1b0 100644 --- a/namefence.go +++ b/namefence.go @@ -11,22 +11,22 @@ import ( // checkNameReads refuses each binding of t that checkUses refuses, a read of a name inside the // field bound to it, and a read {n} beside {n.w} where n's pick renders w twice: the pick keeps one // draw of w. -func checkNameReads(path string, t *template) error { +func checkNameReads(label string, t *template) error { for _, tok := range t.tokens { if tok.Kind != grammar.NameBind { continue } if err := t.nameScope.bindings[tok.Bound].checkUses(); err != nil { - return fmt.Errorf("%s: token {%s}: %w", path, tok.Body, err) + return fmt.Errorf("%s: token {%s}: %w", label, tok.Body, err) } } return namedReads(t, func(o *op, a *arm) error { if a.named.bindsField() && rendersInside(compilePath(a.named.head, a.named.tail).leaves, t) { - return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", path, o.Body, a.named.name, a.named.ref) + return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", label, o.Body, a.named.name, a.named.ref) } for _, leaf := range a.leaves { if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil { - return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err) + return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err) } } return nil diff --git a/node.go b/node.go index 6ede50b..422e2fa 100644 --- a/node.go +++ b/node.go @@ -101,7 +101,7 @@ func compile(v any) (node, error) { func compileCategory(c datafiles.Category) (node, error) { if m, ok := c.JSON.(map[string]any); ok { if _, isTable := m["rows"]; isTable { - return compileTable(m, c.Name, join(strings.Join(c.Folders, "."), c.Name), c.ReadRows) + return compileTable(m, c.Folders, c.Name, c.ReadRows) } } if items, ok := c.JSON.([]any); ok { diff --git a/reference.go b/reference.go index c383a62..bba3c92 100644 --- a/reference.go +++ b/reference.go @@ -39,13 +39,13 @@ func refSegments(name string, folder []string) ([]string, error) { // so a reference sees the override-resolved tree. A reference's head binds into // refHeads under its root path and its tail reads like a sibling path. func linkRefs(sites []categorySite, root map[string]node) error { - if err := eachTemplate(sites, func(s categorySite, path string, t *template) error { - return linkTemplate(s.dir, path, s.path, t, root) + if err := eachTemplate(sites, func(s categorySite, label string, t *template) error { + return linkTemplate(s.dir, label, s.path, t, root) }); err != nil { return err } for _, pass := range linkPasses { - if err := eachTemplate(sites, func(_ categorySite, path string, t *template) error { return pass(path, t) }); err != nil { + if err := eachTemplate(sites, func(_ categorySite, label string, t *template) error { return pass(label, t) }); err != nil { return err } } @@ -54,8 +54,8 @@ func linkRefs(sites []categorySite, root map[string]node) error { // linkTemplate resolves t's references in category, "" for an inline template, and // compiles its format against them. -func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error { - link, err := t.resolveLink(folder, path, category, root) +func linkTemplate(folder []string, label, category string, t *template, root map[string]node) error { + link, err := t.resolveLink(folder, label, category, root) if err != nil { return err } @@ -92,7 +92,7 @@ func compileArm(t *template, a *arm) { // resolveLink binds every reference t reads, refusing one to t's own category: // docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load -func (t *template) resolveLink(folder []string, path, category string, root map[string]node) (templateLink, error) { +func (t *template) resolveLink(folder []string, label, category string, root map[string]node) (templateLink, error) { var link templateLink names := refTokens(t.tokens) if len(names) == 0 { @@ -103,18 +103,18 @@ func (t *template) resolveLink(folder []string, path, category string, root map[ for _, name := range names { segments, err := refSegments(name, folder) if err != nil { - return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err) + return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err) } categorySegs, target, tail, err := resolveCategory(root, segments) if err != nil { - return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err) + return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err) } head := "/" + strings.Join(categorySegs, ".") if category != "" && head == "/"+category { - return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name) + return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", label, name) } if err := checkPathResolves(target, tail, head); err != nil { - return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err) + return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err) } link.refHeads[head] = target link.refs[name] = refBinding{head, tail} @@ -160,12 +160,12 @@ func linkColumnRead(_ string, t *template) error { } // eachTemplate calls fn once per template of the categories, with the category it sits -// in and the dot path reaching it. -func eachTemplate(sites []categorySite, fn func(s categorySite, path string, t *template) error) error { +// in and eachNode's label for it. +func eachTemplate(sites []categorySite, fn func(s categorySite, label string, t *template) error) error { for _, s := range sites { - if err := eachNode(s.n, s.path, func(path string, n node) error { + if err := eachNode(s.n, s.path, func(label string, n node) error { if t, isTemplate := n.(*template); isTemplate { - return fn(s, path, t) + return fn(s, label, t) } return nil }); err != nil { diff --git a/shipped.go b/shipped.go index 05bd208..41cd50e 100644 --- a/shipped.go +++ b/shipped.go @@ -101,7 +101,7 @@ func loadShipped(root *folder, wanted []unloadedCategory) { } site, err := u.load() if err != nil { - panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", join(strings.Join(u.dir, "."), u.name), err)) + panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", categoryPath(u.dir, u.name), err)) } sites = append(sites, site) queue = append(queue, unloadedReads(root, u.dir, sitesScope([]categorySite{site}))...) diff --git a/table.go b/table.go index 3d4e91b..7029075 100644 --- a/table.go +++ b/table.go @@ -85,7 +85,7 @@ func isTableOption(name string) bool { // token arm. const inSelector = `[]{}"|` -func compileTable(m map[string]any, segment, path string, readRows func(file string) (string, error)) (*table, error) { +func compileTable(m map[string]any, dir []string, name string, readRows func(file string) (string, error)) (*table, error) { o, err := readTableOptions(m) if err != nil { return nil, err @@ -94,7 +94,7 @@ func compileTable(m map[string]any, segment, path string, readRows func(file str if err != nil { return nil, err } - t := &table{segment: segment, path: path, file: o.rows, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1} + t := &table{segment: name, path: categoryPath(dir, name), file: o.rows, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1} if err := t.parseRows(data); err != nil { return nil, fmt.Errorf("%s: %w", o.rows, err) } @@ -404,8 +404,7 @@ func linkTables(sites []categorySite) error { } // proveParent is the table among siblings that t's link column names, proved: it has a key, -// every link cell is one, every row of it is linked to, no chain of parents closes, and no -// ancestor has a column named like t. +// every link cell is one, every row of it is linked to, and its ancestors pass checkAncestors. // docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error func (t *table) proveParent(siblings map[string]node) (*table, error) { name := t.header[t.parentIndex] @@ -418,21 +417,8 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) { case p.keyIndex < 0: return nil, fmt.Errorf("parent %q has no key column to link to", name) } - var ancestors []*table - for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.header[q.parentIndex]].(*table) { - if seen[q] { - return nil, fmt.Errorf("parent cycle: %s reaches itself through its parents", q.segment) - } - seen[q] = true - ancestors = append(ancestors, q) - if q.parentIndex < 0 { - break - } - } - for _, q := range ancestors { - if _, clash := q.col[t.segment]; clash { - return nil, fmt.Errorf("%q is named like a column of %q, its ancestor, so %s.%s could read either; rename one", t.segment, q.segment, q.segment, t.segment) - } + if err := t.checkAncestors(p, siblings); err != nil { + return nil, err } linked := make(map[string]bool, p.rowCount()) for r := 0; r < t.rowCount(); r++ { @@ -450,6 +436,28 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) { return p, nil } +// checkAncestors proves the chain of parents from p, t's parent, closes nowhere, and holds no +// column named like t. +func (t *table) checkAncestors(p *table, siblings map[string]node) error { + var ancestors []*table + for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.header[q.parentIndex]].(*table) { + if seen[q] { + return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.segment) + } + seen[q] = true + ancestors = append(ancestors, q) + if q.parentIndex < 0 { + break + } + } + for _, q := range ancestors { + if _, clash := q.col[t.segment]; clash { + return fmt.Errorf("%q is named like a column of %q, its ancestor, so %s.%s could read either; rename one", t.segment, q.segment, q.segment, t.segment) + } + } + return nil +} + func (t *table) builtLookup() *rowLookup { t.once.Do(func() { if t.nameIndex >= 0 { diff --git a/todo.md b/todo.md index c598eec..1009488 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ `Bar = 9` -`Next ID = 115` +`Next ID = 117` | Goal | W | |---|---| @@ -118,6 +118,7 @@ | 13 | 0.3.0 | defect | **Place xlsx cells by their `r` reference in `data-import/xlsx.py`.** | 2 | 2 | 3 | 6 | 4 | 13.7 | | 39 | 0.3.0 | | **Stop the `columnKinds` suggestion naming a field's own kind or a narrower one.** | 1 | 2 | 3 | 6 | 6 | 13.5 | | 51 | 0.3.0 | | **Spell a table one way across the errors that name it.** | 1 | 2 | 3 | 6 | 6 | 13.5 | +| 115 | 0.3.0 | | **Spell a rows file's line one way in every error, naming the file: today a cell reads `g.t.b, line 3`, a cell check `t.tsv: line 3, b` and a link `t.tsv line 3`.** | 1 | 2 | 3 | 6 | 6 | 13.5 | | 10 | 0.3.0 | | **Test that every node kind reaches each switch over node kinds.** | 1 | 3 | 2 | 6 | 3.2 | 13.1 | | 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 3 | 5 | 4 | 13.0 | | 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 4 | 5 | 8 | 13.0 | @@ -132,6 +133,7 @@ | 72 | 0.3.0 | principle | **Fail the merge gate when test coverage falls below the last recorded figure.** | 2 | 3 | 2 | 5 | 3 | 10.2 | | 43 | 0.3.0 | | **Call `templateError` a compile failure in its doc.** | 1 | 1 | 1 | 5 | 3.3 | 9.8 | | 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 | +| 116 | 0.3.0 | | **Turn a row index into its rows file's line in one function, where eleven sites add 2 today.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 | | 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 14 | 9.2 | | 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 1 | 4 | 1 | 8.8 | | 109 | 0.3.0 | principle | **Indent every script under `data-import/` with hard tabs, as `AGENTS.md` requires and `release-tooling/` does.** | 2 | 4 | 1 | 4 | 3 | 6.2 | @@ -421,6 +423,10 @@ An `int64` or `float64` field unproven in range is told to become itself, and a `pinRow` names `t.path` and `mustRow` panics with `t.category`, so one table is `territory` and `misc.territory`, and the short spelling names no file where two folders hold that name. +### 115. Spell a rows file's line one way in every error, naming the file: today a cell reads `g.t.b, line 3`, a cell check `t.tsv: line 3, b` and a link `t.tsv line 3`. + +The cell's label names the category and column but not the TSV the line is in. + ### 10. Test that every node kind reaches each switch over node kinds. Today nine, from `render` to `columnItems`, must agree, and only `render` and `renderEdges` say so. @@ -461,6 +467,10 @@ Its doc says render failure, and it wraps what `NewTemplate` and `NewRecordTempl `structShape.columns` and `structShape.fieldIndexes` pair up by index alone, against the technical principle "one owner per value". +### 116. Turn a row index into its rows file's line in one function, where eleven sites add 2 today. + +`namedNode`'s cell line in `graph.go` and ten sites in `table.go` each add the header line and the one-based count. Item 102 moves the rows into `internal/rows`; the function goes with them. + ### 56. Build on a manual run of `test.yml`. `workflow_dispatch` leaves `github.event.before` empty, so the diff compares `HEAD` with itself and skips `docker build`. diff --git a/valueproof.go b/valueproof.go index 968a7f4..fb66c70 100644 --- a/valueproof.go +++ b/valueproof.go @@ -19,16 +19,16 @@ type valueProof struct { // checkDatatype rejects a typed column item some render of which is not text of its datatype, // and one restating the datatype of the column it is. // docs/decisions.md#a-column-that-only-reads-one-reference-or-name-is-the-column-it-reads -func (p *valueProof) checkDatatype(path string, n node) error { +func (p *valueProof) checkDatatype(label string, n node) error { t, ok := n.(*template) if !ok || t.datatype == DataTypeString { return nil } if r := t.link.readsColumn; r != nil && columnDatatype(r.column) == t.datatype { - return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, t.datatype) + return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, label, t.format, t.datatype) } if reason := p.proveColumnItem(t).Not[t.datatype]; reason != "" { - return fmt.Errorf("%s: datatype %s: %s", path, t.datatype, reason) + return fmt.Errorf("%s: datatype %s: %s", label, t.datatype, reason) } return nil }