From 05dc042e4945714dbc2c5254f65f58ff7270d28a Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 1 Sep 2026 20:24:03 +0200 Subject: [PATCH] Rename the project to fejkdata and record the fork source --- README.md | 48 +++++++++++++++------------- bench_test.go | 4 +-- bound_test.go | 2 +- builtins.go | 6 ++-- builtins_test.go | 2 +- calc.go | 8 ++--- calc_test.go | 12 +++---- cmd/{fakes => fejkdata}/main.go | 30 ++++++++--------- cmd/{fakes => fejkdata}/main_test.go | 0 data.go | 2 +- data_test.go | 24 +++++++------- edge_test.go | 18 +++++------ fakes.go => fejkdata.go | 24 +++++++------- fakes_test.go => fejkdata_test.go | 18 +++++------ go.mod | 2 +- loading_test.go | 20 ++++++------ node.go | 2 +- reference.go | 2 +- reference_test.go | 20 ++++++------ render.go | 14 ++++---- template.go | 2 +- template_stability_test.go | 4 +-- template_test.go | 6 ++-- 23 files changed, 136 insertions(+), 134 deletions(-) rename cmd/{fakes => fejkdata}/main.go (73%) rename cmd/{fakes => fejkdata}/main_test.go (100%) rename fakes.go => fejkdata.go (87%) rename fakes_test.go => fejkdata_test.go (60%) diff --git a/README.md b/README.md index e19c3b7..61ddfa4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# fakes +# fejkdata + +Forked from [github.com/Timewave-AB/fakes](https://github.com/Timewave-AB/fakes). A Go library and CLI for generating fake data, built for **internationalization**. It exists because the existing Go fake libraries @@ -26,21 +28,21 @@ lacked the locale coverage and format control we needed. ## CLI -Install the `fakes` command, then give it one or more `-data-path` directories +Install the `fejkdata` command, then give it one or more `-data-path` directories and a path — it prints one value to stdout. Each dot segment descends one level: folders, then the category (a JSON file), then fields inside it. ```sh -go install github.com/Timewave-AB/fakes/cmd/fakes@latest +go install gitea.larvit.se/larvit/fejkdata/cmd/fejkdata@latest -fakes -data-path ./data/sv_SE person # Sara Eriksson -fakes -data-path ./data/sv_SE person.last # Eriksson (dotted path into a category) -fakes -data-path ./data sv_SE.person # point at the tree; the folder is a segment -fakes -data-path ./data/sv_SE -data-path ./mydata word # layer dirs; the last wins a name clash -fakes -seed 42 -data-path ./data/sv_SE address -fakes -repeat 3 -data-path ./data/sv_SE person # three values, one per line -fakes -repeat 3 -separator ', ' -data-path ./data/sv_SE word # nät, barn, sol -fakes -data-path ./data/sv_SE -list # every path this data offers +fejkdata -data-path ./data/sv_SE person # Sara Eriksson +fejkdata -data-path ./data/sv_SE person.last # Eriksson (dotted path into a category) +fejkdata -data-path ./data sv_SE.person # point at the tree; the folder is a segment +fejkdata -data-path ./data/sv_SE -data-path ./mydata word # layer dirs; the last wins a name clash +fejkdata -seed 42 -data-path ./data/sv_SE address +fejkdata -repeat 3 -data-path ./data/sv_SE person # three values, one per line +fejkdata -repeat 3 -separator ', ' -data-path ./data/sv_SE word # nät, barn, sol +fejkdata -data-path ./data/sv_SE -list # every path this data offers ``` `-data-path` is repeatable (last wins a name clash) and the path comes last — @@ -49,7 +51,7 @@ independent draw — joined by `-separator` (default a newline, so values land o per line). Not sure what a data set offers? `-list` prints every path you can ask for; `-version` prints the build version. -Without installing, run it from a checkout with `go run ./cmd/fakes …`. Exit +Without installing, run it from a checkout with `go run ./cmd/fejkdata …`. Exit codes: `0` success (including `-list`, `-version`, `-h`), `1` runtime error (missing dir, unknown path), `2` misuse. @@ -76,7 +78,7 @@ the `),(` separator; the outer `V#ALUES(…)` wraps that into one valid row list letter token — see [Data format](#data-format).) ```sh -fakes -seed 1 -data-path ./data/sv_SE sql +fejkdata -seed 1 -data-path ./data/sv_SE sql # INSERT INTO users VALUES('zoom'),('wahoo'),('blip'); ``` @@ -84,13 +86,13 @@ Raise the template's `repeat` for more rows per statement; use the CLI's `-repeat` for more statements — together they build a whole seed file: ```sh -fakes -repeat 100 -data-path ./data/sv_SE sql > seed.sql +fejkdata -repeat 100 -data-path ./data/sv_SE sql > seed.sql ``` ## Library ```sh -go get github.com/Timewave-AB/fakes # requires Go 1.22+ (for math/rand/v2) +go get gitea.larvit.se/larvit/fejkdata # requires Go 1.22+ (for math/rand/v2) ``` Point `New` at one or more data directories, then generate values by path with @@ -104,11 +106,11 @@ import ( "fmt" "log" - "github.com/Timewave-AB/fakes" + "gitea.larvit.se/larvit/fejkdata" ) func main() { - f, err := fakes.New([]string{"./data/sv_SE"}) + f, err := fejkdata.New([]string{"./data/sv_SE"}) if err != nil { log.Fatal(err) } @@ -135,8 +137,8 @@ Seed a faker for reproducible output — same seed + locale yields an identical sequence, handy for stable tests: ```go -a, _ := fakes.New([]string{"./data/sv_SE"}, fakes.WithSeed(42)) -b, _ := fakes.New([]string{"./data/sv_SE"}, fakes.WithSeed(42)) +a, _ := fejkdata.New([]string{"./data/sv_SE"}, fejkdata.WithSeed(42)) +b, _ := fejkdata.New([]string{"./data/sv_SE"}, fejkdata.WithSeed(42)) av, _ := a.Fake("person") bv, _ := b.Fake("person") av == bv // true @@ -146,7 +148,7 @@ av == bv // true dotted fields and folder segments (what the CLI's `-list` prints). Every path it lists renders. -A `*Fakes` is **not** safe for concurrent use — create one per goroutine. +A `*Fejkdata` is **not** safe for concurrent use — create one per goroutine. ## Data @@ -167,7 +169,7 @@ by their children, and any other clash is won by the last directory loaded. That lets you layer your own data over the built-ins without copying them: ```go -fakes.New([]string{"./data/sv_SE", "./mydata"}) // mydata overrides on a clash +fejkdata.New([]string{"./data/sv_SE", "./mydata"}) // mydata overrides on a clash ``` Each shipped locale carries these categories, formatted per locale (e.g. `date` @@ -510,7 +512,7 @@ docker compose run --rm test # latest ## Layout ``` -fakes.go Fakes, New, List, options, seeding +fejkdata.go Fejkdata, New, List, options, seeding node.go the node model and JSON -> node compilation render.go Fake and the recursive renderer (choices, format strings, paths, bound draws) template.go the {token} grammar: scanning, function and path tokens, validation @@ -518,7 +520,7 @@ reference.go {..path} binding across the tree, the render graph, and the walk builtins.go the {name()} function registry and its implementations calc.go the {calc()} arithmetic evaluator: parser, eval, validation data.go data loading: folders/files -> namespace tree, multi-path merge -cmd/fakes/ the `fakes` CLI (New + Fake/List over stdout) +cmd/fejkdata/ the `fejkdata` CLI (New + Fake/List over stdout) data/ shipped data (JSON): locale folders + a misc folder ``` diff --git a/bench_test.go b/bench_test.go index 3d47162..a580693 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "os" @@ -6,7 +6,7 @@ import ( "testing" ) -func benchFaker(b *testing.B, dir string) *Fakes { +func benchFaker(b *testing.B, dir string) *Fejkdata { b.Helper() f, err := New([]string{dir}, WithSeed(1)) if err != nil { diff --git a/bound_test.go b/bound_test.go index fc68936..b6eeac0 100644 --- a/bound_test.go +++ b/bound_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" diff --git a/builtins.go b/builtins.go index 4249a32..462c950 100644 --- a/builtins.go +++ b/builtins.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "encoding/base64" @@ -97,7 +97,7 @@ const hexDigits = "0123456789abcdef" func atoi(s string) int { n, err := strconv.Atoi(s) if err != nil { - panic(fmt.Sprintf("fakes: builtin arg %q reached prep unvalidated: %v", s, err)) + panic(fmt.Sprintf("fejkdata: builtin arg %q reached prep unvalidated: %v", s, err)) } return n } @@ -106,7 +106,7 @@ func atoi(s string) int { func atof(s string) float64 { f, err := strconv.ParseFloat(s, 64) if err != nil { - panic(fmt.Sprintf("fakes: builtin arg %q reached prep unvalidated: %v", s, err)) + panic(fmt.Sprintf("fejkdata: builtin arg %q reached prep unvalidated: %v", s, err)) } return f } diff --git a/builtins_test.go b/builtins_test.go index 01b79fc..3237f89 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "encoding/base64" diff --git a/calc.go b/calc.go index bf0c28e..038fc3e 100644 --- a/calc.go +++ b/calc.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" @@ -48,7 +48,7 @@ func (n calcIdx) eval(operands []string) float64 { // than returning NaN, so a node kind indexVars forgets is a stack trace and not a // silently wrong number. func (n calcVar) eval([]string) float64 { - panic(fmt.Sprintf("fakes: calc operand %q was never placed", string(n))) + panic(fmt.Sprintf("fejkdata: calc operand %q was never placed", string(n))) } func (n calcNeg) eval(operands []string) float64 { return -n.x.eval(operands) } @@ -98,7 +98,7 @@ func checkCalc(fields map[string]node, args []string) error { func calcPrep(args []string) callFn { expr, err := parseCalc(args[0]) if err != nil { // a nil AST would be a nil dereference per render, with no message - panic(fmt.Sprintf("fakes: calc(%q) reached prep unparsed: %v", args[0], err)) + panic(fmt.Sprintf("fejkdata: calc(%q) reached prep unparsed: %v", args[0], err)) } at := make(map[string]int) for i, name := range calcVars(expr) { @@ -121,7 +121,7 @@ func indexVars(n calcNode, at map[string]int) calcNode { case calcVar: i, placed := at[string(n)] if !placed { // calcVars named every operand, so a miss means the two disagree - panic(fmt.Sprintf("fakes: calc operand %q is not among the names read for it", string(n))) + panic(fmt.Sprintf("fejkdata: calc operand %q is not among the names read for it", string(n))) } return calcIdx(i) case calcNeg: diff --git a/calc_test.go b/calc_test.go index 32fe72d..46a3261 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" @@ -126,7 +126,7 @@ func TestCalcOperandReadsTheExpansionsDraw(t *testing.T) { dir := writeData(t, map[string]string{ "inv": `{"format":"{net} x {qty} = {calc(net * qty, 2)}","net":["19.99","5.00","100.00"],"qty":["2","3","7"]}`, }) - f := newFakes(t, dir, WithSeed(3)) + f := newFejkdata(t, dir, WithSeed(3)) for i := 0; i < 300; i++ { got := fake(t, f, "inv") var net, qty, want float64 @@ -145,7 +145,7 @@ func TestCalcOperandSharesOneDraw(t *testing.T) { dir := writeData(t, map[string]string{ "same": `{"format":"{w} {w} {calc(w)}","w":["1","2","3","4","5"]}`, }) - f := newFakes(t, dir, WithSeed(5)) + f := newFejkdata(t, dir, WithSeed(5)) for i := 0; i < 200; i++ { got := fake(t, f, "same") if p := strings.Fields(got); len(p) != 3 || p[0] != p[1] || p[0] != p[2] { @@ -158,7 +158,7 @@ func TestCalcOperandSharesOneDraw(t *testing.T) { // held, so an ordinary {w} {w} still draws twice. func TestFieldNoCalcReadsDrawsEachTime(t *testing.T) { dir := writeData(t, map[string]string{"two": `{"format":"{w} {w}","w":["1","2","3","4","5"]}`}) - f := newFakes(t, dir, WithSeed(5)) + f := newFejkdata(t, dir, WithSeed(5)) for i := 0; i < 200; i++ { if p := strings.Fields(fake(t, f, "two")); p[0] != p[1] { return @@ -173,7 +173,7 @@ func TestCalcHoldIsPerExpansion(t *testing.T) { dir := writeData(t, map[string]string{ "rep": `{"format":"{n}={calc(n * 1)}","repeat":8,"separator":" ","n":["2","3","4","5","6","7","8","9"]}`, }) - f := newFakes(t, dir, WithSeed(11)) + f := newFejkdata(t, dir, WithSeed(11)) varied := false for i := 0; i < 50; i++ { got := fake(t, f, "rep") @@ -199,7 +199,7 @@ func TestCalcHoldIsPerTemplate(t *testing.T) { "nest": `{"format":"{v}={calc(v * 1)} {inner}","v":["2","3","4","5","6","7","8","9"], "inner":{"format":"{v}={calc(v * 1)}","v":["2","3","4","5","6","7","8","9"]}}`, }) - f := newFakes(t, dir, WithSeed(13)) + f := newFejkdata(t, dir, WithSeed(13)) differed := false for i := 0; i < 200; i++ { got := fake(t, f, "nest") diff --git a/cmd/fakes/main.go b/cmd/fejkdata/main.go similarity index 73% rename from cmd/fakes/main.go rename to cmd/fejkdata/main.go index 78a166c..24ac085 100644 --- a/cmd/fakes/main.go +++ b/cmd/fejkdata/main.go @@ -1,12 +1,12 @@ -// Command fakes prints one fake value from one or more data directories. +// Command fejkdata prints one fake value from one or more data directories. // -// fakes -data-path ./data/sv_SE person # a full person -// fakes -data-path ./data/sv_SE person.last # just the surname (dotted path) -// fakes -data-path ./data sv_SE.person # point at the tree, address by folder -// fakes -data-path ./data/sv_SE -data-path ./mydata person # layer custom data; last dir wins -// fakes -seed 42 -data-path ./data/sv_SE address +// fejkdata -data-path ./data/sv_SE person # a full person +// fejkdata -data-path ./data/sv_SE person.last # just the surname (dotted path) +// fejkdata -data-path ./data sv_SE.person # point at the tree, address by folder +// fejkdata -data-path ./data/sv_SE -data-path ./mydata person # layer custom data; last dir wins +// fejkdata -seed 42 -data-path ./data/sv_SE address // -// It is a thin CLI over the fakes library: New(dirs) then Fake(path). +// It is a thin CLI over the fejkdata library: New(dirs) then Fake(path). package main import ( @@ -18,10 +18,10 @@ import ( "runtime/debug" "strings" - "github.com/Timewave-AB/fakes" + "gitea.larvit.se/larvit/fejkdata" ) -const usage = `Usage: fakes -data-path D [-data-path D]... [-seed N] [-repeat N] [-separator S] +const usage = `Usage: fejkdata -data-path D [-data-path D]... [-seed N] [-repeat N] [-separator S] -data-path D a data directory, e.g. ./data/sv_SE (repeatable; last wins on clash) a category, or a dotted path into one (person, person.last) @@ -45,7 +45,7 @@ func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } // run is main's testable core: it returns the process exit code (0 ok, 1 // runtime error, 2 misuse) and writes only to the given streams. func run(args []string, stdout, stderr io.Writer) int { - fs := flag.NewFlagSet("fakes", flag.ContinueOnError) + fs := flag.NewFlagSet("fejkdata", flag.ContinueOnError) fs.SetOutput(stderr) fs.Usage = func() { fmt.Fprintln(stderr, usage) } seed := fs.Uint64("seed", 0, "seed for reproducible output") @@ -62,7 +62,7 @@ func run(args []string, stdout, stderr io.Writer) int { return 2 } if *showVersion { - fmt.Fprintln(stdout, "fakes "+buildVersion()) + fmt.Fprintln(stdout, "fejkdata "+buildVersion()) return 0 } if len(dirs) == 0 { @@ -70,15 +70,15 @@ func run(args []string, stdout, stderr io.Writer) int { return 2 } - var opts []fakes.Option + var opts []fejkdata.Option fs.Visit(func(fl *flag.Flag) { if fl.Name == "seed" { - opts = append(opts, fakes.WithSeed(*seed)) + opts = append(opts, fejkdata.WithSeed(*seed)) } }) if *list { - f, err := fakes.New(dirs, opts...) + f, err := fejkdata.New(dirs, opts...) if err != nil { fmt.Fprintln(stderr, err) return 1 @@ -98,7 +98,7 @@ func run(args []string, stdout, stderr io.Writer) int { } path := fs.Arg(0) - f, err := fakes.New(dirs, opts...) + f, err := fejkdata.New(dirs, opts...) if err != nil { fmt.Fprintln(stderr, err) return 1 diff --git a/cmd/fakes/main_test.go b/cmd/fejkdata/main_test.go similarity index 100% rename from cmd/fakes/main_test.go rename to cmd/fejkdata/main_test.go diff --git a/data.go b/data.go index 73ed80c..a5b0403 100644 --- a/data.go +++ b/data.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "encoding/json" diff --git a/data_test.go b/data_test.go index ffe1994..92dfb39 100644 --- a/data_test.go +++ b/data_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "regexp" @@ -51,8 +51,8 @@ func TestShippedDataCategories(t *testing.T) { regexp.MustCompile(`^\d{1,3}( \d{3})?(,\d{2})? kr$`)}, } - en := newFakes(t, "data/en_US", WithSeed(1)) - sv := newFakes(t, "data/sv_SE", WithSeed(1)) + en := newFejkdata(t, "data/en_US", WithSeed(1)) + sv := newFejkdata(t, "data/sv_SE", WithSeed(1)) for _, c := range cases { for i := 0; i < 200; i++ { if v := fake(t, en, c.path); !c.en.MatchString(v) { @@ -69,7 +69,7 @@ func TestShippedDataCategories(t *testing.T) { // v4 UUID (version/variant nibbles fixed), a MAC address, and credit-card numbers // whose trailing {luhn()} check passes. func TestShippedMiscCategories(t *testing.T) { - f := newFakes(t, "data/misc", WithSeed(1)) + f := newFejkdata(t, "data/misc", WithSeed(1)) v4 := regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) mac := regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`) objectid := regexp.MustCompile(`^[0-9a-f]{24}$`) @@ -93,7 +93,7 @@ func TestShippedMiscCategories(t *testing.T) { // codes follow their standard shape, dotted sub-paths resolve, emoji is a real // glyph, and a coordinate parses to a point within geographic bounds. func TestShippedMiscReferenceData(t *testing.T) { - f := newFakes(t, "data/misc", WithSeed(1)) + f := newFejkdata(t, "data/misc", WithSeed(1)) re := map[string]*regexp.Regexp{ "currency": regexp.MustCompile(`^[A-Z]{3}$`), "currency.name": regexp.MustCompile(`\p{L}`), @@ -134,7 +134,7 @@ func TestShippedMiscReferenceData(t *testing.T) { // TestSwedishPersonNamesHaveNoTripleLetter pins an orthographic rule the shape // regexes miss: no generated name repeats a character three times over. func TestSwedishPersonNamesHaveNoTripleLetter(t *testing.T) { - f := newFakes(t, "data/sv_SE", WithSeed(11)) + f := newFejkdata(t, "data/sv_SE", WithSeed(11)) for _, path := range []string{"person", "person.last"} { for i := 0; i < 20000; i++ { name := fake(t, f, path) @@ -152,7 +152,7 @@ func TestSwedishPersonNamesHaveNoTripleLetter(t *testing.T) { // is a real calendar date (so month-length variants never emit e.g. Apr 31 or // Feb 30) and the trailing digit is a valid Luhn checksum over the other nine. func TestSwedishPersonnummer(t *testing.T) { - sv := newFakes(t, "data/sv_SE", WithSeed(1)) + sv := newFejkdata(t, "data/sv_SE", WithSeed(1)) sawLongMonthEnd := false for i := 0; i < 2000; i++ { v := fake(t, sv, "ssn") @@ -176,7 +176,7 @@ func TestSwedishPersonnummer(t *testing.T) { } func TestShippedSwedishPhone(t *testing.T) { - f := newFakes(t, "data/sv_SE", WithSeed(11)) + f := newFejkdata(t, "data/sv_SE", WithSeed(11)) re := regexp.MustCompile(`^0\d{1,2}-\d{3} \d{2} \d{2}$`) for i := 0; i < 50; i++ { if n := fake(t, f, "phone"); !re.MatchString(n) { @@ -186,7 +186,7 @@ func TestShippedSwedishPhone(t *testing.T) { } func TestShippedSwedishAddress(t *testing.T) { - f := newFakes(t, "data/sv_SE", WithSeed(3)) + f := newFejkdata(t, "data/sv_SE", WithSeed(3)) digit := regexp.MustCompile(`\d`) for i := 0; i < 30; i++ { a := fake(t, f, "address") @@ -205,7 +205,7 @@ func TestShippedSwedishAddress(t *testing.T) { func TestShippedPersonHasParts(t *testing.T) { for _, dir := range []string{"data/sv_SE", "data/en_US"} { - f := newFakes(t, dir, WithSeed(7)) + f := newFejkdata(t, dir, WithSeed(7)) for i := 0; i < 30; i++ { if name := fake(t, f, "person"); len(name) < 3 || !regexp.MustCompile(`\S \S`).MatchString(name) { t.Fatalf("%s person %q lacks first and last name", dir, name) @@ -215,7 +215,7 @@ func TestShippedPersonHasParts(t *testing.T) { } func TestShippedUSPhone(t *testing.T) { - f := newFakes(t, "data/en_US", WithSeed(11)) + f := newFejkdata(t, "data/en_US", WithSeed(11)) re := regexp.MustCompile(`^(\(\d{3}\) \d{3}-\d{4}|\d{3}-\d{3}-\d{4})$`) for i := 0; i < 50; i++ { if n := fake(t, f, "phone"); !re.MatchString(n) { @@ -227,7 +227,7 @@ func TestShippedUSPhone(t *testing.T) { // TestShippedNamespacedTree loads the whole data/ tree (not a single locale) and // reaches each locale through its folder segment: data/sv_SE/person -> sv_SE.person. func TestShippedNamespacedTree(t *testing.T) { - f := newFakes(t, "data", WithSeed(1)) + f := newFejkdata(t, "data", WithSeed(1)) for _, path := range []string{"sv_SE.person", "en_US.person", "sv_SE.address.locality"} { if got := fake(t, f, path); got == "" { t.Fatalf("Fake(%q) returned empty", path) diff --git a/edge_test.go b/edge_test.go index 4d72ec5..f00a582 100644 --- a/edge_test.go +++ b/edge_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "encoding/json" @@ -240,7 +240,7 @@ func TestPathThroughChoice(t *testing.T) { "notall": `[{"format":"{f}","f":["1"]},["plain"]]`, "some": `[{"format":"{f}","f":["1"]},{"format":"{f}","f":["2"],"extra":["x"]}]`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) for i := 0; i < 200; i++ { if got := fake(t, f, "every.f"); got != "1" && got != "2" { t.Fatalf("every.f = %q, want 1 or 2", got) @@ -277,7 +277,7 @@ func TestPathKeyIsUnambiguous(t *testing.T) { t.Fatalf("New = %v, want the dotted field name rejected", err) } // The same data without the dotted key is fine, and the path resolves. - f := newFakes(t, writeData(t, map[string]string{ + f := newFejkdata(t, writeData(t, map[string]string{ "cat": `{"format":"{a.b}","a":{"format":"{b}","b":["2"]}}`, }), WithSeed(1)) if !slices.Contains(f.List(), "cat.a.b") { @@ -292,7 +292,7 @@ func TestPathKeyIsUnambiguous(t *testing.T) { // single-variant choice always picks the same item, so it needs no every-variant // guard and the error can name the field that is missing. func TestMissingFieldNamesItself(t *testing.T) { - f := newFakes(t, "data/sv_SE", WithSeed(1)) + f := newFejkdata(t, "data/sv_SE", WithSeed(1)) _, err := f.Fake("person.typo") if err == nil || !strings.Contains(err.Error(), `no field "typo"`) { t.Errorf("Fake(person.typo) = %v, want it to name the missing field", err) @@ -306,7 +306,7 @@ func TestCategoryRootShapes(t *testing.T) { "obj": `{"format":"00"}`, // object root "lit": `"hello"`, // bare-string root }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "obj"); !regexp.MustCompile(`^\d\d$`).MatchString(got) { t.Errorf("object-root category = %q, want two digits", got) } @@ -327,7 +327,7 @@ func TestLongStringList(t *testing.T) { if err != nil { t.Fatal(err) } - f := newFakes(t, writeData(t, map[string]string{"name": string(list)}), WithSeed(1)) + f := newFejkdata(t, writeData(t, map[string]string{"name": string(list)}), WithSeed(1)) valid := map[string]bool{} for _, v := range names { @@ -355,7 +355,7 @@ var swedishName = regexp.MustCompile(`^\p{L}+([ -]\p{L}+)*$`) func TestShippedStreetComposition(t *testing.T) { // street is a choice of composed {first}{last} templates and literal names. - f := newFakes(t, "data/sv_SE", WithSeed(5)) + f := newFejkdata(t, "data/sv_SE", WithSeed(5)) for i := 0; i < 300; i++ { if s := fake(t, f, "address.street"); !swedishName.MatchString(s) { t.Fatalf("street %q is not a Swedish street name", s) @@ -366,7 +366,7 @@ func TestShippedStreetComposition(t *testing.T) { func TestShippedLastNameComposition(t *testing.T) { // last is a choice of patronymic {first}sson templates, compound // {first}{last} templates and literal surnames. - f := newFakes(t, "data/sv_SE", WithSeed(6)) + f := newFejkdata(t, "data/sv_SE", WithSeed(6)) for i := 0; i < 300; i++ { if s := fake(t, f, "person.last"); !swedishName.MatchString(s) { t.Fatalf("last name %q is not a Swedish surname", s) @@ -376,7 +376,7 @@ func TestShippedLastNameComposition(t *testing.T) { func TestShippedStreetNumberFormats(t *testing.T) { // Reachable via a hyphenated path; covers all five weighted number variants. - f := newFakes(t, "data/sv_SE", WithSeed(8)) + f := newFejkdata(t, "data/sv_SE", WithSeed(8)) re := regexp.MustCompile(`^[1-9]\d{0,2}[A-Z]?$`) for i := 0; i < 300; i++ { if n := fake(t, f, "address.street-number"); !re.MatchString(n) { diff --git a/fakes.go b/fejkdata.go similarity index 87% rename from fakes.go rename to fejkdata.go index ef59cbc..2c88c96 100644 --- a/fakes.go +++ b/fejkdata.go @@ -1,10 +1,10 @@ -// Package fakes generates fake data from recursive JSON templates. +// Package fejkdata generates fake data from recursive JSON templates. // // Data lives in JSON on disk, not in Go. Point [New] at one or more data // directories; folders and files become a dot-path namespace, then generate // values by path: // -// f, _ := fakes.New([]string{"./data/sv_SE"}, fakes.WithSeed(42)) +// f, _ := fejkdata.New([]string{"./data/sv_SE"}, fejkdata.WithSeed(42)) // f.Fake("address") // "Storgatan 12\n234 56 Göteborg" // f.Fake("address.locality") // "Göteborg" // @@ -13,8 +13,8 @@ // the last one wins on a name clash, so you can layer custom data over the // built-ins. The JSON template format is documented in the README. // -// A [Fakes] is not safe for concurrent use; create one per goroutine. -package fakes +// A [Fejkdata] is not safe for concurrent use; create one per goroutine. +package fejkdata import ( crand "crypto/rand" @@ -24,14 +24,14 @@ import ( "sort" ) -// Fakes generates fake data from a loaded namespace tree. Create one with [New]. -type Fakes struct { +// Fejkdata generates fake data from a loaded namespace tree. Create one with [New]. +type Fejkdata struct { rand *session categories map[string]node // root namespace: name -> compiled node tree } // session is one faker's mutable render state: the seeded rng plus the {seq()} -// counters. Scoping it to the Fakes means sequences (and randomness) belong to +// counters. Scoping it to the Fejkdata means sequences (and randomness) belong to // that faker and reset when you create a new one. Embedding *rand.Rand makes a // *session satisfy the rng interface the renderer draws from. type session struct { @@ -50,7 +50,7 @@ type config struct { seeded bool } -// Option configures a [Fakes]. +// Option configures a [Fejkdata]. type Option func(*config) // WithSeed makes output reproducible: two fakers with the same seed and locale @@ -64,16 +64,16 @@ func WithSeed(seed uint64) Option { // "address") and each subdirectory a namespace segment; directories are merged // in order, the last winning a name clash. It errors on a missing directory, // invalid JSON, or no data found. -func New(paths []string, opts ...Option) (*Fakes, error) { +func New(paths []string, opts ...Option) (*Fejkdata, error) { cats, err := loadData(paths) if err != nil { - return nil, fmt.Errorf("fakes: %w", err) + return nil, fmt.Errorf("fejkdata: %w", err) } var c config for _, opt := range opts { opt(&c) } - return &Fakes{rand: newRand(c.seed, c.seeded), categories: cats}, nil + return &Fejkdata{rand: newRand(c.seed, c.seeded), categories: cats}, nil } // List returns the sorted dotted paths Fake can render: every category, the dotted @@ -81,7 +81,7 @@ func New(paths []string, opts ...Option) (*Fakes, error) { // single-variant choices the way a reference does. A choice consumes no segment, so // a path continues through a multi-variant one only where every variant carries it, // which is the rule Fake applies too: List is the set of paths Fake accepts. -func (f *Fakes) List() []string { +func (f *Fejkdata) List() []string { var out []string for _, name := range sortedNames(f.categories) { for _, p := range paths(f.categories[name]) { diff --git a/fakes_test.go b/fejkdata_test.go similarity index 60% rename from fakes_test.go rename to fejkdata_test.go index 361d661..fbf2301 100644 --- a/fakes_test.go +++ b/fejkdata_test.go @@ -1,18 +1,18 @@ -package fakes +package fejkdata import ( "strings" "testing" ) -// newFakes creates a faker over a single data directory, failing on error. Most -// tests load one dir; newFakesN loads several (last-loaded wins on conflicts). -func newFakes(t *testing.T, dir string, opts ...Option) *Fakes { +// newFejkdata creates a faker over a single data directory, failing on error. Most +// tests load one dir; newFejkdataN loads several (last-loaded wins on conflicts). +func newFejkdata(t *testing.T, dir string, opts ...Option) *Fejkdata { t.Helper() - return newFakesN(t, []string{dir}, opts...) + return newFejkdataN(t, []string{dir}, opts...) } -func newFakesN(t *testing.T, dirs []string, opts ...Option) *Fakes { +func newFejkdataN(t *testing.T, dirs []string, opts ...Option) *Fejkdata { t.Helper() f, err := New(dirs, opts...) if err != nil { @@ -22,7 +22,7 @@ func newFakesN(t *testing.T, dirs []string, opts ...Option) *Fakes { } // fake generates a value, failing the test on error. -func fake(t *testing.T, f *Fakes, path string) string { +func fake(t *testing.T, f *Fejkdata, path string) string { t.Helper() s, err := f.Fake(path) if err != nil { @@ -39,7 +39,7 @@ func TestNewMissingDirectory(t *testing.T) { } func TestWithSeedIsDeterministic(t *testing.T) { - a, b := newFakes(t, "data/sv_SE", WithSeed(42)), newFakes(t, "data/sv_SE", WithSeed(42)) + a, b := newFejkdata(t, "data/sv_SE", WithSeed(42)), newFejkdata(t, "data/sv_SE", WithSeed(42)) for i := 0; i < 50; i++ { if x, y := fake(t, a, "person"), fake(t, b, "person"); x != y { t.Fatalf("same seed diverged at %d: %q != %q", i, x, y) @@ -48,7 +48,7 @@ func TestWithSeedIsDeterministic(t *testing.T) { } func TestDifferentSeedsDiffer(t *testing.T) { - a, b := newFakes(t, "data/en_US", WithSeed(1)), newFakes(t, "data/en_US", WithSeed(2)) + a, b := newFejkdata(t, "data/en_US", WithSeed(1)), newFejkdata(t, "data/en_US", WithSeed(2)) for i := 0; i < 50; i++ { if fake(t, a, "person") != fake(t, b, "person") { return diff --git a/go.mod b/go.mod index 5f92fb6..d45362a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/Timewave-AB/fakes +module gitea.larvit.se/larvit/fejkdata go 1.22 diff --git a/loading_test.go b/loading_test.go index f0fb5e1..b2c9598 100644 --- a/loading_test.go +++ b/loading_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "os" @@ -21,7 +21,7 @@ func TestList(t *testing.T) { // Only the fields every variant carries are addressable, so "extra" is not. "coin": `[{"format":"{code}","code":["A"],"name":["Aa"]},{"format":"{code}","code":["B"],"name":["Bb"],"extra":["x"]}]`, }) - got := newFakes(t, dir, WithSeed(1)).List() + got := newFejkdata(t, dir, WithSeed(1)).List() want := []string{"coin", "coin.code", "coin.name", "geo.city", "greeting", "greeting.own", "person", "person.first", "person.last", "word"} if !reflect.DeepEqual(got, want) { t.Fatalf("List() = %v, want %v", got, want) @@ -50,7 +50,7 @@ func writeData(t *testing.T, files map[string]string) string { func TestNewLoadsAnyDirName(t *testing.T) { // No locale tag required: a directory named anything loads fine. dir := writeData(t, map[string]string{"greeting": `["hej", "hallå"]`}) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "greeting"); got != "hej" && got != "hallå" { t.Fatalf("greeting = %q, want hej or hallå", got) } @@ -69,7 +69,7 @@ func TestFoldersBecomeDotPaths(t *testing.T) { "sv_SE/greeting": `["hej"]`, "en_US/greeting": `["hi"]`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "sv_SE.greeting"); got != "hej" { t.Fatalf("sv_SE.greeting = %q, want hej", got) } @@ -84,7 +84,7 @@ func TestNestedFoldersAndJSON(t *testing.T) { dir := writeData(t, map[string]string{ "a/b/c/thing": `{"format":"{x}","x":{"format":"{y}","y":{"format":"{z}","z":["leaf"]}}}`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) // Folders a.b.c, file thing, then JSON fields x.y.z — one continuous path. if got := fake(t, f, "a.b.c.thing.x.y.z"); got != "leaf" { t.Fatalf("a.b.c.thing.x.y.z = %q, want leaf", got) @@ -97,7 +97,7 @@ func TestNestedFoldersAndJSON(t *testing.T) { func TestRenderingAFolderErrors(t *testing.T) { dir := writeData(t, map[string]string{"sv_SE/greeting": `["hej"]`}) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if _, err := f.Fake("sv_SE"); err == nil { t.Fatal("Fake(folder) = nil error, want a not-a-value error") } @@ -108,7 +108,7 @@ func TestRenderingAFolderErrors(t *testing.T) { func TestMultiPathLastWins(t *testing.T) { a := writeData(t, map[string]string{"greeting": `["from-a"]`, "only-a": `["a"]`}) b := writeData(t, map[string]string{"greeting": `["from-b"]`, "only-b": `["b"]`}) - f := newFakesN(t, []string{a, b}, WithSeed(1)) + f := newFejkdataN(t, []string{a, b}, WithSeed(1)) if got := fake(t, f, "greeting"); got != "from-b" { t.Fatalf("greeting = %q, want from-b (last loaded wins)", got) } @@ -126,7 +126,7 @@ func TestMultiPathLastWins(t *testing.T) { func TestMultiPathMergesFolders(t *testing.T) { a := writeData(t, map[string]string{"sv_SE/person": `["from-a"]`, "sv_SE/shared": `["a"]`}) b := writeData(t, map[string]string{"sv_SE/company": `["from-b"]`, "sv_SE/shared": `["b"]`}) - f := newFakesN(t, []string{a, b}, WithSeed(1)) + f := newFejkdataN(t, []string{a, b}, WithSeed(1)) if got := fake(t, f, "sv_SE.person"); got != "from-a" { t.Fatalf("sv_SE.person = %q, want from-a (folder merged, not replaced)", got) } @@ -142,7 +142,7 @@ func TestMultiPathMergesFolders(t *testing.T) { // tree: everything List advertises renders, every time, and the sub-fields the // README advertises are discoverable. func TestListedPathsAllRender(t *testing.T) { - f := newFakes(t, "data", WithSeed(4)) + f := newFejkdata(t, "data", WithSeed(4)) paths := f.List() for _, p := range paths { for i := 0; i < 20; i++ { @@ -171,7 +171,7 @@ func TestHiddenEntriesAreSkipped(t *testing.T) { if err := os.Rename(filepath.Join(dir, "empty.folder", "doc.json"), filepath.Join(dir, "empty.folder", "doc.txt")); err != nil { t.Fatal(err) } - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "cat"); got != "V" { t.Fatalf("cat = %q, want V", got) } diff --git a/node.go b/node.go index f5f73c9..4e81294 100644 --- a/node.go +++ b/node.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" diff --git a/reference.go b/reference.go index f5094c7..e449855 100644 --- a/reference.go +++ b/reference.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" diff --git a/reference_test.go b/reference_test.go index 3ddf688..e0027a2 100644 --- a/reference_test.go +++ b/reference_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import "testing" @@ -9,7 +9,7 @@ func TestRootReferenceAcrossFolders(t *testing.T) { "en_US/person": `["Pat Smith"]`, "sv_SE/greeting": `{"format":"Hej, {..en_US.person}!"}`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "sv_SE.greeting"); got != "Hej, Pat Smith!" { t.Fatalf("greeting = %q, want \"Hej, Pat Smith!\"", got) } @@ -22,7 +22,7 @@ func TestReferenceIntoAField(t *testing.T) { "who": `[{"format":"{first} {last}","first":["Ada"],"last":["Byron"]}]`, "card": `{"format":"signed {..who.last}"}`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "card"); got != "signed Byron" { t.Fatalf("card = %q, want \"signed Byron\"", got) } @@ -35,7 +35,7 @@ func TestReferenceInAlternation(t *testing.T) { "far": `["X"]`, "near": `{"format":"{here|..far}","here":["H"]}`, }) - f := newFakes(t, dir, WithSeed(2)) + f := newFejkdata(t, dir, WithSeed(2)) seen := map[string]bool{} for i := 0; i < 100; i++ { seen[fake(t, f, "near")] = true @@ -50,7 +50,7 @@ func TestReferenceInAlternation(t *testing.T) { func TestReferenceCombinesLoadedPaths(t *testing.T) { a := writeData(t, map[string]string{"en_US/word": `["river"]`}) b := writeData(t, map[string]string{"mine/slug": `{"format":"the-{..en_US.word}"}`}) - f := newFakesN(t, []string{a, b}, WithSeed(1)) + f := newFejkdataN(t, []string{a, b}, WithSeed(1)) if got := fake(t, f, "mine.slug"); got != "the-river" { t.Fatalf("slug = %q, want the-river", got) } @@ -64,7 +64,7 @@ func TestReferenceChain(t *testing.T) { "b": `{"format":"{..c}"}`, "c": `["deep"]`, }) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "a"); got != "deep" { t.Fatalf("a = %q, want deep", got) } @@ -118,7 +118,7 @@ func TestReferenceErrors(t *testing.T) { // starting with the reference prefix is covered by that same rule, since ".." // starts with "." — it is skipped, not rejected. func TestDotPrefixedDataEntriesAreSkipped(t *testing.T) { - f := newFakes(t, writeData(t, map[string]string{ + f := newFejkdata(t, writeData(t, map[string]string{ "sv_SE/ok": `["fine"]`, "sv_SE/..bad": `{"format":"{..nope}"}`, "sv_SE/..y/ct": `{"format":"{..nope}"}`, @@ -134,7 +134,7 @@ func TestDotPrefixedDataEntriesAreSkipped(t *testing.T) { // category, which terminates, and stays renderable by path. func TestReferenceFromUnrenderedFieldTerminates(t *testing.T) { dir := writeData(t, map[string]string{"cat": `{"format":"hi","x":{"format":"see {..cat}"}}`}) - f := newFakes(t, dir, WithSeed(1)) + f := newFejkdata(t, dir, WithSeed(1)) if got := fake(t, f, "cat"); got != "hi" { t.Fatalf("cat = %q, want hi", got) } @@ -189,12 +189,12 @@ func TestNewErrorPathIsCanonical(t *testing.T) { { "cycle inside a choice arm", map[string]string{"cat": `[{"format":"hi","x":{"format":"{..cat.x}"}}]`}, - "fakes: reference cycle: cat.x -> ..cat.x", + "fejkdata: reference cycle: cat.x -> ..cat.x", }, { "bad reference reached through another reference", map[string]string{"a": `{"format":"{..b}"}`, "b": `{"format":"{..nope}"}`}, - `fakes: b: reference {..nope}: no entry "nope"`, + `fejkdata: b: reference {..nope}: no entry "nope"`, }, } for _, c := range cases { diff --git a/render.go b/render.go index ee60f9c..8542b27 100644 --- a/render.go +++ b/render.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" @@ -17,13 +17,13 @@ type rng interface { // names and the category (JSON file) come first, then named fields within it, // e.g. "sv_SE.address" or "sv_SE.address.street". Choices along the way are // resolved at random. A path naming a folder (no value of its own) is an error. -func (f *Fakes) Fake(path string) (string, error) { +func (f *Fejkdata) Fake(path string) (string, error) { n, err := descend(f.rand, &group{children: f.categories}, strings.Split(path, ".")) if err != nil { - return "", fmt.Errorf("fakes: %s: %w", path, err) + return "", fmt.Errorf("fejkdata: %s: %w", path, err) } if _, ok := n.(*group); ok { - return "", fmt.Errorf("fakes: %s names a folder, not a value", path) + return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path) } return render(f.rand, n), nil } @@ -101,7 +101,7 @@ func render(s *session, n node) string { } return b.String() default: - panic(fmt.Sprintf("fakes: uncompiled node %T", n)) + panic(fmt.Sprintf("fejkdata: uncompiled node %T", n)) } } @@ -225,11 +225,11 @@ func readField(s *session, t *template, held *draws, a arm) string { func child(n node, seg string) node { t, ok := n.(*template) if !ok { - panic(fmt.Sprintf("fakes: %q under %T, which carries no fields", seg, n)) + panic(fmt.Sprintf("fejkdata: %q under %T, which carries no fields", seg, n)) } c, ok := t.fields[seg] if !ok { - panic(fmt.Sprintf("fakes: no field %q under a drawn level", seg)) + panic(fmt.Sprintf("fejkdata: no field %q under a drawn level", seg)) } return c } diff --git a/template.go b/template.go index fe41d8f..2ca0fb6 100644 --- a/template.go +++ b/template.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "fmt" diff --git a/template_stability_test.go b/template_stability_test.go index 88dbfdb..745a3e3 100644 --- a/template_stability_test.go +++ b/template_stability_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import "testing" @@ -31,7 +31,7 @@ func TestSeededOutputIsStable(t *testing.T) { "weights": {"big", "big", "big", "big"}, } for path := range want { - f := newFakes(t, dir, WithSeed(42)) + f := newFejkdata(t, dir, WithSeed(42)) for i, expect := range want[path] { if got := fake(t, f, path); got != expect { t.Errorf("%s draw %d = %q, want %q", path, i, got, expect) diff --git a/template_test.go b/template_test.go index 3d94595..060c49c 100644 --- a/template_test.go +++ b/template_test.go @@ -1,4 +1,4 @@ -package fakes +package fejkdata import ( "encoding/json" @@ -8,7 +8,7 @@ import ( ) // engine builds a seeded faker with no loaded categories, for rendering tests. -func engine(seed uint64) *Fakes { return &Fakes{rand: newRand(seed, true)} } +func engine(seed uint64) *Fejkdata { return &Fejkdata{rand: newRand(seed, true)} } // parse unmarshals a JSON template fragment into its dynamic form. func parse(t *testing.T, s string) any { @@ -30,7 +30,7 @@ func compiled(t *testing.T, s string) node { return n } -func mustRender(t *testing.T, f *Fakes, s string) string { +func mustRender(t *testing.T, f *Fejkdata, s string) string { t.Helper() return render(f.rand, compiled(t, s)) }