Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
5 changed files with 31 additions and 34 deletions
Showing only changes of commit 8e8fa92e77 - Show all commits
+4 -4
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+1 -1
View File
@@ -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