Answer architecture round 9: each data package exports Modules, a locale's importing its country's geo package as a bench chose, and a namespace mounts a tree through an fs.FS wrapper, so internal/datafiles walks file-system folders alone
This commit is contained in:
@@ -6,22 +6,18 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||
)
|
||||
|
||||
// Source is one tree to load, from the root of an fs.FS. label prefixes file names in
|
||||
// errors; onDisk marks label as a directory that must exist. mount is the folders the
|
||||
// tree's root sits under, in the namespace its categories are reached by.
|
||||
// errors; onDisk marks label as a directory that must exist.
|
||||
type Source struct {
|
||||
fsys fs.FS
|
||||
label string
|
||||
onDisk bool
|
||||
mount []string
|
||||
}
|
||||
|
||||
// Dir is the tree in the directory path, its errors labelled with it.
|
||||
@@ -32,16 +28,16 @@ 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 tree under the folders the dot path namespace spells, "" for none.
|
||||
func (s Source) Mounted(namespace string) Source {
|
||||
if namespace != "" {
|
||||
s.mount = strings.Split(namespace, ".")
|
||||
s.fsys = mounted{fsys: s.fsys, at: strings.ReplaceAll(namespace, ".", "/")}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Category is one category file: the folders above it in the namespace, its name, and its
|
||||
// parsed JSON.
|
||||
// Category is one category file: the folders above it from the source's root, its name,
|
||||
// and its parsed JSON.
|
||||
type Category struct {
|
||||
Folders []string
|
||||
Name string
|
||||
@@ -52,26 +48,16 @@ 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 {
|
||||
switch {
|
||||
case s.label != "":
|
||||
return path.Join(s.label, p)
|
||||
case s.mount != nil && p == ".":
|
||||
return strings.Join(s.mount, ".")
|
||||
case s.mount != nil:
|
||||
return fmt.Sprintf("%s (%s)", strings.Join(s.mount, "."), p)
|
||||
if s.label == "" {
|
||||
return p
|
||||
}
|
||||
return p
|
||||
return path.Join(s.label, p)
|
||||
}
|
||||
|
||||
// dirPath is the folder that the namespace folders dir name, as fsys spells it.
|
||||
// dirPath is the folder that dir names, as fsys spells it.
|
||||
func (s Source) dirPath(dir []string) string {
|
||||
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 != "" {
|
||||
if p := path.Join(dir...); p != "" {
|
||||
return p
|
||||
}
|
||||
return "."
|
||||
@@ -84,7 +70,7 @@ func (s Source) Walk(compile func(Category) error) error {
|
||||
if err := s.check(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := s.walkDir(slices.Clip(s.mount), compile)
|
||||
_, err := s.walkDir(nil, compile)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -145,7 +131,7 @@ 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 namespace folder dir.
|
||||
// Load hands compile the category file name.json in the folder dir.
|
||||
func (s Source) Load(dir []string, name string, compile func(Category) error) error {
|
||||
full := s.dirPath(dir)
|
||||
entries, err := fs.ReadDir(s.fsys, full)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package datafiles
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// mounted is fsys with its root moved down to the folder at, so a file f of fsys is at/f;
|
||||
// the folders above at hold only the next folder towards it.
|
||||
type mounted struct {
|
||||
fsys fs.FS
|
||||
at string
|
||||
}
|
||||
|
||||
func (m mounted) Open(name string) (fs.File, error) {
|
||||
inner, ok := m.inner(name)
|
||||
if ok && inner != "." {
|
||||
return m.fsys.Open(inner)
|
||||
}
|
||||
if ok {
|
||||
f, err := m.fsys.Open(".")
|
||||
return mountRoot{File: f, name: path.Base(name)}, err
|
||||
}
|
||||
if next, ok := m.towards(name); ok {
|
||||
return &mountDir{name: path.Base(name), next: next}, nil
|
||||
}
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
||||
}
|
||||
|
||||
func (m mounted) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
if inner, ok := m.inner(name); ok {
|
||||
return fs.ReadDir(m.fsys, inner)
|
||||
}
|
||||
if next, ok := m.towards(name); ok {
|
||||
return []fs.DirEntry{mountEntry(next)}, nil
|
||||
}
|
||||
return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrNotExist}
|
||||
}
|
||||
|
||||
// inner is name as fsys spells it, where name lies at or below at.
|
||||
func (m mounted) inner(name string) (string, bool) {
|
||||
if name == m.at {
|
||||
return ".", true
|
||||
}
|
||||
rest, below := strings.CutPrefix(name, m.at+"/")
|
||||
return rest, below && fs.ValidPath(name)
|
||||
}
|
||||
|
||||
// towards is the folder below name on the way to at, where name lies above at.
|
||||
func (m mounted) towards(name string) (string, bool) {
|
||||
rest := m.at
|
||||
if name != "." {
|
||||
var above bool
|
||||
if rest, above = strings.CutPrefix(m.at, name+"/"); !above {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
next, _, _ := strings.Cut(rest, "/")
|
||||
return next, true
|
||||
}
|
||||
|
||||
// mountRoot is fsys's root, named as the folder it is mounted at.
|
||||
type mountRoot struct {
|
||||
fs.File
|
||||
name string
|
||||
}
|
||||
|
||||
func (r mountRoot) Stat() (fs.FileInfo, error) {
|
||||
info, err := r.File.Stat()
|
||||
return renamed{FileInfo: info, name: r.name}, err
|
||||
}
|
||||
|
||||
func (r mountRoot) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
if d, ok := r.File.(fs.ReadDirFile); ok {
|
||||
return d.ReadDir(n)
|
||||
}
|
||||
return nil, &fs.PathError{Op: "readdir", Path: r.name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
type renamed struct {
|
||||
fs.FileInfo
|
||||
name string
|
||||
}
|
||||
|
||||
func (r renamed) Name() string { return r.name }
|
||||
|
||||
// mountDir is a folder above the mount, holding only the folder next towards it.
|
||||
type mountDir struct {
|
||||
name, next string
|
||||
read bool
|
||||
}
|
||||
|
||||
func (d *mountDir) Stat() (fs.FileInfo, error) { return mountEntry(d.name), nil }
|
||||
func (d *mountDir) Read([]byte) (int, error) {
|
||||
return 0, &fs.PathError{Op: "read", Path: d.name, Err: fs.ErrInvalid}
|
||||
}
|
||||
func (d *mountDir) Close() error { return nil }
|
||||
|
||||
func (d *mountDir) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
if d.read {
|
||||
if n > 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
d.read = true
|
||||
return []fs.DirEntry{mountEntry(d.next)}, nil
|
||||
}
|
||||
|
||||
// mountEntry is a folder above the mount, as both its DirEntry and its FileInfo.
|
||||
type mountEntry string
|
||||
|
||||
func (e mountEntry) Name() string { return string(e) }
|
||||
func (e mountEntry) IsDir() bool { return true }
|
||||
func (e mountEntry) Type() fs.FileMode { return fs.ModeDir }
|
||||
func (e mountEntry) Info() (fs.FileInfo, error) { return e, nil }
|
||||
func (e mountEntry) Size() int64 { return 0 }
|
||||
func (e mountEntry) Mode() fs.FileMode { return fs.ModeDir | 0o555 }
|
||||
func (e mountEntry) ModTime() time.Time { return time.Time{} }
|
||||
func (e mountEntry) Sys() any { return nil }
|
||||
Reference in New Issue
Block a user