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
8 changed files with 21 additions and 22 deletions
Showing only changes of commit 71b0754679 - Show all commits
+4 -4
View File
@@ -458,9 +458,9 @@ func main() { os.Exit(cli.Main(append(data.Modules(), mydata)...)) }
The embed's folders are the paths: `//go:embed acme` serves `acme/customer.json` as
`{/acme.customer}`. Add `*.tsv` to the embed where your data holds a table. The CLI loads
its data when it runs, so a test calling `fejkdata.New(fejkdata.WithDataFS(...))` over the
same modules, rendering each path once where a module carries an index, catches broken
data at `go test`. The CLI takes `fejkdata`'s flags
its data only when it runs. To catch broken data at `go test`, call
`fejkdata.New(fejkdata.WithDataFS(...))` over the same modules, and render each path once:
a module with an index checks a category only when a call first reaches it. The CLI takes `fejkdata`'s flags
and exit codes, and names itself by the file it runs from in its help and errors. `--no-shipped-data` leaves out the modules passed to `Main`, and
`--version` prints your module's version beside the fejkdata version it is built with.
@@ -781,7 +781,7 @@ Rejected at `New`: a bound that is no calendar date, or after the other; an unqu
or double-quoted layout, naming the single-quoted one; and `time` with a layout naming a
date field, naming `date`, since `time` draws no date. `{seq()}` spans `Fake`
calls and `repeat`, resets with a new generator, and is the natural primary key for
the SQL example above; give each table its own counter, `{seq(orders)}`, where one
the SQL example above; give each record its own counter, `{seq(orders)}`, where one
generator fills several.
### Computation
+4 -4
View File
@@ -36,10 +36,10 @@ born {date(1990-01-01,2010-12-31,'2006-01-02')}
EOF
A template is a format string, or a JSON object, array, string or null. Its {…} tokens
reach the data by reference from the root, {/path}, for any path --list prints, whether
the data is shipped or layered with --data-path. A reference names a category or a
dotted path into one, a table's row by key or name ({/table[KEY]}), and .. steps up
from a row to the row it links to.
reach the data by reference from the root: {/path}, for any path --list prints, shipped
or layered with --data-path. A reference names a category or a dotted path into one, a
table's row by key or name ({/table[KEY]}), and .. steps up from a row to the row it
links to.
Under --format text, the default, what you send is what renders, and COMMAND adds
nothing: a format string keeps every byte, so echo's newline ends each render, and
+1 -1
View File
@@ -149,7 +149,7 @@ A directory a machine happens to have would make `--seed 42` machine-dependent;
- In the CLI, a flag of its own says a `--data-path` replaces what it clashes with, so flags still go anywhere on the line. Data authors and hand fixture authors bench it.
- The README asks for functions with no side effects of their own.
- The CLI is a library call. One exported list names every shipped package, and `fejkdata`'s own `main` passes it to that call. The list lives in a package of its own, so a library importing the core links no data it did not import. A custom CLI is the same few lines with other modules added. No API and no flag exists only for custom CLIs; `Main` tells one apart only by its build info and file name, so its help and errors carry its name and its `--version` its own module's version beside fejkdata's. The maintainer chose so on 2026-10-11.
- The help's examples and its hint for an empty stdin read no data, so each renders in any custom CLI; `--list` points to the paths. "Shipped data", in `--no-shipped-data` and in `-d`'s help, names the modules passed to `Main`, a custom CLI's own included. A custom-CLI builder ruled so on 2026-10-11, in the audience bench that preceded the module API's release.
- The help's examples and its hint for an empty stdin read no data, so each renders in any custom CLI, and both point to `--list` for the paths. "Shipped data", in `--no-shipped-data` and in `-d`'s help, names the modules passed to `Main`, a custom CLI's own included. The audience bench's custom-CLI builder seat ruled so on 2026-10-11, before 0.1.0.
Valid while the shipped data stays under 100 MiB, which a gate test holds (`todo.md` item 192): `go get` fetches the whole Go module, and the `go` command refuses a Go module zip over 500 MiB (`MaxZipFile` in `golang.org/x/mod/zip`). Past it, `todo.md` item 193 moves the largest packages into nested Go modules that keep their import paths, each requiring the core release that drops the package, so no build sees it twice.
+2 -2
View File
@@ -92,8 +92,8 @@ func (loadError) Is(target error) bool { return target == ErrLoad }
// winning a name clash; it fails where 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. WithDataFS() alone loads no data, and the
// generator renders templates that read none. A source whose .fejkdata.json carries an
// 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].
+1 -1
View File
@@ -104,7 +104,7 @@ func (s Source) check() error {
return nil
}
// noDirectory is a data path naming nothing, which errors.Is still matches as fs.ErrNotExist.
// noDirectory is a data path naming nothing.
type noDirectory string
func (d noDirectory) Error() string { return "data path " + string(d) + ": no such directory" }
+1 -1
View File
@@ -25,7 +25,7 @@ func Decode(b []byte) (Value, error) {
return Value{v}, err
}
// located prefixes a syntax error with the line and column of the byte it stopped at.
// located prefixes a syntax error with the line and column where decoding stopped.
func located(b []byte, err error) error {
var syntax *json.SyntaxError
if !errors.As(err, &syntax) {
+2 -3
View File
@@ -209,11 +209,10 @@ func (f *Generator) FakeRecordTemplate(input string) (*Record, error) {
return t.Fake(), nil
}
// ErrNoColumns is a record fence a path can answer, so its entry point names the record to
// write instead.
// ErrNoColumns matches the error for a record with no fields; FakeRecord's names the
// one-column record to write instead.
var ErrNoColumns = errors.New("has no fields, so no columns")
// errChoiceRecord is the other fence a path can answer.
var errChoiceRecord = errors.New("names a choice, not a template; a record is a template whose fields are its columns")
// recordOf is the record template n is; settleRecords fixed its columns at load.
+6 -6
View File
@@ -123,12 +123,12 @@
| 24 | 0.2.0 | defect | **Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does not hold.** | 1 | 1 | 4 | 7 | 4 | 18.6 |
| 178 | 0.2.0 | | **Plan with the systems architect which `internal/` packages the engine splits into, by area, and how each type stops being written by more than one stage, then file the moves as items.** | 1 | 3 | 2 | 9 | 3, 3.2 | 18.4 |
| 193 | 0.2.0 | decision | **Move the largest shipped packages into nested Go modules that keep their import paths, once item 192's gate fails.** | 6 | 6 | 6 | 7 | 2.2, 2 | 17.8 |
| 228 | 0.2.0 | | **Offer `{/path as p}` for a token whose unbound head names no loaded data, in a record's format too: today `{p.first}` with `p` unbound suggests `{/p.first}`.** | 2 | 2 | 5 | 6 | 6, 4.1 | 17.7 |
| 228 | 0.2.0 | | **Offer `{/path as p}` where a token starts with a name nothing binds and no loaded path starts with, in a record's format too: today `{p.first}` with `p` unbound suggests `{/p.first}`.** | 2 | 2 | 5 | 6 | 6, 4.1 | 17.7 |
| 92 | 0.2.0 | | **Name the hyphenated field in a `calc`'s refusal, and say to rename it: today `{calc(postal-code * 2)}` reports `no field "postal"`.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
| 194 | 0.2.0 | decision | **Let a data author write and check their own `.fejkdata.json` index with the library or the CLI, and fail a load whose index leaves out a category file.** | 2 | 4 | 5 | 7 | 6.3, 9.1 | 17.5 |
| 219 | 0.2.0 | | **Name the path and the Go type where a struct field's type cannot hold what its tag renders: `sv_SE.person renders text, which int cannot hold`.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
| 221 | 0.2.0 | | **Refuse two references under a record `--format` by saying a run renders one record: today `{/users}{/orders}` reports that an inline record has no fields.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
| 229 | 0.2.0 | | **Say where a template that opens like JSON fails to parse as JSON, by line and column, before it renders as a format string.** | 2 | 2 | 5 | 6 | 6, 5.1 | 17.1 |
| 229 | 0.2.0 | | **Name the line and column where JSON parsing stopped, when a template that opens like a JSON object then fails as a format string.** | 2 | 2 | 5 | 6 | 6, 5.1 | 17.1 |
| 53 | 0.2.0 | | **Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.** | 4 | 5 | 6 | 6 | 4 | 16.9 |
| 55 | 0.2.0 | | **Document `ErrNoColumns` and `MaxRepeat` in the README's Library section.** | 1 | 2 | 6 | 7 | 12 | 16.8 |
| 18 | 0.2.0 | | **Give the address records one column set across countries: `region` and `municipality` as columns on `geo.SE.address` too.** | 4 | 4 | 6 | 6 | 5 | 16.6 |
@@ -136,7 +136,7 @@
| 31 | 0.2.0 | | **Rename `misc.territory.country` to `sovereign`.** | 3 | 2 | 4 | 7 | 5.8 | 15.7 |
| 20 | 0.2.0 | defect | **Weight `sv_SE.sex` and `en_US.sex` by bearers, as the README's Data section says they are.** | 2 | 2 | 4 | 6 | 4 | 15.7 |
| 50 | 0.2.0 | | **Reword `prove`'s refusal of a typed column reading a row, `{/region}`.** | 1 | 2 | 4 | 6 | 6 | 15.5 |
| 224 | 0.2.0 | | **Name the module in every error about its manifest or its files, where the module is an `fs.FS` whose root is the module.** | 1 | 2 | 4 | 6 | 6 | 15.5 |
| 224 | 0.2.0 | | **Name the module in every error about its manifest or its files: today an `fs.FS` module's errors name only the file inside it.** | 1 | 2 | 4 | 6 | 6 | 15.5 |
| 17 | 0.2.0 | | **Give every shipped category a column for each part its format composes, and one record shape across the national ids; re-pin the shape.** | 5 | 6 | 6 | 6 | 5 | 15.1 |
| 170 | 0.2.0 | | **Decide in one function whether a read lands in a name's pick, which `refuseTwiceDrawnIn` calls at load and `keptInPick` at render.** | 3 | 3 | 2 | 8 | 3, 3.2 | 14.7 |
| 29 | 0.2.0 | | **Let a path go from a selected row into the template beside its family: `geo.US.locality[1714000].address`.** | 4 | 4 | 5 | 6 | 5 | 14.6 |
@@ -450,11 +450,11 @@ Needs item 107. The decision "comprehension floor: every dimension and the overa
### 91. Accept a path through a name as a `calc` operand, so `{/misc.coordinate as c}{c}: {calc(c.lat * 60)}` computes from the coordinate `{c}` prints.
A calc operand parses as a bare identifier, so `{calc(c.lat * 60)}` fails with `unexpected ".lat * 60"`, and a second binding of `/misc.coordinate.lat` would draw another coordinate. Resolve the path through `compileArm`'s leaves, as `operandNodes` does for a bare name. Binding the path instead, `{p.price as pr}`, says `no field "p"; a binding names a field, or a reference…` though `p` is a bound name.
A calc operand parses as a bare identifier, so `{calc(c.lat * 60)}` fails with `unexpected ".lat * 60"`, and a second binding of `/misc.coordinate.lat` would draw another coordinate. Resolve the path through `compileArm`'s leaves, as `operandNodes` does for a bare name. A data author who bound a row, `{.product as p}`, then bound its column, `{p.price as pr}`, got `no field "p"; a binding names a field, or a reference…` though `p` is a bound name.
### 223. Let a column draw a key from another record's rows, so an order's `user_id` names a user the same run wrote.
From the 2026-10-11 audience bench: `"user_id": "{/users.id}"` draws a fresh user, so `{seq()}` makes it 1, 2, 3 beside the order's own id, and the workaround types the user count twice, in `-n` and in `{int(1,N)}`.
From the 2026-10-11 audience bench: `"user_id": "{/users.id}"` draws a fresh user for each order, so its `{seq()}` id names no user the run wrote. The workaround types the user count twice, in `-n` and in `{int(1,N)}`.
### 36. Report every mistake a load finds in one error, as the decision "A load reports every mistake at once" states.
@@ -504,7 +504,7 @@ The decision "The template engine stays the root package until item 107; what re
Carries out the decision "The shipped data is Go packages a library imports by choice, and the CLI carries every one, from 0.1.0". Each nested module requires the core release that drops its package, so no build sees the package twice. It brings per-module tags and a workspace, so it moves into the release of the chunk whose data trips the gate.
### 228. Offer `{/path as p}` for a token whose unbound head names no loaded data, in a record's format too: today `{p.first}` with `p` unbound suggests `{/p.first}`.
### 228. Offer `{/path as p}` where a token starts with a name nothing binds and no loaded path starts with, in a record's format too: today `{p.first}` with `p` unbound suggests `{/p.first}`.
From the 2026-10-11 audience bench, a Go test author tagging `{p.first}` before binding `p`. The hint is built in `checkArm` before the root is known, and only where the template has no fields; offering the binding there also told `{sv_SE.person.last}` to bind a name `sv_SE`. Decide between the reference and the binding once the root is loaded.