diff --git a/cmd/fejkdata/main.go b/cmd/fejkdata/main.go index 6ad55db..6f3db37 100644 --- a/cmd/fejkdata/main.go +++ b/cmd/fejkdata/main.go @@ -459,6 +459,9 @@ func parseInput(raw string) (input, error) { if trimmed == "" { return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint) } + if trimmed == "null" { + return input{}, fmt.Errorf("stdin holds only null, as jq prints for a missing key: %s", pipeHint) + } text := raw if json.Valid([]byte(trimmed)) { text = trimmed @@ -511,7 +514,7 @@ func hasEmptySegment(path string) bool { switch { case seg != "": stepUp = false - case stepUp && !last, !stepUp && last: + case stepUp != last: return true default: stepUp = !stepUp diff --git a/docs/decisions.md b/docs/decisions.md index 74989da..135077e 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -102,7 +102,7 @@ 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` and `withoutSelectors` in `cmd/fejkdata`, pinned to the +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. ## 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 @@ -119,8 +119,8 @@ until item 129: it reads a path, and only the data is absent. Padding is where the two readings disagree: a format string renders it, JSON drops it. A template that parses as JSON is its JSON, so its padding is dropped, as `encoding/json` -drops it. Goal 5.7 refuses only a spelling that suggests a mistake, and padding around -JSON suggests none. +drops it. Goal 5.7: only an obvious mistake fails to load, and padding around JSON is +none. ## A JSON string stays a template even where its bare text renders alike @@ -640,8 +640,8 @@ 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'`, and a bare `2006-01-02` reads like a third date, so a bare layout is -refused naming the 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 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 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. diff --git a/node.go b/node.go index 4d2e277..23f84e2 100644 --- a/node.go +++ b/node.go @@ -118,12 +118,13 @@ const ( // compileAt compiles a node drawn against no other: no choice's item, or a one-item choice's. // A weight there does nothing, bar the default 1 written out. func compileAt(v any, pos position) (node, error) { - w, err := weightOf(v) - if err != nil { - return nil, err + if o, isObject := v.(map[string]any); isObject { + if w, isNumber := o["weight"].(float64); isNumber && w != 1 { + return nil, fmt.Errorf(`weight %v skews a draw between items, and nothing here is drawn against this one; drop "weight"`, w) + } } - if w != 1 { - return nil, fmt.Errorf(`weight %v skews a draw between items, and nothing here is drawn against this one; drop "weight"`, w) + if _, err := weightOf(v); err != nil { + return nil, err } return compileItem(v, pos) } diff --git a/todo.md b/todo.md index 2373069..1cbef8c 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ `Bar = 9` -`Next ID = 161` +`Next ID = 162` | Goal | W | |---|---| @@ -123,6 +123,7 @@ | 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 | | 27 | 0.2.0 | | **Merge `en_US.ip` and `sv_SE.ip`, today byte-identical, into one `misc.ip`.** | 3 | 2 | 4 | 6 | 8 | 12.3 | | 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 | +| 161 | 0.2.0 | | **Refuse an identifier that starts or ends with whitespace, so `{ x as p}` loads as `{x as p}` or every field can be bound to a name.** | 2 | 2 | 2 | 6 | 5.6, 4.1 | 11.7 | | 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 | | 108 | 0.2.0 | defect | **Name item 26 in the decision "No builtin reads the clock, so a date is bounded by days, never by an age", as the item that ends its conflict with goal 4.** | 1 | 1 | 1 | 4 | 3.4 | 8.0 | | 38 | 0.3.0 | defect | **Compare `calcParser.binary`'s operator as a rune.** | 1 | 1 | 3 | 6 | 6 | 13.8 | @@ -465,6 +466,10 @@ The decision "With only the shipped set, a category loads on the first call reac The engine returns column values and the root builds `Record`. Revises the decision "The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/`", and, if the vocabulary moves, "The vocabulary sits below `doc.go`'s package clause, not in the package doc". +### 161. Refuse an identifier that starts or ends with whitespace, so `{ x as p}` loads as `{x as p}` or every field can be bound to a name. + +`grammar/identifier.go` allows a field named ` x`, and `{ x}` reads it, but `checkBind` in `format.go` refuses every padded binding, so no spelling binds that field to a name (goal 4.1), and the refusal names one meaning only (goal 5.6). The goals audit of the narrowed goal 5.7 found it. + ### 112. Check a calc's field operands where its template resolves, beside its name operands, once item 36 reports every mistake at once. Needs item 36. `checkCalcFields` runs at compile and `checkCalcNames` in `resolveTemplates`, two passes over one rule. Moving the field check there before item 36 would change which of two mistakes in two category files a load reports, since every category compiles before any resolves.