diff --git a/CHANGELOG.md b/CHANGELOG.md index ef74664..c79fd4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,8 +131,8 @@ replacement, and each removed path, column or flag. and the CLI carries them all. `New` loads the modules `WithDataFS(modules...)` names and the folders `WithDataPath` names, and fails, naming `WithDataFS`, where no such option is given. A manifest's `reads` lists the modules its source reads by default, and a - reference to a path nothing provides fails, naming those modules or saying there are - none. + reference to a path nothing provides fails to load, naming every module the loaded + manifests read, or saying there are none. - A data source whose root holds a `.fejkdata.json` manifest with an `index` loads each category on the first call reaching it, as each shipped module does. Data failing to load, in `New` or on that first call, gives an error matching `ErrLoad`, and the CLI exits 1. diff --git a/README.md b/README.md index d3594d7..8f0296a 100644 --- a/README.md +++ b/README.md @@ -195,8 +195,9 @@ A manifest's `reads` lists, by any name its author chooses, every module the sou references read unless another source provides the same paths, directly or through another module; a manifest may carry it without an index. Nothing loads these modules: pass them yourself. A shipped module names a Go package: `sv_SE`'s manifest reads -`github.com/larvit/fejkdata/data/geo/SE`. A reference to a path nothing provides fails, -naming those modules, or saying the manifest names none. +`github.com/larvit/fejkdata/data/geo/SE`. Data whose reference names a path nothing +provides fails to load, naming every module the loaded manifests read, or saying they +name none. Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`, `ip`, `last-name`, `person`, `phone`, `price`, `sentence`, `sex`, `time`, `url`, diff --git a/data.go b/data.go index 68dbc53..2cd962f 100644 --- a/data.go +++ b/data.go @@ -1,6 +1,7 @@ package fejkdata import ( + "slices" "strings" "github.com/larvit/fejkdata/internal/datafiles" @@ -8,22 +9,31 @@ import ( // 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 -// the unloaded categories it needs. -func loadSources(sources []datafiles.Source) (folder, error) { +// the unloaded categories it needs. It also returns, sorted, every module the sources' +// manifests read by default. +// 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, []string, error) { root := folder{children: map[string]node{}} + var modules []string for _, src := range sources { - if err := addSource(&root, src); err != nil { - return folder{}, err + m, err := src.Manifest() + if err != nil { + return folder{}, nil, err + } + if err := addSource(&root, src, m); err != nil { + return folder{}, nil, err + } + for _, module := range m.Reads { + if !slices.Contains(modules, module) { + modules = append(modules, module) + } } } - return root, loadReached(&root, categorySites(&root), nil) + slices.Sort(modules) + return root, modules, loadReached(&root, categorySites(&root), nil) } -func addSource(root *folder, src datafiles.Source) error { - m, err := src.Manifest() - if err != nil { - return err - } +func addSource(root *folder, src datafiles.Source, m datafiles.Manifest) error { if m.Index != nil { placeIndex(root, src, m.Index) return nil diff --git a/fejkdata.go b/fejkdata.go index 4fcb36a..118c527 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -31,6 +31,8 @@ type Generator struct { drawState *drawstate.State root folder // the categories as the node a path walks from, owned here so a walk allocates none structs map[reflect.Type]structResult + // defaultModules is every module the loaded sources' manifests read by default. + defaultModules []string } type config struct { @@ -107,15 +109,15 @@ func New(opts ...Option) (*Generator, error) { if c.err != nil { return nil, c.err } - root, err := loadSources(c.sources) + root, modules, err := loadSources(c.sources) if err != nil { - return nil, fmt.Errorf("fejkdata: %w", loadError{err}) + return nil, fmt.Errorf("fejkdata: %w", withDefaultModules(loadError{err}, modules)) } seed, err := c.drawSeed() if err != nil { return nil, fmt.Errorf("fejkdata: %w", err) } - return &Generator{drawState: drawstate.New(seed), root: root}, nil + return &Generator{drawState: drawstate.New(seed), root: root, defaultModules: modules}, nil } // List returns the sorted dotted paths Fake renders: each category and every field, diff --git a/index.go b/index.go index fa1c67e..45b37c8 100644 --- a/index.go +++ b/index.go @@ -59,7 +59,7 @@ func (f *Generator) loadCallerPath(path string) (string, []string, error) { } if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() { if err := loadReached(&f.root, nil, []categoryAt{c}); err != nil { - return "", nil, fmt.Errorf("fejkdata: %w", loadError{err}) + return "", nil, fmt.Errorf("fejkdata: %w", withDefaultModules(loadError{err}, f.defaultModules)) } } return path, segs, nil diff --git a/inline.go b/inline.go index a9e15b3..50ae382 100644 --- a/inline.go +++ b/inline.go @@ -37,7 +37,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) { f.mu.Lock() defer f.mu.Unlock() if err := loadInline(&f.root, n, "template", false); err != nil { - return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", err)) + return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", withDefaultModules(err, f.defaultModules))) } return &Template{g: f, n: n}, nil } diff --git a/reference.go b/reference.go index 60e7433..2977158 100644 --- a/reference.go +++ b/reference.go @@ -121,6 +121,19 @@ type noEntry struct{ seg string } func (e noEntry) Error() string { return fmt.Sprintf("no entry %q", e.seg) } +// withDefaultModules adds to err, where data fails to load because a segment names nothing, the +// modules the loaded sources' manifests read by default, or that they name none. +func withDefaultModules(err error, modules []string) error { + var ne noEntry + if !errors.Is(err, ErrLoad) || !errors.As(err, &ne) { + return err + } + if len(modules) == 0 { + return fmt.Errorf("%w, and no loaded module names a module it reads by default", err) + } + return fmt.Errorf("%w; the loaded modules read by default %s", err, strings.Join(modules, ", ")) +} + // noDataNote adds to err, where a segment names nothing and f holds no data at all, that no // data is loaded. func (f *Generator) noDataNote(err error) error { diff --git a/resolve.go b/resolve.go index 6ad41bd..5556502 100644 --- a/resolve.go +++ b/resolve.go @@ -4,8 +4,8 @@ import ( "fmt" ) -// templateSite is a template to resolve, with its folder and category, both empty for an inline -// template, and eachNode's label for it. +// templateSite is a template to resolve, with its folder, empty for an inline template, and +// eachNode's label for it. type templateSite struct { t *template folder []string diff --git a/struct.go b/struct.go index 61a41eb..cfe93fa 100644 --- a/struct.go +++ b/struct.go @@ -31,7 +31,7 @@ func (f *Generator) FakeStruct(v any) error { defer f.mu.Unlock() shape, err := f.structShapeOf(p.Elem().Type()) if err != nil { - return fmt.Errorf("fejkdata: %w", err) + return fmt.Errorf("fejkdata: %w", withDefaultModules(err, f.defaultModules)) } shape.fill(f.drawState, p.Elem()) return nil