Developer tools

Fake data

Realistic fake people, addresses, companies and whole tables by locale and seed, the same for the same seed: demo data, seed scripts and tests without real customers.

Version 1.0.0 · 5 functions · 4 crates

#testing#seed#random#names#addresses#prototypes

Functions

What use fake_data.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use fake_people, fake_addresses from fake_data (Quillo 0.6).

fake_people(locale: str, count: int, seed: str) → listor an error

count fake people (up to 100,000) in locale ("en", "fr_fr", "pt_br", "ja_jp", "zh_cn", "zh_tw", "ar_sa"), the same ones for the same seed: { first_name, last_name, name, email, phone, username, job, birth_year }. Emails are made from the names, at example.com, .org and .net, so none reaches a real person.

fake_addresses(locale: str, count: int, seed: str) → listor an error

count fake addresses (up to 100,000) in locale, the same for the same seed: { street, city, postcode, state, country }.

fake_companies(locale: str, count: int, seed: str) → listor an error

count fake companies (up to 100,000) in locale, the same for the same seed: { name, industry, catch_phrase, profession, domain }.

fake_rows(columns: map, count: int, locale: str, seed: str) → listor an error

count rows (up to 100,000) shaped by columns, a map from column name to kind: "first_name", "email" (from that row's first_name and last_name when it has them), "city", "company", "int:1-100", "money:5-500", "date:2020-2026", "bool:0.8", "pick:new|paid|shipped", "uuid", "id" (1, 2, 3...) and more; the same rows for the same seed. For seed scripts: insert them as they are.

fake_locales() → list

The locales there are: their codes.

Get it

$ quillo install fake_data               # fake_data.qwer into this folder$ quillo install fake_data --example     # and its example, fake_data.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/fake_data.qwer$ curl -O https://quillolang.org/plugins/files/fake_data.qwe

Then, in your program, all of it or only what you call:

main.qwe
use fake_data.qweruse fake_people, fake_addresses from fake_data      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
fake = "=2.10.0"rand = "0.8"rand_chacha = "0.3"deunicode = "1"

Example

fake_data.qwe
// Demo data for a CRM: customers in three countries' styles, their addresses and// companies, and an orders table, the same every time for the same seed.////   quillo run fake_data.qwe      then open http://localhost:8080/customersuse "fake_data.qwer"route GET /customers ? { locale: str = "en", n: int = 3 } = fake_people(locale, n, "demo-2026")?route GET /mixed = {    en: fake_people("en", 1, "demo-2026")?[0],    fr: fake_people("fr_fr", 1, "demo-2026")?[0].name,    ja: fake_people("ja_jp", 1, "demo-2026")?[0].name,    address: fake_addresses("pt_br", 1, "demo-2026")?[0],    company: fake_companies("en", 1, "demo-2026")?[0],    locales: fake_locales(),}route GET /orders = fake_rows({    id: "id",    first_name: "first_name",    last_name: "last_name",    email: "email",    city: "city",    total: "money:5-500",    status: "pick:new|paid|shipped",    placed: "date:2025-2026",    gift: "bool:0.1",}, 5, "en", "orders-1")?// The same seed gives the same rows: a test can rely on them.route GET /same = { same: fake_rows({ n: "int:1-1000" }, 50, "en", "x")? == fake_rows({ n: "int:1-1000" }, 50, "en", "x")?, other: fake_rows({ n: "int:1-1000" }, 3, "en", "y")? }
$ quillo run fake_data.qwe$ curl 'localhost:8080/customers'$ curl 'localhost:8080/mixed'$ curl 'localhost:8080/orders'$ curl 'localhost:8080/same'

Source

fake_data.qwer
//! Realistic fake people, addresses, companies and rows by locale and seed, the same for the same seed: demo data, seed scripts and tests without real customers.//! version: 1.0.0//!//! [dependencies]//! fake = "=2.10.0"//! rand = "0.8"//! rand_chacha = "0.3"//! deunicode = "1"use fake::faker::address::raw as addr;use fake::faker::company::raw as co;use fake::faker::internet::raw as net;use fake::faker::job::raw as job;use fake::faker::lorem::raw as lorem;use fake::faker::name::raw as name;use fake::faker::phone_number::raw as phone;use fake::locales::{Data, AR_SA, EN, FR_FR, JA_JP, PT_BR, ZH_CN, ZH_TW};use fake::Fake;use rand::{Rng, SeedableRng};use rand_chacha::ChaCha8Rng;use serde_json::{json, Map, Value};const LOCALES: &str = "en, fr_fr, pt_br, ja_jp, zh_cn, zh_tw or ar_sa";fn rng(seed: &str) -> ChaCha8Rng {    let mut h: u64 = 0xcbf29ce484222325;    for b in seed.as_bytes() {        h ^= *b as u64;        h = h.wrapping_mul(0x100000001b3);    }    ChaCha8Rng::seed_from_u64(h)}fn ascii_word(s: &str) -> String {    deunicode::deunicode(s).to_lowercase().chars().filter(|c| c.is_ascii_alphanumeric()).collect()}fn email_for(first: &str, last: &str, r: &mut ChaCha8Rng) -> String {    let (f, l) = (ascii_word(first), ascii_word(last));    let domain = ["example.com", "example.org", "example.net"][r.gen_range(0..3)];    let user = match (f.is_empty(), l.is_empty()) {        (false, false) => match r.gen_range(0..3) {            0 => format!("{}.{}", f, l),            1 => format!("{}{}", &f[..1], l),            _ => format!("{}.{}{}", f, l, r.gen_range(1..100)),        },        (false, true) => f,        (true, false) => l,        _ => format!("user{}", r.gen_range(1000..10000)),    };    format!("{}@{}", user, domain)}fn s<T>(f: T, r: &mut ChaCha8Rng) -> Stringwhere    String: fake::Dummy<T>,{    f.fake_with_rng::<String, _>(r)}/// fake's city names put a space before a lower-case suffix ("Lockman berg"): joined, as Faker writes them ("Lockmanberg").fn city<L: Data + Copy>(l: L, r: &mut ChaCha8Rng) -> String {    let c = s(addr::CityName(l), r);    match c.rsplit_once(' ') {        Some((head, tail)) if L::ADDRESS_CITY_SUFFIX.contains(&tail) && tail.chars().next().is_some_and(char::is_lowercase) => format!("{}{}", head, tail),        _ => c,    }}fn person<L: Data + Copy>(l: L, r: &mut ChaCha8Rng) -> Value {    let first = s(name::FirstName(l), r);    let last = s(name::LastName(l), r);    let email = email_for(&first, &last, r);    let full = L::NAME_TPL.replace("{FirstName}", &first).replace("{LastName}", &last);    json!({        "first_name": first, "last_name": last, "name": full, "email": email,        "phone": s(phone::PhoneNumber(l), r), "username": s(net::Username(l), r),        "job": s(job::Title(l), r), "birth_year": r.gen_range(1950..2006),    })}fn address<L: Data + Copy>(l: L, r: &mut ChaCha8Rng) -> Value {    json!({        "street": format!("{} {}", s(addr::BuildingNumber(l), r), s(addr::StreetName(l), r)),        "city": city(l, r), "postcode": s(addr::PostCode(l), r), "state": s(addr::StateName(l), r),        "country": s(addr::CountryName(l), r),    })}fn company<L: Data + Copy>(l: L, r: &mut ChaCha8Rng) -> Value {    json!({        "name": s(co::CompanyName(l), r), "industry": s(co::Industry(l), r), "catch_phrase": s(co::CatchPhrase(l), r),        "profession": s(co::Profession(l), r), "domain": format!("{}.example", ascii_word(&s(name::LastName(l), r))),    })}fn range(spec: &str) -> Option<(f64, f64)> {    let (a, b) = spec.split_once('-').filter(|(a, _)| !a.is_empty()).or_else(|| spec.split_once(".."))?;    Some((a.trim().parse().ok()?, b.trim().parse().ok()?))}fn value<L: Data + Copy>(l: L, kind: &str, r: &mut ChaCha8Rng, row: &Map<String, Value>) -> Result<Value, String> {    let (k, arg) = kind.split_once(':').map(|(a, b)| (a.trim(), Some(b.trim()))).unwrap_or((kind.trim(), None));    Ok(match k {        "first_name" => json!(s(name::FirstName(l), r)),        "last_name" => json!(s(name::LastName(l), r)),        "name" => json!(s(name::Name(l), r)),        "email" => {            let (f, la) = (row.get("first_name").and_then(Value::as_str), row.get("last_name").and_then(Value::as_str));            match (f, la) {                (Some(f), Some(la)) => json!(email_for(f, la, r)),                _ => json!(s(net::SafeEmail(l), r)),            }        }        "phone" => json!(s(phone::PhoneNumber(l), r)),        "username" => json!(s(net::Username(l), r)),        "street" => json!(format!("{} {}", s(addr::BuildingNumber(l), r), s(addr::StreetName(l), r))),        "city" => json!(city(l, r)),        "postcode" => json!(s(addr::PostCode(l), r)),        "state" => json!(s(addr::StateName(l), r)),        "country" => json!(s(addr::CountryName(l), r)),        "company" => json!(s(co::CompanyName(l), r)),        "industry" => json!(s(co::Industry(l), r)),        "job" => json!(s(job::Title(l), r)),        "word" => json!(s(lorem::Word(l), r)),        "sentence" => json!(lorem::Sentence(l, 4..10).fake_with_rng::<String, _>(r)),        "paragraph" => json!(lorem::Paragraph(l, 3..6).fake_with_rng::<String, _>(r)),        "ip" => json!(format!("{}.{}.{}.{}", [192, 198, 203][r.gen_range(0..3)], r.gen_range(0..256), r.gen_range(0..256), r.gen_range(1..255))),        "uuid" => {            let mut b: [u8; 16] = r.gen();            b[6] = (b[6] & 0x0f) | 0x40;            b[8] = (b[8] & 0x3f) | 0x80;            let h: String = b.iter().map(|x| format!("{:02x}", x)).collect();            json!(format!("{}-{}-{}-{}-{}", &h[..8], &h[8..12], &h[12..16], &h[16..20], &h[20..]))        }        "bool" => json!(r.gen_bool(arg.and_then(|a| a.parse::<f64>().ok()).unwrap_or(0.5).clamp(0.0, 1.0))),        "int" => {            let (a, b) = arg.and_then(range).unwrap_or((1.0, 100.0));            json!(r.gen_range(a as i64..=b.max(a) as i64))        }        "float" | "money" => {            let (a, b) = arg.and_then(range).unwrap_or((0.0, 1.0));            let x: f64 = if b > a { r.gen_range(a..b) } else { a };            json!((x * 100.0).round() / 100.0)        }        "date" => {            // "date:2020-2026": a day in those years, ISO 8601.            let (a, b) = arg.and_then(range).unwrap_or((2020.0, 2026.0));            let y = r.gen_range(a as i32..=b.max(a) as i32);            let m = r.gen_range(1..=12);            let days = [31, if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m as usize - 1];            json!(format!("{:04}-{:02}-{:02}", y, m, r.gen_range(1..=days)))        }        "pick" => {            let opts: Vec<&str> = arg.unwrap_or("").split('|').map(str::trim).filter(|o| !o.is_empty()).collect();            if opts.is_empty() {                return Err("`pick` takes its options: \"pick:new|paid|shipped\"".into());            }            json!(opts[r.gen_range(0..opts.len())])        }        "id" => Value::Null,        other => return Err(format!("unknown kind `{}`: first_name, last_name, name, email, phone, username, street, city, postcode, state, country, company, industry, job, word, sentence, paragraph, ip, uuid, bool, int:1-100, float:0-1, money:5-500, date:2020-2026, pick:a|b|c or id", other)),    })}macro_rules! by_locale {    ($locale:expr, |$l:ident| $body:expr) => {        match $locale.to_lowercase().replace('-', "_").as_str() {            "en" | "en_us" => { let $l = EN; $body }            "fr" | "fr_fr" => { let $l = FR_FR; $body }            "pt" | "pt_br" => { let $l = PT_BR; $body }            "ja" | "ja_jp" => { let $l = JA_JP; $body }            "zh" | "zh_cn" => { let $l = ZH_CN; $body }            "zh_tw" => { let $l = ZH_TW; $body }            "ar" | "ar_sa" => { let $l = AR_SA; $body }            other => return Err(format!("no locale `{}`: {}", other, LOCALES)),        }    };}/// `count` fake people (up to 100,000) in `locale` ("en", "fr_fr", "pt_br", "ja_jp", "zh_cn", "zh_tw", "ar_sa"), the same ones for the/// same `seed`: `{ first_name, last_name, name, email, phone, username, job, birth_year }`. Emails are made from the names, at example.com,/// .org and .net, so none reaches a real person.pub fn fake_people(locale: &str, count: i64, seed: &str) -> Result<Vec<Value>, String> {    let mut r = rng(seed);    by_locale!(locale, |l| Ok((0..count.clamp(0, 100_000)).map(|_| person(l, &mut r)).collect()))}/// `count` fake addresses (up to 100,000) in `locale`, the same for the same `seed`: `{ street, city, postcode, state, country }`.pub fn fake_addresses(locale: &str, count: i64, seed: &str) -> Result<Vec<Value>, String> {    let mut r = rng(seed);    by_locale!(locale, |l| Ok((0..count.clamp(0, 100_000)).map(|_| address(l, &mut r)).collect()))}/// `count` fake companies (up to 100,000) in `locale`, the same for the same `seed`: `{ name, industry, catch_phrase, profession, domain }`.pub fn fake_companies(locale: &str, count: i64, seed: &str) -> Result<Vec<Value>, String> {    let mut r = rng(seed);    by_locale!(locale, |l| Ok((0..count.clamp(0, 100_000)).map(|_| company(l, &mut r)).collect()))}/// `count` rows (up to 100,000) shaped by `columns`, a map from column name to kind: "first_name", "email" (from that row's/// `first_name` and `last_name` when it has them), "city", "company", "int:1-100", "money:5-500", "date:2020-2026", "bool:0.8",/// "pick:new|paid|shipped", "uuid", "id" (1, 2, 3...) and more; the same rows for the same `seed`. For seed scripts: insert them as they are.pub fn fake_rows(columns: std::collections::HashMap<String, String>, count: i64, locale: &str, seed: &str) -> Result<Vec<Value>, String> {    let mut r = rng(seed);    let mut cols: Vec<(String, String)> = columns.into_iter().collect();    // Names before emails, so an email can follow its row's name; otherwise in key order, so the same seed gives the same rows.    cols.sort_by_key(|(k, kind)| (if kind.starts_with("email") { 1 } else { 0 }, k.clone()));    by_locale!(locale, |l| {        let mut out = vec![];        for i in 0..count.clamp(0, 100_000) {            let mut row = Map::new();            for (k, kind) in &cols {                let v = if kind == "id" { json!(i + 1) } else { value(l, kind, &mut r, &row)? };                row.insert(k.clone(), v);            }            out.push(Value::Object(row));        }        Ok(out)    })}/// The locales there are: their codes.pub fn fake_locales() -> Vec<String> {    LOCALES.replace(" or ", ", ").split(", ").map(String::from).collect()}
Lorem ipsum lorem.qwer · 1.0.0Placeholder text for mock-ups, seed data and tests: Lorem ipsum words, sentences, paragraphs and titles, the same every time for a given seed.seedmockupstestingtext7 views · 7 downloadsSeeded random seeded.qwer · 1.0.0Random that repeats for a seed: daily puzzles, fair draws, A/B groups and repeatable tests.randomgamestestingexperiments7 views · 7 downloadsJUnit XML reports junit_xml.qwer · 1.0.0JUnit XML test reports from Surefire, pytest, Jest and the rest read and written: nested suites, failures, errors and skips, Surefire's flaky and rerun elements, times with commas in them, totals that do not match the file noted, the slowest tests, shards merged, and several runs compared to find what is flaky.testingcireportsxml4 views · 7 downloadsTest Anything Protocol tap_results.qwer · 1.0.0TAP streams read and written, version 14 and older ones: plans first or last, SKIP and TODO in the spellings producers use, indented and buffered subtests, YAML diagnostics, Bail out!, escaped hashes. A summary across subtests that passes or fails by TAP's own rules: a failing TODO does not count, a wrong plan does.testingcireportstap3 views · 8 downloadsDice notation dice.qwer · 1.0.0Dice notation parsed and rolled from a seed: `4d6kh3`, `d20adv`, exploding and compounding dice, rerolls, success counts, Fudge dice and arithmetic, each die shown, and the exact distribution of an expression worked out by convolution rather than sampled, with the chance of meeting a target.dicerpgrandomseedprobabilitygame3 views · 9 downloads
Thread
79Useful? Like it, or add to its thread below.

Thread 0 posts

On Fake data: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.