From 63e985a53d02af32453482d76fef491677eab68c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 7 Oct 2026 02:08:38 +0200 Subject: [PATCH] Move List's path enumeration into list.go, position into node.go and sortedNames into sorted.go --- datatype.go | 10 ----- fejkdata.go | 98 ------------------------------------------------- graph.go | 10 ----- list.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++ node.go | 10 +++++ sorted.go | 12 ++++++ 6 files changed, 126 insertions(+), 118 deletions(-) create mode 100644 list.go create mode 100644 sorted.go diff --git a/datatype.go b/datatype.go index 265c082..15b0c03 100644 --- a/datatype.go +++ b/datatype.go @@ -20,16 +20,6 @@ const ( DataTypeBoolean DataType = datatype.Boolean ) -// position is where a JSON value sits, which decides whether it may carry a datatype or -// be null. -type position int - -const ( - inFormat position = iota // rendered by a format, so neither - atTop // a category or an inline template, whose fields may be columns - inColumn // a column, or a choice item standing in for one -) - // datatypeOf reads a template's "datatype" (default DataTypeString). func datatypeOf(m map[string]any, pos position) (DataType, error) { v, ok := m["datatype"] diff --git a/fejkdata.go b/fejkdata.go index 5114031..01eb871 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -12,7 +12,6 @@ import ( "github.com/larvit/fejkdata/internal/datafiles" "github.com/larvit/fejkdata/internal/drawstate" - "github.com/larvit/fejkdata/internal/invariant" ) // MaxRepeat caps a repeat, and caps the renders nested repeats multiply to along @@ -122,103 +121,6 @@ func (f *Generator) List() []string { return out } -// paths lists the dot paths addressable from n, relative to it, where "" is n -// itself. A folder has no value of its own, so it contributes only its children's. -// With intoRepeats, it also lists the paths below a level carrying a repeat, so a path -// there meets the repeat's refusal. -func paths(n node, intoRepeats bool) []string { - switch n := n.(type) { - case *folder: - var out []string - for _, name := range sortedNames(n.children) { - out = appendUnder(out, name, paths(n.children[name], intoRepeats)) - } - for _, name := range sortedNames(n.unloaded) { - out = appendUnder(out, name, n.unloaded[name].paths) - } - return out - case *template: - out := []string{""} - if n.repeat > 1 && !intoRepeats { - return out - } - for _, name := range sortedNames(n.fields) { - out = appendUnder(out, name, paths(n.fields[name], intoRepeats)) - } - return out - case *table: - return tablePaths(n, intoRepeats) - case *tableColumn, *tableRow, *nullItem: - return []string{""} - case *choice: - shared := n.shared - if !intoRepeats { - shared = sharedPaths(n.items, false) - } - return append([]string{""}, sortedNames(shared)...) - default: - panic(invariant.Broken("paths has no case for node %T", n)) - } -} - -func appendUnder(out []string, name string, ps []string) []string { - for _, p := range ps { - out = append(out, join(name, p)) - } - return out -} - -// tablePaths is a table's columns, then each table linked to it under its name: the -// direct descents, a step at a time. -func tablePaths(t *table, intoRepeats bool) []string { - out := append([]string{""}, t.rows.Header()...) - sort.Strings(out[1:]) - for _, c := range t.rows.Children() { - out = appendUnder(out, c.Segment(), paths(c.Owner(), intoRepeats)) - } - return out -} - -// sharedPaths is the sub-paths every item carries — the only ones a path may step -// through a choice to reach. It intersects, bailing as soon as the set -// is empty, which is immediate for a choice of plain strings. -func sharedPaths(items []node, intoRepeats bool) map[string]bool { - shared := subPaths(items[0], intoRepeats) - for _, it := range items[1:] { - if len(shared) == 0 { - return nil - } - next := subPaths(it, intoRepeats) - for p := range shared { - if !next[p] { - delete(shared, p) - } - } - } - return shared -} - -// subPaths is paths(n, intoRepeats) as a set, without the empty path that means n itself. -func subPaths(n node, intoRepeats bool) map[string]bool { - out := map[string]bool{} - for _, p := range paths(n, intoRepeats) { - if p != "" { - out[p] = true - } - } - return out -} - -func join(prefix, name string) string { - switch { - case prefix == "": - return name - case name == "": - return prefix - } - return prefix + "." + name -} - var randomBytes = crand.Read // drawSeed is the seed WithSeed gave, or one read from the system's entropy. diff --git a/graph.go b/graph.go index 44f4b71..3de89dd 100644 --- a/graph.go +++ b/graph.go @@ -3,7 +3,6 @@ package fejkdata import ( "fmt" "slices" - "sort" "github.com/larvit/fejkdata/internal/invariant" ) @@ -88,15 +87,6 @@ func namedNodes(m map[string]node) []namedNode { return out } -func sortedNames[V any](m map[string]V) []string { - names := make([]string, 0, len(m)) - for name := range m { - names = append(names, name) - } - sort.Strings(names) - return names -} - // renderEdge is a child a node renders into, labelled by what reaches it (a field // name, reference, or choice index) for a readable cycle report. type renderEdge struct { diff --git a/list.go b/list.go new file mode 100644 index 0000000..730dc9f --- /dev/null +++ b/list.go @@ -0,0 +1,104 @@ +package fejkdata + +import ( + "sort" + + "github.com/larvit/fejkdata/internal/invariant" +) + +// paths lists the dot paths addressable from n, relative to it, where "" is n +// itself. A folder has no value of its own, so it contributes only its children's. +// With intoRepeats, it also lists the paths below a level carrying a repeat, so a path +// there meets the repeat's refusal. +func paths(n node, intoRepeats bool) []string { + switch n := n.(type) { + case *folder: + var out []string + for _, name := range sortedNames(n.children) { + out = appendUnder(out, name, paths(n.children[name], intoRepeats)) + } + for _, name := range sortedNames(n.unloaded) { + out = appendUnder(out, name, n.unloaded[name].paths) + } + return out + case *template: + out := []string{""} + if n.repeat > 1 && !intoRepeats { + return out + } + for _, name := range sortedNames(n.fields) { + out = appendUnder(out, name, paths(n.fields[name], intoRepeats)) + } + return out + case *table: + return tablePaths(n, intoRepeats) + case *tableColumn, *tableRow, *nullItem: + return []string{""} + case *choice: + shared := n.shared + if !intoRepeats { + shared = sharedPaths(n.items, false) + } + return append([]string{""}, sortedNames(shared)...) + default: + panic(invariant.Broken("paths has no case for node %T", n)) + } +} + +func appendUnder(out []string, name string, ps []string) []string { + for _, p := range ps { + out = append(out, join(name, p)) + } + return out +} + +// tablePaths is a table's columns, then each table linked to it under its name: the +// direct descents, a step at a time. +func tablePaths(t *table, intoRepeats bool) []string { + out := append([]string{""}, t.rows.Header()...) + sort.Strings(out[1:]) + for _, c := range t.rows.Children() { + out = appendUnder(out, c.Segment(), paths(c.Owner(), intoRepeats)) + } + return out +} + +// sharedPaths is the sub-paths every item carries — the only ones a path may step +// through a choice to reach. It intersects, bailing as soon as the set +// is empty, which is immediate for a choice of plain strings. +func sharedPaths(items []node, intoRepeats bool) map[string]bool { + shared := subPaths(items[0], intoRepeats) + for _, it := range items[1:] { + if len(shared) == 0 { + return nil + } + next := subPaths(it, intoRepeats) + for p := range shared { + if !next[p] { + delete(shared, p) + } + } + } + return shared +} + +// subPaths is paths(n, intoRepeats) as a set, without the empty path that means n itself. +func subPaths(n node, intoRepeats bool) map[string]bool { + out := map[string]bool{} + for _, p := range paths(n, intoRepeats) { + if p != "" { + out[p] = true + } + } + return out +} + +func join(prefix, name string) string { + switch { + case prefix == "": + return name + case name == "": + return prefix + } + return prefix + "." + name +} diff --git a/node.go b/node.go index c1404ee..567aa1d 100644 --- a/node.go +++ b/node.go @@ -163,6 +163,16 @@ func rowShape(raw any) (format string, fields []string, weighted, isRow bool) { return format, fields, weighted, true } +// position is where a JSON value sits, which decides whether it may carry a datatype or +// be null. +type position int + +const ( + inFormat position = iota // rendered by a format, so neither + atTop // a category or an inline template, whose fields may be columns + inColumn // a column, or a choice item standing in for one +) + // compileAt compiles a node that is no choice's item. Only a choice's items carry a // weight, so one here would be inert whatever its type. func compileAt(v any, pos position) (node, error) { diff --git a/sorted.go b/sorted.go new file mode 100644 index 0000000..8194f20 --- /dev/null +++ b/sorted.go @@ -0,0 +1,12 @@ +package fejkdata + +import "sort" + +func sortedNames[V any](m map[string]V) []string { + names := make([]string, 0, len(m)) + for name := range m { + names = append(names, name) + } + sort.Strings(names) + return names +}