Files
fejkdata/shipped.go
T

169 lines
4.7 KiB
Go

package fejkdata
import (
"embed"
"fmt"
"io/fs"
"path"
"sort"
"strings"
)
//go:embed data
var shippedFS embed.FS
var shippedSource = dataSource{fsys: shippedFS, baseDir: "data"}
// shippedCategory is a shipped category as shippedindex.go lists it: the table beside it
// that it links to, "" for none, and the paths List advertises below it.
type shippedCategory struct {
parent string
paths []string
}
// shelve builds the folders of the shipped index, with every category in them unloaded.
func shelve() folder {
root := folder{children: map[string]node{}}
for p, c := range shippedIndex {
segs := strings.Split(p, ".")
g := &root
for _, seg := range segs[:len(segs)-1] {
sub, isFolder := g.children[seg].(*folder)
if !isFolder {
sub = &folder{children: map[string]node{}}
g.children[seg] = sub
}
g = sub
}
if g.unloaded == nil {
g.unloaded = map[string]shippedCategory{}
}
g.unloaded[segs[len(segs)-1]] = c
}
return root
}
// pendingLoad is an unloaded shipped category: the folder holding it, that folder's path,
// and its name there.
type pendingLoad struct {
dir []string
in *folder
name string
}
// unloadedAt is the unloaded shipped category a path from root names, or descends into.
func unloadedAt(root *folder, segs []string) (pendingLoad, bool) {
g := root
for i, seg := range segs {
switch n := g.children[seg].(type) {
case *folder:
g = n
continue
case nil:
if _, unloaded := g.unloaded[seg]; unloaded {
return pendingLoad{dir: segs[:i:i], in: g, name: seg}, true
}
}
return pendingLoad{}, false
}
return pendingLoad{}, false
}
// loadShippedAt loads the shipped category a caller's path names or descends into, unless
// it is loaded already.
func (f *Generator) loadShippedAt(segs []string) error {
if p, unloaded := unloadedAt(&f.root, segs); unloaded {
return loadShipped(&f.root, []pendingLoad{p})
}
return nil
}
// shippedReads is every unloaded shipped category the templates of scope reference, each
// reference read from the folder dir.
func shippedReads(root *folder, dir []string, scope nodeScope) []pendingLoad {
var out []pendingLoad
_ = scope(func(_ string, n node) error {
t, isTemplate := n.(*template)
if !isTemplate {
return nil
}
for _, name := range refTokens(t.tokens) {
segs, err := refSegments(name, dir)
if err != nil {
continue // the link reports it
}
if p, unloaded := unloadedAt(root, segs); unloaded {
out = append(out, p)
}
}
return nil
})
return out
}
// loadShipped loads the shipped categories wanted, with every category they read and the
// whole table family of each, then binds what it loaded, as New binds a whole load. A failed
// bind leaves every category it loaded unloaded again.
func loadShipped(root *folder, wanted []pendingLoad) (err error) {
var loaded []pendingLoad
var entries []shippedCategory
defer func() {
if err == nil {
return
}
for i, p := range loaded {
delete(p.in.children, p.name)
p.in.unloaded[p.name] = entries[i]
}
}()
var sites []categorySite
for queue := wanted; len(queue) > 0; queue = queue[1:] {
p := queue[0]
c, unloaded := p.in.unloaded[p.name]
if !unloaded {
continue
}
site, err := p.load()
if err != nil {
return err
}
loaded, entries = append(loaded, p), append(entries, c)
sites = append(sites, site)
queue = append(queue, shippedReads(root, p.dir, sitesScope([]categorySite{site}))...)
queue = append(queue, p.family(c)...)
}
sort.Slice(sites, func(i, j int) bool { return sites[i].path < sites[j].path })
return categoryBinding(sites, root.children).bind()
}
// load parses and compiles the category, and moves it from its folder's unloaded
// categories to its children.
func (p pendingLoad) load() (categorySite, error) {
dir := path.Join(append([]string{shippedSource.baseDir}, p.dir...)...)
entries, err := fs.ReadDir(shippedSource.fsys, dir)
if err != nil {
return categorySite{}, fmt.Errorf("%s: %w", dir, err)
}
file := p.name + ".json"
if err := loadFile(shippedSource, p.in, path.Join(dir, file), file, newCategoryFiles(shippedSource, dir, entries)); err != nil {
return categorySite{}, err
}
delete(p.in.unloaded, p.name)
return categorySite{dir: p.dir, path: join(strings.Join(p.dir, "."), p.name), in: p.in, n: p.in.children[p.name]}, nil
}
// family is the tables beside p that a table family links it to: its parent, and every
// table whose parent it is.
func (p pendingLoad) family(c shippedCategory) []pendingLoad {
var out []pendingLoad
if c.parent != "" {
out = append(out, pendingLoad{dir: p.dir, in: p.in, name: c.parent})
}
for name, sibling := range p.in.unloaded {
if sibling.parent == p.name {
out = append(out, pendingLoad{dir: p.dir, in: p.in, name: name})
}
}
return out
}