From c422c9e436bb737dc10dc00ec9ba41953053312d Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 29 Sep 2026 04:34:50 +0200 Subject: [PATCH] Build the walk root in New, drop the categories copy, and correct the node and renders comments --- drawfence_test.go | 6 +++--- fejkdata.go | 17 ++++++++--------- holdfence.go | 5 ++--- inline.go | 2 +- node.go | 2 +- path_test.go | 6 +++--- record.go | 2 +- render.go | 1 - shape_test.go | 4 ++-- struct.go | 2 +- table_test.go | 4 ++-- todo.md | 4 ---- 12 files changed, 24 insertions(+), 31 deletions(-) diff --git a/drawfence_test.go b/drawfence_test.go index f690f1b..83ffe6f 100644 --- a/drawfence_test.go +++ b/drawfence_test.go @@ -28,7 +28,7 @@ func fenceRoots(t *testing.T, f *Generator) ([]fenceRoot, map[string]*table) { t.Helper() var roots []fenceRoot 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) { case *template: roots = append(roots, fenceRoot{path, n}) @@ -61,7 +61,7 @@ func TestReplayPairsFindsWhatAWholeReplayFinds(t *testing.T) { if err != nil { 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 { roots = append(roots, fenceRoot{path, tm}) } @@ -85,7 +85,7 @@ func TestReplayPairsFindsWhatAWholeReplayOfSelectedRowsFinds(t *testing.T) { t.Fatalf("New = %v", err) } var reads []pathRead - _ = walkNodes(f.categories, func(_ string, n node) error { + _ = walkNodes(f.root.children, func(_ string, n node) error { tb, isTable := n.(*table) if !isTable || tb.keyIndex < 0 || tb.familyRoot().path != "region" { return nil diff --git a/fejkdata.go b/fejkdata.go index 21a75cc..5c2c232 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -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 // and NewRecordTemplate read it without a lock; mu guards the rest, which every render takes. type Generator struct { - mu sync.Mutex - rand *session - categories map[string]node - root folder // the categories as the node a path walks from, owned here so a walk allocates none - records map[node]recordShape - structs map[reflect.Type]structResult + mu sync.Mutex + rand *session + root folder // the categories as the node a path walks from, owned here so a walk allocates none + records map[node]recordShape + structs map[reflect.Type]structResult } // 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 { 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, @@ -120,8 +119,8 @@ func New(opts ...Option) (*Generator, error) { // segment, so a path continues through one only where every variant carries it. func (f *Generator) List() []string { var out []string - for _, name := range sortedNames(f.categories) { - for _, p := range paths(f.categories[name]) { + for _, name := range sortedNames(f.root.children) { + for _, p := range paths(f.root.children[name]) { out = append(out, join(name, p)) } } diff --git a/holdfence.go b/holdfence.go index 53a54ef..627a79e 100644 --- a/holdfence.go +++ b/holdfence.go @@ -152,9 +152,8 @@ func isFixed(n node) bool { } // 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 -// walked twice; checkNoCycles has already proved the graph is a DAG, so the walk -// ends. +// same edges expand does. seen keeps a node from being walked twice, so the walk +// ends across a cycle too. func renders(n node, want, seen map[node]bool) bool { if want[n] { return true diff --git a/inline.go b/inline.go index 06ca0c4..b090ecd 100644 --- a/inline.go +++ b/inline.go @@ -32,7 +32,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) { if err != nil { 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 &Template{g: f, n: n}, nil diff --git a/node.go b/node.go index 46c751c..56bd89a 100644 --- a/node.go +++ b/node.go @@ -11,7 +11,7 @@ import ( // 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) -// 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. type node interface{ isNode() } diff --git a/path_test.go b/path_test.go index cd11e10..fde337b 100644 --- a/path_test.go +++ b/path_test.go @@ -50,7 +50,7 @@ func TestDeepDottedPath(t *testing.T) { // A 5-segment path descends through alternating object/array nodes; choices // on the path are single-variant, so it resolves deterministically. 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"}}}}`), } 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) { 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"`) { 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) { f := engine(1) - f.categories = map[string]node{ + f.root.children = map[string]node{ "addr": compiled(t, `{"format":"{street}","street":"Main"}`), } if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") { diff --git a/record.go b/record.go index b69b31f..0298571 100644 --- a/record.go +++ b/record.go @@ -126,7 +126,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) { if err != nil { 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 { return nil, fmt.Errorf("fejkdata: %s: %w", path, err) } diff --git a/render.go b/render.go index 3a33a6f..efc37dc 100644 --- a/render.go +++ b/render.go @@ -25,7 +25,6 @@ func (f *Generator) Fake(path string) (string, error) { } var set holdSet sc := renderScope{set: &set} - f.root.children = f.categories n, err := descend(f.rand, &f.root, segments, sc) if err != nil { return "", fmt.Errorf("fejkdata: %s: %w", path, err) diff --git a/shape_test.go b/shape_test.go index bc72377..010b2fe 100644 --- a/shape_test.go +++ b/shape_test.go @@ -61,8 +61,8 @@ func shippedShape(f *Generator) string { } } } - for _, name := range sortedNames(f.categories) { - walk(name, f.categories[name]) + for _, name := range sortedNames(f.root.children) { + walk(name, f.root.children[name]) } var b strings.Builder for _, p := range f.List() { diff --git a/struct.go b/struct.go index a6f3df1..8f6b82a 100644 --- a/struct.go +++ b/struct.go @@ -49,7 +49,7 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) { if label == "" { 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) if err == nil && shape.empty() { err = fmt.Errorf("%s has no fake tags, so nothing to fill", t) diff --git a/table_test.go b/table_test.go index c2d5749..3bb3a3f 100644 --- a/table_test.go +++ b/table_test.go @@ -991,7 +991,7 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) { f := newGenerator(t, writeFiles(t, geo()), WithSeed(1)) var tables []*table for _, category := range []string{"region", "municipality", "locality"} { - tbl, isTable := f.categories[category].(*table) + tbl, isTable := f.root.children[category].(*table) if !isTable { t.Fatalf("%s is not a table", category) } @@ -1063,7 +1063,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) { if err != nil { t.Fatal(err) } - head := f.categories[segs[0]].(*table) + head := f.root.children[segs[0]].(*table) probe := &pathProbe{drawn: map[*table]bool{}} if _, err := probe.walk(head, segs[1:]); err != nil { t.Fatalf("%s: %v", path, err) diff --git a/todo.md b/todo.md index 052f2b3..d813adc 100644 --- a/todo.md +++ b/todo.md @@ -26,10 +26,6 @@ least modify. 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. ### Data