Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
+4
-4
@@ -26,10 +26,10 @@ and any release may change what a seed draws, so pin one exact version
|
||||
a record's columns, one record per row. The CLI exits `0` on success, `1` on a runtime
|
||||
error such as data that fails to load, and `2` on misuse.
|
||||
- The library, `github.com/larvit/fejkdata`, for Go 1.22 or later on a 64-bit target.
|
||||
`New` loads the modules `WithDataFS` names and the folders `WithDataPath` names, and
|
||||
fails, naming `WithDataFS`, where neither is given. `WithSeed` is required, and `New`
|
||||
fails naming it where none is given; its draws repeat while the version and data stay
|
||||
the same. `Fake` renders a path, and `FakeTemplate` and `NewTemplate` render a
|
||||
`New` loads the modules `WithDataFS` names and the folders `WithDataPath` names, drawing
|
||||
from the seed `WithSeed` gives, and fails naming each of `WithSeed` and `WithDataFS`
|
||||
that is missing. The same seed repeats the draws while the version and data stay the
|
||||
same. `Fake` renders a path, and `FakeTemplate` and `NewTemplate` render a
|
||||
template. `FakeRecord`, `FakeRecordTemplate`
|
||||
and `NewRecordTemplate` render a record, whose `JSON`, `CSVHeader`, `CSVLine` and
|
||||
`SQLInsert` methods write its columns as the CLI does. `FakeStruct` fills a struct's
|
||||
|
||||
@@ -372,8 +372,8 @@ links all of them. Two locales, with `enus` from `github.com/larvit/fejkdata/dat
|
||||
are `WithDataFS(append(svse.Modules(), enus.Modules()...)...)`. To stand in your
|
||||
own places, pass the locale's `FS` alone beside a tree providing `geo.SE.address`, a record
|
||||
with a `format` and the columns `sv_SE.address` reads, `street`, `street-number`,
|
||||
`postal-code` and `locality`: `WithDataFS(svse.FS, myPlaces)`. `New` fails where no `WithDataFS` or `WithDataPath` names the
|
||||
data to load.
|
||||
`postal-code` and `locality`: `WithDataFS(svse.FS, myPlaces)`. `New` fails where no `WithSeed` gives the seed, or no
|
||||
`WithDataFS` or `WithDataPath` names the data to load.
|
||||
|
||||
`New` refuses data that breaks the grammar, could mean two things or cannot render a valid
|
||||
value, and `NewTemplate` refuses such a template. A category of an indexed source is
|
||||
@@ -430,7 +430,7 @@ first call for a type compiles its tags and reports what they get wrong, with th
|
||||
error on every later call; a `datatype` in a tag other than the one its Go type sets is
|
||||
refused, naming that type.
|
||||
|
||||
A `*Generator` is safe for concurrent use; a seeded sequence is reproducible only
|
||||
A `*Generator` is safe for concurrent use; a sequence is reproducible only
|
||||
when drawn from one goroutine. Changing how a value is composed shifts the seeded
|
||||
stream for that value and everything drawn after it.
|
||||
|
||||
|
||||
+7
-7
@@ -320,7 +320,7 @@ func (in invocation) check(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (in invocation) options(modules []fs.FS) []fejkdata.Option {
|
||||
func (in invocation) options(modules []fs.FS, systemSeed uint64) []fejkdata.Option {
|
||||
if in.noShipped {
|
||||
modules = nil
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func (in invocation) options(modules []fs.FS) []fejkdata.Option {
|
||||
}
|
||||
seed := in.seed
|
||||
if !in.seeded {
|
||||
seed = rand.Uint64()
|
||||
seed = systemSeed
|
||||
}
|
||||
return append(opts, fejkdata.WithSeed(seed))
|
||||
}
|
||||
@@ -572,7 +572,7 @@ func Main(modules ...fs.FS) int {
|
||||
if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
|
||||
stdin = nil
|
||||
}
|
||||
return run(commandName(os.Args[0]), modules, os.Args[1:], stdin, os.Stdout, os.Stderr)
|
||||
return run(commandName(os.Args[0]), modules, os.Args[1:], rand.Uint64(), stdin, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// commandName is the name the command runs under: the file arg0 names, less a Windows
|
||||
@@ -588,9 +588,9 @@ func commandName(arg0 string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// run is Main over the given streams, run as the command name. A nil stdin is a terminal,
|
||||
// which holds no template.
|
||||
func run(name string, modules []fs.FS, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
// run is Main over the given streams, run as the command name, seeded by systemSeed where
|
||||
// no --seed is given. A nil stdin is a terminal, which holds no template.
|
||||
func run(name string, modules []fs.FS, args []string, systemSeed uint64, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
in, err := parseArgs(args)
|
||||
if err != nil {
|
||||
return misuse(stderr, name, err)
|
||||
@@ -613,7 +613,7 @@ func run(name string, modules []fs.FS, args []string, stdin io.Reader, stdout, s
|
||||
return fail(stderr, name, err)
|
||||
}
|
||||
}
|
||||
f, err := fejkdata.New(in.options(modules)...)
|
||||
f, err := fejkdata.New(in.options(modules, systemSeed)...)
|
||||
if err != nil {
|
||||
return fail(stderr, name, err)
|
||||
}
|
||||
|
||||
+16
-19
@@ -20,7 +20,7 @@ const MaxRepeat = 1 << 20
|
||||
var _ [^uint(0)>>63 - 1]struct{}
|
||||
|
||||
// Generator generates fake data from a namespace tree. Create one with [New].
|
||||
// It is safe for concurrent use; a seeded sequence is reproducible only when drawn
|
||||
// It is safe for concurrent use; a sequence is reproducible only when drawn
|
||||
// from one goroutine.
|
||||
type Generator struct {
|
||||
// mu guards drawState, structs and root, which gains an indexed category on the
|
||||
@@ -36,8 +36,8 @@ type config struct {
|
||||
seeded bool
|
||||
sources []datafiles.Source
|
||||
nilModules error
|
||||
// given is whether an option named the data to load, though it may name none.
|
||||
given bool
|
||||
// dataGiven is whether an option named the data to load, though it may name none.
|
||||
dataGiven bool
|
||||
}
|
||||
|
||||
// Option configures a [Generator].
|
||||
@@ -54,7 +54,7 @@ func WithSeed(seed uint64) Option {
|
||||
// layer several; the last wins a name clash.
|
||||
func WithDataPath(dir string) Option {
|
||||
return func(c *config) {
|
||||
c.sources, c.given = append(c.sources, datafiles.Dir(dir)), true
|
||||
c.sources, c.dataGiven = append(c.sources, datafiles.Dir(dir)), true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func WithDataFS(modules ...fs.FS) Option {
|
||||
}
|
||||
c.sources = append(c.sources, datafiles.FS(fsys))
|
||||
}
|
||||
c.given = true
|
||||
c.dataGiven = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,24 +91,21 @@ func (loadError) Is(target error) bool { return target == ErrLoad }
|
||||
|
||||
// New builds a generator from the options' sources, merged in order with the last
|
||||
// winning a name clash; it fails where no [WithSeed] gives the seed, or no [WithDataFS]
|
||||
// or [WithDataPath] names the data to load. Each JSON file becomes a category named after the file (address.json ->
|
||||
// "address") and each subdirectory a namespace segment. It errors on a missing
|
||||
// directory, invalid JSON or invalid data. Given only WithDataFS() with no modules, New
|
||||
// loads no data, and the generator renders templates that read none. A source whose .fejkdata.json carries an
|
||||
// index, as each shipped module's does, loads each category on the first call reaching
|
||||
// it. Data failing to load, in New or on that first call, gives an error matching
|
||||
// or [WithDataPath] names the data to load. Each JSON file becomes a category named
|
||||
// after the file (address.json -> "address") and each subdirectory a namespace segment.
|
||||
// It errors on a missing directory, invalid JSON or invalid data. Given only
|
||||
// WithDataFS() with no modules, New loads no data, and the generator renders templates
|
||||
// that read none. A source whose .fejkdata.json carries an index, as each shipped
|
||||
// module's does, loads each category on the first call reaching it. Data failing to load, in New or on that first call, gives an error matching
|
||||
// [ErrLoad].
|
||||
func New(opts ...Option) (*Generator, error) {
|
||||
var c config
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
if err := c.missing(); err != nil {
|
||||
if err := c.misuse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.nilModules != nil {
|
||||
return nil, c.nilModules
|
||||
}
|
||||
root, err := loadSources(c.sources)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: %w", loadError{err})
|
||||
@@ -128,14 +125,14 @@ func (f *Generator) List() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// missing names each option New requires that no option gave.
|
||||
func (c config) missing() error {
|
||||
// misuse names every mistake in the options, before any data loads.
|
||||
func (c config) misuse() error {
|
||||
var err error
|
||||
if !c.given {
|
||||
if !c.dataGiven {
|
||||
err = errNoData
|
||||
}
|
||||
if !c.seeded {
|
||||
err = errors.Join(err, errNoSeed)
|
||||
}
|
||||
return err
|
||||
return errors.Join(err, c.nilModules)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// Template is an inline template compiled, referenced and validated against a
|
||||
// generator's loaded data once, ready to render many times with [Template.Fake].
|
||||
// It is safe for concurrent use: Fake serializes on its generator's lock, so a
|
||||
// seeded sequence is reproducible only when a generator — and its templates — are
|
||||
// sequence is reproducible only when a generator — and its templates — are
|
||||
// drawn from one goroutine.
|
||||
type Template struct {
|
||||
g *Generator
|
||||
|
||||
Reference in New Issue
Block a user