Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -14,9 +14,98 @@
|
||||
lowers the last score.
|
||||
- Hard tabs. No comment by default; delete a restatement, a rationale, history, or a file preamble.
|
||||
- One spelling per result: reject the other at `New`, and let the error name the spelling to use.
|
||||
- A standing choice a reader would relitigate goes under Decisions in the README, not in a comment.
|
||||
- Prose naming a source states what that source states: read the register's own field
|
||||
before paraphrasing it, and name the register you read before claiming none
|
||||
publishes a spelling.
|
||||
- A README example is a `json` block that loads and renders as a category; `readme_test.go` runs every one.
|
||||
- Cyclomatic complexity is gated at 14: the table-shaped dispatches (`linkParent`, `compileTemplate`, `renderEdges`) sit at it and stay whole; a function that would pass it is decomposed.
|
||||
|
||||
# Decisions
|
||||
|
||||
In [docs/decisions.md](docs/decisions.md):
|
||||
|
||||
- GitHub is canonical, and the module path names it
|
||||
- The vocabulary sits below `doc.go`'s package clause, not in the package doc
|
||||
- Options and fields share one namespace
|
||||
- `{a|b}` stays beside nested choices
|
||||
- Flags follow getopt_long
|
||||
- An argument is a template by its shape, not by a flag
|
||||
- An inline template skips the cycle fence
|
||||
- An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
|
||||
- A padded JSON argument is rejected, not trimmed
|
||||
- `FakeTemplate` and `NewTemplate` both stay
|
||||
- The shipped data is embedded, not discovered
|
||||
- A bare reference draws each time; a reference path is held
|
||||
- Reference sigils follow the filesystem
|
||||
- A change to what exists is a major; a minor only adds
|
||||
- Seeded output is promised within one version
|
||||
- An error is a contract by what it names, not its bytes
|
||||
- Raising the lowest supported Go is a major
|
||||
- The format check runs on the latest Go only
|
||||
- The changelog heading is the one spelling of a release; CI cuts the tag
|
||||
- A `--data-path` override rebinds every reference to the category it replaces
|
||||
- The repeat cap bounds renders, not bytes
|
||||
- 64-bit targets only
|
||||
- A constant zero divisor is a load error; in a string column a divisor that is not constant prints `Inf`
|
||||
- In data, a default written out and a constant spelled as a sample are load errors
|
||||
- Samples say what they emit, transforms what they do
|
||||
- A record is a template seen as columns; a Go struct is the one second schema
|
||||
- A struct's records follow Go's field access, and compile on first use
|
||||
- A render shares one reference draw per category, per group
|
||||
- A draw group name is local to its category
|
||||
- The expansion hold and the render's draws are two fences
|
||||
- A record makes its draw maps up front, a `Fake` on its first read
|
||||
- A category never references itself, and a record's fences run at load
|
||||
- A record's column set is fixed before the first draw
|
||||
- Null is a `null` item, not a rate
|
||||
- A typed column holds one value, not composed text
|
||||
- A column of one reference alone is the column it reads
|
||||
- A typed column's calc is refused unless proven
|
||||
- `Column` carries text, not a Go value
|
||||
- `hold` names what a draw is kept in, `draw` the draw itself
|
||||
- A function may not spell a method; two types may
|
||||
- The package stays flat
|
||||
- The performance gate asserts allocations, not wall-clock time
|
||||
- Rows live in a TSV, the shape in JSON
|
||||
- A selector is bracketed, and a dot inside it is literal
|
||||
- `parent` names the link column and the table alike
|
||||
- After a row, a path names a column or a linked table
|
||||
- A table read into is pinned; a table read whole draws afresh
|
||||
- Every reference path into one family selects the same rows, per render and group
|
||||
- A parent row with no child row is a load error
|
||||
- The choice-of-rows fence guards a data file's root, and requires string fields
|
||||
- A table is a record of string columns
|
||||
- The key index is built at load, the rest on first draw
|
||||
- Two categories may name one TSV
|
||||
- The rows of a table are alternatives
|
||||
- A table never reaches its own family, by any route
|
||||
- Tables carrying token cells stay small
|
||||
- A path is walked once without drawing before it is walked for real
|
||||
- A country's postal codes and streets are siblings under its locality
|
||||
- A locale's `address` reads its country's `geo` tree, so the shipped set loads whole
|
||||
- The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more
|
||||
- A locale's `address` restates its country record's format
|
||||
- A postort's kommun comes from its name, its tätort or its codes, never from distance
|
||||
- A highway designation is not a street, and a US postal code belongs to the place holding most of its land inside places
|
||||
- `--list` stays a plain list of paths
|
||||
- A layout is always quoted
|
||||
- A title is a table under `sex`
|
||||
- A table owns the spelling of a selector on it
|
||||
- No builtin reads the clock, so a date is bounded by days, never by an age
|
||||
- A name column without a key resolves inside its parent
|
||||
- `misc` is what every locale shares
|
||||
- A register's canonical spelling loses to the one its domain writes
|
||||
- `misc.territory` is the spine, and a `misc` table naming a territory links to it
|
||||
- A table whose register publishes no frequency draws evenly
|
||||
- `misc.timezone` weighs a zone by the people living in it
|
||||
- `misc.port` selects by number, and carries no `name`
|
||||
- `misc.car` is one flat table, not a make linked to its models
|
||||
- `misc.territory` names its sovereign in a column, and there is no `misc.country` table
|
||||
- `misc.loglevel` is a table keyed by the code, rendering POSIX's keyword
|
||||
- `misc.tld` keys carry the leading dot, where other tables key on a bare code
|
||||
- `misc.tld` is a table of its own, and `misc.territory.tld` stays a column
|
||||
- `misc.territory` carries a currency code, it does not link to `misc.currency`
|
||||
- An extension may name two media types
|
||||
- The Swedish ids draw Skatteverket's test series
|
||||
- The US given names come from a mirror of the SSA file
|
||||
- `List` advertises direct descents only
|
||||
|
||||
@@ -35,7 +35,8 @@ spot. Its tokens reach the data by reference from the root —
|
||||
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike
|
||||
available. A path never contains a brace or a quote, and a bracket only as a selector
|
||||
after a name, so the two spellings cannot collide; which spellings an inline template
|
||||
rejects, and what each names instead, is under [Decisions](#decisions).
|
||||
rejects, and what each names instead, is under
|
||||
[Decisions](docs/decisions.md#an-argument-is-a-template-by-its-shape-not-by-a-flag).
|
||||
|
||||
| Flag | |
|
||||
|------|--|
|
||||
@@ -51,7 +52,7 @@ rejects, and what each names instead, is under [Decisions](#decisions).
|
||||
|
||||
`--name value` and `--name=value` both work, a short flag's value attaches or
|
||||
follows (`-n3`, `-n 3`) and short flags bundle (`-hn 3`) — see
|
||||
[Decisions](#decisions); 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, unknown path), `2` misuse — a bad flag, an argument that names neither a
|
||||
template nor a path, or an inline template that does not compile. From a checkout:
|
||||
`go run ./cmd/fejkdata …`.
|
||||
@@ -144,7 +145,8 @@ category never references itself — `{/users.first}` or a bare `{/users}` insid
|
||||
describes a draw other than the fields beside it — so read a sibling as a path, and put
|
||||
a value two fields share in its own category and reference that. A field hold, transform or
|
||||
operand ties fields together within one column as always (see
|
||||
[Correlated fields](#correlated-fields) and [Decisions](#decisions)).
|
||||
[Correlated fields](#correlated-fields) and
|
||||
[Decisions](docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load)).
|
||||
|
||||
## Data
|
||||
|
||||
@@ -547,12 +549,12 @@ it, and a cell or format of a table that references a table of its own family, t
|
||||
any template, a `repeat` or a `drawGroup` included, since a row rendered whole would
|
||||
draw the family apart from itself: read the family from a template beside it, or add
|
||||
the value as a column. A cell's reads are the render's too; which pairs are weighed
|
||||
against each other is under [Decisions](#decisions).
|
||||
against each other is under [Decisions](docs/decisions.md#the-rows-of-a-table-are-alternatives).
|
||||
|
||||
### Options and fields
|
||||
|
||||
`format`, `weight`, `repeat`, `separator`, `datatype` and `drawGroup` are the only options;
|
||||
**any other key is a field** (see [Decisions](#decisions)), and `rows` makes a category
|
||||
**any other key is a field** (see [Decisions](docs/decisions.md#options-and-fields-share-one-namespace)), and `rows` makes a category
|
||||
a [table](#table), so no template carries a field of that name. An object that does nothing a
|
||||
string can't — only a `format` — is rejected naming the string, as is a one-item
|
||||
choice naming its item.
|
||||
@@ -855,475 +857,6 @@ the Development section below, and who ships a register the four above then draw
|
||||
nothing, each on its own path. A fixture that looks nothing like production
|
||||
tests nothing; the caller who needs a value that can touch nothing asks for it
|
||||
by name.
|
||||
## Decisions
|
||||
|
||||
- **GitHub is canonical, and the module path names it.** Goal 1 wants the usage the
|
||||
tool earns and goal 2 expects extenders who did not write it; both need a stranger
|
||||
to file an issue and open a pull request. Gitea has no anonymous issue, and no
|
||||
cross-host pull request at all, so a contributor would need an account and a fork on
|
||||
a personal instance. Valid while the project wants contribution from outside: the
|
||||
workflow decided nothing, being portable already — `github.api_url` and
|
||||
`secrets.GITHUB_TOKEN` resolve on either host, so only the names moved.
|
||||
- **The vocabulary sits below `doc.go`'s package clause, not in the package doc.**
|
||||
Goal 1 is what a developer choosing a library reads first, and the package doc is
|
||||
that page: the glossary would fill two thirds of it with the render path's units,
|
||||
mostly unexported, and Go doc comments have no code markup, so every name would
|
||||
render its backticks literally. Below the clause it reaches goal 2's reader — who
|
||||
opens the file — and nothing else. Valid while the glossary's subject is the render
|
||||
path; one written to teach the exported API belongs above the clause.
|
||||
- **Options and fields share one namespace.** `format`, `weight`, `repeat`,
|
||||
`separator`, `datatype` and `drawGroup` are reserved; every other key is a field. Nesting fields under a
|
||||
key, or prefixing options, would tax every template to guard against a
|
||||
misspelt option.
|
||||
- **`{a|b}` stays beside nested choices.** `[[…], […]]` picks the same way, but
|
||||
its arms are anonymous; `{female|male}` keeps `person.female` addressable.
|
||||
- **Flags follow getopt_long.** `--name value` and `--name=value` both work; a
|
||||
short flag's value attaches or follows (`-s42`, `-s 42`) and short flags bundle
|
||||
(`-hn 3`), as every shell user expects. A single-dash long flag is rejected
|
||||
naming the double-dash spelling, and `-s=42` is rejected naming both short
|
||||
spellings: `=` belongs to the long form, and reading `=42` as the value would
|
||||
make `-d=./x` a directory named `=./x`.
|
||||
- **An argument is a template by its shape, not by a flag.** 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 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 says so. No `--template` flag is needed. Reserving the characters
|
||||
whole — though only a leading one could collide — keeps one simple name rule
|
||||
instead of a leading-position special case. The JSON string is what makes the
|
||||
library's own advice reachable: the error for an object holding only a format
|
||||
names `"…"`, and that spelling has to work where it is printed. An argument or struct
|
||||
tag of one reference alone, `{/users}`, is refused naming the path `users`: both
|
||||
render the same text, and only the path names a record. A folder-relative `{.name}`
|
||||
or `{..name}` is refused naming `{/name}`, since an inline template sits in no
|
||||
folder. `IsTemplate` exports the
|
||||
rule, so the CLI, struct tags and any other caller read one.
|
||||
- **An inline template skips the cycle fence.** `New` proves the loaded tree
|
||||
acyclic, an inline node is a finite tree of its own, and nothing in the tree can
|
||||
reference it, so no render of it reaches itself. Every other fence runs over both,
|
||||
from one `checkScope`, except that struct tags leave column agreement to their Go
|
||||
types.
|
||||
- **An inline template that does not compile is misuse (exit 2), including a
|
||||
reference that resolves to nothing** — the whole argument is the spelling under
|
||||
test, and `NewTemplate` compiles, links and validates as one step. An unknown
|
||||
*path* stays a runtime error (exit 1): there the argument is well-formed and only
|
||||
the data is absent.
|
||||
- **A padded JSON argument is rejected, not trimmed.** 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.
|
||||
- **`FakeTemplate` and `NewTemplate` both stay.** They reach the same value but not
|
||||
at the same cost: `NewTemplate` pays the compile and validation once and renders
|
||||
many times, `FakeTemplate` is the one-shot call, and `--repeat` is exactly the
|
||||
case that needs the first. The pair is `regexp.MustCompile` and `regexp.Match`,
|
||||
not two spellings of one result.
|
||||
- **The shipped data is embedded, not discovered.** A directory a machine happens
|
||||
to have would make `--seed 42` machine-dependent. Data still lives in `data/`
|
||||
as JSON; `--data-path` layers over it.
|
||||
- **A bare reference draws each time; a reference path is held.** `{/p} {/p}`
|
||||
is two draws, as `{word} {word}` is, while every `{/p.first}` in one render reads
|
||||
one draw, and a bare `{/p}` beside them is a load error: a bare token
|
||||
is by contract an independent draw, a path pins its level, and a fresh draw of a
|
||||
pinned level could show another row. A builtin's operand holds what it reads for
|
||||
its expansion, references included, so `{uppercase(/p)} {/p}` is one draw — the
|
||||
rule every operand follows.
|
||||
- **Reference sigils follow the filesystem.** `/` is the root, `.` this file's
|
||||
folder, `..` the folder above — what those spellings already mean to anyone who
|
||||
has typed a path. A locale's files reach each other without naming the locale,
|
||||
so a folder renames and copies without editing its references.
|
||||
- **A change to what exists is a major; a minor only adds.** Data files, the CLI
|
||||
and the Go API are the public API, and a consumer must be able to take a minor
|
||||
without an edit — so an added column is a major, since it changes the CSV header
|
||||
and the `INSERT` column list, as is a removed value, which changes what a fixture
|
||||
holds, and a new option, which reserves a field name. One spelling per result
|
||||
grows by tightening, so every fence invalidates some file. Each such release
|
||||
names the rejected spelling and its replacement in the changelog and in the load
|
||||
error, and that is the whole migration: a fence rejects one spelling with one
|
||||
replacement, so the fix is local to each site. A fence that would need a
|
||||
non-local rewrite ships a converter with its release instead. Before `v1.0.0` a
|
||||
minor carries what a major would.
|
||||
- **Seeded output is promised within one version.** Any edit to a category shifts
|
||||
its stream and everything drawn after it, so a promise across versions would
|
||||
freeze every shipped list; a fixture is re-pinned on a bump, as this repo's own are.
|
||||
- **An error is a contract by what it names, not its bytes.** A script branches on
|
||||
the exit code and reads the named path or spelling, so those hold; wording improves
|
||||
in a minor.
|
||||
- **Raising the lowest supported Go is a major.** A consumer building on it breaks,
|
||||
which is the one test every rule above applies; Go's convention of a minor is not
|
||||
followed.
|
||||
- **The format check runs on the latest Go only.** `gofmt`'s output is the
|
||||
toolchain's, not the code's: 1.27 stopped padding a map literal's values out to a
|
||||
lone long key, so no source satisfies both it and 1.22's. A consumer on the lowest
|
||||
supported Go depends on the code compiling and its tests passing there, which is
|
||||
the `portable` stage, and never runs `gofmt` over this source, so the latest
|
||||
toolchain alone defines the one canonical form goal 2 asks a reader to meet. Valid
|
||||
while the lowest supported Go is not the latest.
|
||||
- **The changelog heading is the one spelling of a release; CI cuts the tag.** A
|
||||
tag pushed by hand is served by `go get` at once, so a tag whose commit lacks its
|
||||
heading is burnt, not fixed. The heading on a gate-passed `main` commit is the
|
||||
trigger instead: the tag can land only there, and the GitHub release the same job
|
||||
publishes keeps one text as its body and is where prebuilt binaries will attach.
|
||||
- **A `--data-path` override rebinds every reference to the category it
|
||||
replaces.** References bind against the merged tree, so once shipped data uses
|
||||
`{.person}`, a consumer's `sv_SE/person.json` is what every shipped reference
|
||||
into `person` reads, and `New` fails on shipped data the consumer never wrote
|
||||
when that file lacks a field those references read. Accepted: overriding is the
|
||||
point of layering, the error names the reference and the field, and the fix is
|
||||
the consumer's file carrying the fields the shipped tree reads.
|
||||
- **The repeat cap bounds renders, not bytes.** A repeat, alone or nested, may
|
||||
ask for at most 1 048 576 renders; how large each render is stays what the data
|
||||
asked for, so `{hex(1048576)}` repeated to the cap is a terabyte, loaded without
|
||||
complaint. A byte estimate would need every builtin to declare a width to fence
|
||||
a shape no data comes near, and the harm lands on the author who wrote it.
|
||||
- **64-bit targets only.** The gate builds amd64, and the buffer sizing a render
|
||||
pre-computes (renders × bytes) assumes a 64-bit int; on a 32-bit target it could
|
||||
overflow and panic.
|
||||
- **A constant zero divisor is a load error; in a string column a divisor that is not
|
||||
constant prints `Inf`.** `1/0` and a fixed `"0"` field are decidable, so they join
|
||||
the never-numeric operand as a load error; the fold stops where an operand varies,
|
||||
so `a/(b*c)` with `b` fixed at `0` and `c` varying loads and prints `Inf` every
|
||||
draw — catching it needs zero-absorbing algebra for a shape nobody writes. A
|
||||
[typed column](#datatype) bounds its operands instead and refuses a divisor it
|
||||
cannot keep from zero.
|
||||
- **In data, a default written out and a constant spelled as a sample are load
|
||||
errors.** `weight: 1`, `repeat: 1`, `separator: ""`, `datatype: "string"`,
|
||||
`int(5,5)`, `float(1,1,2)`,
|
||||
`+5` and `05` each spell what a shorter form already spells, so each is rejected
|
||||
naming that form. The CLI's numbers follow the shell instead: `--seed 007` and
|
||||
`--repeat +3` are 7 and 3, as every command line reads them.
|
||||
- **Samples say what they emit, transforms what they do.** `{upper(2)}` is two
|
||||
letters, `{uppercase(x)}` is `x` upper-cased; one name for both would turn on
|
||||
whether the argument looks like a number.
|
||||
- **A record is a template seen as columns; a Go struct is the one second schema.** A
|
||||
template's `format` composes its fields into one string; `FakeRecord` and
|
||||
`--format` project the same fields as columns. Two views of one dataset, so a
|
||||
record author writes the same JSON they already know, and a column is the same
|
||||
field `Fake` renders by dotted path. The `format` is inert to a record — a
|
||||
record-only template writes `"format": ""` — but it is compiled and fenced, so
|
||||
a template that loads renders as whichever shape is asked for. `FakeStruct` takes
|
||||
its columns from a struct instead, because a Go caller has already written that
|
||||
schema: the fields name the columns and their types are the datatypes, so a tag
|
||||
says only what to draw, and a `datatype` in it would be a second spelling of the
|
||||
type. The Go type is a struct column's one datatype, so its items need not agree on
|
||||
one among themselves; each must only hold that type.
|
||||
- **A struct's records follow Go's field access, and compile on first use.** The
|
||||
fields an embedded struct promotes are the struct's own — `e.First`, as
|
||||
`encoding/json` and SQL mappers read them — so they are columns of its record and
|
||||
share its draws; a tagged field that another field hides is refused, not dropped. A
|
||||
named struct field is another entity and a record of its own. `fake:"-"` leaves a
|
||||
struct field, embedded or named, unfilled, so no name may be `-`; a pointer back to
|
||||
a struct already being filled is left alone, since filling it would never end. `New` cannot
|
||||
see a caller's types, so the first `FakeStruct` for a type compiles its tags and the
|
||||
answer, error included, is kept per type: a test's first call is its load, and no
|
||||
`NewStruct` handle is needed, as the cache already compiles once.
|
||||
- **A render shares one reference draw per category, per group.** Every reference
|
||||
path into a category in one `Fake`, or one record, reads one draw of it, so a
|
||||
value's facts agree across its fields, nested templates and columns alike —
|
||||
`{/currency.code}` in one field and `{/currency.symbol}` in another name one
|
||||
currency, whichever view renders them. A `repeat` iteration is a render of its
|
||||
own, since repeating asks for another entity, and a [draw group](#draw-group) names further
|
||||
entities within one render, so a payer and a payee are two groups over one
|
||||
`person` rather than two copies of it. Only references share: a sibling field is
|
||||
local to its own expansion, so a `first` column does not silently bind to a
|
||||
`first` in the column next to it.
|
||||
- **A draw group name is local to its category.** A category's groups are its own
|
||||
entities, so a caller naming a group the same way never joins them by accident,
|
||||
and renaming a group inside one file changes no render elsewhere. The unnamed
|
||||
group still spans categories, since facts that belong together across categories
|
||||
must agree.
|
||||
- **The expansion hold and the render's draws are two fences.** One proves a sibling
|
||||
path or an operand is reached only by its readers within an expansion, the other
|
||||
does the same for reference paths across a render and its draw groups. They pin
|
||||
different things — an operand pins the value its own render produced and stops at a
|
||||
reference, a path pins every level it passes through — so one walk would carry both
|
||||
rules and both scopes anyway, and tell them apart at every step.
|
||||
- **A record makes its draw maps up front, a `Fake` on its first read.** A record's
|
||||
columns always read through the render's draws, so making the maps where the set is
|
||||
declared keeps them on that frame's stack. A `Fake` often reads no reference path at
|
||||
all, and making them anyway cost about a fifth of the cheapest render, so it makes
|
||||
them on the first read instead, at two heap allocations for a render that does share
|
||||
a draw. The allocation gate over a repeat of a reference path and over a named draw
|
||||
group prices that, and pins the two measures that keep a hold set off the heap.
|
||||
- **A category never references itself, and a record's fences run at load.** A category
|
||||
is one unit: a reference back into it — `{/users.first}` inside `users` — describes a
|
||||
draw other than the fields beside it, so `New` refuses it and the sibling path stays
|
||||
the one spelling for a field of one's own. A value two fields share goes in its own
|
||||
category, which both reference. That settled, a record's column fences run at `New`
|
||||
too, so a category that loads renders as whichever shape is asked for, and a reference
|
||||
reaching back into a category through another one is refused there as the overlap it
|
||||
is. Which reads those fences weigh differs on purpose: a record-only template's inert
|
||||
format renders nothing, so a `drawGroup` on it can never matter and is refused, while
|
||||
one on a rendering format can matter to a caller that bare-references it.
|
||||
- **A record's column set is fixed before the first draw.** 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 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 to agree.
|
||||
- **Null is a `null` item, not a rate.** A null is one more outcome of a column's
|
||||
draw, so a choice's weights skew it like any other; a null-rate option would be a
|
||||
second way to state odds.
|
||||
- **A typed column holds one value, not composed text.** Its bounds come from a
|
||||
literal or a call's arguments, so a load error names a real value, a range check is
|
||||
one comparison, and `1{digits(2)}` is a second spelling of `{int(100,199)}`.
|
||||
- **A column of one reference alone is the column it reads.** `{/src.score}` renders
|
||||
exactly what `src.score` draws, so it takes that column's datatype and null rather
|
||||
than restating them, and a `datatype` restating the one it takes is a second
|
||||
spelling. Any other `datatype` still types the values — the one way to type a column
|
||||
someone else wrote.
|
||||
- **A typed column's calc is refused unless proven.** Operand bounds must keep each
|
||||
divisor from zero and the result finite; what they cannot show is refused rather
|
||||
than trusted, since a bare `NaN` breaks the JSON and SQL it lands in.
|
||||
- **`Column` carries text, not a Go value.** `Value` is the rendered string beside
|
||||
`DataType` and `Null`, which each serializer writes as the load check proved it; a
|
||||
`Value any` would hand every caller a type switch.
|
||||
- **`hold` names what a draw is kept in, `draw` the draw itself.** Goal 2 wants a name
|
||||
to reach one unit, so a unit takes the stem of what it is, and a file the stem of the
|
||||
units it holds.
|
||||
- **A function may not spell a method; two types may.** A call writes a method with its
|
||||
receiver and a function bare, so one name on both greps as one unit, which is the
|
||||
reaching cost goal 2 counts, while the receiver before a shared method name says
|
||||
which type answers. `TestNoFunctionSpellsAMethod` holds it. A type is outside the
|
||||
rule: `renderScope.hold` answers with a `*hold`, so the two spell one unit, which no
|
||||
test can judge.
|
||||
- **The package stays flat.** Go ties a package to one directory, so folders would
|
||||
split the API into packages.
|
||||
- **The performance gate asserts allocations, not wall-clock time.** `AllocsPerRun`
|
||||
is deterministic across machines, so a ±10% ceiling does not flake under CI load,
|
||||
while time varies with the machine and its neighbours. A rendering slowdown
|
||||
almost always costs an allocation too (a lost pre-size, a per-item map, an extra
|
||||
copy). The benchmark suite (see Development) reports time for a human, not as a
|
||||
pass/fail gate.
|
||||
- **Rows live in a TSV, the shape in JSON.** `New` allocates once per node, so a
|
||||
register of thirty thousand rows written as JSON objects would cost it a second;
|
||||
a TSV is one allocation whose cells are substrings, and the JSON says only how a
|
||||
row is composed. The TSV sits beside its category file, named by `rows`, so a
|
||||
data directory stays a directory of categories, and one nothing names is a
|
||||
load error rather than a file silently ignored.
|
||||
- **A selector is bracketed, and a dot inside it is literal.** `municipality[0180]`
|
||||
reads as selection to anyone who has indexed an array, and `[St. Louis]` keeps a
|
||||
name whole where a colon or a dot-separated spelling could not; zsh needs the
|
||||
brackets quoted, which the README's examples show. A name that spells another
|
||||
row's key is refused at load rather than shadowed: a key names one row by
|
||||
contract, so the name could never select its own, and the check is one lookup per
|
||||
row against the key index that already exists.
|
||||
- **`parent` names the link column and the table alike.** One word says both, so a
|
||||
child table sits in its parent's folder and links on a column of the parent's
|
||||
name; the geo plan wants exactly that, and a table needing another folder or
|
||||
another column name would be asking for a second spelling.
|
||||
- **After a row, a path names a column or a linked table.** `locality[Lund].address`,
|
||||
a template beside the family under a selected row, is refused today; admitting it
|
||||
later is additive, since a refused spelling gains a meaning and no accepted one
|
||||
changes, so the door stays open for the address records the plan describes.
|
||||
- **A table read into is pinned; a table read whole draws afresh.** A path into a
|
||||
table pins its row for the render and group, as a
|
||||
reference path pins its level, and a bare `{/city}` draws a row each time and pins no
|
||||
row of its family, as a bare reference does; so a bare table beside a path into its
|
||||
family is refused like a bare reference beside a path into it. A bare table reference
|
||||
still counts as a read for a `drawGroup`, since the group is what draws it apart from
|
||||
the family's pins.
|
||||
- **Every reference path into one family selects the same rows, per render and
|
||||
group.** A selector pins rows for the render, and a read that draws freely before it
|
||||
could pin a row the selector contradicts, so accepting both would make the result
|
||||
depend on which token rendered first. Requiring one selection per family per group
|
||||
is checkable at load with no data lookup beyond the selectors themselves, and the
|
||||
error names the rewrite. Two selectors naming one row by key and by name compare
|
||||
equal, since the load check resolves them.
|
||||
- **A parent row with no child row is a load error.** A descendant is drawn inside the
|
||||
nearest pinned ancestor, so every ancestor row must lead to a row at every level
|
||||
below it, or a render could find nothing to draw. The import script drops or fills
|
||||
such rows; the alternative, falling back to a free draw, would break the consistency
|
||||
the link exists for without saying so.
|
||||
- **The choice-of-rows fence guards a data file's root, and requires string fields.**
|
||||
A table is a category with a TSV beside its file, so only a root choice has the
|
||||
spelling the fence names; a nested choice of same-shaped templates and an inline
|
||||
one keep loading. Fields must all be strings because a cell is a string node: a
|
||||
choice whose items carry a nested choice is not one table but two linked ones.
|
||||
- **A table is a record of string columns.** Its columns are the CSV header and the
|
||||
`INSERT` column list, fixed by the TSV header, so a table is a record by
|
||||
construction; every column is a string until a typed column option earns its place.
|
||||
- **The key index is built at load, the rest on first draw.** A link is proved
|
||||
against the parent's keys and a key's uniqueness is a data mistake, so both are
|
||||
load-time; the name index and the per-parent child lists serve only a draw or a
|
||||
selection, so they wait for the first one, keeping `New` linear in the bytes read.
|
||||
- **Two categories may name one TSV.** Each is a view of the file with its own
|
||||
format and options, at the cost of holding the rows twice, which is what a
|
||||
category over a register with two natural formats asks for; a TSV nothing names
|
||||
stays a load error, since that one is a file forgotten rather than shared.
|
||||
- **The rows of a table are alternatives.** Only one row renders per read, so within one
|
||||
read a cell in one row and a cell in another never meet, and each may select its own
|
||||
row of another table; the cells of one row, and whatever they reach, do meet, and so
|
||||
does the format beside them. The rule the fence applies is that two reads meet unless
|
||||
they sit in rows that never render together: rows falling under different rows of a
|
||||
table a path pinned, two rows of that table itself included. A choice's items get no
|
||||
such treatment yet: two items selecting different rows are still refused.
|
||||
- **A table never reaches its own family, by any route.** A `repeat` iteration and a
|
||||
`drawGroup` each draw apart on purpose, but a row that lists three localities from
|
||||
other regions is the output the family exists to prevent, so the own-family fence
|
||||
walks through both rather than stopping where the draw fences do.
|
||||
- **Tables carrying token cells stay small.** The family fence compares the reads of
|
||||
every pair of rows that can render together, so a table whose every row's cell
|
||||
selects a row of another table loads in time quadratic in its rows: about a
|
||||
second at four thousand rows. No shipped table carries such cells, and a register
|
||||
is a column set rather than a set of references, so the fence is left as it is
|
||||
until a real data set needs the indexed form.
|
||||
- **A path is walked once without drawing before it is walked for real.** A path
|
||||
that fails below its first level then moves no seeded stream, at the cost of one
|
||||
draw-free walk per call, which allocates nothing.
|
||||
- **A country's postal codes and streets are siblings under its locality.** No open
|
||||
source pairs a Swedish street with its postnummer, and pairing the US through its
|
||||
ZIPs would shape the two trees differently, so both draw inside the pinned
|
||||
locality and an address agrees at that level. A street's own code is the exact
|
||||
pairing to add when a source carries it.
|
||||
- **A locale's `address` reads its country's `geo` tree, so the shipped set loads
|
||||
whole.** `data/sv_SE` alone no longer loads: a test loads `data` and prefixes
|
||||
the locale, and `--no-shipped-data -d` takes the whole `data` folder or a set of
|
||||
one's own.
|
||||
- **The default embed holds every Swedish postort the import can place and give a
|
||||
street-delivery code and a street, and the US places of 25,000 or more.** Sweden
|
||||
fits whole in 700 KB; every US place of 10,000 would pass a
|
||||
megabyte and fetch 1,200 counties of TIGER files, so the threshold sits where the
|
||||
two countries match in size, and `--min-population` and
|
||||
`--streets-per-locality` on the import scripts build a fuller set. The two trees
|
||||
add about 20 ms to `New`, which loads the shipped set in about 45 ms.
|
||||
- **A locale's `address` restates its country record's format.** A record cannot
|
||||
read another whole and keep its columns, so `sv_SE.address` names the same four
|
||||
columns as `geo.SE.address`, each a reference into it, and the format appears
|
||||
twice; a column is spelled the same in both, `street-number`, so the two never
|
||||
disagree on a name.
|
||||
- **A postort's kommun comes from its name, its tätort or its codes, never from
|
||||
distance.** GeoNames leaves a fifth of Sweden's codes without a kommun and
|
||||
carries stale spellings; the nearest code across a border named the wrong kommun
|
||||
half the time it was tried, so a postort none of the three rules place is
|
||||
dropped, as is one not cased like a place name.
|
||||
- **A highway designation is not a street, and a US postal code belongs to the place
|
||||
holding most of its land inside places.** `I- 55 Bus` and `US Hwy 1` carry the
|
||||
most address ranges in many places and would head every address, so the import
|
||||
drops names spelled as a route. A ZCTA goes to the place its largest in-place part
|
||||
lies in, census-designated places left out since they never ship, and ships only
|
||||
when that place does, so a few dozen places whose every code lies mostly in a
|
||||
bigger neighbour ship no address; counting the land outside every place too would
|
||||
drop a quarter of the places, whose codes straddle unincorporated land, for a
|
||||
postal city the USPS mostly names the same way.
|
||||
- **`--list` stays a plain list of paths.** It is what a script reads, so every line
|
||||
has to be a path that `Fake` takes; a marker for the tables a `[selector]` follows,
|
||||
or a legend above them, would make the output something to parse before use.
|
||||
`--help` names the selector spelling instead, and the Table section teaches it.
|
||||
- **A layout is always quoted.** A layout may carry the comma that separates
|
||||
arguments, `'January 2, 2006'`, and one spelling for every layout beats a rule
|
||||
about which ones need the quotes, so the bare spelling is refused naming the
|
||||
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.
|
||||
- **A title is a table under `sex`.** A prefix drawn apart would put `Mr` on a record
|
||||
whose `sex` column says `female`, which is the disagreement the record exists to
|
||||
prevent; the tables this set already has are what a title needs, so `en_US.title`
|
||||
links to `sex` as `first-name` does. Swedish has no everyday sexed honorific, so
|
||||
`sv_SE.title` is a table as well but carries no `parent`. Its weight column is
|
||||
`share`, not the `count` a name table carries, because the values are a curated
|
||||
proportion rather than bearers anyone counted.
|
||||
- **A table owns the spelling of a selector on it.** A reference reaches a table by a
|
||||
path that carries no selector — `sv_SE.person.first` reads `first-name` through
|
||||
`sex` — so the walk that resolved a name cannot say where a reader would type one.
|
||||
The table's own location can, which is why it keeps its path, and why an ambiguity
|
||||
error names `sv_SE.sex[f].first-name[Kim]` rather than the table's own name.
|
||||
- **No builtin reads the clock, so a date is bounded by days, never by an age.**
|
||||
An `age(min,max)` would make a seeded fixture change with the day it runs on,
|
||||
which is what a seed exists to prevent; a birthdate for someone 20 to 60 is
|
||||
`date(1966-01-01,2006-12-31,…)`, re-pinned as any fixture is.
|
||||
- **A name column without a key resolves inside its parent.** A given name both
|
||||
sexes carry is a row under each, so `name` cannot be the key; the parent's row
|
||||
tells the two apart, `sex[f].first-name[Kim]`, the ambiguity error spells each
|
||||
row inside its parent, and a name repeating inside one parent row is refused at
|
||||
load, since nothing could then select it.
|
||||
- **`misc` is what every locale shares.** A category whose facts differ by country
|
||||
belongs in that country's locale, read from the register that country's own
|
||||
records use; `misc` takes only sources that are international. NHTSA vPIC and
|
||||
Mobility Sweden's registrations are national, so they are for `en_US.car` and
|
||||
`sv_SE.car`, and `misc.car` waits for an international source.
|
||||
- **A register's canonical spelling loses to the one its domain writes.** Where a
|
||||
source offers several spellings of one fact, the shipped one is what records in
|
||||
that domain carry. `misc.timezone` reads `zone.tab` and not the `zone1970.tab`
|
||||
that supersedes it, because the latter keeps one zone per set of countries that
|
||||
have agreed since 1970: it spells Sweden `Europe/Berlin`, and no Swedish system
|
||||
writes that. For the same reason `misc.language` takes the ISO 639-2 register's
|
||||
first synonym over CLDR, which says "Chinese, Mandarin" for `zh`.
|
||||
- **`misc.territory` is the spine, and a `misc` table naming a territory links to
|
||||
it.** Where every territory row has a child the column is a `parent`, and the
|
||||
import drops the child rows whose territory the set does not ship — 17 of
|
||||
`misc.timezone`'s, Antarctica's ten among them. Agreement across a record is
|
||||
worth more than the last rows of a table. Where no such link can hold the fact
|
||||
stays a column. Layer your own `misc.territory` over the shipped one and you
|
||||
must layer `misc.timezone` too, or the link fails at load naming the row.
|
||||
- **A table whose register publishes no frequency draws evenly.** `misc.httpmethod`,
|
||||
`misc.port`, `misc.httpstatus`, `misc.mimetype` and `misc.tld` weigh every row alike,
|
||||
so GET is a ninth of the methods drawn. Goal 10 keeps an authored fact out of a
|
||||
sourced table, and no register publishes how often a method, a port or a TLD is used,
|
||||
so a weight here would be invented. Where one exists it is read, as `misc.timezone`
|
||||
reads GeoNames populations and `sv_SE.first-name` SCB bearers.
|
||||
- **`misc.port` selects by number, and carries no `name`.** 29 of its services sit on
|
||||
more than one port, `http-alt` on three, so `misc.port[http-alt]` could name no one
|
||||
row. The number is what a port field holds anyway, and `misc.port[443].service`
|
||||
reads the other way.
|
||||
- **`misc.car` is one flat table, not a make linked to its models.** A row is a make
|
||||
and a model drawn together, so no render pairs a Volvo with a RAV4. Two linked
|
||||
tables would reach the same pairs and add a selector nothing asks for; a make alone
|
||||
is `misc.car.make`.
|
||||
- **`misc.territory` names its sovereign in a column, and there is no `misc.country`
|
||||
table.** ISO 3166-1 codes territories, so `territory` is the honest name, and
|
||||
`is_independent` in the register gives each one's state. A second table of the 195
|
||||
sovereigns would hold a `DK` row beside the territory `DK` row, both carrying
|
||||
Denmark's capital, currency, flag and TLD — two owners for one fact, drifting at
|
||||
the next import. Splitting the columns to avoid that is worse: put `capital` on
|
||||
the territory alone and a country row can no longer name Copenhagen. The column
|
||||
cannot be a `parent`, since a table never reaches its own family; a test proves
|
||||
every value names a row instead. A territory the register records no state for
|
||||
stands alone, which is `EH` alone, and naming one for it would be a claim
|
||||
fejkdata has no business making.
|
||||
- **`misc.loglevel` is a table keyed by the code, rendering POSIX's keyword.** A flat
|
||||
list of names carries neither the code a PRI encodes nor a selector reaching it, and
|
||||
goal 3 draws the two as one fact. The canonical spelling losing to the one its domain
|
||||
writes, above, settles the rest: a configuration writes `info`, so RFC 5424's
|
||||
`Informational` stays the `severity` column, and the code and the keyword hold the
|
||||
two selector slots.
|
||||
- **`misc.tld` keys carry the leading dot, where other tables key on a bare code.**
|
||||
The register spells a TLD `.se` and `misc.territory.tld` already ships it so, which a
|
||||
bare key would make two spellings of one fact; `{/misc.tld}` also composes onto a
|
||||
host with no separator. `misc.tld[se]` misses for it, which `todo.md` carries.
|
||||
- **`misc.tld` is a table of its own, and `misc.territory.tld` stays a column.** A
|
||||
`parent` demands a child for every parent row, so linking them would drop every root
|
||||
zone row naming no territory, which is most of them, and goal 12 holds a sourced
|
||||
table whole. The loader refuses the link outright anyway: `tld` is a column of
|
||||
`misc.territory`, and a table may not be named like a column of its ancestor.
|
||||
- **`misc.territory` carries a currency code, it does not link to `misc.currency`.**
|
||||
A `parent` demands a child for every parent row, and ISO 4217 registers codes no
|
||||
country's row can name: the funds codes (Mvdol, WIR Euro, US Dollar (Next day)),
|
||||
and VED beside VES, both Venezuela's, of which a country row names one. Linking
|
||||
would trade the register for the link, and `misc.territory.currency` already pairs a
|
||||
country with its currency in one draw.
|
||||
- **An extension may name two media types.** `.xml`, `.rtf`, `.sub`, `.mpp` and `.ac`
|
||||
each name two rows of `misc.mimetype`. Separating them would mean dropping a
|
||||
registered type, or naming one by an extension that is not its own — `.mpt` is
|
||||
Project's template, not its document. Selecting such a name is an error listing
|
||||
both keys; select by type instead.
|
||||
- **The Swedish ids draw Skatteverket's test series.** A Luhn-valid personnummer
|
||||
over a random birth number may be a living person's; 238 and 239 after any date
|
||||
are blocked from assignment, so the shipped `personnummer` and
|
||||
`samordningsnummer` use those. They sit in a `birth-number` table under `sex`
|
||||
rather than as a column of it: the render's shared draw of the family is what
|
||||
makes the number and the name agree on sex, and `sex` stays one shape across
|
||||
locales instead of collecting every sex-keyed id fact. A samordningsnummer's
|
||||
day, the birthday plus 60, is drawn from 61 to 88, valid in every month, rather
|
||||
than computed from the date drawn.
|
||||
- **The US given names come from a mirror of the SSA file.** ssa.gov refuses a
|
||||
client outside the US, so `names-us.py` reads a GitHub copy that ends at 2020,
|
||||
which a count over the births since 1930 barely feels; `--names` takes the
|
||||
official zip. The SSA's placeholder rows are top-1000 entries that name nobody, so
|
||||
the import drops them by name rather than by a rank a regeneration would move.
|
||||
- **`List` advertises direct descents only.** `region.municipality.locality` is
|
||||
listed, and `region.locality` resolves too but is not: the set of every descent
|
||||
through a chain of five tables is every subsequence of it, and the direct chain is
|
||||
the one a reader can predict from the tables' parents.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -1357,7 +890,7 @@ data.go the load path: fs.FS folders/files -> merged namespace tree, and
|
||||
cmd/fejkdata/ the fejkdata CLI
|
||||
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
|
||||
data-import/ the scripts that rebuild each sourced table (see DATA-LICENSES.md)
|
||||
docs/ the register research behind the shipped data, and the survey of other libraries
|
||||
docs/ the decision log, the register research behind the shipped data, and the survey of other libraries
|
||||
release-tooling/ the release CI publishes from the changelog heading
|
||||
testdata/ the pinned shipped shape (see Versioning)
|
||||
```
|
||||
@@ -1396,7 +929,7 @@ docker compose run --rm --user root --entrypoint chown test -R "$(id -u):$(id -g
|
||||
Every pull request runs `docker build .` against both the latest and the lowest
|
||||
supported Go, and must pass before it can be merged — unless it changes none of
|
||||
the files the build and its tests read, nor the workflow itself, in which case
|
||||
it's skipped (see [Decisions](#decisions)). That build is the whole gate but the
|
||||
it's skipped. That build is the whole gate but the
|
||||
changelog check, which CI runs against the PR base — vet, complexity, format check
|
||||
and tests — so run it locally before pushing. The lowest Go builds the `portable`
|
||||
stage, all of that bar the format check:
|
||||
|
||||
@@ -0,0 +1,640 @@
|
||||
# Decisions
|
||||
|
||||
## GitHub is canonical, and the module path names it
|
||||
|
||||
Goal 1 wants the usage the tool earns and goal 2 expects extenders who did not write it;
|
||||
both need a stranger to file an issue and open a pull request. Gitea has no anonymous
|
||||
issue, and no cross-host pull request at all, so a contributor would need an account and
|
||||
a fork on a personal instance. Valid while the project wants contribution from outside:
|
||||
the workflow decided nothing, being portable already — `github.api_url` and
|
||||
`secrets.GITHUB_TOKEN` resolve on either host, so only the names moved.
|
||||
|
||||
## The vocabulary sits below `doc.go`'s package clause, not in the package doc
|
||||
|
||||
Goal 1 is what a developer choosing a library reads first, and the package doc is that
|
||||
page: the glossary would fill two thirds of it with the render path's units, mostly
|
||||
unexported, and Go doc comments have no code markup, so every name would render its
|
||||
backticks literally. Below the clause it reaches goal 2's reader — who opens the file —
|
||||
and nothing else. Valid while the glossary's subject is the render path; one written to
|
||||
teach the exported API belongs above the clause.
|
||||
|
||||
## Options and fields share one namespace
|
||||
|
||||
`format`, `weight`, `repeat`, `separator`, `datatype` and `drawGroup` are reserved;
|
||||
every other key is a field. Nesting fields under a key, or prefixing options, would tax
|
||||
every template to guard against a misspelt option.
|
||||
|
||||
## `{a|b}` stays beside nested choices
|
||||
|
||||
`[[…], […]]` picks the same way, but its arms are anonymous; `{female|male}` keeps
|
||||
`person.female` addressable.
|
||||
|
||||
## Flags follow getopt_long
|
||||
|
||||
`--name value` and `--name=value` both work; a short flag's value attaches or follows
|
||||
(`-s42`, `-s 42`) and short flags bundle (`-hn 3`), as every shell user expects. A
|
||||
single-dash long flag is rejected naming the double-dash spelling, and `-s=42` is
|
||||
rejected naming both short spellings: `=` belongs to the long form, and reading `=42` as
|
||||
the value would make `-d=./x` a directory named `=./x`.
|
||||
|
||||
## An argument is a template by its shape, not by a flag
|
||||
|
||||
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
|
||||
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
|
||||
says so. No `--template` flag is needed. Reserving the characters whole — though only a
|
||||
leading one could collide — keeps one simple name rule instead of a leading-position
|
||||
special case. The JSON string is what makes the library's own advice reachable: the
|
||||
error for an object holding only a format names `"…"`, and that spelling has to work
|
||||
where it is printed. An argument or struct tag of one reference alone, `{/users}`, is
|
||||
refused naming the path `users`: both render the same text, and only the path names a
|
||||
record. A folder-relative `{.name}` or `{..name}` is refused naming `{/name}`, since an
|
||||
inline template sits in no folder. `IsTemplate` exports the rule, so the CLI, struct
|
||||
tags and any other caller read one.
|
||||
|
||||
## An inline template skips the cycle fence
|
||||
|
||||
`New` proves the loaded tree acyclic, an inline node is a finite tree of its own, and
|
||||
nothing in the tree can reference it, so no render of it reaches itself. Every other
|
||||
fence runs over both, from one `checkScope`, except that struct tags leave column
|
||||
agreement to their Go types.
|
||||
|
||||
## An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
|
||||
|
||||
The whole argument is the spelling under test, and `NewTemplate` compiles, links and
|
||||
validates as one step. An unknown *path* stays a runtime error (exit 1): there the
|
||||
argument is well-formed and only the data is absent.
|
||||
|
||||
## A padded JSON argument is rejected, not trimmed
|
||||
|
||||
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.
|
||||
|
||||
## `FakeTemplate` and `NewTemplate` both stay
|
||||
|
||||
They reach the same value but not at the same cost: `NewTemplate` pays the compile and
|
||||
validation once and renders many times, `FakeTemplate` is the one-shot call, and
|
||||
`--repeat` is exactly the case that needs the first. The pair is `regexp.MustCompile`
|
||||
and `regexp.Match`, not two spellings of one result.
|
||||
|
||||
## The shipped data is embedded, not discovered
|
||||
|
||||
A directory a machine happens to have would make `--seed 42` machine-dependent. Data
|
||||
still lives in `data/` as JSON; `--data-path` layers over it.
|
||||
|
||||
## A bare reference draws each time; a reference path is held
|
||||
|
||||
`{/p} {/p}` is two draws, as `{word} {word}` is, while every `{/p.first}` in one render
|
||||
reads one draw, and a bare `{/p}` beside them is a load error: a bare token is by
|
||||
contract an independent draw, a path pins its level, and a fresh draw of a pinned level
|
||||
could show another row. A builtin's operand holds what it reads for its expansion,
|
||||
references included, so `{uppercase(/p)} {/p}` is one draw — the rule every operand
|
||||
follows.
|
||||
|
||||
## Reference sigils follow the filesystem
|
||||
|
||||
`/` is the root, `.` this file's folder, `..` the folder above — what those spellings
|
||||
already mean to anyone who has typed a path. A locale's files reach each other without
|
||||
naming the locale, so a folder renames and copies without editing its references.
|
||||
|
||||
## A change to what exists is a major; a minor only adds
|
||||
|
||||
Data files, the CLI and the Go API are the public API, and a consumer must be able to
|
||||
take a minor without an edit — so an added column is a major, since it changes the CSV
|
||||
header and the `INSERT` column list, as is a removed value, which changes what a fixture
|
||||
holds, and a new option, which reserves a field name. One spelling per result grows by
|
||||
tightening, so every fence invalidates some file. Each such release names the rejected
|
||||
spelling and its replacement in the changelog and in the load error, and that is the
|
||||
whole migration: a fence rejects one spelling with one replacement, so the fix is local
|
||||
to each site. A fence that would need a non-local rewrite ships a converter with its
|
||||
release instead. Before `v1.0.0` a minor carries what a major would.
|
||||
|
||||
## Seeded output is promised within one version
|
||||
|
||||
Any edit to a category shifts its stream and everything drawn after it, so a promise
|
||||
across versions would freeze every shipped list; a fixture is re-pinned on a bump, as
|
||||
this repo's own are.
|
||||
|
||||
## An error is a contract by what it names, not its bytes
|
||||
|
||||
A script branches on the exit code and reads the named path or spelling, so those hold;
|
||||
wording improves in a minor.
|
||||
|
||||
## Raising the lowest supported Go is a major
|
||||
|
||||
A consumer building on it breaks, which is the one test every rule above applies; Go's
|
||||
convention of a minor is not followed.
|
||||
|
||||
## The format check runs on the latest Go only
|
||||
|
||||
`gofmt`'s output is the toolchain's, not the code's: 1.27 stopped padding a map
|
||||
literal's values out to a lone long key, so no source satisfies both it and 1.22's. A
|
||||
consumer on the lowest supported Go depends on the code compiling and its tests passing
|
||||
there, which is the `portable` stage, and never runs `gofmt` over this source, so the
|
||||
latest toolchain alone defines the one canonical form goal 2 asks a reader to meet.
|
||||
Valid while the lowest supported Go is not the latest.
|
||||
|
||||
## The changelog heading is the one spelling of a release; CI cuts the tag
|
||||
|
||||
A tag pushed by hand is served by `go get` at once, so a tag whose commit lacks its
|
||||
heading is burnt, not fixed. The heading on a gate-passed `main` commit is the trigger
|
||||
instead: the tag can land only there, and the GitHub release the same job publishes
|
||||
keeps one text as its body and is where prebuilt binaries will attach.
|
||||
|
||||
## A `--data-path` override rebinds every reference to the category it replaces
|
||||
|
||||
References bind against the merged tree, so once shipped data uses `{.person}`, a
|
||||
consumer's `sv_SE/person.json` is what every shipped reference into `person` reads, and
|
||||
`New` fails on shipped data the consumer never wrote when that file lacks a field those
|
||||
references read. Accepted: overriding is the point of layering, the error names the
|
||||
reference and the field, and the fix is the consumer's file carrying the fields the
|
||||
shipped tree reads.
|
||||
|
||||
## The repeat cap bounds renders, not bytes
|
||||
|
||||
A repeat, alone or nested, may ask for at most 1 048 576 renders; how large each render
|
||||
is stays what the data asked for, so `{hex(1048576)}` repeated to the cap is a terabyte,
|
||||
loaded without complaint. A byte estimate would need every builtin to declare a width to
|
||||
fence a shape no data comes near, and the harm lands on the author who wrote it.
|
||||
|
||||
## 64-bit targets only
|
||||
|
||||
The gate builds amd64, and the buffer sizing a render pre-computes (renders × bytes)
|
||||
assumes a 64-bit int; on a 32-bit target it could overflow and panic.
|
||||
|
||||
## A constant zero divisor is a load error; in a string column a divisor that is not constant prints `Inf`
|
||||
|
||||
`1/0` and a fixed `"0"` field are decidable, so they join the never-numeric operand as a
|
||||
load error; the fold stops where an operand varies, so `a/(b*c)` with `b` fixed at `0`
|
||||
and `c` varying loads and prints `Inf` every draw — catching it needs zero-absorbing
|
||||
algebra for a shape nobody writes. A [typed column](../README.md#datatype) bounds its operands
|
||||
instead and refuses a divisor it cannot keep from zero.
|
||||
|
||||
## In data, a default written out and a constant spelled as a sample are load errors
|
||||
|
||||
`weight: 1`, `repeat: 1`, `separator: ""`, `datatype: "string"`, `int(5,5)`,
|
||||
`float(1,1,2)`, `+5` and `05` each spell what a shorter form already spells, so each is
|
||||
rejected naming that form. The CLI's numbers follow the shell instead: `--seed 007` and
|
||||
`--repeat +3` are 7 and 3, as every command line reads them.
|
||||
|
||||
## Samples say what they emit, transforms what they do
|
||||
|
||||
`{upper(2)}` is two letters, `{uppercase(x)}` is `x` upper-cased; one name for both
|
||||
would turn on whether the argument looks like a number.
|
||||
|
||||
## A record is a template seen as columns; a Go struct is the one second schema
|
||||
|
||||
A template's `format` composes its fields into one string; `FakeRecord` and `--format`
|
||||
project the same fields as columns. Two views of one dataset, so a record author writes
|
||||
the same JSON they already know, and a column is the same field `Fake` renders by dotted
|
||||
path. The `format` is inert to a record — a record-only template writes `"format": ""` —
|
||||
but it is compiled and fenced, so a template that loads renders as whichever shape is
|
||||
asked for. `FakeStruct` takes its columns from a struct instead, because a Go caller has
|
||||
already written that schema: the fields name the columns and their types are the
|
||||
datatypes, so a tag says only what to draw, and a `datatype` in it would be a second
|
||||
spelling of the type. The Go type is a struct column's one datatype, so its items need
|
||||
not agree on one among themselves; each must only hold that type.
|
||||
|
||||
## A struct's records follow Go's field access, and compile on first use
|
||||
|
||||
The fields an embedded struct promotes are the struct's own — `e.First`, as
|
||||
`encoding/json` and SQL mappers read them — so they are columns of its record and share
|
||||
its draws; a tagged field that another field hides is refused, not dropped. A named
|
||||
struct field is another entity and a record of its own. `fake:"-"` leaves a struct
|
||||
field, embedded or named, unfilled, so no name may be `-`; a pointer back to a struct
|
||||
already being filled is left alone, since filling it would never end. `New` cannot see a
|
||||
caller's types, so the first `FakeStruct` for a type compiles its tags and the answer,
|
||||
error included, is kept per type: a test's first call is its load, and no `NewStruct`
|
||||
handle is needed, as the cache already compiles once.
|
||||
|
||||
## A render shares one reference draw per category, per group
|
||||
|
||||
Every reference path into a category in one `Fake`, or one record, reads one draw of it,
|
||||
so a value's facts agree across its fields, nested templates and columns alike —
|
||||
`{/currency.code}` in one field and `{/currency.symbol}` in another name one currency,
|
||||
whichever view renders them. A `repeat` iteration is a render of its own, since
|
||||
repeating asks for another entity, and a [draw group](../README.md#draw-group) names further
|
||||
entities within one render, so a payer and a payee are two groups over one `person`
|
||||
rather than two copies of it. Only references share: a sibling field is local to its own
|
||||
expansion, so a `first` column does not silently bind to a `first` in the column next to
|
||||
it.
|
||||
|
||||
## A draw group name is local to its category
|
||||
|
||||
A category's groups are its own entities, so a caller naming a group the same way never
|
||||
joins them by accident, and renaming a group inside one file changes no render
|
||||
elsewhere. The unnamed group still spans categories, since facts that belong together
|
||||
across categories must agree.
|
||||
|
||||
## The expansion hold and the render's draws are two fences
|
||||
|
||||
One proves a sibling path or an operand is reached only by its readers within an
|
||||
expansion, the other does the same for reference paths across a render and its draw
|
||||
groups. They pin different things — an operand pins the value its own render produced
|
||||
and stops at a reference, a path pins every level it passes through — so one walk would
|
||||
carry both rules and both scopes anyway, and tell them apart at every step.
|
||||
|
||||
## A record makes its draw maps up front, a `Fake` on its first read
|
||||
|
||||
A record's columns always read through the render's draws, so making the maps where the
|
||||
set is declared keeps them on that frame's stack. A `Fake` often reads no reference path
|
||||
at all, and making them anyway cost about a fifth of the cheapest render, so it makes
|
||||
them on the first read instead, at two heap allocations for a render that does share a
|
||||
draw. The allocation gate over a repeat of a reference path and over a named draw group
|
||||
prices that, and pins the two measures that keep a hold set off the heap.
|
||||
|
||||
## A category never references itself, and a record's fences run at load
|
||||
|
||||
A category is one unit: a reference back into it — `{/users.first}` inside `users` —
|
||||
describes a draw other than the fields beside it, where goal 3 asks facts that belong
|
||||
together to come from one draw, so `New` refuses it and the sibling path stays the one
|
||||
spelling for a field of one's own. A value two fields share goes in its own category,
|
||||
which both reference. That settled, a record's column fences run at `New` too, so a
|
||||
category that loads renders as whichever shape is asked for, and a reference reaching
|
||||
back into a category through another one is refused there as the overlap it is. Which
|
||||
reads those fences weigh differs on purpose: a record-only template's inert format
|
||||
renders nothing, so a `drawGroup` on it can never matter and is refused, while one on a
|
||||
rendering format can matter to a caller that bare-references it.
|
||||
|
||||
## A record's column set is fixed before the first draw
|
||||
|
||||
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
|
||||
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
|
||||
to agree.
|
||||
|
||||
## Null is a `null` item, not a rate
|
||||
|
||||
A null is one more outcome of a column's draw, so a choice's weights skew it like any
|
||||
other; a null-rate option would be a second way to state odds.
|
||||
|
||||
## A typed column holds one value, not composed text
|
||||
|
||||
Its bounds come from a literal or a call's arguments, so a load error names a real
|
||||
value, a range check is one comparison, and `1{digits(2)}` is a second spelling of
|
||||
`{int(100,199)}`.
|
||||
|
||||
## A column of one reference alone is the column it reads
|
||||
|
||||
`{/src.score}` renders exactly what `src.score` draws, so it takes that column's
|
||||
datatype and null rather than restating them, and a `datatype` restating the one it
|
||||
takes is a second spelling. Any other `datatype` still types the values — the one way to
|
||||
type a column someone else wrote.
|
||||
|
||||
## A typed column's calc is refused unless proven
|
||||
|
||||
Operand bounds must keep each divisor from zero and the result finite; what they cannot
|
||||
show is refused rather than trusted, since a bare `NaN` breaks the JSON and SQL it lands
|
||||
in.
|
||||
|
||||
## `Column` carries text, not a Go value
|
||||
|
||||
`Value` is the rendered string beside `DataType` and `Null`, which each serializer
|
||||
writes as the load check proved it; a `Value any` would hand every caller a type switch.
|
||||
|
||||
## `hold` names what a draw is kept in, `draw` the draw itself
|
||||
|
||||
Goal 2 wants a name to reach one unit, so a unit takes the stem of what it is, and a
|
||||
file the stem of the units it holds.
|
||||
|
||||
## A function may not spell a method; two types may
|
||||
|
||||
A call writes a method with its receiver and a function bare, so one name on both greps
|
||||
as one unit, which is the reaching cost goal 2 counts, while the receiver before a
|
||||
shared method name says which type answers. `TestNoFunctionSpellsAMethod` holds it. A
|
||||
type is outside the rule: `renderScope.hold` answers with a `*hold`, so the two spell
|
||||
one unit, which no test can judge.
|
||||
|
||||
## The package stays flat
|
||||
|
||||
Go ties a package to one directory, so folders would split the API into packages.
|
||||
|
||||
## The performance gate asserts allocations, not wall-clock time
|
||||
|
||||
`AllocsPerRun` is deterministic across machines, so a ±10% ceiling does not flake under
|
||||
CI load, while time varies with the machine and its neighbours. A rendering slowdown
|
||||
almost always costs an allocation too (a lost pre-size, a per-item map, an extra copy).
|
||||
The benchmark suite (the README's Development) reports time for a human, not as a pass/fail gate.
|
||||
|
||||
## Rows live in a TSV, the shape in JSON
|
||||
|
||||
`New` allocates once per node, so a register of thirty thousand rows written as JSON
|
||||
objects would cost it a second; a TSV is one allocation whose cells are substrings, and
|
||||
the JSON says only how a row is composed. The TSV sits beside its category file, named
|
||||
by `rows`, so a data directory stays a directory of categories, and one nothing names is
|
||||
a load error rather than a file silently ignored.
|
||||
|
||||
## A selector is bracketed, and a dot inside it is literal
|
||||
|
||||
`municipality[0180]` reads as selection to anyone who has indexed an array, and `[St.
|
||||
Louis]` keeps a name whole where a colon or a dot-separated spelling could not; zsh
|
||||
needs the brackets quoted, which the README's examples show. A name that spells another
|
||||
row's key is refused at load rather than shadowed: a key names one row by contract, so
|
||||
the name could never select its own, and the check is one lookup per row against the key
|
||||
index that already exists.
|
||||
|
||||
## `parent` names the link column and the table alike
|
||||
|
||||
One word says both, so a child table sits in its parent's folder and links on a column
|
||||
of the parent's name; the geo plan wants exactly that, and a table needing another
|
||||
folder or another column name would be asking for a second spelling.
|
||||
|
||||
## After a row, a path names a column or a linked table
|
||||
|
||||
`locality[Lund].address`, a template beside the family under a selected row, is refused
|
||||
today; admitting it later is additive, since a refused spelling gains a meaning and no
|
||||
accepted one changes, so the door stays open for the address records the plan describes.
|
||||
|
||||
## A table read into is pinned; a table read whole draws afresh
|
||||
|
||||
A path into a table pins its row for the render and group, as a reference path pins its
|
||||
level, and a bare `{/city}` draws a row each time and pins no row of its family, as a
|
||||
bare reference does; so a bare table beside a path into its family is refused like a
|
||||
bare reference beside a path into it. A bare table reference still counts as a read for
|
||||
a `drawGroup`, since the group is what draws it apart from the family's pins.
|
||||
|
||||
## Every reference path into one family selects the same rows, per render and group
|
||||
|
||||
A selector pins rows for the render, and a read that draws freely before it could pin a
|
||||
row the selector contradicts, so accepting both would make the result depend on which
|
||||
token rendered first. Requiring one selection per family per group is checkable at load
|
||||
with no data lookup beyond the selectors themselves, and the error names the rewrite.
|
||||
Two selectors naming one row by key and by name compare equal, since the load check
|
||||
resolves them.
|
||||
|
||||
## A parent row with no child row is a load error
|
||||
|
||||
A descendant is drawn inside the nearest pinned ancestor, so every ancestor row must
|
||||
lead to a row at every level below it, or a render could find nothing to draw. The
|
||||
import script drops or fills such rows; the alternative, falling back to a free draw,
|
||||
would break the consistency the link exists for without saying so.
|
||||
|
||||
## The choice-of-rows fence guards a data file's root, and requires string fields
|
||||
|
||||
A table is a category with a TSV beside its file, so only a root choice has the spelling
|
||||
the fence names; a nested choice of same-shaped templates and an inline one keep
|
||||
loading. Fields must all be strings because a cell is a string node: a choice whose
|
||||
items carry a nested choice is not one table but two linked ones.
|
||||
|
||||
## A table is a record of string columns
|
||||
|
||||
Its columns are the CSV header and the `INSERT` column list, fixed by the TSV header, so
|
||||
a table is a record by construction; every column is a string until a typed column
|
||||
option earns its place.
|
||||
|
||||
## The key index is built at load, the rest on first draw
|
||||
|
||||
A link is proved against the parent's keys and a key's uniqueness is a data mistake, so
|
||||
both are load-time; the name index and the per-parent child lists serve only a draw or a
|
||||
selection, so they wait for the first one, keeping `New` linear in the bytes read.
|
||||
|
||||
## Two categories may name one TSV
|
||||
|
||||
Each is a view of the file with its own format and options, at the cost of holding the
|
||||
rows twice, which is what a category over a register with two natural formats asks for;
|
||||
a TSV nothing names stays a load error, since that one is a file forgotten rather than
|
||||
shared.
|
||||
|
||||
## The rows of a table are alternatives
|
||||
|
||||
Only one row renders per read, so within one read a cell in one row and a cell in
|
||||
another never meet, and each may select its own row of another table; the cells of one
|
||||
row, and whatever they reach, do meet, and so does the format beside them. The rule the
|
||||
fence applies is that two reads meet unless they sit in rows that never render together:
|
||||
rows falling under different rows of a table a path pinned, two rows of that table
|
||||
itself included. A choice's items get no such treatment yet: two items selecting
|
||||
different rows are still refused.
|
||||
|
||||
## A table never reaches its own family, by any route
|
||||
|
||||
A `repeat` iteration and a `drawGroup` each draw apart on purpose, but a row that lists
|
||||
three localities from other regions is the output the family exists to prevent, so the
|
||||
own-family fence walks through both rather than stopping where the draw fences do.
|
||||
|
||||
## Tables carrying token cells stay small
|
||||
|
||||
The family fence compares the reads of every pair of rows that can render together, so a
|
||||
table whose every row's cell selects a row of another table loads in time quadratic in
|
||||
its rows: about a second at four thousand rows. No shipped table carries such cells, and
|
||||
a register is a column set rather than a set of references, so the fence is left as it
|
||||
is until a real data set needs the indexed form.
|
||||
|
||||
## A path is walked once without drawing before it is walked for real
|
||||
|
||||
A path that fails below its first level then moves no seeded stream, at the cost of one
|
||||
draw-free walk per call, which allocates nothing.
|
||||
|
||||
## A country's postal codes and streets are siblings under its locality
|
||||
|
||||
No open source pairs a Swedish street with its postnummer, and pairing the US through
|
||||
its ZIPs would shape the two trees differently, so both draw inside the pinned locality
|
||||
and an address agrees at that level. A street's own code is the exact pairing to add
|
||||
when a source carries it.
|
||||
|
||||
## A locale's `address` reads its country's `geo` tree, so the shipped set loads whole
|
||||
|
||||
`data/sv_SE` alone no longer loads: a test loads `data` and prefixes the locale, and
|
||||
`--no-shipped-data -d` takes the whole `data` folder or a set of one's own.
|
||||
|
||||
## The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more
|
||||
|
||||
Sweden fits whole in 700 KB; every US place of 10,000 would pass a megabyte and fetch
|
||||
1,200 counties of TIGER files, so the threshold sits where the two countries match in
|
||||
size, and `--min-population` and `--streets-per-locality` on the import scripts build a
|
||||
fuller set. The two trees add about 20 ms to `New`, which loads the shipped set in about
|
||||
45 ms.
|
||||
|
||||
## A locale's `address` restates its country record's format
|
||||
|
||||
A record cannot read another whole and keep its columns, so `sv_SE.address` names the
|
||||
same four columns as `geo.SE.address`, each a reference into it, and the format appears
|
||||
twice; a column is spelled the same in both, `street-number`, so the two never disagree
|
||||
on a name.
|
||||
|
||||
## A postort's kommun comes from its name, its tätort or its codes, never from distance
|
||||
|
||||
GeoNames leaves a fifth of Sweden's codes without a kommun and carries stale spellings;
|
||||
the nearest code across a border named the wrong kommun half the time it was tried, so a
|
||||
postort none of the three rules place is dropped, as is one not cased like a place name.
|
||||
|
||||
## A highway designation is not a street, and a US postal code belongs to the place holding most of its land inside places
|
||||
|
||||
`I- 55 Bus` and `US Hwy 1` carry the most address ranges in many places and would head
|
||||
every address, so the import drops names spelled as a route. A ZCTA goes to the place
|
||||
its largest in-place part lies in, census-designated places left out since they never
|
||||
ship, and ships only when that place does, so a few dozen places whose every code lies
|
||||
mostly in a bigger neighbour ship no address; counting the land outside every place too
|
||||
would drop a quarter of the places, whose codes straddle unincorporated land, for a
|
||||
postal city the USPS mostly names the same way.
|
||||
|
||||
## `--list` stays a plain list of paths
|
||||
|
||||
It is what a script reads, so every line has to be a path that `Fake` takes; a marker
|
||||
for the tables a `[selector]` follows, or a legend above them, would make the output
|
||||
something to parse before use. `--help` names the selector spelling instead, and the
|
||||
Table section teaches it.
|
||||
|
||||
## A layout is always quoted
|
||||
|
||||
A layout may carry the comma that separates arguments, `'January 2, 2006'`, and one
|
||||
spelling for every layout beats a rule about which ones need the quotes, so the bare
|
||||
spelling is refused naming the 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.
|
||||
|
||||
## A title is a table under `sex`
|
||||
|
||||
A prefix drawn apart would put `Mr` on a record whose `sex` column says `female`, which
|
||||
is the disagreement the record exists to prevent; the tables this set already has are
|
||||
what a title needs, so `en_US.title` links to `sex` as `first-name` does. Swedish has no
|
||||
everyday sexed honorific, so `sv_SE.title` is a table as well but carries no `parent`.
|
||||
Its weight column is `share`, not the `count` a name table carries, because the values
|
||||
are a curated proportion rather than bearers anyone counted.
|
||||
|
||||
## A table owns the spelling of a selector on it
|
||||
|
||||
A reference reaches a table by a path that carries no selector — `sv_SE.person.first`
|
||||
reads `first-name` through `sex` — so the walk that resolved a name cannot say where a
|
||||
reader would type one. The table's own location can, which is why it keeps its path, and
|
||||
why an ambiguity error names `sv_SE.sex[f].first-name[Kim]` rather than the table's own
|
||||
name.
|
||||
|
||||
## No builtin reads the clock, so a date is bounded by days, never by an age
|
||||
|
||||
An `age(min,max)` would make a seeded fixture change with the day it runs on, which is
|
||||
what a seed exists to prevent; a birthdate for someone 20 to 60 is
|
||||
`date(1966-01-01,2006-12-31,…)`, re-pinned as any fixture is.
|
||||
|
||||
## A name column without a key resolves inside its parent
|
||||
|
||||
A given name both sexes carry is a row under each, so `name` cannot be the key; the
|
||||
parent's row tells the two apart, `sex[f].first-name[Kim]`, the ambiguity error spells
|
||||
each row inside its parent, and a name repeating inside one parent row is refused at
|
||||
load, since nothing could then select it.
|
||||
|
||||
## `misc` is what every locale shares
|
||||
|
||||
A category whose facts differ by country belongs in that country's locale, read from the
|
||||
register that country's own records use; `misc` takes only sources that are
|
||||
international. NHTSA vPIC and Mobility Sweden's registrations are national, so they are
|
||||
for `en_US.car` and `sv_SE.car`, and `misc.car` waits for an international source.
|
||||
|
||||
## A register's canonical spelling loses to the one its domain writes
|
||||
|
||||
Where a source offers several spellings of one fact, the shipped one is what records in
|
||||
that domain carry. `misc.timezone` reads `zone.tab` and not the `zone1970.tab` that
|
||||
supersedes it, because the latter keeps one zone per set of countries that have agreed
|
||||
since 1970: it spells Sweden `Europe/Berlin`, and no Swedish system writes that. For the
|
||||
same reason `misc.language` takes the ISO 639-2 register's first synonym over CLDR,
|
||||
which says "Chinese, Mandarin" for `zh`.
|
||||
|
||||
## `misc.territory` is the spine, and a `misc` table naming a territory links to it
|
||||
|
||||
Where every territory row has a child the column is a `parent`, and the import drops the
|
||||
child rows whose territory the set does not ship — 17 of `misc.timezone`'s, Antarctica's
|
||||
ten among them. Agreement across a record is worth more than the last rows of a table.
|
||||
Where no such link can hold the fact stays a column. Layer your own `misc.territory`
|
||||
over the shipped one and you must layer `misc.timezone` too, or the link fails at load
|
||||
naming the row.
|
||||
|
||||
## A table whose register publishes no frequency draws evenly
|
||||
|
||||
`misc.httpmethod`, `misc.port`, `misc.httpstatus`, `misc.mimetype` and `misc.tld` weigh
|
||||
every row alike, so GET is a ninth of the methods drawn. Goal 12 keeps an authored fact
|
||||
out of a sourced table, and no register publishes how often a method, a port or a TLD is
|
||||
used, so a weight here would be invented. Where one exists it is read, as
|
||||
`misc.timezone` reads GeoNames populations and `sv_SE.first-name` SCB bearers.
|
||||
|
||||
## `misc.timezone` weighs a zone by the people living in it
|
||||
|
||||
The weight is the population GeoNames records in the zone's cities of 15,000 or more,
|
||||
floored at 15,000, which goal 14 asks for: over 300 seeded draws of
|
||||
`misc.territory[US].timezone`, the four zones most Americans live in took 278 where an
|
||||
even weight gave them 44, and `America/Indiana/Petersburg`, a town of 2,400, fell from 17
|
||||
to 0. Valid while a draw weighted this way lands where people live.
|
||||
|
||||
## `misc.port` selects by number, and carries no `name`
|
||||
|
||||
29 of its services sit on more than one port, `http-alt` on three, so
|
||||
`misc.port[http-alt]` could name no one row. The number is what a port field holds
|
||||
anyway, and `misc.port[443].service` reads the other way.
|
||||
|
||||
## `misc.car` is one flat table, not a make linked to its models
|
||||
|
||||
A row is a make and a model drawn together, so no render pairs a Volvo with a RAV4. Two
|
||||
linked tables would reach the same pairs and add a selector nothing asks for; a make
|
||||
alone is `misc.car.make`.
|
||||
|
||||
## `misc.territory` names its sovereign in a column, and there is no `misc.country` table
|
||||
|
||||
ISO 3166-1 codes territories, so `territory` is the honest name, and `is_independent` in
|
||||
the register gives each one's state. A second table of the 195 sovereigns would hold a
|
||||
`DK` row beside the territory `DK` row, both carrying Denmark's capital, currency, flag
|
||||
and TLD — two owners for one fact, drifting at the next import. Splitting the columns to
|
||||
avoid that is worse: put `capital` on the territory alone and a country row can no
|
||||
longer name Copenhagen. The column cannot be a `parent`, since a table never reaches its
|
||||
own family; a test proves every value names a row instead. A territory the register
|
||||
records no state for stands alone, which is `EH` alone, and naming one for it would be a
|
||||
claim fejkdata has no business making.
|
||||
|
||||
## `misc.loglevel` is a table keyed by the code, rendering POSIX's keyword
|
||||
|
||||
A flat list of names carries neither the code a PRI encodes nor a selector reaching it,
|
||||
and goal 3 draws the two as one fact. The canonical spelling losing to the one its
|
||||
domain writes, above, settles the rest: a configuration writes `info`, so RFC 5424's
|
||||
`Informational` stays the `severity` column, and the code and the keyword hold the two
|
||||
selector slots.
|
||||
|
||||
## `misc.tld` keys carry the leading dot, where other tables key on a bare code
|
||||
|
||||
The register spells a TLD `.se` and `misc.territory.tld` already ships it so, which a
|
||||
bare key would make two spellings of one fact; `{/misc.tld}` also composes onto a host
|
||||
with no separator. `misc.tld[se]` misses for it, which `todo.md` carries.
|
||||
|
||||
## `misc.tld` is a table of its own, and `misc.territory.tld` stays a column
|
||||
|
||||
A `parent` demands a child for every parent row, so linking them would drop every root
|
||||
zone row naming no territory, which is most of them, and goal 12 holds a sourced table
|
||||
whole. The loader refuses the link outright anyway: `tld` is a column of
|
||||
`misc.territory`, and a table may not be named like a column of its ancestor.
|
||||
|
||||
## `misc.territory` carries a currency code, it does not link to `misc.currency`
|
||||
|
||||
A `parent` demands a child for every parent row, and ISO 4217 registers codes no
|
||||
country's row can name: the funds codes (Mvdol, WIR Euro, US Dollar (Next day)), and VED
|
||||
beside VES, both Venezuela's, of which a country row names one. Linking would trade the
|
||||
register for the link, and `misc.territory.currency` already pairs a country with its
|
||||
currency in one draw.
|
||||
|
||||
## An extension may name two media types
|
||||
|
||||
`.xml`, `.rtf`, `.sub`, `.mpp` and `.ac` each name two rows of `misc.mimetype`.
|
||||
Separating them would mean dropping a registered type, or naming one by an extension
|
||||
that is not its own — `.mpt` is Project's template, not its document. Selecting such a
|
||||
name is an error listing both keys; select by type instead.
|
||||
|
||||
## The Swedish ids draw Skatteverket's test series
|
||||
|
||||
A Luhn-valid personnummer over a random birth number may be a living person's; 238 and
|
||||
239 after any date are blocked from assignment, so the shipped `personnummer` and
|
||||
`samordningsnummer` use those. They sit in a `birth-number` table under `sex` rather
|
||||
than as a column of it: the render's shared draw of the family is what makes the number
|
||||
and the name agree on sex, and `sex` stays one shape across locales instead of
|
||||
collecting every sex-keyed id fact. A samordningsnummer's day, the birthday plus 60, is
|
||||
drawn from 61 to 88, valid in every month, rather than computed from the date drawn.
|
||||
|
||||
## The US given names come from a mirror of the SSA file
|
||||
|
||||
ssa.gov refuses a client outside the US, so `names-us.py` reads a GitHub copy that ends
|
||||
at 2020, which a count over the births since 1930 barely feels; `--names` takes the
|
||||
official zip. The SSA's placeholder rows are top-1000 entries that name nobody, so the
|
||||
import drops them by name rather than by a rank a regeneration would move.
|
||||
|
||||
## `List` advertises direct descents only
|
||||
|
||||
`region.municipality.locality` is listed, and `region.locality` resolves too but is not:
|
||||
the set of every descent through a chain of five tables is every subsequence of it, and
|
||||
the direct chain is the one a reader can predict from the tables' parents.
|
||||
@@ -38,6 +38,8 @@ func checkNestedDrawGroup(fields map[string]node, group string) error {
|
||||
|
||||
// drawCheck fences each template of a scope as a render of its own, remembering which nodes read a
|
||||
// reference path.
|
||||
// docs/decisions.md#a-render-shares-one-reference-draw-per-category-per-group
|
||||
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
|
||||
type drawCheck struct {
|
||||
memo map[readsMemo]bool
|
||||
}
|
||||
@@ -79,6 +81,7 @@ func (c *drawCheck) checkDraws(path string, n node) error {
|
||||
|
||||
// checkRecordDraws fences the columns of a record — a template compiled at the top without a
|
||||
// repeat — so a load proves the record view of it as well as the string view.
|
||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||
func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || !t.record {
|
||||
|
||||
@@ -96,6 +96,7 @@ func (r *tableRead) selected(t *table) (tableSel, bool) {
|
||||
// read one consistent draw: a table rendered whole beside a path into the family,
|
||||
// a table one read draws that another pins, and two reads pinning different rows.
|
||||
// The reads come sorted by group and path, so which pair is reported does not vary.
|
||||
// docs/decisions.md#every-reference-path-into-one-family-selects-the-same-rows-per-render-and-group
|
||||
func checkFamilies(reads []pathRead) error {
|
||||
for i, r := range reads {
|
||||
if r.tr == nil {
|
||||
@@ -155,6 +156,7 @@ func (r *tableRead) replay(d *pinSet) error {
|
||||
// checkFamilyPair refuses a table read whole beside a path into its family, and a
|
||||
// table one read draws that the other pins, since which token renders first would
|
||||
// then decide the row.
|
||||
// docs/decisions.md#a-table-read-into-is-pinned-a-table-read-whole-draws-afresh
|
||||
func checkFamilyPair(a, b pathRead) error {
|
||||
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
|
||||
x, y := pair[0], pair[1]
|
||||
@@ -192,6 +194,7 @@ func (r *tableRead) drawnOf(pins *pinSet) *table {
|
||||
// away and through a repeat or a draw group too, a table of its own family: a row
|
||||
// rendered whole draws its row without pinning it, so the family would draw apart
|
||||
// from the row being rendered, whichever draws the reaching template holds.
|
||||
// docs/decisions.md#a-table-never-reaches-its-own-family-by-any-route
|
||||
func checkOwnFamily(t *template) error {
|
||||
own := t.table
|
||||
if own == nil {
|
||||
@@ -227,6 +230,7 @@ func checkOwnFamily(t *template) error {
|
||||
|
||||
// alternatives reports whether two reads sit in different rows of one table. Not clash: a row drawn
|
||||
// whole is held without its ancestors, since the render draws it over the whole table.
|
||||
// docs/decisions.md#the-rows-of-a-table-are-alternatives
|
||||
func alternatives(a, b drawAt) bool {
|
||||
found := false
|
||||
a.alt.each(func(t *table, r int) {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ var shippedFS embed.FS
|
||||
// any one path; the CLI's --repeat shares it.
|
||||
const MaxRepeat = 1 << 20
|
||||
|
||||
var _ [^uint(0)>>63 - 1]struct{} // 64-bit only, per the README's Decisions
|
||||
var _ [^uint(0)>>63 - 1]struct{} // docs/decisions.md#64-bit-targets-only
|
||||
|
||||
// ErrNoData is returned by New when no source is loaded at all.
|
||||
var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithDataPath or WithDataFS")
|
||||
|
||||
@@ -206,6 +206,7 @@ func (m reachMemo) of(n node) int {
|
||||
|
||||
// repeatCheck bounds the renders a repeat multiplies to along any root-to-leaf
|
||||
// path, so nested repeats cannot build what one repeat may not.
|
||||
// docs/decisions.md#the-repeat-cap-bounds-renders-not-bytes
|
||||
func repeatCheck(path string, n node, mem reachMemo) error {
|
||||
if t, ok := n.(*template); ok && t.repeat > 1 && mem.of(n) > MaxRepeat {
|
||||
return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", path, t.repeat, mem.of(n), MaxRepeat)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
// heldCheck rejects every route to a held sibling name except the ones that read its
|
||||
// draw. An expansion holds one draw of that name; anything else that renders it draws
|
||||
// again, and the two disagree.
|
||||
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
|
||||
func heldCheck(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || len(t.held) == 0 {
|
||||
@@ -181,6 +182,7 @@ func renders(n node, want, seen map[node]bool) bool {
|
||||
// level's held draw while rendering the level expands it afresh, so their values would
|
||||
// disagree. Reads are compared in sorted order, so which pair is reported does not
|
||||
// depend on where the tokens sit.
|
||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||
func checkNoOverlap(format string, bound map[string]string, refs map[string]refBinding) error {
|
||||
names := boundReaders(format, bound, refs)
|
||||
// Stable over one format-order scan, so two readers of one name (a token and a
|
||||
@@ -231,6 +233,7 @@ func boundReaders(format string, bound map[string]string, refs map[string]refBin
|
||||
// checkNoRepeatedRead rejects a bare token repeated on a held name: {w} {w} beside
|
||||
// {uppercase(w)} would read one draw twice, where {w} {w} alone draws twice. The
|
||||
// error names the single-token spelling.
|
||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||
func checkNoRepeatedRead(format string, c formatOps, refs map[string]refBinding) error {
|
||||
count := map[string]int{}
|
||||
return eachToken(format, func(t ftoken) error {
|
||||
|
||||
@@ -34,6 +34,7 @@ func quotedLayout(a string) bool {
|
||||
}
|
||||
|
||||
// layoutArg is the Go layout a quoted arg holds, refused when unquoted or constant.
|
||||
// docs/decisions.md#a-layout-is-always-quoted
|
||||
func layoutArg(a string) (string, error) {
|
||||
if !quotedLayout(a) {
|
||||
bare := strings.Trim(a, `'"`)
|
||||
|
||||
@@ -117,6 +117,7 @@ func compileCategory(v any, name string, files *categoryFiles) (node, error) {
|
||||
|
||||
// checkNotRows refuses a choice of templates sharing one format and one set of
|
||||
// string fields: each item is a row, and the rows file is the spelling for that.
|
||||
// docs/decisions.md#the-choice-of-rows-fence-guards-a-data-files-root-and-requires-string-fields
|
||||
func checkNotRows(items []any, name string) error {
|
||||
var format string
|
||||
var fields []string
|
||||
|
||||
+2
-2
@@ -84,8 +84,8 @@ func linkRefs(root map[string]node) error {
|
||||
}
|
||||
|
||||
// linkTemplateRefs binds one template's references against root, refusing one that names the
|
||||
// category it sits in: a category is a unit, and a reference back into it describes a draw other
|
||||
// than the fields beside it.
|
||||
// category it sits in.
|
||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||
func linkTemplateRefs(folder []string, path, category string, t *template, root map[string]node) error {
|
||||
names := refTokens(t.format)
|
||||
if len(names) == 0 {
|
||||
|
||||
@@ -358,6 +358,7 @@ func setTablePaths(root map[string]node) {
|
||||
// linkTables binds every table's parent to the table beside it, and proves the
|
||||
// links: a parent has a key, every link cell is one, every parent row is linked
|
||||
// to, no chain of parents closes, and no child is named like a parent's column.
|
||||
// docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error
|
||||
func linkTables(root map[string]node) error {
|
||||
var walk func(dir string, children map[string]node) error
|
||||
walk = func(dir string, children map[string]node) error {
|
||||
|
||||
@@ -19,17 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
|
||||
|
||||
This round, in order:
|
||||
|
||||
- Move the README's Decisions section to `docs/decisions.md`, leaving a one-line index
|
||||
of the titles in `AGENTS.md`, and drop the `AGENTS.md` line pointing decisions at the
|
||||
README. Give each fence a bare link to the entry it enforces: every seat needed
|
||||
"The rows of a table are alternatives", "The expansion hold and the render's draws
|
||||
are two fences" or "A table read into is pinned" and found it by grepping 470 lines.
|
||||
Carry the timezone weight's premise in with it: weighting by GeoNames city
|
||||
population moved 300 seeded draws of `misc.territory[US].timezone` from 44 landing on
|
||||
the four zones most Americans live in to 278, and dropped `America/Indiana/Petersburg`
|
||||
(pop. 2,400) from 17 draws to 0. Carry in the decision `linkTemplateRefs`'s doc holds
|
||||
today, which no entry records: a category is a unit, so a reference back into it
|
||||
describes a draw other than the fields beside it, which is what goal 3 asks for.
|
||||
- Keep reference bindings out of `template.fields`, so no walk over a template's fields
|
||||
needs the `isRef` filter `named`, `paths`, `recordColumns` and `template.field` carry.
|
||||
- Compile a template's format once: `compileFormat` runs again from `linkTemplateRefs`,
|
||||
@@ -285,6 +274,8 @@ because pairing a street with its exact postnummer rewrites shipped rows.
|
||||
|
||||
## Not release-bound
|
||||
|
||||
- Give every entry in `docs/decisions.md` the goal it serves, its date, who made it and
|
||||
the premise it rests on; propose a goal for an entry that serves none.
|
||||
- Hold every change the charter says owes a `CHANGELOG.md` entry to one in CI: the
|
||||
check covers `data` and `testdata/shipped_shape.txt` alone, where `AGENTS.md` adds a
|
||||
flag, an exit code, an exported name, a fence, a builtin and the lowest Go.
|
||||
|
||||
@@ -27,6 +27,7 @@ type valueProof struct {
|
||||
|
||||
// checkDatatype rejects a typed column item some render of which is not text of its datatype,
|
||||
// and one restating the datatype of the column it is.
|
||||
// docs/decisions.md#a-column-of-one-reference-alone-is-the-column-it-reads
|
||||
func (p *valueProof) checkDatatype(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || t.datatype == DataTypeString {
|
||||
|
||||
Reference in New Issue
Block a user