Answer architecture round 6: a manifest's reads names namespaces, loadReached notes only those nothing loaded provides, a mounted module labels its files by its namespace, Source.Load checks its mount, and the README keeps namespace and reads in an edited copy

This commit is contained in:
2026-10-11 12:41:11 +02:00
committed by Lillem4n
parent 3750770735
commit 89cafb1a21
13 changed files with 102 additions and 78 deletions
+12 -3
View File
@@ -6,6 +6,7 @@ import (
"io/fs"
"os"
"path"
"slices"
"strings"
"github.com/larvit/fejkdata/internal/grammar"
@@ -30,10 +31,15 @@ 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.
// 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.
func (s Source) Mounted(namespace string) Source {
if namespace != "" {
s.mount = strings.Split(namespace, ".")
if namespace == "" {
return s
}
s.mount = strings.Split(namespace, ".")
if s.label == "" {
s.label = path.Join(s.mount...)
}
return s
}
@@ -139,6 +145,9 @@ func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, er
// Load hands compile the category file name.json in the folder dir of the namespace, which
// lies under the source's mount.
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):])
entries, err := fs.ReadDir(s.fsys, full)
if err != nil {
+13 -13
View File
@@ -17,12 +17,12 @@ const ManifestFile = ".fejkdata.json"
// Manifest is what a source's manifest says of it.
type Manifest struct {
// Index is every category of the source by dot path; nil where the manifest has none.
Index map[string]IndexEntry `json:"index"`
// Reads is every module the source reads by default, directly or through another;
// nothing loads them.
Reads []string `json:"reads"`
Index map[string]IndexEntry
// Namespace is the dot path the source's root sits under, "" for the namespace's root.
Namespace string `json:"namespace"`
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
@@ -108,7 +108,7 @@ func decodeNamespace(v any) (string, error) {
return namespace, nil
}
// decodeReads decodes the modules a source reads, each named by a non-empty string.
// decodeReads decodes the namespaces of the modules a source reads.
func decodeReads(v any) ([]string, error) {
items, isList := v.([]any)
if !isList {
@@ -118,17 +118,17 @@ func decodeReads(v any) ([]string, error) {
seen := map[string]bool{}
var errs []error
for i, item := range items {
module, isString := item.(string)
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 module == "":
errs = append(errs, fmt.Errorf("reads item %d is empty; it names a module", i+1))
case seen[module]:
errs = append(errs, fmt.Errorf("reads item %d repeats %q", i+1, module))
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[module] = true
out = append(out, module)
seen[namespace] = true
out = append(out, namespace)
}
}
return out, errors.Join(errs...)