Rename the exported type to Generator and retire the faker wording #2

Merged
lilleman merged 3 commits from generator-rename into main 2026-09-01 21:46:31 +02:00
5 changed files with 30 additions and 29 deletions
Showing only changes of commit 5d33f4c0f0 - Show all commits
+13 -13
View File
@@ -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 27 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).
+7 -7
View File
@@ -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) }
}
+6 -6
View File
@@ -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]
+1 -1
View File
@@ -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-- {
+3 -2
View File
@@ -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...)