///|
priv struct LocaleTable {
values : Array[String]
cumulative : Array[Double]
weighted : Bool
mode : String
}
///|
fn load_locales() -> Map[String, Map[String, LocaleTable]] {
let result : Map[String, Map[String, LocaleTable]] = Map([])
try {
let data = object(@json.parse(locale_source))
for locale, entries in data {
let tables : Map[String, LocaleTable] = Map([])
for provider_name, entry in object(entries) {
let fields = object(entry)
let values : Array[String] = @json.from_json(required(fields, "values"))
let weights : Array[Double] = @json.from_json(
required(fields, "weights"),
)
let cumulative = []
let mut sum = 0.0
for weight in weights {
sum += weight
cumulative.push(sum)
}
tables[provider_name] = {
values,
cumulative,
weighted: required(fields, "weighted") == true.to_json(),
mode: text_option(fields, "mode", "literal"),
}
}
result[locale] = tables
}
} catch {
_ => abort("invalid bundled locale data")
}
result
}
///|
let locale_tables : Map[String, Map[String, LocaleTable]] = load_locales()
///|
fn locale_table(
locale : String,
provider_name : String,
) -> LocaleTable raise FakeError {
guard locale_tables.get(locale) is Some(tables) else {
raise Invalid("unknown locale: " + locale)
}
guard tables.get(provider_name) is Some(table) else {
raise Invalid(
"unsupported localized provider: " + locale + "/" + provider_name,
)
}
table
}
///|
pub fn locales() -> Array[String] {
["en_US", "zh_CN", "en_GB", "de_DE", "fr_FR", "ja_JP"]
}
///|
pub fn locale_methods(locale : String) -> Array[String] raise FakeError {
guard locale_tables.get(locale) is Some(tables) else {
raise Invalid("unknown locale: " + locale)
}
let names = []
for name, _ in tables {
if !name.has_prefix("postcode_set_") {
names.push(name)
}
}
names.sort_by((a, b) => a.lexical_compare(b))
names
}
///|
/// A copy of the attributed source values/templates, useful for data inspection.
pub fn locale_values(
locale : String,
provider_name : String,
) -> Array[String] raise FakeError {
locale_table(locale, provider_name).values.copy()
}
///|
fn Generator::locale_pick(
self : Generator,
table : LocaleTable,
) -> String raise FakeError {
if !table.weighted {
return self.choose(table.values)
}
let draw = self.next().to_double() /
4294967296.0 *
table.cumulative[table.cumulative.length() - 1]
let mut low = 0
let mut high = table.cumulative.length() - 1
while low < high {
let middle = (low + high) / 2
if draw < table.cumulative[middle] {
high = middle
} else {
low = middle + 1
}
}
table.values[low]
}
///|
fn Generator::locale_symbols(
self : Generator,
pattern : String,
) -> String raise FakeError {
let mut text = pattern
// Faker numerify processes each placeholder kind in this order, then lexify.
for symbol in ['#', '%', '$', '!', '@', '?'] {
let out = StringBuilder()
for c in text {
if c != symbol {
out.write_char(c)
continue
}
let replacement = match symbol {
'#' => self.integer(0, 9).to_string()
'%' => self.integer(1, 9).to_string()
'$' => self.integer(2, 9).to_string()
'!' =>
if self.integer(0, 1) == 0 {
""
} else {
self.integer(0, 9).to_string()
}
'@' =>
if self.integer(0, 1) == 0 {
""
} else {
self.integer(1, 9).to_string()
}
_ =>
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".to_array()[self.integer(
0, 51,
)].to_string()
}
out.write_string(replacement)
}
text = out.to_string()
}
text
}
///|
fn Generator::localized_inner(
self : Generator,
locale : String,
provider_name : String,
depth : Int,
) -> String raise FakeError {
if depth > 16 {
raise Invalid("locale expansion depth")
}
let table = locale_table(locale, provider_name)
if table.mode == "us_postcode" {
let mut code = self.integer(501, 99950).to_string()
while code.length() < 5 {
code = "0" + code
}
return code
}
if table.mode == "jp_postcode" {
let mut first = self.integer(0, 999).to_string()
let mut last = self.integer(0, 9999).to_string()
while first.length() < 3 {
first = "0" + first
}
while last.length() < 4 {
last = "0" + last
}
return first + "-" + last
}
let selected = if table.mode == "fixed_template" {
table.values[0]
} else {
self.locale_pick(table)
}
if table.mode == "fr_postcode" {
let department = if selected == "2A" || selected == "2B" {
"20"
} else {
selected
}
return department +
(if department.length() == 2 {
self.integer(100, 999)
} else {
self.integer(10, 99)
}).to_string()
}
if table.mode == "literal" {
return selected
}
if table.mode == "delegate" {
return self.localized_inner(locale, selected, depth + 1)
}
if table.mode == "gb_postcode" {
let out = StringBuilder()
for c in selected {
out.write_string(
self.locale_pick(locale_table(locale, "postcode_set_" + c.to_string())),
)
}
return out.to_string()
}
let cs = selected.to_array()
let out = StringBuilder()
let mut i = 0
while i < cs.length() {
if i + 1 < cs.length() && cs[i] == '{' && cs[i + 1] == '{' {
i += 2
let token = StringBuilder()
while i + 1 < cs.length() && !(cs[i] == '}' && cs[i + 1] == '}') {
token.write_char(cs[i])
i += 1
}
if i + 1 >= cs.length() {
raise Invalid("unclosed locale token")
}
out.write_string(
self.localized_inner(locale, token.to_string(), depth + 1),
)
i += 2
} else {
out.write_char(cs[i])
i += 1
}
}
let expanded = out.to_string()
if expanded.length() > 4096 {
raise Invalid("locale output length")
}
self.locale_symbols(expanded)
}
///|
/// Locale-aware attributed provider data; the PRNG is local, not Python MT19937.
pub fn Generator::localized(
self : Generator,
locale : String,
provider_name : String,
) -> String raise FakeError {
if provider_name.has_prefix("postcode_set_") {
raise Invalid("internal locale table")
}
self.localized_inner(locale, provider_name, 0)
}