From 5d33f4c0f0d8326d3eaa69c8bf7985880d88847e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 1 Sep 2026 21:41:35 +0200 Subject: [PATCH] Rename the builtin kind from generator to sample --- README.md | 26 +++++++++++++------------- builtins.go | 14 +++++++------- builtins_test.go | 12 ++++++------ data_test.go | 2 +- fejkdata_test.go | 5 +++-- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6b07978..13aeb6f 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ lacked the locale coverage and format control we needed. id), never for what character classes and choices already do. - **Composable** — templates nest without limit: weighted choices, character classes and sub-templates combine to model any format. -- **Reproducible** — seed a generator and it emits the same sequence every time, for a - given version of the data: changing how a value is composed shifts the stream for - that value and for everything drawn after it in the same generator. +- **Reproducible** — seed a generator and it emits the same sequence every time, + for a given version of the data: changing how a value is composed shifts the + stream for that value and for everything drawn after it in the same generator. Every built-in draws only from that seed — no wall-clock, no `crypto/rand` — so determinism holds end to end. - **Zero dependencies** — standard library only. @@ -270,7 +270,7 @@ the rng, not the clock — the result is a valid, reproducible value, not a real point in time. There are four kinds. **Derivations** read the digits emitted so far, so put them -after their payload; **generators** read only the rng, so they stand alone; one +after their payload; **samples** read only the rng, so they stand alone; one **session counter** (`seq`) advances state held on the generator; and one **computation** (`calc`) evaluates arithmetic over sibling fields. Arguments are validated at `New` (a bad count, range, country, or expression fails fast); a @@ -282,19 +282,19 @@ fat-fingered `hex(2000000000)` can't try to allocate gigabytes at render. | `{luhn()}` | derivation | Luhn check digit (mod-10) over preceding digits | | `{mod11()}` | derivation | weighted mod-11 check char (weights 2–7 from the right); `X` when it would be 10 | | `{ean()}` | derivation | EAN-13 / UPC-A / ISBN-13 / GTIN check digit | -| `{uuid()}` | generator | UUID v7 (v4 ships as data — see [Data](#data)) | -| `{ulid()}` | generator | ULID, 26-char Crockford base32 | -| `{nanoid(n)}` | generator | URL-safe Nano ID, `n` chars | -| `{hex(n)}` | generator | `n` lowercase hex digits | -| `{base64(n)}` | generator | `n` random bytes, base64 | -| `{int(min,max)}` | generator | uniform integer in `[min, max]` | -| `{float(min,max,dp)}` | generator | number in `[min, max]` with `dp` decimals | -| `{iban(CC)}` | generator | a length- and mod-97-valid IBAN for country `CC` (BE, DE, DK, ES, FI, NO, SE) | +| `{uuid()}` | sample | UUID v7 (v4 ships as data — see [Data](#data)) | +| `{ulid()}` | sample | ULID, 26-char Crockford base32 | +| `{nanoid(n)}` | sample | URL-safe Nano ID, `n` chars | +| `{hex(n)}` | sample | `n` lowercase hex digits | +| `{base64(n)}` | sample | `n` random bytes, base64 | +| `{int(min,max)}` | sample | uniform integer in `[min, max]` | +| `{float(min,max,dp)}` | sample | number in `[min, max]` with `dp` decimals | +| `{iban(CC)}` | sample | a length- and mod-97-valid IBAN for country `CC` (BE, DE, DK, ES, FI, NO, SE) | | `{seq()}`, `{seq(name)}` | session counter | next integer (from 1) in this generator's sequence; `name` selects an independent counter | | `{calc(expr)}`, `{calc(expr,dp)}` | computation | value of an arithmetic expression over number literals and sibling fields; `dp` rounds | `{ean()}` is also the ISBN-13 check (an ISBN-13 *is* an EAN-13 — build the 978/979 -prefix in data and call `{ean()}`). `{iban()}` is a generator, not a derivation: +prefix in data and call `{ean()}`). `{iban()}` is a sample, not a derivation: an IBAN's check digits sit *before* the account number, which a left-to-right reader can't reach, so it emits the whole value (a generic numeric BBAN — valid length and checksum, not real bank routing). diff --git a/builtins.go b/builtins.go index 39f1d32..6105aae 100644 --- a/builtins.go +++ b/builtins.go @@ -8,7 +8,7 @@ import ( "strings" ) -// maxLen caps generator output lengths (hex, nanoid, base64) and maxDecimals caps +// maxLen caps sample output lengths (hex, nanoid, base64) and maxDecimals caps // float/calc decimal places, so a fat-fingered or overflowing argument fails at // New instead of trying to allocate gigabytes — or panicking — at render. const ( @@ -18,7 +18,7 @@ const ( // builtins is the registry of {name(args)} functions. Two kinds: derivations read // the digits emitted so far in the current expansion (luhn, mod11, ean — place -// them after their payload); generators read only the rng (uuid, ulid, ...). All +// them after their payload); samples read only the rng (uuid, ulid, ...). All // must stay pure over (rng, emitted, args) so seeded output is reproducible — a // time-based id (uuid v7, ulid) draws its timestamp from the rng, not the wall // clock. Add a builtin only for what data can't express: a random v4 UUID and a @@ -27,8 +27,8 @@ var builtins = map[string]builtin{ "luhn": {arity: 0, prep: derive(func(e string) string { return string(rune('0' + luhnCheck(e))) })}, "mod11": {arity: 0, prep: derive(mod11Check)}, "ean": {arity: 0, prep: derive(eanCheck)}, - "uuid": {arity: 0, prep: generate(uuidV7)}, - "ulid": {arity: 0, prep: generate(ulid)}, + "uuid": {arity: 0, prep: sample(uuidV7)}, + "ulid": {arity: 0, prep: sample(ulid)}, "nanoid": {arity: 1, check: posIntArg, prep: func(a []string) callFn { n := atoi(a[0]) return func(s *session, _ string, _ []string) string { return nanoid(s, n) } @@ -74,8 +74,8 @@ var builtins = map[string]builtin{ }}, } -// derive and generate are the two argument-free builtin shapes the README names: a -// derivation reads the output emitted so far, a generator reads only the rng. Each +// derive and sample are the two argument-free builtin shapes the README names: a +// derivation reads the output emitted so far, a sample reads only the rng. Each // lifts that one function into the prep every registry entry supplies. func derive(f func(emitted string) string) func([]string) callFn { return func([]string) callFn { @@ -83,7 +83,7 @@ func derive(f func(emitted string) string) func([]string) callFn { } } -func generate(f func(rng) string) func([]string) callFn { +func sample(f func(rng) string) func([]string) callFn { return func([]string) callFn { return func(s *session, _ string, _ []string) string { return f(s) } } diff --git a/builtins_test.go b/builtins_test.go index cfc9d27..fcac338 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -34,9 +34,9 @@ func TestBuiltinIDGenerators(t *testing.T) { } } -// TestBuiltinGeneratorsReproducible pins the determinism guardrail: every -// generator draws only from the seeded rng, so same seed -> same value. -func TestBuiltinGeneratorsReproducible(t *testing.T) { +// TestBuiltinSamplesReproducible pins the determinism guardrail: every +// sample draws only from the seeded rng, so same seed -> same value. +func TestBuiltinSamplesReproducible(t *testing.T) { for _, tmpl := range []string{ `{"format":"{uuid()}"}`, `{"format":"{ulid()}"}`, `{"format":"{nanoid(12)}"}`, `{"format":"{int(1,1000000)}"}`, @@ -96,8 +96,8 @@ func TestBuiltinChecksums(t *testing.T) { } // TestBuiltinSeqPerSession pins seq's contract: a counter from 1, advancing on -// each call, named counters independent, and the whole thing scoped to one session -// so a fresh Generator restarts at 1. +// each call, named counters independent, and the whole thing scoped to one +// session so a fresh Generator restarts at 1. func TestBuiltinSeqPerSession(t *testing.T) { f := engine(1) for i := 1; i <= 5; i++ { @@ -163,7 +163,7 @@ func TestBuiltinIBAN(t *testing.T) { } } -// ibanValid checks the IBAN mod-97 rule independently of the generator: move the +// ibanValid checks the IBAN mod-97 rule independently of the builtin: move the // first four chars to the end, map letters A-Z to 10-35, the number mod 97 == 1. func ibanValid(s string) bool { r := s[4:] + s[:4] diff --git a/data_test.go b/data_test.go index 5caeff9..7de80ec 100644 --- a/data_test.go +++ b/data_test.go @@ -249,7 +249,7 @@ func digitsOnly(s string) string { } // luhnValid verifies a full number (payload + trailing check digit). It doubles -// from the second-from-right, independent of the generator's own Luhn code. +// from the second-from-right, independent of the builtin's own Luhn code. func luhnValid(s string) bool { sum, double := 0, false for i := len(s) - 1; i >= 0; i-- { diff --git a/fejkdata_test.go b/fejkdata_test.go index 666b359..ff5f564 100644 --- a/fejkdata_test.go +++ b/fejkdata_test.go @@ -5,8 +5,9 @@ import ( "testing" ) -// newGenerator creates a generator over a single data directory, failing on error. Most -// tests load one dir; newGeneratorN loads several (last-loaded wins on conflicts). +// newGenerator creates a generator over a single data directory, failing on +// error. Most tests load one dir; newGeneratorN loads several (last-loaded wins +// on conflicts). func newGenerator(t *testing.T, dir string, opts ...Option) *Generator { t.Helper() return newGeneratorN(t, []string{dir}, opts...)