Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
// loadSources merges sources in order, the last winning a clash. A source whose manifest
|
// loadSources merges sources in order, the last winning a clash. A source whose manifest
|
||||||
// carries an index leaves its categories unloaded; every other source loads here, with
|
// carries an index leaves its categories unloaded; every other source loads here, with
|
||||||
// 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.
|
||||||
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{}}
|
||||||
for _, src := range sources {
|
for _, src := range sources {
|
||||||
@@ -25,16 +25,13 @@ func addSource(root *folder, src datafiles.Source) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.Index != nil {
|
if m.Index != nil {
|
||||||
placeIndex(root, src, m)
|
placeIndex(root, src, m.Index)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
g := &folder{children: map[string]node{}}
|
g := &folder{children: map[string]node{}}
|
||||||
if err := src.Walk(compileInto(func(dir []string) *folder { return madeFolder(g, dir) })); err != nil {
|
if err := src.Walk(compileInto(func(dir []string) *folder { return madeFolder(g, dir) })); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, s := range categorySites(g) {
|
|
||||||
s.in.setReads(s.name, m.Reads)
|
|
||||||
}
|
|
||||||
mergeFolder(root, g)
|
mergeFolder(root, g)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -73,7 +70,6 @@ func mergeFolder(dst, src *folder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst.put(k, v)
|
dst.put(k, v)
|
||||||
dst.setReads(k, src.reads[k])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +80,8 @@ 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 and its own dot path.
|
||||||
type categorySite struct {
|
type categorySite struct {
|
||||||
categoryAt
|
categoryAt
|
||||||
n node
|
n node
|
||||||
path string
|
path string
|
||||||
reads []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.
|
||||||
@@ -107,7 +102,7 @@ func categorySites(root *folder) []categorySite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func siteIn(dir []string, in *folder, name string) categorySite {
|
func siteIn(dir []string, in *folder, name string) categorySite {
|
||||||
return categorySite{categoryAt: categoryAt{dir: dir, in: in, name: name}, n: in.children[name], path: categoryPath(dir, name), reads: in.reads[name]}
|
return categorySite{categoryAt: categoryAt{dir: dir, in: in, name: name}, n: in.children[name], path: categoryPath(dir, name)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) }
|
func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) }
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ func TestPackageImports(t *testing.T) {
|
|||||||
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
|
"internal/rows": {"internal/drawstate", "internal/grammar", "internal/invariant"},
|
||||||
}
|
}
|
||||||
testsAlso := map[string][]string{
|
testsAlso := map[string][]string{
|
||||||
".": {"data", "data/sv_SE"},
|
".": {"data", "data/en_US", "data/geo/SE", "data/geo/US", "data/misc", "data/sv_SE"},
|
||||||
"cmd/fejkdata": {"internal/grammar"},
|
"cmd/fejkdata": {"internal/grammar"},
|
||||||
"data/en_US": {"."},
|
"data/en_US": {"."},
|
||||||
"data/geo/SE": {"."},
|
"data/geo/SE": {"."},
|
||||||
|
|||||||
@@ -49,13 +49,6 @@ func TestWalkHandsEachCategoryInOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkStartsAtTheBaseDir(t *testing.T) {
|
|
||||||
got, err := walk(t, Source{fsys: fstest.MapFS{"data/a.json": {Data: []byte(`"x"`)}, "b.json": {Data: []byte(`"y"`)}}, base: "data"})
|
|
||||||
if err != nil || len(got) != 1 || got[0].name != "a" || got[0].dir != nil {
|
|
||||||
t.Errorf("Walk = %#v, %v, want a alone at the base", got, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
||||||
src := Source{fsys: fstest.MapFS{
|
src := Source{fsys: fstest.MapFS{
|
||||||
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
@@ -138,10 +131,10 @@ func TestWalkRefusesADiskPathThatIsNoDirectory(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
||||||
src := Source{fsys: fstest.MapFS{
|
src := Source{fsys: fstest.MapFS{
|
||||||
"data/sub/t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
"sub/t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
"data/sub/t.tsv": {Data: []byte("key\nA\n")},
|
"sub/t.tsv": {Data: []byte("key\nA\n")},
|
||||||
"data/sub/u.json": {Data: []byte(`{`)},
|
"sub/u.json": {Data: []byte(`{`)},
|
||||||
}, base: "data"}
|
}}
|
||||||
var got handed
|
var got handed
|
||||||
var rows string
|
var rows string
|
||||||
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ type node interface{ isNode() }
|
|||||||
type folder struct {
|
type folder struct {
|
||||||
children map[string]node
|
children map[string]node
|
||||||
unloaded map[string]*indexed
|
unloaded map[string]*indexed
|
||||||
// reads is, for each category, the modules its source's manifest names it reading by default.
|
|
||||||
reads map[string][]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*folder) isNode() {}
|
func (*folder) isNode() {}
|
||||||
@@ -35,17 +33,6 @@ func (g *folder) put(name string, n node) {
|
|||||||
delete(g.unloaded, name)
|
delete(g.unloaded, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *folder) setReads(name string, reads []string) {
|
|
||||||
if len(reads) == 0 {
|
|
||||||
delete(g.reads, name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if g.reads == nil {
|
|
||||||
g.reads = map[string][]string{}
|
|
||||||
}
|
|
||||||
g.reads[name] = reads
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *folder) putUnloaded(name string, x *indexed) {
|
func (g *folder) putUnloaded(name string, x *indexed) {
|
||||||
delete(g.children, name)
|
delete(g.children, name)
|
||||||
if g.unloaded == nil {
|
if g.unloaded == nil {
|
||||||
|
|||||||
+56
-22
@@ -8,7 +8,6 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -19,6 +18,10 @@ import (
|
|||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/data"
|
"github.com/larvit/fejkdata/data"
|
||||||
|
"github.com/larvit/fejkdata/data/en_US"
|
||||||
|
"github.com/larvit/fejkdata/data/geo/SE"
|
||||||
|
"github.com/larvit/fejkdata/data/geo/US"
|
||||||
|
"github.com/larvit/fejkdata/data/misc"
|
||||||
"github.com/larvit/fejkdata/data/sv_SE"
|
"github.com/larvit/fejkdata/data/sv_SE"
|
||||||
"github.com/larvit/fejkdata/internal/datafiles"
|
"github.com/larvit/fejkdata/internal/datafiles"
|
||||||
)
|
)
|
||||||
@@ -47,25 +50,16 @@ func (m shippedModule) holds(path string) bool {
|
|||||||
return strings.HasPrefix(path, strings.ReplaceAll(m.dir, "/", ".")+".")
|
return strings.HasPrefix(path, strings.ReplaceAll(m.dir, "/", ".")+".")
|
||||||
}
|
}
|
||||||
|
|
||||||
// shippedModules is every module data.Modules lists, each folder found by descending its
|
// shippedModules is every module data.Modules lists, each beside its folder under data/.
|
||||||
// tree while a folder holds one folder and nothing else.
|
|
||||||
func shippedModules(t testing.TB) []shippedModule {
|
func shippedModules(t testing.TB) []shippedModule {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var out []shippedModule
|
out := []shippedModule{{en_US.FS, "en_US"}, {SE.FS, "geo/SE"}, {US.FS, "geo/US"}, {misc.FS, "misc"}, {sv_SE.FS, "sv_SE"}}
|
||||||
for _, fsys := range data.Modules() {
|
var listed []fs.FS
|
||||||
dir := "."
|
for _, m := range out {
|
||||||
for {
|
listed = append(listed, m.fsys)
|
||||||
entries, err := fs.ReadDir(fsys, dir)
|
}
|
||||||
if err != nil {
|
if !slices.Equal(listed, data.Modules()) {
|
||||||
t.Fatal(err)
|
t.Fatal("data.Modules() lists other modules than shippedModules does")
|
||||||
}
|
|
||||||
entries = slices.DeleteFunc(entries, func(e fs.DirEntry) bool { return strings.HasPrefix(e.Name(), ".") })
|
|
||||||
if len(entries) != 1 || !entries[0].IsDir() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dir = path.Join(dir, entries[0].Name())
|
|
||||||
}
|
|
||||||
out = append(out, shippedModule{fsys: fsys, dir: dir})
|
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -180,7 +174,7 @@ func shippedIndex(t *testing.T) map[string]datafiles.IndexEntry {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
index := map[string]datafiles.IndexEntry{}
|
index := map[string]datafiles.IndexEntry{}
|
||||||
for _, m := range shippedModules(t) {
|
for _, m := range shippedModules(t) {
|
||||||
manifest, err := datafiles.FS(m.fsys, "").Manifest()
|
manifest, err := datafiles.FS(m.fsys).Manifest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -191,7 +185,7 @@ func shippedIndex(t *testing.T) map[string]datafiles.IndexEntry {
|
|||||||
|
|
||||||
func TestEachShippedModuleNamesWhatItReads(t *testing.T) {
|
func TestEachShippedModuleNamesWhatItReads(t *testing.T) {
|
||||||
for _, m := range shippedModules(t) {
|
for _, m := range shippedModules(t) {
|
||||||
manifest, err := datafiles.FS(m.fsys, "").Manifest()
|
manifest, err := datafiles.FS(m.fsys).Manifest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -204,6 +198,33 @@ func TestEachShippedModuleNamesWhatItReads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEachShippedModuleRendersBesideTheModulesItReads(t *testing.T) {
|
||||||
|
modules := shippedModules(t)
|
||||||
|
for _, m := range modules {
|
||||||
|
manifest, err := datafiles.FS(m.fsys).Manifest()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
set := []fs.FS{m.fsys}
|
||||||
|
for _, read := range manifest.Reads {
|
||||||
|
i := slices.IndexFunc(modules, func(r shippedModule) bool { return r.importPath() == read })
|
||||||
|
if i < 0 {
|
||||||
|
t.Fatalf("%s reads %s, which no shipped module is", m.dir, read)
|
||||||
|
}
|
||||||
|
set = append(set, modules[i].fsys)
|
||||||
|
}
|
||||||
|
f, err := New(WithDataFS(set...))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New(%s with what it reads) = %v", m.dir, err)
|
||||||
|
}
|
||||||
|
for _, p := range sortedNames(manifest.Index) {
|
||||||
|
if _, err := f.Fake(p); err != nil {
|
||||||
|
t.Errorf("%s with what it reads: Fake(%q) = %v", m.dir, p, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) {
|
func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) {
|
||||||
f, err := New(WithDataFS(sv_SE.FS))
|
f, err := New(WithDataFS(sv_SE.FS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,6 +257,19 @@ func TestADataPathManifestNamesWhatItReads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnIndexedModuleNamesWhatItReads(t *testing.T) {
|
||||||
|
f, err := New(WithDataFS(fstest.MapFS{
|
||||||
|
".fejkdata.json": {Data: []byte(`{"index": {"x": {"paths": [""]}}, "reads": ["example.com/places"]}`)},
|
||||||
|
"x.json": {Data: []byte(`"{/place}"`)},
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New = %v", err)
|
||||||
|
}
|
||||||
|
if _, err := f.Fake("x"); err == nil || !strings.Contains(err.Error(), "the module holding x reads by default example.com/places") {
|
||||||
|
t.Errorf(`Fake("x") = %v, want the module x reads named`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewLoadsNoShippedCategory(t *testing.T) {
|
func TestNewLoadsNoShippedCategory(t *testing.T) {
|
||||||
f, err := New(withShipped())
|
f, err := New(withShipped())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -405,8 +439,8 @@ func TestABrokenManifestFailsNewNamingEveryMistake(t *testing.T) {
|
|||||||
`{"index": {"a": {"paths": ["", "x", "x"]}}}`: {`paths item 3 repeats "x"`},
|
`{"index": {"a": {"paths": ["", "x", "x"]}}}`: {`paths item 3 repeats "x"`},
|
||||||
`{"index": {"a": {"paths": [""], "parent": "b"}}}`: {`parent "b" names no entry`},
|
`{"index": {"a": {"paths": [""], "parent": "b"}}}`: {`parent "b" names no entry`},
|
||||||
`{"index": {"a": {"paths": [""], "reads": ["b"]}}}`: {`unknown key "reads"`},
|
`{"index": {"a": {"paths": [""], "reads": ["b"]}}}`: {`unknown key "reads"`},
|
||||||
`{"reads": "b"}`: {"reads must be a list, not a string"},
|
`{"index": {}, "reads": "b"}`: {"reads must be a list, not a string"},
|
||||||
`{"reads": ["", 3]}`: {"reads item 1 is empty", "reads item 2 must be a string, not a number"},
|
`{"index": {}, "reads": ["", 3]}`: {"reads item 1 is empty", "reads item 2 must be a string, not a number"},
|
||||||
} {
|
} {
|
||||||
_, err := New(WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(manifest)}}))
|
_, err := New(WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(manifest)}}))
|
||||||
for _, w := range want {
|
for _, w := range want {
|
||||||
|
|||||||
Reference in New Issue
Block a user