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 95 additions and 68 deletions
Showing only changes of commit b9c1a28a61 - Show all commits
+4 -3
View File
@@ -11,8 +11,8 @@
wait for one.
- README goal 3 is gated at 7.0 on the `comprehension-panel` skill, and below it
nothing else merges — no feature, no category, no data — bar a security fix, a
dependency bump, a repair of behaviour the README documents, and the infrastructure
the round itself runs on. `todo.md` carries the round: a PR per item it plans, the nine-seat
dependency bump, a repair of behaviour the README documents, the infrastructure
the round itself runs on, and the CLI reading its template from stdin. `todo.md` carries the round: a PR per item it plans, the nine-seat
findings at depth 1 or a restructure the maintainer approved, then the nine-seat run
again, until the score passes. At or above 7.0
every pull request scores with the four-seat run and answers it in one run. No merge
@@ -39,8 +39,9 @@ In [docs/decisions.md](docs/decisions.md):
- `{a|b}` stays beside nested choices
- Flags follow getopt_long
- An argument is a template by its shape, not by a flag
- The CLI renders the template on its stdin, less one newline ending it
- An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
- A padded JSON argument is rejected, not trimmed
- The library rejects a padded JSON template, and the CLI trims the whitespace around one on stdin
- `FakeTemplate` and `NewTemplate` both stay
- The shipped data is embedded, not discovered
- With only the shipped set, a category loads on the first call reaching it; beside a `--data-path`, every category loads in `New`
+6 -1
View File
@@ -12,7 +12,7 @@ replacement, and each removed path, column or flag.
`{/city[Oslo]..country.name}` renders `Norway`, and `.` after it steps down again,
drawing afresh. `New` refuses a `..` naming anything but the parent table, and a
selector after a `..`.
- A path, given to `Fake` or the CLI or referenced in a template, may not read into a
- A path, given to `Fake` or referenced in a template, may not read into a
level carrying a `repeat`, and `List` advertises no such path: where `x` carries
`"repeat": 3`, `Fake("x.a")` fails naming the level `x`, and `Fake("x")` renders it
whole.
@@ -27,6 +27,11 @@ replacement, and each removed path, column or flag.
operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. What `New` refuses is listed
under [Names](README.md#names).
- First release: the CLI, the library and the shipped data set.
- The CLI renders the template piped to its stdin, `echo -n '{/sv_SE.person} hihi' |
fejkdata`, and takes no path or template as an argument. One newline ending stdin is
dropped, and so is the whitespace around a JSON template. A template of one reference
alone, `{/users}`, is the record `users` under `--format`. An argument, or nothing
piped in, exits 2.
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
`key`, `name`, `weight` and `parent`; a path selects a row by key or name,
`misc.territory[SE]`, and descends to a linked table by name, one path drawing each
+55 -52
View File
@@ -6,55 +6,58 @@ reproducible.
```sh
go install github.com/larvit/fejkdata/cmd/fejkdata@latest
fejkdata sv_SE.person # Sara Eriksson
echo -n '{/sv_SE.person}' | fejkdata # Sara Eriksson
```
## CLI
```sh
fejkdata sv_SE.person # Sara Eriksson
fejkdata sv_SE.person.last # Eriksson
fejkdata --seed 42 sv_SE.address # the same address every run
fejkdata -n 3 --separator ', ' sv_SE.word # nät, barn, sol
fejkdata --list # every path the data offers
fejkdata 'misc.territory[SE].capital' # Stockholm — a table's row, selected by key or name
fejkdata 'geo.SE.locality[Lund].street' # Fjelievägen — a linked table, drawn inside the row
fejkdata --data-path ./mydata sv_SE.word # layer a directory over the shipped data
fejkdata --no-shipped-data -d ./mydata --list # only your data
fejkdata 'name: {/sv_SE.person.last}' # name: <a surname> — an inline template
fejkdata "{date(1990-01-01,2010-12-31,'2006-01-02')}" # 2003-11-27 — the argument in "…", the layout in '…'
fejkdata '{"format":"name: {x}","x":["bosse","lina"]}' # name: bosse or name: lina
echo -n '{/sv_SE.person}' | fejkdata # Sara Eriksson
echo -n '{/sv_SE.person.last}' | fejkdata # Eriksson
echo -n '{/sv_SE.person} hihi' | fejkdata # Sara Eriksson hihi
echo -n '{/sv_SE.address}' | fejkdata --seed 42 # the same address every run
echo -n '{/sv_SE.word}' | fejkdata -n 3 --separator ', ' # nät, barn, sol
fejkdata --list # every path the data offers
echo -n '{/misc.territory[SE].capital}' | fejkdata # Stockholm — a table's row, selected by key or name
echo -n '{/geo.SE.locality[Lund].street}' | fejkdata # Fjelievägen — a linked table, drawn inside the row
echo -n '{/sv_SE.word}' | fejkdata --data-path ./mydata # layer a directory over the shipped data
fejkdata --no-shipped-data -d ./mydata --list # only your data
echo -n "{date(1990-01-01,2010-12-31,'2006-01-02')}" | fejkdata # 2003-11-27 — the layout in '…'
echo -n '{"format":"name: {x}","x":["bosse","lina"]}' | fejkdata # name: bosse or name: lina
fejkdata < template.txt # a template kept in a file
```
A path names a category, or a field inside one: each dot segment descends one
level — folders, then the category (a JSON file), then fields — and `[SE]` after a
[table](#table) selects its row. An argument that is
a JSON object, array or string, or that carries a `{` token, is instead an
**inline template**: a format string or a JSON value compiled and rendered on the
spot. Its tokens reach the data by reference from the root —
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike
available. A path never contains a brace or a quote, and a bracket only as a selector
after a name, so the two spellings cannot collide; which spellings an inline template
rejects, and what each names instead, is under
[Decisions](docs/decisions.md#an-argument-is-a-template-by-its-shape-not-by-a-flag).
fejkdata renders the template on its stdin: a format string, or a JSON object, array or
string. Its `{…}` tokens reach the data by reference from the root,
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike available,
and everything outside them prints as written. A reference names a category, or a field
inside one: each dot segment descends one level — folders, then the category (a JSON
file), then fields — and `[SE]` after a [table](#table) selects its row. `--list` prints
these paths; a template reads one as `{/path}`.
One newline ending stdin is dropped, so `echo` and `echo -n`, a file and a heredoc
render alike; end stdin with two newlines to print one. Whitespace around a JSON
template is dropped too. Which spellings a template rejects, and what each names
instead, is under
[Decisions](docs/decisions.md#the-cli-renders-the-template-on-its-stdin-less-one-newline-ending-it).
| Flag | |
|------|--|
| `-d`, `--data-path D` | a directory to layer over the shipped data; repeatable, the last wins a name clash |
| `--no-shipped-data` | load only the `--data-path` directories |
| `-s`, `--seed N` | same seed, version and data: identical output |
| `-n`, `--repeat N` | render the value N times (up to 1048576), each an independent draw, streamed |
| `-n`, `--repeat N` | render the template N times (up to 1048576), each an independent draw, streamed |
| `--separator S` | between repeated values (default a newline) |
| `--format F` | `text` (default), `json`, `ndjson`, `csv` or `sql` — a record's columns, one record per row (json frames them as an array) |
| `--table T` | the INSERT target for `--format sql` (default: the path's last segment, or `records` for an inline template) |
| `--table T` | the INSERT target for `--format sql` (default: a lone reference's last segment, such as `users` for `{/users}`, else `records`) |
| `--list` | print every path, then exit |
| `--version`, `-h`, `--help` | print, then exit |
`--name value` and `--name=value` both work, a short flag's value attaches or
follows (`-n3`, `-n 3`) and short flags bundle (`-hn 3`) — see
[Decisions](docs/decisions.md#flags-follow-getopt_long); flags go anywhere, `--` ends them. Exit codes: `0` success, `1` runtime error (missing
dir, unknown path), `2` misuse — a bad flag, an argument that names neither a
template nor a path, or an inline template that does not compile. From a checkout:
dir, unknown path), `2` misuse — a bad flag, an argument, nothing on stdin, or a
template that does not compile. From a checkout:
`go run ./cmd/fejkdata …`.
### Your own data
@@ -74,9 +77,9 @@ A category is a JSON file in a directory. Save this as `mydata/sql.json`:
```
```sh
fejkdata --seed 1 --data-path ./mydata sql
echo -n '{/sql}' | fejkdata --seed 1 --data-path ./mydata
# INSERT INTO users VALUES('zoom'),('wahoo'),('blip');
fejkdata --repeat 100 --data-path ./mydata sql > seed.sql
echo -n '{/sql}' | fejkdata --repeat 100 --data-path ./mydata > seed.sql
```
That `format` string is the free-form spelling — you hand-write the whole row.
@@ -99,14 +102,15 @@ the whole. `--format json|ndjson|csv|sql` writes the records; the library's
```
```sh
fejkdata --seed 1 --data-path ./mydata --format json users # [{"first":"Bo","last":"Lovelace"}]
fejkdata --seed 1 --data-path ./mydata --format ndjson users # {"first":"Bo","last":"Lovelace"}
fejkdata --seed 1 --data-path ./mydata --format csv users # first,last → Bo,Lovelace
fejkdata --seed 1 --data-path ./mydata --format sql users # INSERT INTO "users" ("first", "last") VALUES ('Bo', 'Lovelace');
fejkdata --seed 1 --data-path ./mydata --format sql --table people users # INSERT into another table
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format json # [{"first":"Bo","last":"Lovelace"}]
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format ndjson # {"first":"Bo","last":"Lovelace"}
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format csv # first,last → Bo,Lovelace
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format sql # INSERT INTO "users" ("first", "last") VALUES ('Bo', 'Lovelace');
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format sql --table people # INSERT into another table
```
`--repeat` streams that many records — `json` frames them as one array document,
A record is one reference alone, `{/users}`, or a template whose fields are its
columns. `--repeat` streams that many records — `json` frames them as one array document,
`ndjson` writes one object per line, `csv` a row after a header, `sql` one INSERT
per line. Only a category-level template is a record; a field, choice or folder
errors, and so does a `repeat` on
@@ -342,7 +346,7 @@ type User struct {
`FakeStruct` fills a struct through a pointer: each exported field tagged `fake:"…"` is
a column of one record, its tag a path or an inline template — told apart by
`IsTemplate`, as the CLI tells an argument — and its Go type the column's
`IsTemplate` — and its Go type the column's
[datatype](#datatype): a string, bool, integer or float kind, or a pointer to one,
which a [`null`](#null) item leaves nil. An integer stays within int64 whatever its
kind, and a value the kind cannot hold, such as `{int(0,300)}` in a `uint8`, is refused
@@ -503,11 +507,11 @@ SE Sweden 10500000
```
```sh
fejkdata -d ./mydata country # Sweden (SE), about half the time
fejkdata -d ./mydata country.alpha2 # NO
fejkdata -d ./mydata 'country[SE]' # Sweden (SE)
fejkdata -d ./mydata 'country[Norway].alpha2' # NO
fejkdata -d ./mydata --format csv country # alpha2,name,population → SE,Sweden,10500000
echo -n '{/country}' | fejkdata -d ./mydata # Sweden (SE), about half the time
echo -n '{/country.alpha2}' | fejkdata -d ./mydata # NO
echo -n '{/country[SE]}' | fejkdata -d ./mydata # Sweden (SE)
echo -n '{/country[Norway].alpha2}' | fejkdata -d ./mydata # NO
echo -n '{/country}' | fejkdata -d ./mydata --format csv # alpha2,name,population → SE,Sweden,10500000
```
`rows` names the TSV beside the category file; `key` names the column a path
@@ -542,8 +546,7 @@ A name naming several rows is an error listing their keys, unless a row selected
before it settles which ([Linked tables](#linked-tables)). A selector is part of the path, so it works
wherever a path does: `Fake`, `FakeRecord`, a `{/misc.territory[SE].capital}` reference
and a struct tag. A dot inside the brackets belongs to the key or name, so
`city[St. Louis]` selects it. A path starts with a name, and `[` still opens a JSON
array at the start of a CLI argument, so `'[SE]'` alone names nothing.
`city[St. Louis]` selects it. A path starts with a name, so `[SE]` alone selects nothing.
### Linked tables
@@ -564,10 +567,10 @@ Stockholm SE 990000
```
```sh
fejkdata -d ./mydata 'country[SE].city' # Stockholm or Göteborg
fejkdata -d ./mydata country.city.name # a country drawn, then a city inside it
fejkdata -d ./mydata 'city[Oslo].country' # NO — the link column's cell
fejkdata -d ./mydata '{/city as c}{c.name}, {c..country.name}' # Oslo, Norway — one city and its country
echo -n '{/country[SE].city}' | fejkdata -d ./mydata # Stockholm or Göteborg
echo -n '{/country.city.name}' | fejkdata -d ./mydata # a country drawn, then a city inside it
echo -n '{/city[Oslo].country}' | fejkdata -d ./mydata # NO — the link column's cell
echo -n '{/city as c}{c.name}, {c..country.name}' | fejkdata -d ./mydata # Oslo, Norway — one city and its country
```
A path descends from a row to a linked table by name, at any depth, and `--list`
@@ -589,10 +592,10 @@ chain of parents that closes, and a table named like a column of any table above
after the `..`:
```sh
fejkdata -d ./mydata 'city[Oslo]..country.name' # Norway
fejkdata -d ./mydata 'city..country' # a city drawn, then its country: Sweden (SE)
fejkdata -d ./mydata 'city[Göteborg]..country.city' # Stockholm or Göteborg, drawn afresh
fejkdata -d ./mydata '{/city as c}{c}, {c..country.name}' # Oslo, Norway
echo -n '{/city[Oslo]..country.name}' | fejkdata -d ./mydata # Norway
echo -n '{/city..country}' | fejkdata -d ./mydata # a city drawn, then its country: Sweden (SE)
echo -n '{/city[Göteborg]..country.city}' | fejkdata -d ./mydata # Stockholm or Göteborg, drawn afresh
echo -n '{/city as c}{c}, {c..country.name}' | fejkdata -d ./mydata # Oslo, Norway
```
It steps up from a selected row, a drawn row or a [name](#names) bound to a row, one
+26 -8
View File
@@ -70,23 +70,41 @@ error for an object holding only a format names `"…"`, and that spelling has t
where it is printed. An argument or struct tag of one reference alone, `{/users}`, is
refused naming the path `users`: both render the same text, and only the path names a
record. A folder-relative `{.name}` or `{..name}` is refused naming `{/name}`, since an
inline template sits in no folder. `IsTemplate` exports the rule, so the CLI, struct
tags and any other caller read one.
inline template sits in no folder. `IsTemplate` exports the rule, so struct tags and any
other caller read one.
## The CLI renders the template on its stdin, less one newline ending it
2026-10-07, Lilleman auf Larv. Serves goals 7 and 7.2.
The CLI takes no path or template as an argument: an argument is misuse, and so is a
terminal with nothing piped in, which would otherwise wait for typed input. Both errors
show `echo -n '{/sv_SE.person}' | fejkdata`. One newline ending stdin is dropped, so
`echo`, a file and a heredoc render as `echo -n` does; only one, so a template that
should end in a newline ends stdin with two. Any text that is no JSON object, array or
string, `42` and `null` included, is a format string and prints as written. One
reference from the root and nothing else, `{/users}`, reads the path it names: it renders
the same text, and under `--format` it is the record `users`, with `users` the default
`--table`. `--list` still prints paths, each read as `{/path}`. Valid while the CLI reads
one template per run.
## An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
2026-09-24, Lilleman auf Larv.
The whole argument is the spelling under test, and `NewTemplate` compiles, links and
The whole template is the spelling under test, and `NewTemplate` compiles, links and
validates as one step. An unknown *path* stays a runtime error (exit 1): there the
argument is well-formed and only the data is absent.
template is well-formed and only the data is absent.
## A padded JSON argument is rejected, not trimmed
## The library rejects a padded JSON template, and the CLI trims the whitespace around one on stdin
2026-09-03, Lilleman auf Larv.
2026-09-03, Lilleman auf Larv; the CLI's trim 2026-10-07, Lilleman auf Larv. Serves goal 7.2.
Padding is the one place the two readings disagree — a format string renders it, JSON
drops it — so the spelling that renders is named rather than silently chosen.
drops it — so `NewTemplate` names the spelling that renders rather than silently
choosing. On stdin, `echo` and a file end in a newline nobody meant to render, so the
maintainer ruled that the whitespace around a JSON object, array or string is dropped
before the library reads it. Valid while a JSON template on stdin is one value.
## `FakeTemplate` and `NewTemplate` both stay
@@ -860,6 +878,6 @@ a lone `}` stays an error. Valid while a template escapes a brace by doubling it
## Goal 7.2 holds while a first template can be written with no escape; a literal brace still takes `{{` or `}}`
2026-10-05, Lilleman auf Larv. Serves goal 7.2. Goals are aims: it is enough that a first
template can be written with no escape and no options, as `fejkdata 'name: {/sv_SE.person.last}'`
template can be written with no escape and no options, as `echo -n 'name: {/sv_SE.person.last}' | fejkdata`
is. A template that prints a literal brace writes `{{` or `}}`, and the goal allows that. Valid
while `{{` and `}}` are the template syntax's only escape.
+4 -4
View File
@@ -70,7 +70,7 @@
| 61 | 0.1.0 | | **Add a locale, with its `geo/` tree, for each of the ten most-spoken languages and each Nordic country.** | 4 | 9 | 9 | 10 | 2.1 | 30.8 |
| 16 | 0.1.0 | | **Add locale categories: company, phone, finance, vehicle, words.** | 3 | 8 | 8 | 9 | 2.2, 15 | 28.1 |
| 15 | 0.1.0 | | **Add `misc` tables and templates, one row of its detail's table per chunk.** | 3 | 9 | 8 | 8 | 2, 15, 14 | 25.9 |
| 129 | 0.1.0 | | **Take a template wherever fejkdata renders, so `fejkdata '{/sv_SE.person}'`, `fake:"{/sv_SE.person.first}"` and `Fake("{/sv_SE.person}")` render, and text with no `{…}` prints as written.** | 5 | 8 | 8 | 10 | 5.9, 5.7, 7 | 25.0 |
| 129 | 0.1.0 | | **Take a template wherever the library renders, so `fake:"{/sv_SE.person.first}"` and `Fake("{/sv_SE.person}")` render, and text with no `{…}` prints as written, as the CLI already does.** | 5 | 8 | 8 | 10 | 5.9, 5.7, 7 | 25.0 |
| 57 | 0.1.0 | | **Rewrite `CHANGELOG.md`'s `[Unreleased]` as what v0.1.0 holds.** | 1 | 2 | 6 | 6 | 1.1 | 22.5 |
| 96 | 0.1.0 | | **Refuse two reads into one sibling, category or struct category that no name joins, `{place.postal-code} {place.locality}`, showing the spelling for one pick and the one for two.** | 4 | 4 | 7 | 7 | 5.6, 4 | 20.9 |
| 153 | 0.1.0 | decision | **Run the nine-seat comprehension panel and the scoring run again once items 145 to 152 and item 10 have merged, and file what the panel names.** | 1 | 3 | 3 | 9 | 3 | 20.4 |
@@ -240,15 +240,15 @@ Shape: T = table, t = template, c = choice.
| `lorem`, `hacker`, `hipster`, `catchphrase`, `buzzword`, `quote` | T/c | lorem ipsum, LLM-written | — |
| `direction`, `continent`, `ulid` | c/t | — | — |
### 129. Take a template wherever fejkdata renders, so `fejkdata '{/sv_SE.person}'`, `fake:"{/sv_SE.person.first}"` and `Fake("{/sv_SE.person}")` render, and text with no `{…}` prints as written.
### 129. Take a template wherever the library renders, so `fake:"{/sv_SE.person.first}"` and `Fake("{/sv_SE.person}")` render, and text with no `{…}` prints as written, as the CLI already does.
Goal 5.9. Today a path is a second spelling of the template that reads it; afterwards only the template remains.
- A lone reference, a template of one reference token and nothing else, is what it references, as a record column reading one reference already is. `--format json '{/users}'` prints the columns of `users`, and `--format csv '{/misc.territory[SE]}'` that row. Under `--format sql` the default `--table` is the reference's last segment with selectors removed, taken from the library's parse of the reference, `territory` for `{/misc.territory[SE]}`, where today a template defaults to `records`.
- A lone reference, a template of one reference token and nothing else, is what it references, as a record column reading one reference already is: `FakeRecord("{/users}")` returns the columns of `users`. The CLI's `loneReference` then goes, and `--format sql` takes its default `--table` from the library's parse of the reference.
- `Fake` and `FakeRecord` take a template. `FakeTemplate` folds into `Fake`, `FakeRecordTemplate` into `FakeRecord`, and `IsTemplate` goes, with `grammar.IsTemplate` and the lone-reference refusal.
- `Fake` of a lone reference costs what `Fake` of its path costs today, and the allocation gate holds a case for it.
- `List` and `--list` give each entry as the template that renders it, `{/sv_SE.person}`.
- Text that is not a JSON value and holds no `{…}` prints as written (goal 5.1), in the CLI, a string field's struct tag and `Fake` alike: `fejkdata sv_SE.person` prints `sv_SE.person`. `CHANGELOG.md` says that a path with no `{…}` prints as its text. A struct tag's `-` still skips its field.
- Text that is not a JSON value and holds no `{…}` prints as written (goal 5.1), in a string field's struct tag and `Fake` alike, as stdin's text does in the CLI: `Fake("sv_SE.person")` returns `sv_SE.person`. `CHANGELOG.md` says that a path with no `{…}` prints as its text. A struct tag's `-` still skips its field.
- Where text with no `{…}` is refused, as a record or in a typed struct field, and it spells a path the data offers, the error names `{/that.path}`.
- An unknown reference exits 2, where an unknown path exits 1 today.
- The README's CLI section gives one line per shell family, PowerShell and cmd.exe included, showing how to quote a template.