Files
fejkdata/README.md
T

211 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fakes
A Go library for generating fake data, built for **internationalization**. It
exists because the existing Go fake libraries lacked the locale coverage and
format control we needed.
- **Standards first** — formats, names and structures follow international and
local standards first and foremost.
- **Locale-aware** — names, addresses, postal codes and phone numbers follow
per-locale data and formats. Locales are always full language + territory
tags (`sv_SE`, never `sv`).
- **Data lives in JSON** — all source data is recursive JSON on disk, read when
you create a faker and then served from memory. Add or change locales without
touching the library.
- **Composable** — templates nest without limit: weighted choices, character
classes and sub-templates combine to model any format.
- **Reproducible** — seed a faker and it emits the same sequence every time.
- **Zero dependencies** — standard library only.
## Install
```sh
go get github.com/Timewave-AB/fakes
```
Requires Go 1.26+.
## Usage
Point `New` at a locale directory, then generate values by category path with
`Fake`. The first path segment names a category (a JSON file); deeper dotted
segments descend into it.
```go
package main
import (
"fmt"
"log"
"github.com/Timewave-AB/fakes"
)
func main() {
f, err := fakes.New("./locales/sv_SE")
if err != nil {
log.Fatal(err)
}
for _, path := range []string{"person", "address", "phone", "address.locality"} {
v, err := f.Fake(path)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%-18s %s\n", path, v)
}
}
```
```
person Sara Eriksson
address Kungsvägen 68
379 17 Stockholm
phone 072-402 91 67
address.locality Linköping
```
Seed a faker for reproducible output — same seed + locale yields an identical
sequence, handy for stable tests:
```go
a, _ := fakes.New("./locales/sv_SE", fakes.WithSeed(42))
b, _ := fakes.New("./locales/sv_SE", fakes.WithSeed(42))
av, _ := a.Fake("person")
bv, _ := b.Fake("person")
av == bv // true
```
A `*Fakes` is **not** safe for concurrent use — create one per goroutine.
### Locales
The library ships a ready-to-use set under [`locales/`](locales) (`en_US`,
`sv_SE`). Pass `New` the path to one of them, a copy, or your own directory —
anywhere on disk.
Each ships these categories, formatted per locale (e.g. `date` is `MM/DD/YYYY`
in `en_US`, `YYYY-MM-DD` in `sv_SE`; `ssn` is a US SSN vs a Swedish
personnummer): `address`, `color`, `company`, `date`, `email`, `ip`, `person`,
`phone`, `price`, `sentence`, `ssn`, `time`, `url`, `username`, `uuid`,
`version`, `word`.
The directory's name is the locale and must be a full tag (`sv_SE`, never
`sv`). Any casing or separator is accepted and canonicalised (`sv-se`,
`SV_SE``sv_SE`); a non-full name returns an error.
## Locale data format
Each JSON file in a locale directory is a **category** named after the file
(`address.json``address`), rendered by `Fake("address")`. Drop in a new file
or folder — no code change, no recompile.
Every value is a **node**, one of three shapes, nestable without limit:
| Node | JSON | Meaning |
|------|------|---------|
| literal | `"Malmö"` | emitted verbatim — never formatted |
| choice | `["a", "b", …]` | one element, picked at random |
| template | `{"format": "…", …}` | a format string plus the named sub-nodes it references |
**Weight.** A template node may carry a `weight` (default `1`) to skew its odds
within a choice:
```json
[
{ "format": "#070-000 00 00", "weight": 10 },
{ "format": "#01-000 00 00" },
{ "format": "#010-000 00 00" }
]
```
**Format string.** Every character is literal except:
| Token | Expands to |
|-------|-----------|
| `0` | digit 09 |
| `1` | digit 19 |
| `A` | letter AZ |
| `a` | letter az |
| `#` | escape — the next char is literal (`#0``0`, `##``#`) |
| `{name}` | render the sibling field `name` |
`{a|b}` renders one of the sibling fields `a` or `b`, chosen at random.
**Putting it together** (`person.json`):
```json
[
{
"format": "{prefix}{femalefirst|malefirst} {last}",
"femalefirst": ["Anna", "Astrid", "Elin"],
"malefirst": ["Anders", "Erik", "Gustav"],
"last": [
{ "format": "{first}sson", "first": ["Ander", "Erik", "Karl"] },
["Berg", "von Flemming"]
],
"prefix": [
"",
{ "format": "{string} ", "string": ["dr", "prof"], "weight": 0.05 }
]
}
]
```
This yields e.g. `Anna Eriksson`, `Erik Berg`, or rarely `dr Astrid von Flemming`.
Any field is reachable by dotted path — `Fake("person.last")` renders just a
surname; choices along the path are resolved at random.
### Performance
Each file is parsed, validated and weight-indexed once, in `New`. After that a
`Fake` call costs about what its output costs — it scans the chosen format and
renders nested tokens, independent of how large your lists are:
- Picking from a list is **O(1)** whatever its length — a 10-name list and a
100 000-name list cost the same.
- Giving entries a `weight` makes that list's pick **O(log n)** instead (a
search over cumulative weights). Still tiny, but an unweighted list is the
cheapest — only add `weight` where you actually want skew.
- Long `format` strings, deep nesting and many `{tokens}` add cost in
proportion to the output produced.
## Development
Everything runs in Docker — **no local tooling beyond Docker is needed**.
Source is bind-mounted; build caches persist in the `gocache` volume.
```sh
docker compose run --rm test # run tests
docker compose run --rm ci # vet + format check + tests
docker compose run --rm cover # tests with coverage
docker compose run --rm build # compile the library
docker compose run --rm vet # go vet
docker compose run --rm dev # interactive shell
```
Commands that rewrite source keep your file ownership when run with `--user`:
```sh
docker compose run --rm --user "$(id -u):$(id -g)" fmt # gofmt -w .
docker compose run --rm --user "$(id -u):$(id -g)" tidy # go mod tidy
```
`docker build .` runs `go vet` and the tests, so it works as a CI gate too.
## Layout
```
fakes.go Fakes, New, options, seeding
template.go Fake, the recursive renderer (choices, format strings, paths)
locale.go locale loading + tag parsing
locales/ shipped locale data (JSON)
```
To add a category, drop a JSON file into a locale directory; to add a locale,
add a directory named with its full tag.
## License
MIT — see [LICENSE](LICENSE).