Show the CLI reading its template from stdin in the README, the usage and the decisions, file the stdin change under Unreleased, and leave item 129 the library's half

This commit is contained in:
2026-10-07 15:58:05 +02:00
committed by Lillem4n
parent 2bf043aefc
commit b9c1a28a61
5 changed files with 95 additions and 68 deletions
+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.