Answer architecture round 2: each category site carries its source's default modules, set where the walk or the index loads it, and the note is written beside the resolve that finds the read; the decision states why New checks no reads

This commit is contained in:
2026-10-10 13:20:26 +02:00
committed by Lillem4n
parent 2ddba2ce96
commit 3da487f8ea
5 changed files with 42 additions and 55 deletions
+16 -10
View File
@@ -11,18 +11,22 @@ import (
// the unloaded categories it needs: docs/decisions.md#a-source-whose-manifest-carries-an-index-loads-each-category-on-first-reach-any-other-loads-in-new-with-what-it-reads // the unloaded categories it needs: docs/decisions.md#a-source-whose-manifest-carries-an-index-loads-each-category-on-first-reach-any-other-loads-in-new-with-what-it-reads
func loadSources(sources []datafiles.Source) (folder, error) { func loadSources(sources []datafiles.Source) (folder, error) {
root := folder{children: map[string]node{}} root := folder{children: map[string]node{}}
defaultModules := map[string][]string{} walked := map[string][]string{}
for _, src := range sources { for _, src := range sources {
if err := addSource(&root, src, defaultModules); err != nil { if err := addSource(&root, src, walked); err != nil {
return folder{}, err return folder{}, err
} }
} }
return root, loadReached(&root, categorySites(&root), nil, defaultModules) sites := categorySites(&root)
for i := range sites {
sites[i].defaultModules = walked[sites[i].path]
}
return root, loadReached(&root, sites, nil)
} }
// addSource places src's categories in root, and sets in defaultModules, for each category // addSource places src's categories in root, and sets in walked, for each category it loads
// it loads, the modules src reads by default. // here, the modules src reads by default.
func addSource(root *folder, src datafiles.Source, defaultModules map[string][]string) error { func addSource(root *folder, src datafiles.Source, walked map[string][]string) error {
m, err := src.Manifest() m, err := src.Manifest()
if err != nil { if err != nil {
return err return err
@@ -36,7 +40,7 @@ func addSource(root *folder, src datafiles.Source, defaultModules map[string][]s
return err return err
} }
for _, s := range categorySites(g) { for _, s := range categorySites(g) {
defaultModules[s.path] = m.Reads walked[s.path] = m.Reads
} }
mergeFolder(root, g) mergeFolder(root, g)
return nil return nil
@@ -83,11 +87,13 @@ func mergeFolder(dst, src *folder) {
// single inline node. // single inline node.
type nodeSet func(fn func(label string, n node) error) error type nodeSet func(fn func(label string, n node) error) error
// categorySite is a loaded category, where it sits and its own dot path. // categorySite is a loaded category, where it sits, its own dot path, and the modules its
// source reads by default.
type categorySite struct { type categorySite struct {
categoryAt categoryAt
n node n node
path string path string
defaultModules []string
} }
// categorySites lists every category under root, folders and names in sorted order. // categorySites lists every category under root, folders and names in sorted order.
+3 -2
View File
@@ -583,8 +583,9 @@ when a source carries it.
2026-09-24, Lilleman auf Larv; revised 2026-10-10, when the shipped data became Go packages. 2026-09-24, Lilleman auf Larv; revised 2026-10-10, when the shipped data became Go packages.
Serves goal 6.2. `sv_SE.FS` loads alone, and `sv_SE.address` then fails at first reach, Serves goal 6.2. `sv_SE.FS` loads alone, and `sv_SE.address` then fails at first reach,
naming `github.com/larvit/fejkdata/data/geo/SE`, which `sv_SE`'s manifest reads. `New` naming `github.com/larvit/fejkdata/data/geo/SE`, which `sv_SE`'s manifest reads. `New`
checks no `reads`: a stand-in providing the same paths under another name would fail it checks no `reads`: a check by module name fails a stand-in providing the same paths (goal
(goal 9.2). 9.2), and a check by path needs every reference, which only loading each indexed category
gives, against goal 13.
`--no-shipped-data -d` takes a locale's folder with its country's, or a set of one's own. `--no-shipped-data -d` takes a locale's folder with its country's, or a set of one's own.
## The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more ## The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more
+4 -25
View File
@@ -60,7 +60,7 @@ func (f *Generator) loadCallerPath(path string) (string, []string, error) {
return "", nil, fmt.Errorf("fejkdata: %w", err) return "", nil, fmt.Errorf("fejkdata: %w", err)
} }
if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() { if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() {
if err := loadReached(&f.root, nil, []categoryAt{c}, nil); err != nil { if err := loadReached(&f.root, nil, []categoryAt{c}); err != nil {
return "", nil, fmt.Errorf("fejkdata: %w", loadError{err}) return "", nil, fmt.Errorf("fejkdata: %w", loadError{err})
} }
} }
@@ -107,9 +107,8 @@ func needs(root *folder, s categorySite) []categoryAt {
// loadReached loads the unloaded categories among wanted and every unloaded category they // loadReached loads the unloaded categories among wanted and every unloaded category they
// or sites need, then runs sites and all it loaded through a whole load's pipeline. On an // or sites need, then runs sites and all it loaded through a whole load's pipeline. On an
// error it puts back what it loaded, so the next call fails the same way. defaultModules // error it puts back what it loaded, so the next call fails the same way.
// holds, by path, the modules the source of each of sites reads by default. func loadReached(root *folder, sites []categorySite, wanted []categoryAt) error {
func loadReached(root *folder, sites []categorySite, wanted []categoryAt, defaultModules map[string][]string) error {
queue := wanted queue := wanted
for _, s := range sites { for _, s := range sites {
queue = append(queue, needs(root, s)...) queue = append(queue, needs(root, s)...)
@@ -120,14 +119,6 @@ func loadReached(root *folder, sites []categorySite, wanted []categoryAt, defaul
err = b.run(root, sites) err = b.run(root, sites)
} }
if err != nil { if err != nil {
err = withDefaultModules(err, func(category string) []string {
for _, l := range b {
if l.site.path == category {
return l.was.defaultModules
}
}
return defaultModules[category]
})
b.putBack() b.putBack()
} }
return err return err
@@ -158,6 +149,7 @@ func (b *batch) load(root *folder, queue []categoryAt) error {
return err return err
} }
s := siteIn(c.dir, c.in, c.name) s := siteIn(c.dir, c.in, c.name)
s.defaultModules = x.defaultModules
*b = append(*b, loaded{site: s, was: x}) *b = append(*b, loaded{site: s, was: x})
if parent := parentOf(s.n); parent != x.entry.Parent { if parent := parentOf(s.n); parent != x.entry.Parent {
return fmt.Errorf("%s: the index entry for %s %s, and its table %s", x.src.ManifestPath(), s.path, namesParent(x.entry.Parent), namesParent(parent)) return fmt.Errorf("%s: the index entry for %s %s, and its table %s", x.src.ManifestPath(), s.path, namesParent(x.entry.Parent), namesParent(parent))
@@ -178,19 +170,6 @@ func (b batch) run(root *folder, sites []categorySite) error {
return categoryPipeline(sites, root.children).run() return categoryPipeline(sites, root.children).run()
} }
// withDefaultModules adds to err, where a reference of a category names nothing, the modules
// the category's source reads by default, or that it names none.
func withDefaultModules(err error, of func(category string) []string) error {
var u unresolvedRead
if !errors.As(err, &u) {
return err
}
if modules := of(u.category); len(modules) > 0 {
return fmt.Errorf("%w; the module holding %s reads by default %s", err, u.category, strings.Join(modules, ", "))
}
return fmt.Errorf("%w, and the module holding %s names no module it reads by default", err, u.category)
}
func (b batch) putBack() { func (b batch) putBack() {
for _, l := range b { for _, l := range b {
l.site.in.putUnloaded(l.site.name, l.was) l.site.in.putUnloaded(l.site.name, l.was)
+1 -1
View File
@@ -90,7 +90,7 @@ func loadInline(root *folder, n node, label string, typedByGo bool) error {
if err := refuseFolderRefs(nodes); err != nil { if err := refuseFolderRefs(nodes); err != nil {
return err return err
} }
if err := loadReached(root, nil, referenced(root, nil, nodes), nil); err != nil { if err := loadReached(root, nil, referenced(root, nil, nodes)); err != nil {
return loadError{err} return loadError{err}
} }
return pipeline{ return pipeline{
+18 -17
View File
@@ -3,26 +3,18 @@ package fejkdata
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
) )
// templateSite is a template to resolve, with its folder and its category's path, both empty for an // templateSite is a template to resolve, with its folder and its category, both empty for an
// inline template, and eachNode's label for it. // inline template, and eachNode's label for it.
type templateSite struct { type templateSite struct {
t *template t *template
folder []string folder []string
label string label string
category string category *categorySite
} }
// unresolvedRead is a reference of a category's template naming nothing.
type unresolvedRead struct {
category string
err error
}
func (u unresolvedRead) Error() string { return u.err.Error() }
func (u unresolvedRead) Unwrap() error { return u.err }
// resolveCategoryTemplates resolves every template of the categories, once all data is merged, so a // resolveCategoryTemplates resolves every template of the categories, once all data is merged, so a
// reference sees the override-resolved tree. // reference sees the override-resolved tree.
func resolveCategoryTemplates(sites []categorySite, root map[string]node) error { func resolveCategoryTemplates(sites []categorySite, root map[string]node) error {
@@ -30,7 +22,7 @@ func resolveCategoryTemplates(sites []categorySite, root map[string]node) error
for _, s := range sites { for _, s := range sites {
if err := eachNode(s.n, s.path, func(label string, n node) error { if err := eachNode(s.n, s.path, func(label string, n node) error {
if t, isTemplate := n.(*template); isTemplate { if t, isTemplate := n.(*template); isTemplate {
ts = append(ts, templateSite{t: t, folder: s.dir, label: label, category: s.path}) ts = append(ts, templateSite{t: t, folder: s.dir, label: label, category: &s})
} }
return nil return nil
}); err != nil { }); err != nil {
@@ -66,12 +58,8 @@ type resolvedNames struct {
func resolveTemplates(ts []templateSite, root map[string]node) error { func resolveTemplates(ts []templateSite, root map[string]node) error {
for _, s := range ts { for _, s := range ts {
refs, err := s.t.resolveRefs(s.folder, s.label, root) refs, err := s.t.resolveRefs(s.folder, s.label, root)
var ne noEntry
if err != nil && s.category != "" && errors.As(err, &ne) {
return unresolvedRead{category: s.category, err: err}
}
if err != nil { if err != nil {
return err return s.withDefaultModules(err)
} }
s.t.refs = refs s.t.refs = refs
} }
@@ -102,3 +90,16 @@ func resolveTemplates(ts []templateSite, root map[string]node) error {
} }
return nil return nil
} }
// withDefaultModules adds to err, where a reference of a category names nothing, the modules
// the category's source reads by default, or that it names none.
func (s templateSite) withDefaultModules(err error) error {
var ne noEntry
if s.category == nil || !errors.As(err, &ne) {
return err
}
if modules := s.category.defaultModules; len(modules) > 0 {
return fmt.Errorf("%w; the module holding %s reads by default %s", err, s.category.path, strings.Join(modules, ", "))
}
return fmt.Errorf("%w, and the module holding %s names no module it reads by default", err, s.category.path)
}