Rename the builtin kind from generator to sample
Tests / vet + fmt + tests (pull_request) Successful in 32s
Tests / vet + fmt + tests (pull_request) Successful in 32s
This commit is contained in:
@@ -19,9 +19,9 @@ lacked the locale coverage and format control we needed.
|
|||||||
id), never for what character classes and choices already do.
|
id), never for what character classes and choices already do.
|
||||||
- **Composable** — templates nest without limit: weighted choices, character
|
- **Composable** — templates nest without limit: weighted choices, character
|
||||||
classes and sub-templates combine to model any format.
|
classes and sub-templates combine to model any format.
|
||||||
- **Reproducible** — seed a generator and it emits the same sequence every time, for a
|
- **Reproducible** — seed a generator and it emits the same sequence every time,
|
||||||
given version of the data: changing how a value is composed shifts the stream for
|
for a given version of the data: changing how a value is composed shifts the
|
||||||
that value and for everything drawn after it in the same generator.
|
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` —
|
Every built-in draws only from that seed — no wall-clock, no `crypto/rand` —
|
||||||
so determinism holds end to end.
|
so determinism holds end to end.
|
||||||
- **Zero dependencies** — standard library only.
|
- **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.
|
point in time.
|
||||||
|
|
||||||
There are four kinds. **Derivations** read the digits emitted so far, so put them
|
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
|
**session counter** (`seq`) advances state held on the generator; and one
|
||||||
**computation** (`calc`) evaluates arithmetic over sibling fields. Arguments are
|
**computation** (`calc`) evaluates arithmetic over sibling fields. Arguments are
|
||||||
validated at `New` (a bad count, range, country, or expression fails fast); a
|
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 |
|
| `{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 |
|
| `{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 |
|
| `{ean()}` | derivation | EAN-13 / UPC-A / ISBN-13 / GTIN check digit |
|
||||||
| `{uuid()}` | generator | UUID v7 (v4 ships as data — see [Data](#data)) |
|
| `{uuid()}` | sample | UUID v7 (v4 ships as data — see [Data](#data)) |
|
||||||
| `{ulid()}` | generator | ULID, 26-char Crockford base32 |
|
| `{ulid()}` | sample | ULID, 26-char Crockford base32 |
|
||||||
| `{nanoid(n)}` | generator | URL-safe Nano ID, `n` chars |
|
| `{nanoid(n)}` | sample | URL-safe Nano ID, `n` chars |
|
||||||
| `{hex(n)}` | generator | `n` lowercase hex digits |
|
| `{hex(n)}` | sample | `n` lowercase hex digits |
|
||||||
| `{base64(n)}` | generator | `n` random bytes, base64 |
|
| `{base64(n)}` | sample | `n` random bytes, base64 |
|
||||||
| `{int(min,max)}` | generator | uniform integer in `[min, max]` |
|
| `{int(min,max)}` | sample | uniform integer in `[min, max]` |
|
||||||
| `{float(min,max,dp)}` | generator | number in `[min, max]` with `dp` decimals |
|
| `{float(min,max,dp)}` | sample | 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) |
|
| `{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 |
|
| `{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 |
|
| `{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
|
`{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
|
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
|
reader can't reach, so it emits the whole value (a generic numeric BBAN — valid
|
||||||
length and checksum, not real bank routing).
|
length and checksum, not real bank routing).
|
||||||
|
|||||||
+7
-7
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"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
|
// float/calc decimal places, so a fat-fingered or overflowing argument fails at
|
||||||
// New instead of trying to allocate gigabytes — or panicking — at render.
|
// New instead of trying to allocate gigabytes — or panicking — at render.
|
||||||
const (
|
const (
|
||||||
@@ -18,7 +18,7 @@ const (
|
|||||||
|
|
||||||
// builtins is the registry of {name(args)} functions. Two kinds: derivations read
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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))) })},
|
"luhn": {arity: 0, prep: derive(func(e string) string { return string(rune('0' + luhnCheck(e))) })},
|
||||||
"mod11": {arity: 0, prep: derive(mod11Check)},
|
"mod11": {arity: 0, prep: derive(mod11Check)},
|
||||||
"ean": {arity: 0, prep: derive(eanCheck)},
|
"ean": {arity: 0, prep: derive(eanCheck)},
|
||||||
"uuid": {arity: 0, prep: generate(uuidV7)},
|
"uuid": {arity: 0, prep: sample(uuidV7)},
|
||||||
"ulid": {arity: 0, prep: generate(ulid)},
|
"ulid": {arity: 0, prep: sample(ulid)},
|
||||||
"nanoid": {arity: 1, check: posIntArg, prep: func(a []string) callFn {
|
"nanoid": {arity: 1, check: posIntArg, prep: func(a []string) callFn {
|
||||||
n := atoi(a[0])
|
n := atoi(a[0])
|
||||||
return func(s *session, _ string, _ []string) string { return nanoid(s, n) }
|
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
|
// derive and sample 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
|
// 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.
|
// lifts that one function into the prep every registry entry supplies.
|
||||||
func derive(f func(emitted string) string) func([]string) callFn {
|
func derive(f func(emitted string) string) func([]string) callFn {
|
||||||
return 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([]string) callFn {
|
||||||
return func(s *session, _ string, _ []string) string { return f(s) }
|
return func(s *session, _ string, _ []string) string { return f(s) }
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -34,9 +34,9 @@ func TestBuiltinIDGenerators(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBuiltinGeneratorsReproducible pins the determinism guardrail: every
|
// TestBuiltinSamplesReproducible pins the determinism guardrail: every
|
||||||
// generator draws only from the seeded rng, so same seed -> same value.
|
// sample draws only from the seeded rng, so same seed -> same value.
|
||||||
func TestBuiltinGeneratorsReproducible(t *testing.T) {
|
func TestBuiltinSamplesReproducible(t *testing.T) {
|
||||||
for _, tmpl := range []string{
|
for _, tmpl := range []string{
|
||||||
`{"format":"{uuid()}"}`, `{"format":"{ulid()}"}`,
|
`{"format":"{uuid()}"}`, `{"format":"{ulid()}"}`,
|
||||||
`{"format":"{nanoid(12)}"}`, `{"format":"{int(1,1000000)}"}`,
|
`{"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
|
// TestBuiltinSeqPerSession pins seq's contract: a counter from 1, advancing on
|
||||||
// each call, named counters independent, and the whole thing scoped to one session
|
// each call, named counters independent, and the whole thing scoped to one
|
||||||
// so a fresh Generator restarts at 1.
|
// session so a fresh Generator restarts at 1.
|
||||||
func TestBuiltinSeqPerSession(t *testing.T) {
|
func TestBuiltinSeqPerSession(t *testing.T) {
|
||||||
f := engine(1)
|
f := engine(1)
|
||||||
for i := 1; i <= 5; i++ {
|
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.
|
// first four chars to the end, map letters A-Z to 10-35, the number mod 97 == 1.
|
||||||
func ibanValid(s string) bool {
|
func ibanValid(s string) bool {
|
||||||
r := s[4:] + s[:4]
|
r := s[4:] + s[:4]
|
||||||
|
|||||||
+1
-1
@@ -249,7 +249,7 @@ func digitsOnly(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// luhnValid verifies a full number (payload + trailing check digit). It doubles
|
// 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 {
|
func luhnValid(s string) bool {
|
||||||
sum, double := 0, false
|
sum, double := 0, false
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
|||||||
+3
-2
@@ -5,8 +5,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newGenerator creates a generator over a single data directory, failing on error. Most
|
// newGenerator creates a generator over a single data directory, failing on
|
||||||
// tests load one dir; newGeneratorN loads several (last-loaded wins on conflicts).
|
// error. Most tests load one dir; newGeneratorN loads several (last-loaded wins
|
||||||
|
// on conflicts).
|
||||||
func newGenerator(t *testing.T, dir string, opts ...Option) *Generator {
|
func newGenerator(t *testing.T, dir string, opts ...Option) *Generator {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return newGeneratorN(t, []string{dir}, opts...)
|
return newGeneratorN(t, []string{dir}, opts...)
|
||||||
|
|||||||
Reference in New Issue
Block a user