Draw groups: one reference draw per render, a drawGroup option, and a record's fences at load #16
@@ -28,10 +28,10 @@ func (s dataSource) name(p string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadData loads every source into one namespace tree and returns its root
|
// loadData loads every source into one namespace tree and returns its root
|
||||||
// children. A directory becomes a group; each *.json file in it compiles to a node
|
// children. A directory becomes a folder; each *.json file in it compiles to a node
|
||||||
// keyed by its base name (address.json -> "address"); each subdirectory becomes a
|
// keyed by its base name (address.json -> "address"); each subdirectory becomes a
|
||||||
// nested group, so folders turn into dot-path segments. Sources merge left to right:
|
// nested folder, so folders turn into dot-path segments. Sources merge left to right:
|
||||||
// matching groups merge by their children, and any other clash is won by the last
|
// matching folders merge by their children, and any other clash is won by the last
|
||||||
// source loaded. Once merged, linkRefs binds every reference against the
|
// source loaded. Once merged, linkRefs binds every reference against the
|
||||||
// final tree.
|
// final tree.
|
||||||
func loadData(sources []dataSource) (map[string]node, error) {
|
func loadData(sources []dataSource) (map[string]node, error) {
|
||||||
@@ -74,15 +74,15 @@ func loadData(sources []dataSource) (map[string]node, error) {
|
|||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadDir compiles one directory into a group. fs.ReadDir yields entries sorted
|
// loadDir compiles one directory into a folder. fs.ReadDir yields entries sorted
|
||||||
// by name, so the tree is built deterministically. Empty subdirectories (no JSON
|
// by name, so the tree is built deterministically. Empty subdirectories (no JSON
|
||||||
// anywhere under them) are skipped rather than added as empty namespaces.
|
// anywhere under them) are skipped rather than added as empty namespaces.
|
||||||
func loadDir(src dataSource, dir string) (*group, error) {
|
func loadDir(src dataSource, dir string) (*folder, error) {
|
||||||
entries, err := fs.ReadDir(src.fsys, dir)
|
entries, err := fs.ReadDir(src.fsys, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", src.name(dir), err)
|
return nil, fmt.Errorf("%s: %w", src.name(dir), err)
|
||||||
}
|
}
|
||||||
g := &group{children: map[string]node{}}
|
g := &folder{children: map[string]node{}}
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if strings.HasPrefix(e.Name(), ".") { // hidden: a checkout or an editor's file, never data
|
if strings.HasPrefix(e.Name(), ".") { // hidden: a checkout or an editor's file, never data
|
||||||
continue
|
continue
|
||||||
@@ -99,8 +99,8 @@ func loadDir(src dataSource, dir string) (*group, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadFolder adds a subdirectory as a nested group, unless nothing under it is data.
|
// loadFolder adds a subdirectory as a nested folder, unless nothing under it is data.
|
||||||
func loadFolder(src dataSource, g *group, full, name string) error {
|
func loadFolder(src dataSource, g *folder, full, name string) error {
|
||||||
child, err := loadDir(src, full)
|
child, err := loadDir(src, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -117,7 +117,7 @@ func loadFolder(src dataSource, g *group, full, name string) error {
|
|||||||
|
|
||||||
// loadFile compiles a *.json file into a category named after it; any other file
|
// loadFile compiles a *.json file into a category named after it; any other file
|
||||||
// is skipped.
|
// is skipped.
|
||||||
func loadFile(src dataSource, g *group, full, file string) error {
|
func loadFile(src dataSource, g *folder, full, file string) error {
|
||||||
if !strings.HasSuffix(file, ".json") {
|
if !strings.HasSuffix(file, ".json") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,13 @@ func loadFile(src dataSource, g *group, full, file string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeChildren overlays src onto dst. Two groups under the same key merge
|
// mergeChildren overlays src onto dst. Two folders under the same key merge
|
||||||
// recursively (so locales/categories from several paths combine); every other
|
// recursively (so locales/categories from several paths combine); every other
|
||||||
// key is replaced, making the last-loaded directory win on a conflict.
|
// key is replaced, making the last-loaded directory win on a conflict.
|
||||||
func mergeChildren(dst, src map[string]node) {
|
func mergeChildren(dst, src map[string]node) {
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
if dg, ok := dst[k].(*group); ok {
|
if dg, ok := dst[k].(*folder); ok {
|
||||||
if sg, ok := v.(*group); ok {
|
if sg, ok := v.(*folder); ok {
|
||||||
mergeChildren(dg.children, sg.children)
|
mergeChildren(dg.children, sg.children)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -142,10 +142,10 @@ func (f *Generator) List() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// paths lists the dot paths addressable from n, relative to it, where "" is n
|
// paths lists the dot paths addressable from n, relative to it, where "" is n
|
||||||
// itself. A group has no value of its own, so it contributes only its children's.
|
// itself. A folder has no value of its own, so it contributes only its children's.
|
||||||
func paths(n node) []string {
|
func paths(n node) []string {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *group:
|
case *folder:
|
||||||
var out []string
|
var out []string
|
||||||
for _, name := range sortedNames(n.children) {
|
for _, name := range sortedNames(n.children) {
|
||||||
for _, p := range paths(n.children[name]) {
|
for _, p := range paths(n.children[name]) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ type namedNode struct {
|
|||||||
|
|
||||||
func contained(n node) []namedNode {
|
func contained(n node) []namedNode {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *group:
|
case *folder:
|
||||||
return named(n.children)
|
return named(n.children)
|
||||||
case *choice:
|
case *choice:
|
||||||
out := make([]namedNode, len(n.items))
|
out := make([]namedNode, len(n.items))
|
||||||
@@ -69,7 +69,7 @@ func contained(n node) []namedNode {
|
|||||||
// named skips a bound {/path} key: it is a render edge, not containment, so using
|
// 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. Only
|
// it as a path segment would report a node under a path that does not reach it. Only
|
||||||
// a template's fields hold bindings — loadDir skips a dot-prefixed entry, so a
|
// a template's fields hold bindings — loadDir skips a dot-prefixed entry, so a
|
||||||
// group's children never carry the prefix — so this one skip serves both.
|
// folder's children never carry the prefix — so this one skip serves both.
|
||||||
func named(m map[string]node) []namedNode {
|
func named(m map[string]node) []namedNode {
|
||||||
out := make([]namedNode, 0, len(m))
|
out := make([]namedNode, 0, len(m))
|
||||||
for _, name := range sortedNames(m) {
|
for _, name := range sortedNames(m) {
|
||||||
@@ -111,7 +111,7 @@ func (e renderEdge) reached() string {
|
|||||||
|
|
||||||
// renderEdges lists the children rendering n recurses into, mirroring expand: a
|
// renderEdges lists the children rendering n recurses into, mirroring expand: a
|
||||||
// choice's items, and a template's field/reference tokens plus its operands. A
|
// choice's items, and a template's field/reference tokens plus its operands. A
|
||||||
// group renders nothing, so it has no edges.
|
// folder renders nothing, so it has no edges.
|
||||||
func renderEdges(n node) []renderEdge {
|
func renderEdges(n node) []renderEdge {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *choice:
|
case *choice:
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import (
|
|||||||
// re-sums weights.
|
// re-sums weights.
|
||||||
type node interface{ isNode() }
|
type node interface{ isNode() }
|
||||||
|
|
||||||
// group is a namespace of named children, built from a directory of JSON files
|
// folder is a namespace of named children, built from a directory of JSON files
|
||||||
// and subdirectories. It has no value of its own: descend into a named child by
|
// and subdirectories. It has no value of its own: descend into a named child by
|
||||||
// dot path; rendering one is an error (see Fake).
|
// dot path; rendering one is an error (see Fake).
|
||||||
type group struct{ children map[string]node }
|
type folder struct{ children map[string]node }
|
||||||
|
|
||||||
func (*group) isNode() {}
|
func (*folder) isNode() {}
|
||||||
|
|
||||||
// choice picks one of its items. cum holds cumulative weights for a weighted
|
// choice picks one of its items. cum holds cumulative weights for a weighted
|
||||||
// pick; when nil the choice is uniform and selection is O(1). shared is the set of
|
// pick; when nil the choice is uniform and selection is O(1). shared is the set of
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type pathWalk struct {
|
|||||||
leaf func(n node) error
|
leaf func(n node) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkPath descends tail from n: a group or template by its next segment, a
|
// walkPath descends tail from n: a folder or template by its next segment, a
|
||||||
// choice by w.choice, which consumes no segment. A missing segment is an error,
|
// choice by w.choice, which consumes no segment. A missing segment is an error,
|
||||||
// so no walk reaches past what the data holds. A table-shaped dispatch, one case
|
// so no walk reaches past what the data holds. A table-shaped dispatch, one case
|
||||||
// per node kind, kept whole on purpose.
|
// per node kind, kept whole on purpose.
|
||||||
@@ -28,7 +28,7 @@ func walkPath(n node, tail []string, w pathWalk) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *group:
|
case *folder:
|
||||||
child, ok := n.children[tail[0]]
|
child, ok := n.children[tail[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("no entry %q", tail[0])
|
return fmt.Errorf("no entry %q", tail[0])
|
||||||
|
|||||||
+10
-10
@@ -162,17 +162,17 @@ func eachTemplate(root map[string]node, fn func(folder []string, path string, t
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var inFolder func(folder []string, children map[string]node) error
|
var inFolder func(dir []string, children map[string]node) error
|
||||||
inFolder = func(folder []string, children map[string]node) error {
|
inFolder = func(dir []string, children map[string]node) error {
|
||||||
for _, name := range sortedNames(children) {
|
for _, name := range sortedNames(children) {
|
||||||
path := join(strings.Join(folder, "."), name)
|
path := join(strings.Join(dir, "."), name)
|
||||||
if g, ok := children[name].(*group); ok {
|
if g, ok := children[name].(*folder); ok {
|
||||||
if err := inFolder(append(folder[:len(folder):len(folder)], name), g.children); err != nil {
|
if err := inFolder(append(dir[:len(dir):len(dir)], name), g.children); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := inCategory(folder, path, children[name]); err != nil {
|
if err := inCategory(dir, path, children[name]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,13 +183,13 @@ func eachTemplate(root map[string]node, fn func(folder []string, path string, t
|
|||||||
|
|
||||||
// resolveCategory walks a dotted path through the folders to the category it
|
// resolveCategory walks a dotted path through the folders to the category it
|
||||||
// names, returning that head, the node, and the tail left to read into it. A
|
// names, returning that head, the node, and the tail left to read into it. A
|
||||||
// descent of its own rather than a walkPath: it walks groups only and returns
|
// descent of its own rather than a walkPath: it walks folders only and returns
|
||||||
// where they end, not a leaf.
|
// where they end, not a leaf.
|
||||||
func resolveCategory(root map[string]node, segments []string) (head []string, target node, tail []string, err error) {
|
func resolveCategory(root map[string]node, segments []string) (head []string, target node, tail []string, err error) {
|
||||||
var n node = &group{children: root}
|
var n node = &folder{children: root}
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < len(segments); i++ {
|
for ; i < len(segments); i++ {
|
||||||
g, ok := n.(*group)
|
g, ok := n.(*folder)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ func resolveCategory(root map[string]node, segments []string) (head []string, ta
|
|||||||
}
|
}
|
||||||
n = child
|
n = child
|
||||||
}
|
}
|
||||||
if _, ok := n.(*group); ok {
|
if _, ok := n.(*folder); ok {
|
||||||
return nil, nil, nil, fmt.Errorf("names a folder, not a value")
|
return nil, nil, nil, fmt.Errorf("names a folder, not a value")
|
||||||
}
|
}
|
||||||
return segments[:i], n, segments[i:], nil
|
return segments[:i], n, segments[i:], nil
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ type rng interface {
|
|||||||
func (f *Generator) Fake(path string) (string, error) {
|
func (f *Generator) Fake(path string) (string, error) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
n, err := descend(f.rand, &group{children: f.categories}, strings.Split(path, "."))
|
n, err := descend(f.rand, &folder{children: f.categories}, strings.Split(path, "."))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
|
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||||
}
|
}
|
||||||
if _, ok := n.(*group); ok {
|
if _, ok := n.(*folder); ok {
|
||||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
||||||
}
|
}
|
||||||
return renderOnce(f.rand, n), nil
|
return renderOnce(f.rand, n), nil
|
||||||
|
|||||||
Reference in New Issue
Block a user