From b9c1a28a617c420ccde6be4089149463698a1692 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 7 Oct 2026 15:58:05 +0200 Subject: [PATCH] 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 --- AGENTS.md | 7 +-- CHANGELOG.md | 7 ++- README.md | 107 ++++++++++++++++++++++++---------------------- docs/decisions.md | 34 +++++++++++---- todo.md | 8 ++-- 5 files changed, 95 insertions(+), 68 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0112739..2a00f8b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f382dd..0fe42a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 1435e08..1d94c1d 100644 --- a/README.md +++ b/README.md @@ -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: — 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 diff --git a/docs/decisions.md b/docs/decisions.md index c87002d..ca6109a 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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. diff --git a/todo.md b/todo.md index 194b592..6935b66 100644 --- a/todo.md +++ b/todo.md @@ -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.