diff --git a/AGENTS.md b/AGENTS.md index 06892d9..db50fa5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,8 +43,7 @@ In [docs/decisions.md](docs/decisions.md): - The CLI renders exactly the template on its stdin, and adds nothing - The CLI reads only the library's public API - A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save a lone reference under the CLI's `--format` until item 129 -- Whitespace around a JSON template is dropped, in `NewTemplate` and on stdin -- A JSON string stays a template even where its bare text renders alike +- Whitespace around a JSON object, array, string or null is dropped, and a bare number or boolean is a format string - `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 032d4f9..bece9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,8 @@ replacement, and each removed path, column or flag. `{/misc.territory as t}` binds one row of `misc.territory` to `t` and prints nothing; `{t}`, `{t.capital}` and `{lowercase(t.name)}` read that one row. `{x as n}` binds a reference, a sibling field or a path into one; a name lives in the category binding it, - and picks again on every iteration of a `repeat` binding it. A struct's tags share its + and picks again on every iteration of a `repeat`, or every draw of a choice item, + binding it. A struct's tags share its names, `{/sv_SE.person as p}{p.first}` beside `{p.sex}`. A name is a `{calc()}` operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. What `New` refuses is listed under [Names](README.md#names). @@ -32,7 +33,17 @@ replacement, and each removed path, column or flag. defaults to empty. Whitespace around a JSON object, array or string template is dropped, and a bare number or boolean is a format string, printed as written. A lone reference, `{/users}`, is the record `users` under `--format`. An argument other than a flag, or - nothing piped in, exits 2; an empty or blank stdin is a template, and renders as it is. + nothing piped in, exits 2; an empty or blank stdin is a format string, and renders as + sent. +- A template or data file loads whatever is well-formed and means one thing, even where + part of it does nothing: a default written out, a one-item choice, an object holding only + a format, a constant sample such as `{int(5,5)}`, a `weight`, `separator` or `datatype` + where it does nothing, a name nothing reads, a repeated item, which counts as written, a + weight of `0`, which is never drawn, a `repeat` or count of `0`, which renders nothing, + `+5`, `05` and `-0`, and a reference into the category it sits in. A rows file no + category names is ignored. `New` with no data loads, and renders templates that read + none. The CLI's `-n` takes `0`; `--list` ignores `--repeat`, `--format` and `--table`, + and `--table` is ignored without `--format sql`. - 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 8b43c3a..f1d5167 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ Under `--format text`, the default, what you send is what renders, and fejkdata byte, so `echo`, a file or a heredoc ends each render with its newline, and `printf '%s'` sends none; `-n` joins renders with `--separator`, empty by default. A JSON object, array or string drops the whitespace around it, and `null` alone renders nothing; `42` or `true` -alone is a format string, so it prints as written, newline included. An empty or blank stdin is a template too, and -renders as it is. A JSON template that should end +alone is a format string, so it prints as written, newline included. An empty or blank stdin is a format string like +any other, and renders as sent. A JSON template that should end each render in a newline writes `\n` in its format, from a quoted heredoc or a file. A lone reference, one `{/…}` and nothing else, is the record it names under `--format`, one newline after it allowed. Some shells' `echo` reads a backslash as an escape, so pipe a @@ -120,7 +120,7 @@ echo '{/users}' | fejkdata --seed 1 -d ./mydata --format sql --table people # IN `--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, or a one-item choice of one; a +per line. Only a category-level template is a record, or a one-item choice holding one; a field, a choice of more items or a folder errors, and so does a `repeat` on the template itself, which composes the format into one string rather than projecting columns — ask for more records with `--repeat`. A `repeat` on a column @@ -329,7 +329,7 @@ ok, err := fejkdata.IsTemplate(arg) // an inline template by its shape, el | `WithoutShippedData()` | load only what you give | `New` refuses data that breaks the grammar, could mean two things or cannot render a valid -value, and `NewTemplate` such a template. On a loaded generator: +value, and `NewTemplate` refuses such a template. On a loaded generator: - `Fake` fails only for a path that names nothing, could name two things, or reads one draw of a level carrying a `repeat`, with the same error every call. @@ -388,8 +388,12 @@ Every value is a **node**, nestable without limit: | choice | `["a", "b", …]` | one item, picked at random | | template | `{"format": "…", …}` | its format, with `{name}` tokens rendering the named fields | | table | `{"format": "…", "rows": "x.tsv", …}` | its format over one row of the TSV beside it ([Table](#table)) | -| number | `5`, `1.50` | its text as written; refused in a record column, where it could be text, `"5"`, or a typed value, `{"format": "5", "datatype": "integer"}`, bar a struct tag's, which its field's Go type types | -| boolean | `true` | its text; refused in a record column, where it could be text, `"true"`, or a typed value, `{"format": "true", "datatype": "boolean"}`, bar a struct tag's, which its field's Go type types | +| number | `5`, `1.50` | its text as written | +| boolean | `true` | its text as written | + +In a record column a bare number or boolean is refused, since it could be the text `"5"` +or the typed value `{"format": "5", "datatype": "integer"}`. In a struct tag, the +field's Go type decides. ### Format string @@ -433,8 +437,8 @@ Renders `070-412 38 91` ten times as often as `08-…`. A string item is weighte writing it as `{ "format": "AB", "weight": 3 }`. A repeated item counts as written, so `["a", "a", "b"]` draws `a` two times in three, and an item of weight `0` is never drawn. A weight outside a choice does nothing, and so does one above `0` on the item of -a one-item choice. Rejected at load: a weight that is negative or not a number, and a -choice whose every weight is `0`, a one-item choice of weight `0` among them. +a one-item choice. Rejected at load: a weight that is negative, not a number, or too +close to `0` to tell from it, such as `1e-400`, and a choice whose every weight is `0`, a one-item choice of weight `0` among them. ### Repeat @@ -531,8 +535,8 @@ echo '{/country}' | fejkdata -d ./mydata --format csv # alpha2,name,populati ``` `rows` names the TSV beside the category file; `key` names the column a path -selects a row by, `name` a column it also selects by, `weight` a column of positive -numbers that skews the draw, and `parent` the table a column links to +selects a row by, `name` a column it also selects by, `weight` a column of numbers +of 0 or more that skews the draw, and `parent` the table a column links to ([Linked tables](#linked-tables)). The format's `{tokens}` read the columns, and the columns are the [record](#records)'s columns, so `--format csv` writes the rows and `--list` shows `country.alpha2`. A cell is a string node: `1{digits(2)} {digits(2)}` @@ -540,8 +544,8 @@ in a cell draws digits and `{/misc.uuid}` reads a reference, while `{name}` in a is refused, since a cell has no sibling. Each cell may select its own row of another table, `{/misc.currency[SEK].symbol}` on one row and `{/misc.currency[EUR].symbol}` on the next, since only one row renders. `New` proves the header, the options and every -cell token, and refuses a key that is empty or repeats, a weight that is negative or not -a number, every weight `0` across the table or inside one parent row, and a key or name holding `[`, `]`, `{`, `}`, +cell token, and refuses a key that is empty or repeats, a weight that is negative, not +a number or too close to `0` to tell from it, every weight `0` across the table or inside one parent row, and a key or name holding `[`, `]`, `{`, `}`, `"` or `|`, which a selector cannot spell; `New` maps the keys, and a name or parent column is mapped on the first draw that selects by name or descends through the table. A `name` needs a `key`, since a name naming several rows is reported by their keys, or a `parent`, inside whose row a name names one row, so `first-name[Kim]` @@ -679,10 +683,10 @@ layout prints `UTC` or `Z`. The quotes delimit a layout outside a selector only, so `[O'Fallon]` in an argument stays a name. A layout naming no field prints itself, `date` with a clock-only layout prints the drawn instant's clock, and `date` over one day with no clock field prints -that day; `time` with a layout naming a date field is refused, naming `date`, since `time` -draws no date. -Rejected at `New`: a bound that is no calendar date, or after the other, and an unquoted -or double-quoted layout, naming the single-quoted one. `{seq()}` spans `Fake` +that day. +Rejected at `New`: a bound that is no calendar date, or after the other; an unquoted +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. @@ -743,8 +747,7 @@ so `{.person.first} {.person.last}` may name two people. Bind the person to a [n {p.last}`. Rejected at `New`: a path that is unknown, names a folder, has no folder above, reads a field not every variant of a choice carries, and a reference that leads back to its own value, directly, -mutually or through a chain. A reference into the category it sits in is a fresh draw -like any other. +mutually or through a chain. ### Names @@ -781,8 +784,8 @@ Refused at `New`, each error naming what to write instead: - a binding of anything but a reference, or a field of the template binding it or a path into one; - a read of a name inside the field bound to it: read it outside that field; -- a read outside a choice's item of a name bound inside it, which every other item would - leave unbound; +- reading a name outside the choice item that binds it, since the other items leave it + unbound; - a path through a name that selects a row; - `{n}` beside `{n.w}` where `n`'s category reads `w` twice, as in `{w}-{w}` or `{w.a} {w.b}`; diff --git a/docs/decisions.md b/docs/decisions.md index b77a54a..1c12eb5 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -65,7 +65,9 @@ can never collide with any of those spellings, and the leading `[` or `"` is gat valid JSON so a stray copied bracket never swallows a tag — it names nothing, and says so. Reserving the characters whole — though only a leading one could collide — keeps one simple name rule instead of a leading-position special case. -A folder-relative `{.name}` or `{..name}` is refused naming +A JSON string stays a template even where its bare text renders alike, `"x {/a}"` beside +`x {/a}`: inside the quotes `\t` and `\n` write a tab or a newline on one line. A +folder-relative `{.name}` or `{..name}` is refused naming `{/name}`, since an inline template sits in no folder. `IsTemplate` exports the rule, so struct tags and any other caller read one. @@ -81,9 +83,7 @@ bench of the README's personas picked this on 2026-10-07 over dropping a newline adding one: a rule a user states in one sentence, at the cost of `printf` or a `--separator` now and then. -A JSON template is its JSON, so the whitespace around it is no part of the template, per -the decision "Whitespace around a JSON template is dropped, in `NewTemplate` and on -stdin". Stdin holding only whitespace is a template like any other, and renders it. +Stdin holding only whitespace is a format string, and renders as sent. A lone reference, `{/users}` or `"{/users}"`, names the record `users` under `--format`, one newline after it allowed, with `users` the default `--table`; one to nothing there @@ -100,9 +100,9 @@ template per run. 2026-10-07, Lilleman auf Larv. Serves goal 9.6: a CLI carrying your own modules is a few lines of Go and works just like `fejkdata`, so `fejkdata`'s own CLI is such a CLI and -imports nothing under `internal/`. It copies one reading the API does not export yet, -the lone reference: `isPath`, `hasEmptySegment` and `withoutSelectors` in `cmd/fejkdata`, pinned to the -grammar by a test, until `todo.md` item 129 moves that reading into the library. +its non-test code imports nothing under `internal/`. Until `todo.md` item 129 moves it into +the library, the CLI copies how the grammar tells a lone reference, in `isPath`, +`hasEmptySegment` and `withoutSelectors`, and a test pins the copy to the grammar. ## A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save a lone reference under the CLI's `--format` until item 129 @@ -112,7 +112,7 @@ The whole template is the spelling under test, and `NewTemplate` compiles, links validates as one step. Under the CLI's `--format`, a lone reference to nothing exits 1 until item 129: it reads a path, and only the data is absent. -## Whitespace around a JSON template is dropped, in `NewTemplate` and on stdin +## Whitespace around a JSON object, array, string or null is dropped, and a bare number or boolean is a format string 2026-10-07, Lilleman auf Larv. Applies KISS. @@ -122,15 +122,6 @@ is dropped, as `encoding/json` drops it. `null` stays JSON, since it is how a te renders nothing. A bare number or boolean gains nothing from the JSON reading, so it is a format string, and `echo 42` prints its newline as `echo x` does (goal 5.1). -## A JSON string stays a template even where its bare text renders alike - -2026-10-07, Lilleman auf Larv. Serves goal 5.7: whatever is well-formed and means one thing -loads. - -`"x {/a}"` and `x {/a}` render alike on stdin, in `NewTemplate` and in a struct tag. The -quotes are no mistake: inside them `\t` and `\n` write a tab or a newline where the -template is one line of a Go string, a struct tag or a file. - ## `FakeTemplate` and `NewTemplate` both stay 2026-09-03, Lilleman auf Larv. @@ -345,16 +336,14 @@ handle is needed, as the cache already compiles once. 2026-09-16, Lilleman auf Larv; revised 2026-10-07. Serves goals 5.2 and 5.7. A reference back into a category, `{/users.first}` inside `users`, is a fresh draw, as -every `{…}` is; only a cycle is refused. A value two fields share goes in its own -category, which one field binds to a name and both read (goal 4.1). A record's column -fences run at `New` too, so a category that loads renders as a value and as a record alike. +every `{…}` is; only a cycle is refused. A record's column fences run at `New` too, so a category that loads renders as a value and as a record alike. ## A record's column set is fixed before the first draw 2026-09-04, Lilleman auf Larv. Only a category-level template is a record: a path descending into a field, or naming a -folder or a choice, errors. A tail may pass through a choice whose variants carry +folder or a choice of more than one item, errors. A tail may pass through a choice whose variants carry different fields, so the columns — and with them the CSV header written once ahead of every row — would vary per draw. A fixed column set is what the CSV and `INSERT` contracts rest on, so the restriction holds even where a particular choice would happen @@ -631,8 +620,9 @@ Table section teaches it. 2026-09-18, Lilleman auf Larv. Serves goal 5.6. A layout may carry the comma that separates arguments, -`'January 2, 2006'`, a bare `2006-01-02` reads like a third date, and a double-quoted one -could be quotes printed in the output, so each is refused naming the single-quoted one. The layout is Go's reference time because the +`'January 2, 2006'`. A bare `2006-01-02` reads like a third date, and a double-quoted +layout could be quotes printed in the output, so both are refused, naming the +single-quoted spelling. The layout is Go's reference time because the library renders with it and a Go caller already knows it; its names are English, and a locale's own month and weekday names are data. @@ -855,7 +845,7 @@ Valid while names are read only inside the category binding them. 2026-10-04, Lilleman auf Larv. Serves goal 3 and applies KISS. -Shipped under the floor by the maintainer's decisions of 2026-10-07: the CLI reading its template from stdin, with the scoring run and its ratchet waived for that chunk; and goal 5.7 loading whatever is well-formed and means one thing, dropping every refusal that guessed at a mistake, with the scoring run and its ratchet waived for that chunk too. +Shipped under the floor by the maintainer's decisions of 2026-10-07, each with the scoring run and its ratchet waived: the CLI reading its template from stdin; and goal 5.7 loading whatever is well-formed and means one thing, dropping every refusal that guessed at a mistake. Last scoring run, 2026-10-07, on commit ad59967: Navigation 7.00, Locality 5.88, Shape 6.50, Self-sufficiency 6.13, overall 6.38. The nine-seat panel at depth 1, run beside it: Navigation 7.17, Locality 6.06, Shape 6.67, Self-sufficiency 6.56, overall 6.56. Against the runs on ac6158e, the scoring run's overall rose from 6.25 and its Shape from 6.38, while its Self-sufficiency fell from 6.38; the panel's overall held, and its Locality and Self-sufficiency fell by 0.05 each. Locality is lowest again, as at every panel since 2026-09-22. All 13 seats named the named-pick read path hardest: `readField`, `readName`, `readUnder` and `keeps`, with the pick keys `levelKeys` and `addressedKeys` build at load; twelve kept `doc.go`'s vocabulary open beside it. What the two runs found is filed in `todo.md`, and `todo.md` item 153 runs both again. Valid while `AGENTS.md` gates goal 3 at 7.0. diff --git a/todo.md b/todo.md index 8ae4ce6..606f228 100644 --- a/todo.md +++ b/todo.md @@ -114,7 +114,7 @@ | 50 | 0.2.0 | | **Reword `prove`'s refusal of a typed column reading a row, `{/region}`.** | 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 | | 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 | -| 42 | 0.2.0 | | **Refuse a struct tag's `datatype` naming the Go type that sets it before proving its values, as the README's Library section promises.** | 2 | 2 | 4 | 6 | 6 | 14.5 | +| 42 | 0.2.0 | | **Refuse a struct tag's `datatype` other than its Go type's before proving its values, as the README's Library section promises.** | 2 | 2 | 4 | 6 | 6 | 14.5 | | 32 | 0.2.0 | | **Add `misc.browser` as the parent of `misc.useragent`, so `misc.browser[Chrome].useragent` resolves.** | 3 | 3 | 4 | 6 | 4.1 | 14.4 | | 22 | 0.2.0 | | **Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.** | 3 | 4 | 7 | 8 | 16 | 14.0 | | 46 | 0.2.0 | | **Report the same error every load for a table with two bad options.** | 2 | 2 | 3 | 7 | 6, 10 | 14.0 | @@ -435,7 +435,7 @@ Say the format is the table's own (`region's format "{name}"`), name the read to Require the path step to reach a sibling category. -### 42. Refuse a struct tag's `datatype` naming the Go type that sets it before proving its values, as the README's Library section promises. +### 42. Refuse a struct tag's `datatype` other than its Go type's before proving its values, as the README's Library section promises. `datatype: boolean` over `{int(1,2)}` on an `int` field answers `prints an integer, not a boolean`. @@ -503,7 +503,7 @@ Today a cell reads `g.t.b, line 3` (`namedNode.labelIn`), a cell check `t.tsv: l ### 14. Spell `misc.creditcard`'s digit runs `{digits(n)}`. -A `"repeat": 14` over `{digits(1)}` draws every 14-digit run with the odds `{digits(14)}` does. Both load; the shipped data writes the shorter one. Re-pin seeded output in its own commit. +A `"repeat": 14` over `{digits(1)}` draws every 14-digit run with the odds `{digits(14)}` does. Both load; make the shipped data write the shorter one. Re-pin seeded output in its own commit. ### 11. Test that every `geo` tree holds the five table names and the `address` columns that port across countries.