Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
12 changed files with 24 additions and 31 deletions
Showing only changes of commit c422c9e436 - Show all commits
+3 -3
View File
@@ -28,7 +28,7 @@ func fenceRoots(t *testing.T, f *Generator) ([]fenceRoot, map[string]*table) {
t.Helper() t.Helper()
var roots []fenceRoot var roots []fenceRoot
tables := map[string]*table{} tables := map[string]*table{}
_ = walkNodes(f.categories, func(path string, n node) error { _ = walkNodes(f.root.children, func(path string, n node) error {
switch n := n.(type) { switch n := n.(type) {
case *template: case *template:
roots = append(roots, fenceRoot{path, n}) roots = append(roots, fenceRoot{path, n})
@@ -61,7 +61,7 @@ func TestReplayPairsFindsWhatAWholeReplayFinds(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("New() = %v", err) t.Fatalf("New() = %v", err)
} }
_ = walkNodes(shipped.categories, func(path string, n node) error { _ = walkNodes(shipped.root.children, func(path string, n node) error {
if tm, ok := n.(*template); ok { if tm, ok := n.(*template); ok {
roots = append(roots, fenceRoot{path, tm}) roots = append(roots, fenceRoot{path, tm})
} }
@@ -85,7 +85,7 @@ func TestReplayPairsFindsWhatAWholeReplayOfSelectedRowsFinds(t *testing.T) {
t.Fatalf("New = %v", err) t.Fatalf("New = %v", err)
} }
var reads []pathRead var reads []pathRead
_ = walkNodes(f.categories, func(_ string, n node) error { _ = walkNodes(f.root.children, func(_ string, n node) error {
tb, isTable := n.(*table) tb, isTable := n.(*table)
if !isTable || tb.keyIndex < 0 || tb.familyRoot().path != "region" { if !isTable || tb.keyIndex < 0 || tb.familyRoot().path != "region" {
return nil return nil
+8 -9
View File
@@ -31,12 +31,11 @@ var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithD
// from one goroutine. The compiled tree is immutable after [New], so List, NewTemplate // from one goroutine. The compiled tree is immutable after [New], so List, NewTemplate
// and NewRecordTemplate read it without a lock; mu guards the rest, which every render takes. // and NewRecordTemplate read it without a lock; mu guards the rest, which every render takes.
type Generator struct { type Generator struct {
mu sync.Mutex mu sync.Mutex
rand *session rand *session
categories map[string]node root folder // the categories as the node a path walks from, owned here so a walk allocates none
root folder // the categories as the node a path walks from, owned here so a walk allocates none records map[node]recordShape
records map[node]recordShape structs map[reflect.Type]structResult
structs map[reflect.Type]structResult
} }
// session is one generator's mutable render state: the seeded rng plus the {seq()} // session is one generator's mutable render state: the seeded rng plus the {seq()}
@@ -112,7 +111,7 @@ func New(opts ...Option) (*Generator, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err) return nil, fmt.Errorf("fejkdata: %w", err)
} }
return &Generator{rand: rng, categories: cats}, nil return &Generator{rand: rng, root: folder{children: cats}}, nil
} }
// List returns the sorted dotted paths Fake renders: each category and every field, // List returns the sorted dotted paths Fake renders: each category and every field,
@@ -120,8 +119,8 @@ func New(opts ...Option) (*Generator, error) {
// segment, so a path continues through one only where every variant carries it. // segment, so a path continues through one only where every variant carries it.
func (f *Generator) List() []string { func (f *Generator) List() []string {
var out []string var out []string
for _, name := range sortedNames(f.categories) { for _, name := range sortedNames(f.root.children) {
for _, p := range paths(f.categories[name]) { for _, p := range paths(f.root.children[name]) {
out = append(out, join(name, p)) out = append(out, join(name, p))
} }
} }
+2 -3
View File
@@ -152,9 +152,8 @@ func isFixed(n node) bool {
} }
// renders reports whether rendering n can reach anything in want, following the // renders reports whether rendering n can reach anything in want, following the
// same edges expand does. seen keeps a node shared by several routes from being // same edges expand does. seen keeps a node from being walked twice, so the walk
// walked twice; checkNoCycles has already proved the graph is a DAG, so the walk // ends across a cycle too.
// ends.
func renders(n node, want, seen map[node]bool) bool { func renders(n node, want, seen map[node]bool) bool {
if want[n] { if want[n] {
return true return true
+1 -1
View File
@@ -32,7 +32,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err) return nil, fmt.Errorf("fejkdata: %w", err)
} }
if err := inlineBinding(n, "template", f.categories).bind(); err != nil { if err := inlineBinding(n, "template", f.root.children).bind(); err != nil {
return nil, fmt.Errorf("fejkdata: %w", err) return nil, fmt.Errorf("fejkdata: %w", err)
} }
return &Template{g: f, n: n}, nil return &Template{g: f, n: n}, nil
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// node is a compiled element of the namespace tree: a folder, choice, null, // node is a compiled element of the namespace tree: a folder, choice, null,
// template, table, column or row. Compiling JSON into these once (see compile) // template, table, column or row. Compiling JSON into these once (see compile)
// means rendering never re-inspects the raw JSON or re-sums weights. A new kind needs a // means rendering never re-inspects the raw JSON. A new kind needs a
// case in contained, paths, render and renderEdges; each panics on a kind it does not list. // case in contained, paths, render and renderEdges; each panics on a kind it does not list.
type node interface{ isNode() } type node interface{ isNode() }
+3 -3
View File
@@ -50,7 +50,7 @@ func TestDeepDottedPath(t *testing.T) {
// A 5-segment path descends through alternating object/array nodes; choices // A 5-segment path descends through alternating object/array nodes; choices
// on the path are single-variant, so it resolves deterministically. // on the path are single-variant, so it resolves deterministically.
f := engine(1) f := engine(1)
f.categories = map[string]node{ f.root.children = map[string]node{
"deep": compiled(t, `{"format":"{a}","a":{"format":"{b}","b":{"format":"{c}","c":{"format":"{d}","d":"leaf"}}}}`), "deep": compiled(t, `{"format":"{a}","a":{"format":"{b}","b":{"format":"{c}","c":{"format":"{d}","d":"leaf"}}}}`),
} }
if got, err := f.Fake("deep.a.b.c.d"); err != nil || got != "leaf" { if got, err := f.Fake("deep.a.b.c.d"); err != nil || got != "leaf" {
@@ -64,7 +64,7 @@ func TestDeepDottedPath(t *testing.T) {
func TestDescendIntoStringErrors(t *testing.T) { func TestDescendIntoStringErrors(t *testing.T) {
f := engine(1) f := engine(1)
f.categories = map[string]node{"greeting": compiled(t, `"hej"`)} f.root.children = map[string]node{"greeting": compiled(t, `"hej"`)}
if _, err := f.Fake("greeting.extra"); err == nil || !strings.Contains(err.Error(), `no field "extra"`) { if _, err := f.Fake("greeting.extra"); err == nil || !strings.Contains(err.Error(), `no field "extra"`) {
t.Fatalf("Fake(greeting.extra) = %v, want a no-field error", err) t.Fatalf("Fake(greeting.extra) = %v, want a no-field error", err)
} }
@@ -153,7 +153,7 @@ func TestBindingKeyIsNotAPathSegment(t *testing.T) {
func TestFakePathNavigation(t *testing.T) { func TestFakePathNavigation(t *testing.T) {
f := engine(1) f := engine(1)
f.categories = map[string]node{ f.root.children = map[string]node{
"addr": compiled(t, `{"format":"{street}","street":"Main"}`), "addr": compiled(t, `{"format":"{street}","street":"Main"}`),
} }
if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") { if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") {
+1 -1
View File
@@ -126,7 +126,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err) return nil, fmt.Errorf("fejkdata: %w", err)
} }
_, n, tail, err := resolveCategory(f.categories, segments) _, n, tail, err := resolveCategory(f.root.children, segments)
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %s: %w", path, err) return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
} }
-1
View File
@@ -25,7 +25,6 @@ func (f *Generator) Fake(path string) (string, error) {
} }
var set holdSet var set holdSet
sc := renderScope{set: &set} sc := renderScope{set: &set}
f.root.children = f.categories
n, err := descend(f.rand, &f.root, segments, sc) n, err := descend(f.rand, &f.root, segments, sc)
if err != nil { if err != nil {
return "", fmt.Errorf("fejkdata: %s: %w", path, err) return "", fmt.Errorf("fejkdata: %s: %w", path, err)
+2 -2
View File
@@ -61,8 +61,8 @@ func shippedShape(f *Generator) string {
} }
} }
} }
for _, name := range sortedNames(f.categories) { for _, name := range sortedNames(f.root.children) {
walk(name, f.categories[name]) walk(name, f.root.children[name])
} }
var b strings.Builder var b strings.Builder
for _, p := range f.List() { for _, p := range f.List() {
+1 -1
View File
@@ -49,7 +49,7 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
if label == "" { if label == "" {
label = "struct" label = "struct"
} }
sc := &structCompile{categories: f.categories, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs} sc := &structCompile{categories: f.root.children, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs}
shape, err := sc.compileShape(t, label) shape, err := sc.compileShape(t, label)
if err == nil && shape.empty() { if err == nil && shape.empty() {
err = fmt.Errorf("%s has no fake tags, so nothing to fill", t) err = fmt.Errorf("%s has no fake tags, so nothing to fill", t)
+2 -2
View File
@@ -991,7 +991,7 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) {
f := newGenerator(t, writeFiles(t, geo()), WithSeed(1)) f := newGenerator(t, writeFiles(t, geo()), WithSeed(1))
var tables []*table var tables []*table
for _, category := range []string{"region", "municipality", "locality"} { for _, category := range []string{"region", "municipality", "locality"} {
tbl, isTable := f.categories[category].(*table) tbl, isTable := f.root.children[category].(*table)
if !isTable { if !isTable {
t.Fatalf("%s is not a table", category) t.Fatalf("%s is not a table", category)
} }
@@ -1063,7 +1063,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
head := f.categories[segs[0]].(*table) head := f.root.children[segs[0]].(*table)
probe := &pathProbe{drawn: map[*table]bool{}} probe := &pathProbe{drawn: map[*table]bool{}}
if _, err := probe.walk(head, segs[1:]); err != nil { if _, err := probe.walk(head, segs[1:]); err != nil {
t.Fatalf("%s: %v", path, err) t.Fatalf("%s: %v", path, err)
-4
View File
@@ -26,10 +26,6 @@ least modify.
This round, in order: This round, in order:
- Correct the false comments: `node` says rendering never re-sums weights, which
`builtLookup` does on first draw; `Generator` claims immutable after `New`, while
`Fake` sets `root.children` each call, which belongs in `New`; `renders` credits
`checkNoCycles`, which an inline binding never runs.
- Run the nine-seat panel again and file what it names here. - Run the nine-seat panel again and file what it names here.
### Data ### Data