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
|
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.
|
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.
|
- 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
|
`New` loads the modules `WithDataFS` names and the folders `WithDataPath` names, drawing
|
||||||
fails, naming `WithDataFS`, where neither is given. `WithSeed` is required, and `New`
|
from the seed `WithSeed` gives, and fails naming each of `WithSeed` and `WithDataFS`
|
||||||
fails naming it where none is given; its draws repeat while the version and data stay
|
that is missing. The same seed repeats the draws while the version and data stay the
|
||||||
the same. `Fake` renders a path, and `FakeTemplate` and `NewTemplate` render a
|
same. `Fake` renders a path, and `FakeTemplate` and `NewTemplate` render a
|
||||||
template. `FakeRecord`, `FakeRecordTemplate`
|
template. `FakeRecord`, `FakeRecordTemplate`
|
||||||
and `NewRecordTemplate` render a record, whose `JSON`, `CSVHeader`, `CSVLine` and
|
and `NewRecordTemplate` render a record, whose `JSON`, `CSVHeader`, `CSVLine` and
|
||||||
`SQLInsert` methods write its columns as the CLI does. `FakeStruct` fills a struct's
|
`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
|
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
|
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`,
|
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
|
`postal-code` and `locality`: `WithDataFS(svse.FS, myPlaces)`. `New` fails where no `WithSeed` gives the seed, or no
|
||||||
data to load.
|
`WithDataFS` or `WithDataPath` names the data to load.
|
||||||
|
|
||||||
`New` refuses data that breaks the grammar, could mean two things or cannot render a valid
|
`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
|
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
|
error on every later call; a `datatype` in a tag other than the one its Go type sets is
|
||||||
refused, naming that type.
|
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
|
when drawn from one goroutine. Changing how a value is composed shifts the seeded
|
||||||
stream for that value and everything drawn after it.
|
stream for that value and everything drawn after it.
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -320,7 +320,7 @@ func (in invocation) check(name string) error {
|
|||||||
return nil
|
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 {
|
if in.noShipped {
|
||||||
modules = nil
|
modules = nil
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@ func (in invocation) options(modules []fs.FS) []fejkdata.Option {
|
|||||||
}
|
}
|
||||||
seed := in.seed
|
seed := in.seed
|
||||||
if !in.seeded {
|
if !in.seeded {
|
||||||
seed = rand.Uint64()
|
seed = systemSeed
|
||||||
}
|
}
|
||||||
return append(opts, fejkdata.WithSeed(seed))
|
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 {
|
if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
|
||||||
stdin = nil
|
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
|
// 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
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// run is Main over the given streams, run as the command name. A nil stdin is a terminal,
|
// run is Main over the given streams, run as the command name, seeded by systemSeed where
|
||||||
// which holds no template.
|
// no --seed is given. 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 {
|
func run(name string, modules []fs.FS, args []string, systemSeed uint64, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||||
in, err := parseArgs(args)
|
in, err := parseArgs(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return misuse(stderr, name, err)
|
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)
|
return fail(stderr, name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, err := fejkdata.New(in.options(modules)...)
|
f, err := fejkdata.New(in.options(modules, systemSeed)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail(stderr, name, err)
|
return fail(stderr, name, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-19
@@ -20,7 +20,7 @@ const MaxRepeat = 1 << 20
|
|||||||
var _ [^uint(0)>>63 - 1]struct{}
|
var _ [^uint(0)>>63 - 1]struct{}
|
||||||
|
|
||||||
// Generator generates fake data from a namespace tree. Create one with [New].
|
// 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.
|
// from one goroutine.
|
||||||
type Generator struct {
|
type Generator struct {
|
||||||
// mu guards drawState, structs and root, which gains an indexed category on the
|
// mu guards drawState, structs and root, which gains an indexed category on the
|
||||||
@@ -36,8 +36,8 @@ type config struct {
|
|||||||
seeded bool
|
seeded bool
|
||||||
sources []datafiles.Source
|
sources []datafiles.Source
|
||||||
nilModules error
|
nilModules error
|
||||||
// given is whether an option named the data to load, though it may name none.
|
// dataGiven is whether an option named the data to load, though it may name none.
|
||||||
given bool
|
dataGiven bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option configures a [Generator].
|
// Option configures a [Generator].
|
||||||
@@ -54,7 +54,7 @@ func WithSeed(seed uint64) Option {
|
|||||||
// layer several; the last wins a name clash.
|
// layer several; the last wins a name clash.
|
||||||
func WithDataPath(dir string) Option {
|
func WithDataPath(dir string) Option {
|
||||||
return func(c *config) {
|
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.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
|
// 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]
|
// 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 ->
|
// or [WithDataPath] names the data to load. Each JSON file becomes a category named
|
||||||
// "address") and each subdirectory a namespace segment. It errors on a missing
|
// after the file (address.json -> "address") and each subdirectory a namespace segment.
|
||||||
// directory, invalid JSON or invalid data. Given only WithDataFS() with no modules, New
|
// It errors on a missing directory, invalid JSON or invalid data. Given only
|
||||||
// loads no data, and the generator renders templates that read none. A source whose .fejkdata.json carries an
|
// WithDataFS() with no modules, New loads no data, and the generator renders templates
|
||||||
// index, as each shipped module's does, loads each category on the first call reaching
|
// that read none. A source whose .fejkdata.json carries an index, as each shipped
|
||||||
// it. Data failing to load, in New or on that first call, gives an error matching
|
// 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].
|
// [ErrLoad].
|
||||||
func New(opts ...Option) (*Generator, error) {
|
func New(opts ...Option) (*Generator, error) {
|
||||||
var c config
|
var c config
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&c)
|
opt(&c)
|
||||||
}
|
}
|
||||||
if err := c.missing(); err != nil {
|
if err := c.misuse(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.nilModules != nil {
|
|
||||||
return nil, c.nilModules
|
|
||||||
}
|
|
||||||
root, err := loadSources(c.sources)
|
root, err := loadSources(c.sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", loadError{err})
|
return nil, fmt.Errorf("fejkdata: %w", loadError{err})
|
||||||
@@ -128,14 +125,14 @@ func (f *Generator) List() []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// missing names each option New requires that no option gave.
|
// misuse names every mistake in the options, before any data loads.
|
||||||
func (c config) missing() error {
|
func (c config) misuse() error {
|
||||||
var err error
|
var err error
|
||||||
if !c.given {
|
if !c.dataGiven {
|
||||||
err = errNoData
|
err = errNoData
|
||||||
}
|
}
|
||||||
if !c.seeded {
|
if !c.seeded {
|
||||||
err = errors.Join(err, errNoSeed)
|
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
|
// Template is an inline template compiled, referenced and validated against a
|
||||||
// generator's loaded data once, ready to render many times with [Template.Fake].
|
// 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
|
// 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.
|
// drawn from one goroutine.
|
||||||
type Template struct {
|
type Template struct {
|
||||||
g *Generator
|
g *Generator
|
||||||
|
|||||||
Reference in New Issue
Block a user