Answer architecture round 2, stability round 1 and product-owner round 1: a format string on stdin keeps every byte while JSON or a lone reference drops one newline, an operand's error names stdin, a file or the pipe, {//x} is refused and "{/x}" read as its record, plain echo and a heredoc in the examples, goal 7 reads one command line, decision titles drop argument, and items 157 to 159 filed
This commit is contained in:
@@ -11,8 +11,9 @@
|
|||||||
wait for one.
|
wait for one.
|
||||||
- README goal 3 is gated at 7.0 on the `comprehension-panel` skill, and below it
|
- 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
|
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
|
dependency bump, a repair of behaviour the README documents, the infrastructure
|
||||||
the round itself runs on. `todo.md` carries the round: a PR per item it plans, the nine-seat
|
the round itself runs on, and a chunk the comprehension-floor decision lists as
|
||||||
|
shipped under the floor. `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
|
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
|
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
|
every pull request scores with the four-seat run and answers it in one run. No merge
|
||||||
@@ -38,10 +39,10 @@ In [docs/decisions.md](docs/decisions.md):
|
|||||||
- Options and fields share one namespace
|
- Options and fields share one namespace
|
||||||
- `{a|b}` stays beside nested choices
|
- `{a|b}` stays beside nested choices
|
||||||
- Flags follow getopt_long
|
- Flags follow getopt_long
|
||||||
- An argument is a template by its shape, not by a flag
|
- A struct tag is a template by its shape, not by a flag
|
||||||
- The CLI renders the template on its stdin, less one newline ending it
|
- The CLI renders the template on its stdin, keeping a format string's every byte
|
||||||
- An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
|
- A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save the CLI's lone reference until item 129
|
||||||
- A padded JSON argument is rejected, not trimmed
|
- A padded JSON template is rejected, not trimmed
|
||||||
- `FakeTemplate` and `NewTemplate` both stay
|
- `FakeTemplate` and `NewTemplate` both stay
|
||||||
- The shipped data is embedded, not discovered
|
- 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`
|
- With only the shipped set, a category loads on the first call reaching it; beside a `--data-path`, every category loads in `New`
|
||||||
|
|||||||
+5
-4
@@ -27,10 +27,11 @@ replacement, and each removed path, column or flag.
|
|||||||
operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. What `New` refuses is listed
|
operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. What `New` refuses is listed
|
||||||
under [Names](README.md#names).
|
under [Names](README.md#names).
|
||||||
- First release: the CLI, the library and the shipped data set.
|
- 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' |
|
- The CLI renders the template on its stdin, `echo '{/sv_SE.person} hihi' | fejkdata`,
|
||||||
fejkdata`, and takes no path or template as an argument. One newline ending stdin,
|
and takes no operand. A format string keeps every byte, `echo`'s newline included; a
|
||||||
`\n` or `\r\n`, is dropped. A template of one reference alone, `{/users}`, is the
|
JSON template or one reference alone drops one newline ending stdin, `\n` or `\r\n`.
|
||||||
record `users` under `--format`. An argument, or nothing piped in, exits 2.
|
One reference alone, `{/users}`, is the record `users` under `--format`. An operand,
|
||||||
|
or nothing piped in, exits 2.
|
||||||
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
|
- 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,
|
`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
|
`misc.territory[SE]`, and descends to a linked table by name, one path drawing each
|
||||||
|
|||||||
@@ -6,41 +6,44 @@ reproducible.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/larvit/fejkdata/cmd/fejkdata@latest
|
go install github.com/larvit/fejkdata/cmd/fejkdata@latest
|
||||||
echo -n '{/sv_SE.person}' | fejkdata # Sara Eriksson
|
echo '{/sv_SE.person}' | fejkdata # Sara Eriksson
|
||||||
```
|
```
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/sv_SE.person}' | fejkdata # Sara Eriksson
|
echo '{/sv_SE.person}' | fejkdata # Sara Eriksson
|
||||||
echo -n '{/sv_SE.person.last}' | fejkdata # Eriksson
|
echo '{/sv_SE.person.last}' | fejkdata # Eriksson
|
||||||
echo -n '{/sv_SE.person} hihi' | fejkdata # Sara Eriksson hihi
|
echo '{/sv_SE.person} hihi' | fejkdata # Sara Eriksson hihi, then echo's newline
|
||||||
echo -n '{/sv_SE.address}' | fejkdata --seed 42 # the same address every run
|
echo '{/sv_SE.address}' | fejkdata --seed 42 # the same address every run
|
||||||
echo -n '{/sv_SE.word}' | fejkdata -n 3 --separator ', ' # nät, barn, sol
|
echo '{/sv_SE.word}' | fejkdata -n 3 --separator ', ' # nät, barn, sol
|
||||||
fejkdata --list # every path the data offers
|
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 '{/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 '{/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
|
echo '{/sv_SE.word}' | fejkdata --data-path ./mydata # layer a directory over the shipped data
|
||||||
fejkdata --no-shipped-data -d ./mydata --list # only your 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 '{"format":"name: {x}","x":["bosse","lina"]}' | fejkdata # name: bosse or name: lina
|
||||||
echo -n '{"format":"name: {x}","x":["bosse","lina"]}' | fejkdata # name: bosse or name: lina
|
|
||||||
fejkdata < template.txt # a template kept in a file
|
fejkdata < template.txt # a template kept in a file
|
||||||
|
fejkdata <<'EOF' # born 2003-11-27 — a quoted heredoc passes ', $ and \ as written
|
||||||
|
born {date(1990-01-01,2010-12-31,'2006-01-02')}
|
||||||
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
fejkdata renders the template on its stdin: a format string, or a JSON object, array or
|
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,
|
string. Its `{…}` tokens reach the data by reference from the root,
|
||||||
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike available,
|
`{/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
|
and everything outside them prints as written; stdin that is JSON is read as JSON, so
|
||||||
|
`42`, `true` or `null` alone is refused. A reference names a category, or a field
|
||||||
inside one: each dot segment descends one level — folders, then the category (a JSON
|
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
|
file), then fields — and `[SE]` after a [table](#table) selects its row. `--list` prints
|
||||||
these paths; a template reads one as `{/path}`.
|
these paths; a template reads one as `{/path}`.
|
||||||
|
|
||||||
One newline ending stdin, `\n` or `\r\n`, is dropped, so `echo` and `echo -n`, a file
|
A format string keeps every byte of stdin, so `echo`'s newline renders too; `printf '%s'`
|
||||||
and a heredoc render alike; end stdin with two newlines to print one. Some shells' `echo`
|
leaves it out. A JSON template or one reference alone drops one newline ending stdin,
|
||||||
reads a backslash as an escape, so pipe a template holding one with `printf '%s'` or from
|
`\n` or `\r\n`, which neither could print. One reference alone, `{/users}`, is the record
|
||||||
a file. One reference alone, `{/users}`, is the record `users` under `--format`; the
|
`users` under `--format`. Some shells' `echo` reads a backslash as an escape, so pipe a
|
||||||
rest of stdin's rules are under
|
template holding one from a quoted heredoc or a file. The rest of stdin's rules are under
|
||||||
[Decisions](docs/decisions.md#the-cli-renders-the-template-on-its-stdin-less-one-newline-ending-it).
|
[Decisions](docs/decisions.md#the-cli-renders-the-template-on-its-stdin-keeping-a-format-strings-every-byte).
|
||||||
|
|
||||||
| Flag | |
|
| Flag | |
|
||||||
|------|--|
|
|------|--|
|
||||||
@@ -57,7 +60,7 @@ rest of stdin's rules are under
|
|||||||
`--name value` and `--name=value` both work, a short flag's value attaches or
|
`--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
|
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
|
[Decisions](docs/decisions.md#flags-follow-getopt_long); flags go anywhere, `--` ends them. Exit codes: `0` success, `1` runtime error (missing
|
||||||
dir, a lone reference to nothing), `2` misuse — a bad flag, an argument, nothing on stdin, or a
|
dir, a lone reference to nothing), `2` misuse — a bad flag, an operand, nothing on stdin, or a
|
||||||
template that does not compile. From a checkout:
|
template that does not compile. From a checkout:
|
||||||
`go run ./cmd/fejkdata …`.
|
`go run ./cmd/fejkdata …`.
|
||||||
|
|
||||||
@@ -78,9 +81,9 @@ A category is a JSON file in a directory. Save this as `mydata/sql.json`:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/sql}' | fejkdata --seed 1 --data-path ./mydata
|
echo '{/sql}' | fejkdata --seed 1 --data-path ./mydata
|
||||||
# INSERT INTO users VALUES('zoom'),('wahoo'),('blip');
|
# INSERT INTO users VALUES('zoom'),('wahoo'),('blip');
|
||||||
echo -n '{/sql}' | fejkdata --repeat 100 --data-path ./mydata > seed.sql
|
echo '{/sql}' | fejkdata --repeat 100 --data-path ./mydata > seed.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
That `format` string is the free-form spelling — you hand-write the whole row.
|
That `format` string is the free-form spelling — you hand-write the whole row.
|
||||||
@@ -103,11 +106,11 @@ the whole. `--format json|ndjson|csv|sql` writes the records; the library's
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/users}' | fejkdata --seed 1 -d ./mydata --format json # [{"first":"Bo","last":"Lovelace"}]
|
echo '{/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 '{/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 '{/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 '{/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
|
echo '{/users}' | fejkdata --seed 1 -d ./mydata --format sql --table people # INSERT into another table
|
||||||
```
|
```
|
||||||
|
|
||||||
A record is one reference alone, `{/users}`, or a template whose fields are its
|
A record is one reference alone, `{/users}`, or a template whose fields are its
|
||||||
@@ -508,11 +511,11 @@ SE Sweden 10500000
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/country}' | fejkdata -d ./mydata # Sweden (SE), about half the time
|
echo '{/country}' | fejkdata -d ./mydata # Sweden (SE), about half the time
|
||||||
echo -n '{/country.alpha2}' | fejkdata -d ./mydata # NO
|
echo '{/country.alpha2}' | fejkdata -d ./mydata # NO
|
||||||
echo -n '{/country[SE]}' | fejkdata -d ./mydata # Sweden (SE)
|
echo '{/country[SE]}' | fejkdata -d ./mydata # Sweden (SE)
|
||||||
echo -n '{/country[Norway].alpha2}' | fejkdata -d ./mydata # NO
|
echo '{/country[Norway].alpha2}' | fejkdata -d ./mydata # NO
|
||||||
echo -n '{/country}' | fejkdata -d ./mydata --format csv # alpha2,name,population → SE,Sweden,10500000
|
echo '{/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
|
`rows` names the TSV beside the category file; `key` names the column a path
|
||||||
@@ -568,10 +571,10 @@ Stockholm SE 990000
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/country[SE].city}' | fejkdata -d ./mydata # Stockholm or Göteborg
|
echo '{/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 '{/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 '{/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
|
echo '{/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`
|
A path descends from a row to a linked table by name, at any depth, and `--list`
|
||||||
@@ -593,10 +596,10 @@ chain of parents that closes, and a table named like a column of any table above
|
|||||||
after the `..`:
|
after the `..`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo -n '{/city[Oslo]..country.name}' | fejkdata -d ./mydata # Norway
|
echo '{/city[Oslo]..country.name}' | fejkdata -d ./mydata # Norway
|
||||||
echo -n '{/city..country}' | fejkdata -d ./mydata # a city drawn, then its country: Sweden (SE)
|
echo '{/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 '{/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
|
echo '{/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
|
It steps up from a selected row, a drawn row or a [name](#names) bound to a row, one
|
||||||
@@ -922,7 +925,7 @@ Where the project is heading; the sections before Audience document what ships t
|
|||||||
fails to load instead.
|
fails to load instead.
|
||||||
2. Data that reads something nobody provided fails to load, and the error names the
|
2. Data that reads something nobody provided fails to load, and the error names the
|
||||||
module that provides it by default, or says that none does.
|
module that provides it by default, or says that none does.
|
||||||
7. **One command gets you a value.**
|
7. **One command line gets you a value.**
|
||||||
1. Flags work like in other command-line tools, and can go anywhere on the line.
|
1. Flags work like in other command-line tools, and can go anywhere on the line.
|
||||||
2. Your first template of your own needs no escaping and no options.
|
2. Your first template of your own needs no escaping and no options.
|
||||||
3. The CLI fills in what you leave out with a sane default: all the shipped data, the
|
3. The CLI fills in what you leave out with a sane default: all the shipped data, the
|
||||||
|
|||||||
+68
-26
@@ -1,14 +1,15 @@
|
|||||||
// Command fejkdata renders the template on its stdin with fake values from the shipped
|
// Command fejkdata renders the template on its stdin with fake values from the shipped
|
||||||
// data and any directories layered over it.
|
// data and any directories layered over it.
|
||||||
//
|
//
|
||||||
// echo -n '{/sv_SE.person}' | fejkdata # a full person
|
// echo '{/sv_SE.person}' | fejkdata # a full person
|
||||||
// echo -n 'name: {/sv_SE.person.last}' | fejkdata # text around a surname
|
// echo 'name: {/sv_SE.person.last}' | fejkdata # text around a surname
|
||||||
// echo -n '{/sv_SE.person}' | fejkdata --data-path ./mydata # layer custom data; the last dir wins
|
// echo '{/sv_SE.person}' | fejkdata --data-path ./mydata # layer custom data; the last dir wins
|
||||||
// echo -n '{/sv_SE.address}' | fejkdata --seed 42
|
// echo '{/sv_SE.address}' | fejkdata --seed 42
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -22,21 +23,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: <template> | fejkdata [flags]
|
const usage = `Usage: <template> | fejkdata [flags]
|
||||||
|
fejkdata --list | --help | --version
|
||||||
|
|
||||||
fejkdata reads one template from stdin and renders it:
|
fejkdata reads one template from stdin and renders it:
|
||||||
|
|
||||||
echo -n 'name: {/sv_SE.person.last}' | fejkdata
|
echo 'name: {/sv_SE.person.last}' | fejkdata
|
||||||
echo -n '{"format":"{x}","x":["bosse","lina"]}' | fejkdata
|
echo '{"format":"{x}","x":["bosse","lina"]}' | fejkdata
|
||||||
|
fejkdata < template.txt
|
||||||
|
fejkdata <<'EOF'
|
||||||
|
born {date(1990-01-01,2010-12-31,'2006-01-02')}
|
||||||
|
EOF
|
||||||
|
|
||||||
A template is a format string, or a JSON object, array or string. Its {…} tokens
|
A template is a format string, or a JSON object, array or string. Its {…} tokens
|
||||||
reach the data by reference from the root, {/sv_SE.person.last}, whether the data
|
reach the data by reference from the root, {/sv_SE.person.last}, whether the data
|
||||||
is shipped or layered with --data-path; everything outside them prints as written.
|
is shipped or layered with --data-path; everything outside them prints as written,
|
||||||
|
and stdin that is JSON is read as JSON, so 42, true or null alone is refused.
|
||||||
A reference names a category or a dotted path into one ({/sv_SE.person.last}), a
|
A reference names a category or a dotted path into one ({/sv_SE.person.last}), a
|
||||||
table's row by key or name ({/misc.territory[SE]}), and .. steps up from a row to
|
table's row by key or name ({/misc.territory[SE]}), and .. steps up from a row to
|
||||||
the row it links to ({/geo.SE.locality..municipality.name}).
|
the row it links to ({/geo.SE.locality..municipality.name}).
|
||||||
|
|
||||||
One newline ending stdin, \n or \r\n, is dropped, so echo and echo -n render
|
A format string keeps every byte of stdin, so echo's newline renders too. JSON and a
|
||||||
alike; end stdin with two to print one.
|
lone reference drop one newline ending stdin, \n or \r\n, which neither could print.
|
||||||
|
A quoted heredoc, <<'EOF', passes a template holding $, a backslash or a quote as
|
||||||
|
written.
|
||||||
|
|
||||||
With --format json, ndjson, csv or sql the template must name a record — a
|
With --format json, ndjson, csv or sql the template must name a record — a
|
||||||
template whose fields are its columns, or one reference alone, {/sv_SE.person} —
|
template whose fields are its columns, or one reference alone, {/sv_SE.person} —
|
||||||
@@ -46,7 +55,7 @@ and the rows are written as one JSON array, one JSON object per line, one CSV ro
|
|||||||
-d, --data-path D a data directory to layer over the shipped data (repeatable; last wins on a clash)
|
-d, --data-path D a data directory to layer over the shipped data (repeatable; last wins on a clash)
|
||||||
--format F output form: text (default), json, ndjson, csv or sql
|
--format F output form: text (default), json, ndjson, csv or sql
|
||||||
-h, --help print this help, then exit
|
-h, --help print this help, then exit
|
||||||
--list list the paths the data offers, each read from the root, then exit
|
--list list the paths the data offers, each one a reference's path, then exit
|
||||||
--no-shipped-data load only the --data-path directories
|
--no-shipped-data load only the --data-path directories
|
||||||
-n, --repeat N render the template N times, 1..1048576 (default 1)
|
-n, --repeat N render the template N times, 1..1048576 (default 1)
|
||||||
-s, --seed N same seed, version and data: identical output
|
-s, --seed N same seed, version and data: identical output
|
||||||
@@ -266,16 +275,28 @@ func (in invocation) checkFlags() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const pipeHint = "echo -n '{/sv_SE.person}' | fejkdata"
|
const pipeHint = "echo '{/sv_SE.person}' | fejkdata"
|
||||||
|
|
||||||
// check rejects a flag combination or an argument that cannot run, before any data is
|
// operandError refuses an operand, naming the spelling that reads what it most likely
|
||||||
|
// means: stdin for -, a file for a file's name, else the template piped in.
|
||||||
|
func operandError(arg string) error {
|
||||||
|
if arg == "-" {
|
||||||
|
return errors.New("fejkdata takes no operand, and reads stdin already; drop the -")
|
||||||
|
}
|
||||||
|
if fi, err := os.Stat(arg); err == nil && fi.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("fejkdata takes no operand; read the file from stdin: fejkdata < %s", arg)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fejkdata takes no operand; pipe the template to stdin: %s", pipeHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check rejects a flag combination or an operand that cannot run, before any data is
|
||||||
// read.
|
// read.
|
||||||
func (in invocation) check() error {
|
func (in invocation) check() error {
|
||||||
if err := in.checkFlags(); err != nil {
|
if err := in.checkFlags(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(in.args) > 0 {
|
if len(in.args) > 0 {
|
||||||
return fmt.Errorf("the template comes from stdin, not from an argument: %s", pipeHint)
|
return operandError(in.args[0])
|
||||||
}
|
}
|
||||||
if in.list && (in.repeatSet || in.separatorSet || in.formatSet || in.tableSet) {
|
if in.list && (in.repeatSet || in.separatorSet || in.formatSet || in.tableSet) {
|
||||||
return errors.New("--list takes no --repeat, --separator, --format or --table")
|
return errors.New("--list takes no --repeat, --separator, --format or --table")
|
||||||
@@ -343,7 +364,7 @@ func (in invocation) recordStream(f *fejkdata.Generator, src input) (func() (*fe
|
|||||||
if src.path == "" {
|
if src.path == "" {
|
||||||
t, err := f.NewRecordTemplate(src.template)
|
t, err := f.NewRecordTemplate(src.template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", inputError{err}
|
return nil, "", inputError{spacedReference(src.template, err)}
|
||||||
}
|
}
|
||||||
record = func() (*fejkdata.Record, error) { return t.Fake(), nil }
|
record = func() (*fejkdata.Record, error) { return t.Fake(), nil }
|
||||||
}
|
}
|
||||||
@@ -410,9 +431,9 @@ func defaultTable(path string) string {
|
|||||||
return segments[len(segments)-1]
|
return segments[len(segments)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputError marks a failure that is the input's own fault — no template, or one that
|
// inputError marks a failure that is the input's own fault — no template, one that does
|
||||||
// does not compile. run reports it as misuse (exit 2, with a pointer to --help), unlike
|
// not compile, or a stdin that cannot be read. run reports it as misuse (exit 2, with a
|
||||||
// an unknown path, which is a runtime error (exit 1).
|
// pointer to --help), unlike a lone reference to nothing, which is a runtime error (exit 1).
|
||||||
type inputError struct{ error }
|
type inputError struct{ error }
|
||||||
|
|
||||||
func (e inputError) Unwrap() error { return e.error }
|
func (e inputError) Unwrap() error { return e.error }
|
||||||
@@ -423,37 +444,58 @@ type input struct {
|
|||||||
template string
|
template string
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseInput reads stdin's text less one newline ending it.
|
// parseInput reads stdin's text. A format string keeps every byte; JSON or a lone
|
||||||
|
// reference sheds one newline ending it, which neither could print.
|
||||||
func parseInput(raw string) (input, error) {
|
func parseInput(raw string) (input, error) {
|
||||||
text, found := strings.CutSuffix(raw, "\n")
|
if raw == "" {
|
||||||
if found {
|
|
||||||
text = strings.TrimSuffix(text, "\r")
|
|
||||||
}
|
|
||||||
if text == "" {
|
|
||||||
return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint)
|
return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint)
|
||||||
}
|
}
|
||||||
|
if text, found := strings.CutSuffix(raw, "\n"); found {
|
||||||
|
text = strings.TrimSuffix(text, "\r")
|
||||||
if path, lone := loneReference(text); lone {
|
if path, lone := loneReference(text); lone {
|
||||||
return input{path: path}, nil
|
return input{path: path}, nil
|
||||||
}
|
}
|
||||||
|
if json.Valid([]byte(text)) {
|
||||||
return input{template: text}, nil
|
return input{template: text}, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if path, lone := loneReference(raw); lone {
|
||||||
|
return input{path: path}, nil
|
||||||
|
}
|
||||||
|
return input{template: raw}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// loneReference is the path that text, one reference from the root and nothing else,
|
// loneReference is the path that text, one reference from the root and nothing else,
|
||||||
// reads: the path IsTemplate advises writing instead.
|
// reads: the path IsTemplate advises writing instead. A JSON string holding one is one.
|
||||||
func loneReference(text string) (string, bool) {
|
func loneReference(text string) (string, bool) {
|
||||||
|
var unquoted string
|
||||||
|
if json.Unmarshal([]byte(text), &unquoted) == nil {
|
||||||
|
text = unquoted
|
||||||
|
}
|
||||||
if !strings.HasPrefix(text, "{/") || !strings.HasSuffix(text, "}") {
|
if !strings.HasPrefix(text, "{/") || !strings.HasSuffix(text, "}") {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
if inline, err := fejkdata.IsTemplate(text); inline || err == nil {
|
if inline, err := fejkdata.IsTemplate(text); inline || err == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
path := strings.TrimLeft(text[1:len(text)-1], "/")
|
path := text[2 : len(text)-1]
|
||||||
if inline, err := fejkdata.IsTemplate(path); inline || err != nil {
|
if inline, err := fejkdata.IsTemplate(path); inline || err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return path, true
|
return path, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spacedReference adds to err, a refusal of template as a record, that the whitespace
|
||||||
|
// around a lone reference made it text, where it does.
|
||||||
|
func spacedReference(template string, err error) error {
|
||||||
|
if trimmed := strings.TrimSpace(template); trimmed != template {
|
||||||
|
if path, lone := loneReference(trimmed); lone {
|
||||||
|
return fmt.Errorf("%w; the whitespace around {/%s} makes it text, so remove it to write the record %s", err, path, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var stdin io.Reader = os.Stdin
|
var stdin io.Reader = os.Stdin
|
||||||
if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
|
if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
|
||||||
@@ -512,7 +554,7 @@ func readStdin(stdin io.Reader) (input, error) {
|
|||||||
}
|
}
|
||||||
raw, err := io.ReadAll(stdin)
|
raw, err := io.ReadAll(stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return input{}, err
|
return input{}, inputError{fmt.Errorf("stdin cannot be read: %w", err)}
|
||||||
}
|
}
|
||||||
src, err := parseInput(string(raw))
|
src, err := parseInput(string(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+55
-21
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -224,7 +225,6 @@ func TestRunMisuse(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"", nil},
|
{"", nil},
|
||||||
{"", []string{"--data-path", svSE}},
|
{"", []string{"--data-path", svSE}},
|
||||||
{"\n", []string{"--data-path", svSE}},
|
|
||||||
{"{/person}", []string{"-d", svSE, "word"}},
|
{"{/person}", []string{"-d", svSE, "word"}},
|
||||||
{"", []string{"-d", svSE, "--list", "person"}},
|
{"", []string{"-d", svSE, "--list", "person"}},
|
||||||
{"{/sv_SE.person}", []string{"--no-shipped-data"}},
|
{"{/sv_SE.person}", []string{"--no-shipped-data"}},
|
||||||
@@ -242,14 +242,36 @@ func TestRunMisuse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRunTakesNoArgument pins the error for a path or a template given as an argument:
|
func TestRunTakesNoOperand(t *testing.T) {
|
||||||
// it shows the template piped to stdin.
|
file := filepath.Join(t.TempDir(), "users.tmpl")
|
||||||
func TestRunTakesNoArgument(t *testing.T) {
|
if err := os.WriteFile(file, []byte("{/sv_SE.person}"), 0o644); err != nil {
|
||||||
for _, args := range [][]string{{"sv_SE.person"}, {"{/sv_SE.person}"}, {"name:", "{/sv_SE.person.last}"}} {
|
t.Fatal(err)
|
||||||
code, out, errb := runOut("", args...)
|
|
||||||
if code != 2 || out != "" || !strings.Contains(errb, "stdin") || !strings.Contains(errb, "| fejkdata") {
|
|
||||||
t.Errorf("run(%v) = %d, %q, %q; want misuse showing a template piped to fejkdata", args, code, out, errb)
|
|
||||||
}
|
}
|
||||||
|
for _, c := range []struct {
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{[]string{"sv_SE.person"}, "echo '{/sv_SE.person}' | fejkdata"},
|
||||||
|
{[]string{"{/sv_SE.person}"}, "echo '{/sv_SE.person}' | fejkdata"},
|
||||||
|
{[]string{"name:", "{/sv_SE.person.last}"}, "echo '{/sv_SE.person}' | fejkdata"},
|
||||||
|
{[]string{"-"}, "drop the -"},
|
||||||
|
{[]string{file}, "fejkdata < " + file},
|
||||||
|
} {
|
||||||
|
code, out, errb := runOut("", c.args...)
|
||||||
|
if code != 2 || out != "" || !strings.Contains(errb, "takes no operand") || !strings.Contains(errb, c.want) {
|
||||||
|
t.Errorf("run(%v) = %d, %q, %q; want misuse naming %q", c.args, code, out, errb, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type failingReader struct{}
|
||||||
|
|
||||||
|
func (failingReader) Read([]byte) (int, error) { return 0, errors.New("bad file descriptor") }
|
||||||
|
|
||||||
|
func TestRunUnreadableStdinIsMisuse(t *testing.T) {
|
||||||
|
var out, errb bytes.Buffer
|
||||||
|
if code := run(nil, failingReader{}, &out, &errb); code != 2 || !strings.Contains(errb.String(), "cannot be read") {
|
||||||
|
t.Errorf("run with an unreadable stdin = %d, %q; want misuse naming it", code, errb.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,26 +287,26 @@ func TestRunTerminalStdinIsMisuse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunDropsOneTrailingNewline(t *testing.T) {
|
func TestRunNewlineEndingStdin(t *testing.T) {
|
||||||
for stdin, want := range map[string]string{
|
for stdin, want := range map[string]string{
|
||||||
"hihi": "hihi\n",
|
"hihi": "hihi\n",
|
||||||
"hihi\n": "hihi\n",
|
"hihi\n": "hihi\n\n",
|
||||||
"hihi\r\n": "hihi\n",
|
"hihi\r\n": "hihi\r\n\n",
|
||||||
"hihi\n\n": "hihi\n\n",
|
"\n": "\n\n",
|
||||||
"hihi\r\n\r\n": "hihi\r\n\n",
|
"{{x}}\n": "{x}\n\n",
|
||||||
"a\nb\n": "a\nb\n",
|
|
||||||
" hihi \n": " hihi \n",
|
|
||||||
"{{x}}\n": "{x}\n",
|
|
||||||
"\"a\"\n": "a\n",
|
"\"a\"\n": "a\n",
|
||||||
|
"\"a\"\r\n": "a\n",
|
||||||
|
"{/misc.territory[SE].alpha2}\n": "SE\n",
|
||||||
|
"{/misc.territory[SE].alpha2}\r\n": "SE\n",
|
||||||
} {
|
} {
|
||||||
code, out, errb := runOut(stdin, "--seed", "1")
|
code, out, errb := runOut(stdin, "--seed", "1")
|
||||||
if code != 0 || out != want {
|
if code != 0 || out != want {
|
||||||
t.Errorf("run(%q) = %d, %q, %q; want %q", stdin, code, out, errb, want)
|
t.Errorf("run(%q) = %d, %q, %q; want %q", stdin, code, out, errb, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code, out, errb := runOut("x: {/sv_SE.person.last}\n\n", "--seed", "1")
|
code, out, errb := runOut("x: {/sv_SE.person.last}\n", "--seed", "1")
|
||||||
if code != 0 || !strings.HasPrefix(out, "x: ") || !strings.HasSuffix(out, "\n\n") || strings.Count(out, "\n") != 2 {
|
if code != 0 || !strings.HasPrefix(out, "x: ") || !strings.HasSuffix(out, "\n\n") || strings.Count(out, "\n") != 2 {
|
||||||
t.Errorf("a template ending in a newline = %d, %q, %q; want its own newline, then the line's", code, out, errb)
|
t.Errorf("echo's format string = %d, %q, %q; want echo's newline, then the line's", code, out, errb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,10 +435,10 @@ func TestUsageReferencesResolve(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunArgumentMisuseBeforeLoad(t *testing.T) {
|
func TestRunOperandMisuseBeforeLoad(t *testing.T) {
|
||||||
code, _, errb := runOut("", "--no-shipped-data", "x")
|
code, _, errb := runOut("", "--no-shipped-data", "x")
|
||||||
if code != 2 || !strings.Contains(errb, "stdin") || strings.Contains(errb, "--data-path") {
|
if code != 2 || !strings.Contains(errb, "takes no operand") || strings.Contains(errb, "--data-path") {
|
||||||
t.Fatalf("an argument with no data = %d, %q; want the argument refused before any load", code, errb)
|
t.Fatalf("an operand with no data = %d, %q; want the operand refused before any load", code, errb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +474,7 @@ func TestRunTemplateMisuse(t *testing.T) {
|
|||||||
" [\"a\",\"b\"]": "may not be padded",
|
" [\"a\",\"b\"]": "may not be padded",
|
||||||
"[\"a\",\"b\"]\n\n": "may not be padded",
|
"[\"a\",\"b\"]\n\n": "may not be padded",
|
||||||
"42": "number",
|
"42": "number",
|
||||||
|
"{//sv_SE.person}": "write {/sv_SE.person}",
|
||||||
} {
|
} {
|
||||||
code, out, errb := runOut(stdin, "--seed", "1")
|
code, out, errb := runOut(stdin, "--seed", "1")
|
||||||
if code != 2 || out != "" || !strings.Contains(errb, "try 'fejkdata --help'") || !strings.Contains(errb, want) {
|
if code != 2 || out != "" || !strings.Contains(errb, "try 'fejkdata --help'") || !strings.Contains(errb, want) {
|
||||||
@@ -605,6 +628,17 @@ func TestRunRecordDefaultTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunRecordOfAQuotedLoneReference(t *testing.T) {
|
||||||
|
code, out, errb := runOut(`"{/users}"`, "--seed", "1", "--format", "sql", "--data-path", recordDir(t))
|
||||||
|
if code != 0 || !strings.HasPrefix(out, `INSERT INTO "users" (`) {
|
||||||
|
t.Fatalf("a JSON string holding {/users} = %d, %q, %q; want the record users", code, out, errb)
|
||||||
|
}
|
||||||
|
code, out, errb = runOut("{/users} ", "--format", "csv", "--data-path", recordDir(t))
|
||||||
|
if code != 2 || out != "" || !strings.Contains(errb, "whitespace around {/users}") {
|
||||||
|
t.Errorf("{/users} and a space = %d, %q, %q; want misuse naming the whitespace", code, out, errb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunRecordInlineTemplate(t *testing.T) {
|
func TestRunRecordInlineTemplate(t *testing.T) {
|
||||||
code, out, errb := runOut(`{"format":"{x}","x":["a","b"]}`, "--seed", "1", "--format", "json")
|
code, out, errb := runOut(`{"format":"{x}","x":["a","b"]}`, "--seed", "1", "--format", "json")
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
|
|||||||
+24
-22
@@ -55,41 +55,43 @@ single-dash long flag is rejected naming the double-dash spelling, and `-s=42` i
|
|||||||
rejected naming both short spellings: `=` belongs to the long form, and reading `=42` as
|
rejected naming both short spellings: `=` belongs to the long form, and reading `=42` as
|
||||||
the value would make `-d=./x` a directory named `=./x`.
|
the value would make `-d=./x` a directory named `=./x`.
|
||||||
|
|
||||||
## An argument is a template by its shape, not by a flag
|
## A struct tag is a template by its shape, not by a flag
|
||||||
|
|
||||||
2026-09-03, Lilleman auf Larv.
|
2026-09-03, Lilleman auf Larv.
|
||||||
|
|
||||||
A JSON object, array or string, or a string carrying a `{` token, is an inline template;
|
A JSON object, array or string, or a string carrying a `{` token, is an inline template;
|
||||||
anything else is a path. A name may not contain a brace, a bracket or a quote, so a path
|
anything else is a path. A name may not contain a brace, a bracket or a quote, so a path
|
||||||
can never collide with any of those spellings, and the leading `[` or `"` is gated on
|
can never collide with any of those spellings, and the leading `[` or `"` is gated on
|
||||||
valid JSON so a stray copied bracket never swallows an argument — it names nothing, and
|
valid JSON so a stray copied bracket never swallows a tag — it names nothing, and says
|
||||||
says so. No `--template` flag is needed. Reserving the characters whole — though only a
|
so. No option is needed. Reserving the characters whole — though only a leading one
|
||||||
leading one could collide — keeps one simple name rule instead of a leading-position
|
could collide — keeps one simple name rule instead of a leading-position special case.
|
||||||
special case. The JSON string is what makes the library's own advice reachable: the
|
The JSON string is what makes the library's own advice reachable: the error for an
|
||||||
error for an object holding only a format names `"…"`, and that spelling has to work
|
object holding only a format names `"…"`, and that spelling has to work where it is
|
||||||
where it is printed. A struct tag of one reference alone, `{/users}`, is refused naming
|
printed. A struct tag of one reference alone, `{/users}`, is refused naming the path
|
||||||
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
|
`users`: both render the same text, and only the path names a record. A folder-relative
|
||||||
inline template sits in no folder. `IsTemplate` exports the rule, so struct tags and any
|
`{.name}` or `{..name}` is refused naming `{/name}`, since an inline template sits in no
|
||||||
other caller read one.
|
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
|
## The CLI renders the template on its stdin, keeping a format string's every byte
|
||||||
|
|
||||||
2026-10-07, Lilleman auf Larv. Serves goals 7 and 7.2.
|
2026-10-07, Lilleman auf Larv. Serves goals 7, 7.1 and 7.2.
|
||||||
|
|
||||||
The CLI takes no path or template as an argument: an argument is misuse, and so is a
|
The template comes from stdin, from `echo`, a file or a heredoc, so one command line gets
|
||||||
terminal with nothing piped in, which would otherwise wait for typed input. Both errors
|
a value and a quoted heredoc needs no escape. The CLI takes no operand: one is misuse
|
||||||
show `echo -n '{/sv_SE.person}' | fejkdata`. One newline ending stdin, `\n` or `\r\n`,
|
naming the spelling that reads what it likely means, `fejkdata < FILE` for a file and
|
||||||
is dropped, so `echo`, a file and a heredoc render as `echo -n` does; only one, so a
|
nothing for `-`, and so is a terminal with nothing piped in, which would otherwise wait
|
||||||
template that should end in a newline ends stdin with two. The rest of stdin reaches
|
for typed input. A format string keeps every byte, `echo`'s newline included, since
|
||||||
`NewTemplate` unchanged, so a padded JSON template is refused there, as goal 5.7 asks.
|
everything outside `{…}` prints as written. JSON and one reference alone drop one newline
|
||||||
One reference from the root and nothing else, `{/users}`, reads the path it names: it
|
ending stdin, `\n` or `\r\n`, which neither could print; the rest reaches `NewTemplate`
|
||||||
|
unchanged, so a padded JSON template is refused there, as goal 5.7 asks. One reference
|
||||||
|
from the root and nothing else, `{/users}` or `"{/users}"`, reads the path it names: it
|
||||||
renders the same text, and under `--format` it is the record `users`, with `users` the
|
renders the same text, and under `--format` it is the record `users`, with `users` the
|
||||||
default `--table`; one to nothing exits 1, as an unknown path does. That reading sits in
|
default `--table`; one to nothing exits 1, as an unknown path does. That reading sits in
|
||||||
the CLI against goal 3.2 until `todo.md` item 129 moves it into the library. `--list`
|
the CLI against goal 3.2 until `todo.md` item 129 moves it into the library. `--list`
|
||||||
still prints paths, each read as `{/path}`. Valid while the CLI reads one template per
|
still prints paths, each read as `{/path}`. Valid while the CLI reads one template per
|
||||||
run.
|
run.
|
||||||
|
|
||||||
## An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
|
## A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save the CLI's lone reference until item 129
|
||||||
|
|
||||||
2026-09-24, Lilleman auf Larv.
|
2026-09-24, Lilleman auf Larv.
|
||||||
|
|
||||||
@@ -97,9 +99,9 @@ The whole template is the spelling under test, and `NewTemplate` compiles, links
|
|||||||
validates as one step. An unknown *path* stays a runtime error (exit 1): there the
|
validates as one step. An unknown *path* stays a runtime error (exit 1): there the
|
||||||
template 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
|
## A padded JSON template is rejected, not trimmed
|
||||||
|
|
||||||
2026-09-03, Lilleman auf Larv.
|
2026-09-03, Lilleman auf Larv. Serves goal 5.7.
|
||||||
|
|
||||||
Padding is the one place the two readings disagree — a format string renders it, JSON
|
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 the spelling that renders is named rather than silently chosen.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
`Bar = 9`
|
`Bar = 9`
|
||||||
|
|
||||||
`Next ID = 157`
|
`Next ID = 160`
|
||||||
|
|
||||||
| Goal | W |
|
| Goal | W |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
| 57 | 0.1.0 | | **Rewrite `CHANGELOG.md`'s `[Unreleased]` as what v0.1.0 holds.** | 1 | 2 | 6 | 6 | 1.1 | 22.5 |
|
| 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 |
|
| 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 |
|
| 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 |
|
||||||
|
| 157 | 0.1.0 | | **Show in the README's CLI section how PowerShell and cmd.exe pipe a template, one line per shell family.** | 1 | 1 | 5 | 6 | 7.2 | 17.1 |
|
||||||
| 89 | 0.1.0 | | **Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.** | 3 | 3 | 6 | 7 | 10.1 | 16.0 |
|
| 89 | 0.1.0 | | **Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.** | 3 | 3 | 6 | 7 | 10.1 | 16.0 |
|
||||||
| 145 | 0.1.0 | | **Move `doc.go`'s definitions of a level, a pick key and an addressed key, with their worked example, to `pickKey` in `pick.go`, and gather `levelKeys`, `addressedKeys`, `renderEnv.keeps` and `readField`'s kept read beside `readUnder`, so a reader follows a named read in one file.** | 2 | 3 | 1 | 9 | 3.4, 3.2 | 15.4 |
|
| 145 | 0.1.0 | | **Move `doc.go`'s definitions of a level, a pick key and an addressed key, with their worked example, to `pickKey` in `pick.go`, and gather `levelKeys`, `addressedKeys`, `renderEnv.keeps` and `readField`'s kept read beside `readUnder`, so a reader follows a named read in one file.** | 2 | 3 | 1 | 9 | 3.4, 3.2 | 15.4 |
|
||||||
| 127 | 0.1.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 4 | 6 | 4 | 14.4 |
|
| 127 | 0.1.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 4 | 6 | 4 | 14.4 |
|
||||||
@@ -116,8 +117,10 @@
|
|||||||
| 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` 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 |
|
||||||
| 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 |
|
| 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 |
|
||||||
| 27 | 0.2.0 | | **Merge `en_US.ip` and `sv_SE.ip`, today byte-identical, into one `misc.ip`.** | 3 | 2 | 4 | 6 | 5.7 | 14.1 |
|
| 27 | 0.2.0 | | **Merge `en_US.ip` and `sv_SE.ip`, today byte-identical, into one `misc.ip`.** | 3 | 2 | 4 | 6 | 5.7 | 14.1 |
|
||||||
|
| 159 | 0.2.0 | | **Name the quoted spelling where a template that is a bare JSON number, boolean or null is refused: `42` names `"42"`.** | 1 | 2 | 3 | 6 | 6, 5.1 | 14.1 |
|
||||||
| 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 |
|
| 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, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 10 | 14.0 |
|
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 10 | 14.0 |
|
||||||
|
| 158 | 0.2.0 | | **Drop a UTF-8 byte order mark that starts stdin, and say in the README how PowerShell 5.1 keeps `Göteborg` from reaching the CLI as `G?teborg`.** | 2 | 2 | 4 | 5 | 7.2, 5.1 | 13.5 |
|
||||||
| 79 | 0.2.0 | decision | **Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.** | 6 | 6 | 7 | 8 | 13 | 12.9 |
|
| 79 | 0.2.0 | decision | **Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.** | 6 | 6 | 7 | 8 | 13 | 12.9 |
|
||||||
| 107 | 0.2.0 | decision | **Move the template engine into `internal/`, leaving the root the public API and its options.** | 6 | 9 | 3 | 8 | 3.2, 3.4 | 12.2 |
|
| 107 | 0.2.0 | decision | **Move the template engine into `internal/`, leaving the root the public API and its options.** | 6 | 9 | 3 | 8 | 3.2, 3.4 | 12.2 |
|
||||||
| 112 | 0.2.0 | | **Check a calc's field operands where its template resolves, beside its name operands, once item 36 reports every mistake at once.** | 2 | 2 | 1 | 6 | 3.2 | 10.3 |
|
| 112 | 0.2.0 | | **Check a calc's field operands where its template resolves, beside its name operands, once item 36 reports every mistake at once.** | 2 | 2 | 1 | 6 | 3.2 | 10.3 |
|
||||||
@@ -251,8 +254,8 @@ Goal 5.9. Today a path is a second spelling of the template that reads it; after
|
|||||||
- 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.
|
- 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}`.
|
- 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.
|
- 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.
|
- Decide whether a lone reference draws as its path draws today or as a template draws: under one seed, `{/sv_SE.person}` and `{/sv_SE.person} hihi` pick different people.
|
||||||
- This revises the decisions "An argument is a template by its shape, not by a flag", "`FakeTemplate` and `NewTemplate` both stay", "`--list` stays a plain list of paths", and the exit-1 rule for an unknown path in "An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing". The README's examples and exit codes, the godoc examples in `doc.go`, the CLI's usage text and header comment, and `CHANGELOG.md`'s entries move to the template spelling. Delete items 40 and 110, which this closes.
|
- This revises the decisions "A struct tag is a template by its shape, not by a flag", "`FakeTemplate` and `NewTemplate` both stay", "`--list` stays a plain list of paths", "The CLI renders the template on its stdin, keeping a format string's every byte", and the exit-1 rule for an unknown path in "A template that does not compile is misuse (exit 2), including a reference that resolves to nothing, save the CLI's lone reference until item 129". The README's examples and exit codes, the godoc examples in `doc.go`, the CLI's usage text and header comment, and `CHANGELOG.md`'s entries move to the template spelling. Delete items 40 and 110, which this closes.
|
||||||
|
|
||||||
Split it into items for the CLI, struct tags and the library before starting.
|
Split it into items for the CLI, struct tags and the library before starting.
|
||||||
|
|
||||||
@@ -268,6 +271,10 @@ Asked of the maintainer by the product-owner pass of the chunk that made every `
|
|||||||
|
|
||||||
The decision "comprehension floor: every dimension and the overall at 7.0 or above" holds the scores this run is measured against.
|
The decision "comprehension floor: every dimension and the overall at 7.0 or above" holds the scores this run is measured against.
|
||||||
|
|
||||||
|
### 157. Show in the README's CLI section how PowerShell and cmd.exe pipe a template, one line per shell family.
|
||||||
|
|
||||||
|
Split from item 129. cmd.exe's `echo` keeps its quotes and a trailing space, so `echo {/users} | fejkdata` sends `{/users} ` and stops naming the record.
|
||||||
|
|
||||||
### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.
|
### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.
|
||||||
|
|
||||||
Goal 10.1 has the caller supply the seed, and today `New` without `WithSeed` seeds itself. This item ships in 0.1.0 beside item 77, so `New` breaks once before anyone depends on it. Mark `WithSeed` required in the Library section's options table. Its tests then stop swapping `randomBytes`, as `fejkdata_test.go` does today, which fails every `New` in the package while that test runs.
|
Goal 10.1 has the caller supply the seed, and today `New` without `WithSeed` seeds itself. This item ships in 0.1.0 beside item 77, so `New` breaks once before anyone depends on it. Mark `WithSeed` required in the Library section's options table. Its tests then stop swapping `randomBytes`, as `fejkdata_test.go` does today, which fails every `New` in the package while that test runs.
|
||||||
@@ -431,6 +438,10 @@ Require the path step to reach a sibling category.
|
|||||||
|
|
||||||
`datatype: boolean` over `{int(1,2)}` on an `int` field answers `prints an integer, not a boolean`.
|
`datatype: boolean` over `{int(1,2)}` on an `int` field answers `prints an integer, not a boolean`.
|
||||||
|
|
||||||
|
### 159. Name the quoted spelling where a template that is a bare JSON number, boolean or null is refused: `42` names `"42"`.
|
||||||
|
|
||||||
|
On stdin `42` is refused as "a number" and `null` with advice to write `""`, which prints nothing; neither names the text the author meant. The goals audit of the stdin change found it.
|
||||||
|
|
||||||
### 22. Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.
|
### 22. Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.
|
||||||
|
|
||||||
17 of the 40 distinct ones shipped today sit on `.se`, `.nu`, `.io` and `.co`, which anyone may register, and only RFC 2606's `example.com`, `.net`, `.org`, `.test`, `.example`, `.invalid` and `.localhost` provably reach nothing.
|
17 of the 40 distinct ones shipped today sit on `.se`, `.nu`, `.io` and `.co`, which anyone may register, and only RFC 2606's `example.com`, `.net`, `.org`, `.test`, `.example`, `.invalid` and `.localhost` provably reach nothing.
|
||||||
@@ -439,6 +450,10 @@ Require the path step to reach a sibling category.
|
|||||||
|
|
||||||
`readTableOptions` in `table.go` and `walkDir` in `internal/datafiles` return on the first in Go's map order.
|
`readTableOptions` in `table.go` and `walkDir` in `internal/datafiles` return on the first in Go's map order.
|
||||||
|
|
||||||
|
### 158. Drop a UTF-8 byte order mark that starts stdin, and say in the README how PowerShell 5.1 keeps `Göteborg` from reaching the CLI as `G?teborg`.
|
||||||
|
|
||||||
|
A file a Windows editor saves with a BOM prints an invisible U+FEFF first, and a JSON template with one is read as a format string. PowerShell 5.1 encodes what it pipes to a program by `$OutputEncoding`, ASCII by default. The maintainer chose 0.2.0 on 2026-10-07.
|
||||||
|
|
||||||
### 79. Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.
|
### 79. Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.
|
||||||
|
|
||||||
Needs item 77, which makes an override fail unless it says it replaces.
|
Needs item 77, which makes an override fail unless it says it replaces.
|
||||||
|
|||||||
Reference in New Issue
Block a user