Answer architecture round 4, stability round 3, product-owner round 3 and prose round 2: stdin trims JSON's own whitespace around any JSON value, the padded-JSON title names NewTemplate and the CLI, the JSON examples hold no backslash for echo, an argument's example is built only from a template or a valid reference and shell-quoted, the text scope of adding nothing, blank stdin, and the stdin decision in four paragraphs
This commit is contained in:
@@ -42,7 +42,7 @@ In [docs/decisions.md](docs/decisions.md):
|
||||
- A struct tag is a template by its shape
|
||||
- The CLI renders exactly the template on its stdin, and adds nothing
|
||||
- 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
|
||||
- A padded JSON template is rejected, not trimmed
|
||||
- `NewTemplate` rejects a padded JSON template, and the CLI's stdin drops the whitespace around one
|
||||
- `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`
|
||||
|
||||
+2
-2
@@ -28,10 +28,10 @@ replacement, and each removed path, column or flag.
|
||||
under [Names](README.md#names).
|
||||
- First release: the CLI, the library and the shipped data set.
|
||||
- The CLI renders exactly the template on its stdin, `echo '{/sv_SE.person} hihi' |
|
||||
fejkdata`, and adds nothing: `echo`'s newline ends the render, and `--separator`
|
||||
fejkdata`, and adds nothing to text: `echo`'s newline ends the render, and `--separator`
|
||||
defaults to empty. Whitespace around a JSON template is dropped. A lone reference,
|
||||
`{/users}`, is the record `users` under `--format`. An argument other than a flag, an
|
||||
empty stdin, or nothing piped in, exits 2.
|
||||
empty or blank stdin, 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
|
||||
|
||||
@@ -23,7 +23,7 @@ echo '{/misc.territory[SE].capital}' | fejkdata # Stockholm — a table
|
||||
echo '{/geo.SE.locality[Lund].street}' | fejkdata # Fjelievägen — a linked table, drawn inside the row
|
||||
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
|
||||
echo '{"format":"name: {x}\n","x":["bosse","lina"]}' | fejkdata # name: bosse or name: lina
|
||||
echo '{"format":"name: {x}","x":["bosse","lina"]}' | fejkdata # name: bosse or name: lina
|
||||
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')}
|
||||
@@ -38,10 +38,12 @@ inside one: each dot segment descends one level — folders, then the category (
|
||||
file), then fields — and `[SE]` after a [table](#table) selects its row. `--list` prints
|
||||
these paths; a template reads one as `{/path}`.
|
||||
|
||||
What you send is what renders, and fejkdata adds nothing. A format string keeps every
|
||||
In text, what you send is what renders, and fejkdata adds nothing. A format string keeps every
|
||||
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 template is
|
||||
its JSON, the whitespace around it dropped, so `42`, `true` or `null` alone is refused. A
|
||||
an object, array or string, and the whitespace around it is dropped; `42`, `true` or
|
||||
`null` alone is refused. 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
|
||||
template holding one from a quoted heredoc or a file. The rest of stdin's rules are under
|
||||
@@ -63,7 +65,7 @@ template holding one from a quoted heredoc or a file. The rest of stdin's rules
|
||||
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, a lone reference to nothing under `--format`), `2` misuse — a bad flag, an argument
|
||||
other than a flag, an empty or unreadable stdin, nothing piped in, or a
|
||||
other than a flag, an empty, blank or unreadable stdin, nothing piped in, or a
|
||||
template that does not compile. From a checkout:
|
||||
`go run ./cmd/fejkdata …`.
|
||||
|
||||
|
||||
+26
-19
@@ -28,7 +28,7 @@ const usage = `Usage: <template> | fejkdata [flags]
|
||||
fejkdata reads one template from stdin and renders it:
|
||||
|
||||
echo 'name: {/sv_SE.person.last}' | fejkdata
|
||||
echo '{"format":"{x}\n","x":["bosse","lina"]}' | fejkdata
|
||||
echo '{"format":"{x}","x":["bosse","lina"]}' | fejkdata
|
||||
fejkdata < template.txt
|
||||
|
||||
fejkdata <<'EOF'
|
||||
@@ -42,21 +42,21 @@ 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 the row it links to
|
||||
({/geo.SE.locality..municipality.name}).
|
||||
|
||||
What you send is what renders, and fejkdata adds nothing: a format string keeps
|
||||
In text, what you send is what renders, and fejkdata adds nothing: a format string keeps
|
||||
every byte, so echo's newline ends each render, and printf '%s' sends none. -n
|
||||
joins renders with --separator, empty by default. A JSON template is its JSON, the
|
||||
whitespace around it dropped, so 42, true or null alone is refused. A quoted
|
||||
joins renders with --separator, empty by default. A JSON template is an object, array
|
||||
or string, and the whitespace around it is dropped; 42, true or null alone is refused. A quoted
|
||||
heredoc, <<'EOF', passes $, a backslash or a quote as written.
|
||||
|
||||
With --format json, ndjson, csv or sql the template must be a record — a JSON
|
||||
template whose fields are its columns, or a lone reference, {/sv_SE.person}, one
|
||||
newline after it allowed — and the rows are written as one JSON array, one JSON
|
||||
template whose fields are its columns, or a lone reference, one reference and nothing
|
||||
else such as {/sv_SE.person}, one newline after it allowed — and the rows are written as one JSON array, one JSON
|
||||
object per line, one CSV row (after a header), or one INSERT.
|
||||
|
||||
-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
|
||||
-h, --help print this help, then exit
|
||||
--list list the paths the data offers, each one a reference from the root, then exit
|
||||
--list list the paths the data offers, each written after {/ in a template, then exit
|
||||
--no-shipped-data load only the --data-path directories
|
||||
-n, --repeat N render the template N times, 1..1048576 (default 1)
|
||||
-s, --seed N same seed, version and data: identical output
|
||||
@@ -279,7 +279,8 @@ func (in invocation) checkFlags() error {
|
||||
const pipeHint = "echo '{/sv_SE.person}' | fejkdata"
|
||||
|
||||
// operandError refuses an argument that is no flag, naming the spelling that reads what
|
||||
// it most likely means: stdin for -, a file for a file's name, else a template piped in.
|
||||
// it most likely means: dropping it for -, a file for a file's name, else a template
|
||||
// piped in.
|
||||
func operandError(arg string) error {
|
||||
unexpected := "unexpected argument '" + arg + "'"
|
||||
if arg == "-" {
|
||||
@@ -289,8 +290,10 @@ func operandError(arg string) error {
|
||||
return fmt.Errorf("%s; read a file from stdin: fejkdata < %s", unexpected, shellQuoted(arg))
|
||||
}
|
||||
example := pipeHint
|
||||
if inline, err := fejkdata.IsTemplate(arg); !inline && err == nil {
|
||||
example = "echo '{/" + arg + "}' | fejkdata"
|
||||
if inline, err := fejkdata.IsTemplate(arg); inline {
|
||||
example = "echo " + shellQuoted(arg) + " | fejkdata"
|
||||
} else if _, lone := loneReference("{/" + arg + "}"); lone && err == nil {
|
||||
example = "echo " + shellQuoted("{/"+arg+"}") + " | fejkdata"
|
||||
}
|
||||
return fmt.Errorf("%s; the template is read from stdin: %s", unexpected, example)
|
||||
}
|
||||
@@ -324,9 +327,8 @@ func (in invocation) options() []fejkdata.Option {
|
||||
return opts
|
||||
}
|
||||
|
||||
// write streams the input's renders to w, repeat of them joined by the separator. A
|
||||
// value that renders once renders every time, so a render failure comes before anything
|
||||
// is written; a write failure surfaces from Flush, bufio keeping the first one.
|
||||
// write streams the input's renders to w, repeat of them joined by the separator; a write
|
||||
// failure surfaces from Flush, bufio keeping the first one.
|
||||
func (in invocation) write(f *fejkdata.Generator, src input, w io.Writer) error {
|
||||
if in.writesRecords() {
|
||||
return in.writeRecords(f, src, w)
|
||||
@@ -421,11 +423,15 @@ func defaultTable(path string) string {
|
||||
|
||||
// inputError marks a failure that is the input's own fault — no template, one that does
|
||||
// not compile, or a stdin that cannot be read. run reports it as misuse (exit 2, with a
|
||||
// pointer to --help), unlike a lone reference to nothing, which is a runtime error (exit 1).
|
||||
// pointer to --help), unlike a lone reference to nothing under --format, which is a runtime
|
||||
// error (exit 1).
|
||||
type inputError struct{ error }
|
||||
|
||||
func (e inputError) Unwrap() error { return e.error }
|
||||
|
||||
// jsonSpace is the whitespace JSON allows around a value.
|
||||
const jsonSpace = " \t\r\n"
|
||||
|
||||
// input is what stdin holds: the template, and the path a lone reference names as a
|
||||
// record.
|
||||
type input struct {
|
||||
@@ -433,15 +439,16 @@ type input struct {
|
||||
record string
|
||||
}
|
||||
|
||||
// parseInput reads stdin. A format string is its every byte; a JSON template sheds the
|
||||
// whitespace around it; a lone reference names a record less one newline ending it.
|
||||
// parseInput reads stdin. A format string is every byte of stdin; a JSON template drops
|
||||
// the whitespace around it; a lone reference names a record, with one newline ending it
|
||||
// allowed.
|
||||
func parseInput(raw string) (input, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
trimmed := strings.Trim(raw, jsonSpace)
|
||||
if trimmed == "" {
|
||||
return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint)
|
||||
}
|
||||
template := raw
|
||||
if strings.ContainsRune(`{["`, rune(trimmed[0])) && json.Valid([]byte(trimmed)) {
|
||||
if json.Valid([]byte(trimmed)) {
|
||||
template = trimmed
|
||||
}
|
||||
text, found := strings.CutSuffix(template, "\n")
|
||||
@@ -485,7 +492,7 @@ func spacedReference(template string, err error) error {
|
||||
|
||||
// shellQuoted is s as a shell reads it back: bare where it holds nothing a shell expands.
|
||||
func shellQuoted(s string) string {
|
||||
if s != "" && !strings.ContainsAny(s, " \t'\"$`\\*?[]{}()<>|&;#~!") {
|
||||
if s != "" && !strings.ContainsAny(s, " \t\r\n'\"$`\\*?[]{}()<>|&;#~!") {
|
||||
return s
|
||||
}
|
||||
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
||||
|
||||
@@ -250,7 +250,8 @@ func TestRunTakesNoOperand(t *testing.T) {
|
||||
}{
|
||||
{[]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{"name: {/sv_SE.person.last}"}, "echo 'name: {/sv_SE.person.last}' | fejkdata"},
|
||||
{[]string{"geo.US.locality[O'Fallon].name"}, `echo '{/geo.US.locality[O'\''Fallon].name}' | fejkdata`},
|
||||
{[]string{"en_US.address"}, "echo '{/en_US.address}' | fejkdata"},
|
||||
{[]string{"-"}, "drop it"},
|
||||
{[]string{file}, "fejkdata < " + file},
|
||||
@@ -480,6 +481,8 @@ func TestRunTemplateMisuse(t *testing.T) {
|
||||
"name: {/no.such.path}": "no entry",
|
||||
"a } b": "}}",
|
||||
"42": "number",
|
||||
"42\n": "number",
|
||||
"true\n": "boolean",
|
||||
"{//sv_SE.person}": "write {/sv_SE.person}",
|
||||
} {
|
||||
code, out, errb := runOut(stdin, "--seed", "1")
|
||||
|
||||
+26
-18
@@ -78,22 +78,27 @@ folder. `IsTemplate` exports the rule, so struct tags and any other caller read
|
||||
|
||||
The template comes from stdin, from `echo`, a file or a heredoc, so one command line gets
|
||||
a value and a quoted heredoc needs no escape. A format string is stdin's every byte, and
|
||||
fejkdata adds no newline of its own: `echo` ends each render with its newline,
|
||||
fejkdata adds no newline of its own to text: `echo` ends each render with its newline,
|
||||
`printf '%s'` sends none, and `-n` joins renders with `--separator`, empty by default. A
|
||||
bench of the README's personas picked this over dropping a newline, or adding one, on
|
||||
2026-10-07: one rule a user states in a sentence, at the cost of `printf` or a
|
||||
`--separator` now and then. A JSON template is its JSON: the maintainer ruled that the
|
||||
whitespace around it is not part of the template, so goal 5.7 does not refuse it on
|
||||
stdin, while `NewTemplate` still refuses a padded one. 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 exits 1, as an unknown path
|
||||
does, until `todo.md` item 129 moves the reading into the library, against goal 3.2
|
||||
meanwhile. Stdin holding only whitespace is misuse, since an unset variable sends just a
|
||||
newline. The CLI takes no argument but its flags: one is misuse naming the spelling that
|
||||
reads what it likely means, `fejkdata < FILE` for a file's name, dropping it for `-`,
|
||||
and the template piped in otherwise; so is a terminal with nothing piped in, which would
|
||||
otherwise wait for typed input. `--list` still prints paths, each read as `{/path}`.
|
||||
Valid while the CLI reads one template per run.
|
||||
bench of the README's personas picked this on 2026-10-07 over dropping a newline or
|
||||
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: the maintainer ruled the whitespace around it no part of the
|
||||
template, so goal 5.7 does not refuse it on stdin, while `NewTemplate` still refuses a
|
||||
padded one. Stdin holding only whitespace is misuse, since an unset variable sends just a
|
||||
newline.
|
||||
|
||||
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
|
||||
exits 1. `todo.md` item 129 moves that reading into the library; until then it works
|
||||
against goal 3.2.
|
||||
|
||||
The CLI takes no argument but its flags: one is misuse naming the spelling that reads what
|
||||
it likely means, `fejkdata < FILE` for a file's name, dropping it for `-`, and the template
|
||||
piped in otherwise; so is a terminal with nothing piped in, which would otherwise wait for
|
||||
typed input. `--list` prints paths, each read as `{/path}`. Valid while the CLI reads one
|
||||
template per run.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -103,12 +108,15 @@ 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.
|
||||
|
||||
## A padded JSON template is rejected, not trimmed
|
||||
## `NewTemplate` rejects a padded JSON template, and the CLI's stdin drops the whitespace around one
|
||||
|
||||
2026-09-03, Lilleman auf Larv. Serves goal 5.7.
|
||||
2026-09-03, Lilleman auf Larv; the CLI's stdin 2026-10-07. Serves goal 5.7.
|
||||
|
||||
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 the maintainer ruled the whitespace around JSON no part of the
|
||||
template, as the decision "The CLI renders exactly the template on its stdin, and adds
|
||||
nothing" records.
|
||||
|
||||
## `FakeTemplate` and `NewTemplate` both stay
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 159 | 0.2.0 | | **Name the quoted spelling where a template that is a bare JSON number, boolean or null is refused, padded or not: `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 |
|
||||
| 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 to stop PowerShell 5.1 sending `Göteborg` as `G?teborg`.** | 2 | 2 | 4 | 5 | 7.2, 5.1 | 13.5 |
|
||||
@@ -254,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.
|
||||
- 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}`.
|
||||
- A lone reference to nothing exits 2 under `--format`, where it exits 1 today.
|
||||
- Decide whether a lone reference under `--format` draws as its path draws today or as a template draws: under one seed, the two pick different rows.
|
||||
- This revises the decisions "A struct tag is a template by its shape", "`FakeTemplate` and `NewTemplate` both stay", "`--list` stays a plain list of paths", "The CLI renders exactly the template on its stdin, and adds nothing", 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 a lone reference under the CLI's `--format` until item 129". The README's examples and exit codes, the godoc examples in `doc.go` and `CHANGELOG.md`'s entries move to the template spelling. Delete items 40 and 110, which this closes.
|
||||
- Decide whether a lone reference under `--format` draws as its path draws today or as a template draws: under one seed, drawing `users` as a path and as the template `{/users}` picks different rows.
|
||||
- This revises the decisions "A struct tag is a template by its shape", "`FakeTemplate` and `NewTemplate` both stay", "`--list` stays a plain list of paths", "The CLI renders exactly the template on its stdin, and adds nothing", and the exit-1 rule for a lone reference to nothing in "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". The README's examples and exit codes, the godoc examples in `doc.go` 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.
|
||||
|
||||
@@ -438,7 +438,7 @@ 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`.
|
||||
|
||||
### 159. Name the quoted spelling where a template that is a bare JSON number, boolean or null is refused: `42` names `"42"`.
|
||||
### 159. Name the quoted spelling where a template that is a bare JSON number, boolean or null is refused, padded or not: `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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user