Answer architecture round 7: reads and its note move to item 196, a mounted module's errors read sv_SE (address.json), internal/datafiles handles namespace folders in one form, and a folder outside the mount is a broken invariant

This commit is contained in:
2026-10-11 13:23:30 +02:00
committed by Lillem4n
parent 6a62840373
commit cc1c3bbc7d
12 changed files with 76 additions and 111 deletions
+20 -23
View File
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/larvit/fejkdata/internal/grammar"
"github.com/larvit/fejkdata/internal/invariant"
"github.com/larvit/fejkdata/internal/jsonvalue"
)
@@ -31,15 +32,10 @@ func Dir(path string) Source {
// FS is the tree in fsys.
func FS(fsys fs.FS) Source { return Source{fsys: fsys} }
// Mounted is s with its root under the dot path namespace, "" for the namespace's root. A
// source with no label of its own names its files in errors by the namespace's folders.
// Mounted is s with its root under the dot path namespace, "" for the namespace's root.
func (s Source) Mounted(namespace string) Source {
if namespace == "" {
return s
}
s.mount = strings.Split(namespace, ".")
if s.label == "" {
s.label = path.Join(s.mount...)
if namespace != "" {
s.mount = strings.Split(namespace, ".")
}
return s
}
@@ -56,16 +52,24 @@ type Category struct {
// ReadRows reads a rows file beside the category.
func (c Category) ReadRows(name string) (string, error) { return c.rows.read(name) }
// labelled is the file p of fsys as errors name it: under the source's label, or, for a
// mounted source with none, beside its namespace, "sv_SE (address.json)".
func (s Source) labelled(p string) string {
if s.label == "" {
return p
switch {
case s.label != "":
return path.Join(s.label, p)
case s.mount != nil:
return fmt.Sprintf("%s (%s)", strings.Join(s.mount, "."), p)
}
return path.Join(s.label, p)
return p
}
// dirPath is the folder that dir names, as fsys spells it.
// dirPath is the folder that the namespace folders dir name, as fsys spells it.
func (s Source) dirPath(dir []string) string {
if p := path.Join(dir...); p != "" {
if len(dir) < len(s.mount) || !slices.Equal(dir[:len(s.mount)], s.mount) {
panic(invariant.Broken("folder %s lies outside the mount %s", strings.Join(dir, "."), strings.Join(s.mount, ".")))
}
if p := path.Join(dir[len(s.mount):]...); p != "" {
return p
}
return "."
@@ -78,10 +82,7 @@ func (s Source) Walk(compile func(Category) error) error {
if err := s.check(); err != nil {
return err
}
_, err := s.walkDir(nil, func(c Category) error {
c.Folders = append(s.mount[:len(s.mount):len(s.mount)], c.Folders...)
return compile(c)
})
_, err := s.walkDir(slices.Clip(s.mount), compile)
return err
}
@@ -142,13 +143,9 @@ func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, er
return true, nil
}
// Load hands compile the category file name.json in the folder dir of the namespace, which
// lies under the source's mount.
// Load hands compile the category file name.json in the namespace folder dir.
func (s Source) Load(dir []string, name string, compile func(Category) error) error {
if len(dir) < len(s.mount) || !slices.Equal(dir[:len(s.mount)], s.mount) {
return fmt.Errorf("%s.%s lies outside the namespace %s", strings.Join(dir, "."), name, strings.Join(s.mount, "."))
}
full := s.dirPath(dir[len(s.mount):])
full := s.dirPath(dir)
entries, err := fs.ReadDir(s.fsys, full)
if err != nil {
return fmt.Errorf("%s: %w", s.labelled(full), err)
+1 -34
View File
@@ -20,9 +20,6 @@ type Manifest struct {
Index map[string]IndexEntry
// Namespace is the dot path the source's root sits under, "" for the namespace's root.
Namespace string
// Reads is the namespace of every module the source reads unless another source
// provides the same paths, directly or through another; nothing loads them.
Reads []string
}
// IndexEntry is what an index says of a category: its parent table, "" for none, and the
@@ -78,12 +75,8 @@ func decodeManifest(b []byte) (Manifest, error) {
var err error
m.Namespace, err = decodeNamespace(top[k])
errs = append(errs, err)
case "reads":
var err error
m.Reads, err = decodeReads(top[k])
errs = append(errs, err)
default:
errs = append(errs, fmt.Errorf("unknown key %q; a manifest holds index, namespace and reads", k))
errs = append(errs, fmt.Errorf("unknown key %q; a manifest holds index and namespace", k))
}
}
for _, p := range sortedKeys(m.Index) {
@@ -108,32 +101,6 @@ func decodeNamespace(v any) (string, error) {
return namespace, nil
}
// decodeReads decodes the namespaces of the modules a source reads.
func decodeReads(v any) ([]string, error) {
items, isList := v.([]any)
if !isList {
return nil, fmt.Errorf("reads must be a list, not %s", jsonvalue.Kind(v))
}
out := make([]string, 0, len(items))
seen := map[string]bool{}
var errs []error
for i, item := range items {
namespace, isString := item.(string)
switch {
case !isString:
errs = append(errs, fmt.Errorf("reads item %d must be a string, not %s", i+1, jsonvalue.Kind(item)))
case checkCategoryPath(namespace) != nil:
errs = append(errs, fmt.Errorf("reads item %d: %w", i+1, checkCategoryPath(namespace)))
case seen[namespace]:
errs = append(errs, fmt.Errorf("reads item %d repeats %q", i+1, namespace))
default:
seen[namespace] = true
out = append(out, namespace)
}
}
return out, errors.Join(errs...)
}
func decodeIndex(v any) (map[string]IndexEntry, []error) {
entries, isObject := v.(map[string]any)
if !isObject {