name_bank 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +97 -1
- data/lib/name_bank/pool_schema.rb +7 -2
- data/lib/name_bank/pool_store.rb +47 -11
- data/lib/name_bank/version.rb +1 -1
- data/lib/name_bank.rb +3 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 184aced32d90cfa194129bdb48895055b2738ed7bef92c1c1a42532421e6e4c6
|
|
4
|
+
data.tar.gz: 6305e8defeb53e8a6bbb2d7fd47e73a41c6e870cd83659dfad56f24a96f3ebd8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4eae078ebdd3bab2e6c3d455d3c783a6d03b5d119c386017c81d76e5ead0312e066cc24911621506b8c3b7637a88f679a1f5b8b1dfd3202427c0f274a3d3ef87
|
|
7
|
+
data.tar.gz: 3c7ea3d49e618280f40734193e487c61fc5c6a9c762a16925843533099e7e727feb693dd9c22a1453a29d63316c962506199a493f7469a898ef559f5f5f3020a
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ All notable changes to this project are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-08-11
|
|
8
|
+
|
|
9
|
+
Lookup, documentation and one platform bug. Sampling behaviour and the name
|
|
10
|
+
data are unchanged — verified byte-identical over 1415 samples against 0.2.0.
|
|
11
|
+
Nothing that worked before stops working; on Linux, one thing that failed now
|
|
12
|
+
works.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `scripts` takes an optional `variant:`. A variant can offer different script
|
|
16
|
+
forms from the country it is layered on; previously `scripts(country:)` only
|
|
17
|
+
ever reported the country's own.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Country and variant names now resolve against the directory listing instead
|
|
21
|
+
of being handed to `File.exist?`, so lookup no longer inherits the
|
|
22
|
+
filesystem's case-sensitivity. `country: "de"` found `DE.yml` on macOS and
|
|
23
|
+
raised `UnknownCountry` on Linux; it now finds it on both. An exact match
|
|
24
|
+
still wins, and names that differ only in case are reported as ambiguous
|
|
25
|
+
rather than guessed at.
|
|
26
|
+
- README: a pool with no names was documented as raising
|
|
27
|
+
`NameBank::UnknownScript`. It has raised `NameBank::EmptyPool` since 0.2.0.
|
|
28
|
+
|
|
29
|
+
### Documented
|
|
30
|
+
- The pool file format: the directory layout `NameBank.new(data_dir:)` expects,
|
|
31
|
+
which keys a file must carry, how `_native` pools are named, and which
|
|
32
|
+
failures a malformed file produces. Variant files may carry `_native` pools —
|
|
33
|
+
supported by the reader since 0.1.4, now stated and covered by a spec.
|
|
34
|
+
- `NameBank::PoolSchema` is now documented in the README and is supported
|
|
35
|
+
public API: `KEYS`, `GIVEN_MALE`, `GIVEN_FEMALE`, `SURNAMES`, `native_key`
|
|
36
|
+
and `gender_key`. It shipped before but was described nowhere. Callers
|
|
37
|
+
building pool files for `NameBank.new(data_dir:)` should address the YAML
|
|
38
|
+
keys through it. The key names are covered by semantic versioning from here
|
|
39
|
+
on: renaming one is a breaking change.
|
|
40
|
+
|
|
7
41
|
## [0.2.0] - 2026-08-08
|
|
8
42
|
|
|
9
43
|
An interface release. Sampling behaviour and the name data are unchanged — the
|
data/README.md
CHANGED
|
@@ -163,7 +163,15 @@ NameBank.scripts(country: "DE") # => [:latin]
|
|
|
163
163
|
Germany's script is Latin, so `DE` reports `:latin` only.
|
|
164
164
|
`:latin` and `:native` sample from independent pools. For Latin-script countries
|
|
165
165
|
`:native` returns the same (Latin) pool. Requesting a script with no names
|
|
166
|
-
raises `NameBank::
|
|
166
|
+
raises `NameBank::EmptyPool`; a `script:` that is neither `:latin` nor
|
|
167
|
+
`:native` raises `NameBank::UnknownScript`.
|
|
168
|
+
|
|
169
|
+
`scripts` takes an optional `variant:`, because a variant can offer different
|
|
170
|
+
forms from the country it is layered on:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
NameBank.scripts(country: "US", variant: "african_american") # => [:latin]
|
|
174
|
+
```
|
|
167
175
|
|
|
168
176
|
## Variants
|
|
169
177
|
|
|
@@ -204,6 +212,94 @@ rescue NameBank::UnknownCountry => e
|
|
|
204
212
|
end
|
|
205
213
|
```
|
|
206
214
|
|
|
215
|
+
## Pool file format
|
|
216
|
+
|
|
217
|
+
`NameBank.new(data_dir:)` reads pools from a directory of your own. It holds
|
|
218
|
+
two subdirectories:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
my_names/
|
|
222
|
+
countries/
|
|
223
|
+
DE.yml
|
|
224
|
+
RU.yml
|
|
225
|
+
variants/
|
|
226
|
+
US/
|
|
227
|
+
african_american.yml
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
- `countries/<code>.yml` — the basename is what you pass as `country:`, and
|
|
231
|
+
what `countries` lists.
|
|
232
|
+
- `variants/<code>/<name>.yml` — the basename is what you pass as `variant:`.
|
|
233
|
+
No directory for a country means `variants(country:)` returns `[]`.
|
|
234
|
+
- Files that do not end in `.yml` are ignored, and codes are not validated
|
|
235
|
+
against ISO 3166 — any basename works.
|
|
236
|
+
|
|
237
|
+
Names resolve the same way on every filesystem: an exact match wins, otherwise
|
|
238
|
+
a unique match ignoring case, so `country: "de"` finds `DE.yml` on Linux as
|
|
239
|
+
well as macOS. Two files whose basenames differ only in case are ambiguous, and
|
|
240
|
+
raise `NameBank::UnknownCountry` rather than being guessed at.
|
|
241
|
+
|
|
242
|
+
A pool file must carry the three keys in `NameBank::PoolSchema::KEYS`, each a
|
|
243
|
+
list. Any of them may have a native-script counterpart under the same key plus
|
|
244
|
+
`_native`:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
firstnames_male: [Dmitry, Ivan]
|
|
248
|
+
firstnames_female: [Anna, Olga]
|
|
249
|
+
lastnames: [Ivanov, Petrov]
|
|
250
|
+
|
|
251
|
+
firstnames_male_native: [Дмитрий, Иван]
|
|
252
|
+
lastnames_native: [Иванов, Петров]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Variant files are validated the same way and need all three keys too. They may
|
|
256
|
+
carry `_native` pools, which `script: :native` will use — the shipped variants
|
|
257
|
+
do not, because the build pipeline strips them.
|
|
258
|
+
|
|
259
|
+
Missing a required key, or holding something other than a list under it, raises
|
|
260
|
+
`NameBank::MalformedPool` when the file is read. A key that is present but
|
|
261
|
+
empty is accepted at read time and raises `NameBank::EmptyPool` when sampled.
|
|
262
|
+
Any other key is ignored; the shipped files carry `source:`, which nothing
|
|
263
|
+
reads at runtime.
|
|
264
|
+
|
|
265
|
+
Sampling is uniform over the whole list, so order does not weight anything. It
|
|
266
|
+
is preserved, and visible through `first_names` and `last_names`.
|
|
267
|
+
|
|
268
|
+
## Pool schema
|
|
269
|
+
|
|
270
|
+
`NameBank::PoolSchema` names the YAML keys a pool file uses. It is supported
|
|
271
|
+
public API from 0.2.0 on: the constants and their values will not change
|
|
272
|
+
without a version bump. Use it when you build pool files for
|
|
273
|
+
`NameBank.new(data_dir:)`, so you are not typing key names by hand.
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
NameBank::PoolSchema::KEYS
|
|
277
|
+
# => ["firstnames_male", "firstnames_female", "lastnames"]
|
|
278
|
+
|
|
279
|
+
NameBank::PoolSchema::GIVEN_MALE # => "firstnames_male"
|
|
280
|
+
NameBank::PoolSchema::GIVEN_FEMALE # => "firstnames_female"
|
|
281
|
+
NameBank::PoolSchema::SURNAMES # => "lastnames"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`KEYS` is frozen and lists the three pools every file must carry. A file may
|
|
285
|
+
also carry a native-script pool for any of them, under the same key with a
|
|
286
|
+
`_native` suffix:
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
NameBank::PoolSchema.native_key("lastnames") # => "lastnames_native"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`gender_key` maps a `gender:` argument to its key, and raises
|
|
293
|
+
`NameBank::UnknownGender` for anything else:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
NameBank::PoolSchema.gender_key(:female) # => "firstnames_female"
|
|
297
|
+
NameBank::PoolSchema.gender_key(:x) # raises NameBank::UnknownGender
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Reading a file that is missing one of `KEYS`, or that holds something other
|
|
301
|
+
than a list under it, raises `NameBank::MalformedPool`.
|
|
302
|
+
|
|
207
303
|
## Supported countries
|
|
208
304
|
|
|
209
305
|
106 countries: Afghanistan, Albania, Algeria, Angola, Argentina, Austria,
|
|
@@ -5,8 +5,13 @@ require_relative "errors"
|
|
|
5
5
|
# Declared as a class, not a module: NameBank itself is one (see lib/name_bank.rb).
|
|
6
6
|
class NameBank
|
|
7
7
|
# The pool-key schema: the base YAML keys, how native-script keys are named,
|
|
8
|
-
# and how a gender maps to its key.
|
|
9
|
-
# build-time SplitScripts tool
|
|
8
|
+
# and how a gender maps to its key. One home for the key names, shared by the
|
|
9
|
+
# runtime NameBank and the build-time SplitScripts tool.
|
|
10
|
+
#
|
|
11
|
+
# Supported public API from 0.2.0 on, documented in the README — callers
|
|
12
|
+
# building pool files for NameBank.new(data_dir:) address them through here.
|
|
13
|
+
# The constants and their values are therefore covered by semantic
|
|
14
|
+
# versioning: changing a key name is a breaking change.
|
|
10
15
|
module PoolSchema
|
|
11
16
|
GIVEN_MALE = "firstnames_male"
|
|
12
17
|
GIVEN_FEMALE = "firstnames_female"
|
data/lib/name_bank/pool_store.rb
CHANGED
|
@@ -11,6 +11,10 @@ class NameBank
|
|
|
11
11
|
# shape against PoolSchema, memoizing them, and listing what a data dir holds.
|
|
12
12
|
# NameBank itself never opens a file. Files are read once per instance, and
|
|
13
13
|
# validated once when read rather than on every pool access.
|
|
14
|
+
#
|
|
15
|
+
# Country and variant names are resolved against the directory listing rather
|
|
16
|
+
# than handed to File.exist?, so the result does not depend on whether the
|
|
17
|
+
# filesystem happens to be case-sensitive.
|
|
14
18
|
class PoolStore
|
|
15
19
|
def initialize(data_dir)
|
|
16
20
|
@data_dir = data_dir
|
|
@@ -28,27 +32,49 @@ class NameBank
|
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def variants(country)
|
|
31
|
-
|
|
35
|
+
code = resolve(country, variant_countries)
|
|
36
|
+
return [] if code.nil?
|
|
37
|
+
|
|
38
|
+
@variant_names[code] ||= yml_basenames(File.join(@data_dir, "variants", code))
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
private
|
|
35
42
|
|
|
43
|
+
# An exact match wins; otherwise a unique case-insensitive one; otherwise
|
|
44
|
+
# nil, because guessing between DE and de would be worse than refusing.
|
|
45
|
+
def resolve(name, candidates)
|
|
46
|
+
return name if candidates.include?(name)
|
|
47
|
+
|
|
48
|
+
hits = matches(name, candidates)
|
|
49
|
+
hits.size == 1 ? hits.first : nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def matches(name, candidates)
|
|
53
|
+
candidates.select { |candidate| candidate.casecmp?(name) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# What to put in the error: the name alone when nothing matched, or the
|
|
57
|
+
# rival spellings when several did.
|
|
58
|
+
def unresolved(name, candidates)
|
|
59
|
+
hits = matches(name, candidates)
|
|
60
|
+
hits.empty? ? name : "#{name} matches #{hits.join(", ")}"
|
|
61
|
+
end
|
|
62
|
+
|
|
36
63
|
def country_pools(country)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
raise UnknownCountry, country unless File.exist?(path)
|
|
64
|
+
code = resolve(country, countries)
|
|
65
|
+
raise UnknownCountry, unresolved(country, countries) if code.nil?
|
|
40
66
|
|
|
41
|
-
|
|
42
|
-
end
|
|
67
|
+
@country_pools[code] ||= read(File.join(@data_dir, "countries", "#{code}.yml"))
|
|
43
68
|
end
|
|
44
69
|
|
|
45
70
|
def variant_pools(country, variant)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
raise UnknownVariant, "#{country}/#{variant}" unless File.exist?(path)
|
|
71
|
+
code = resolve(country, variant_countries)
|
|
72
|
+
raise UnknownVariant, "#{country}/#{variant}" if code.nil?
|
|
49
73
|
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
name = resolve(variant, variants(code))
|
|
75
|
+
raise UnknownVariant, "#{country}/#{unresolved(variant, variants(code))}" if name.nil?
|
|
76
|
+
|
|
77
|
+
@variant_pools[[code, name]] ||= read(File.join(@data_dir, "variants", code, "#{name}.yml"))
|
|
52
78
|
end
|
|
53
79
|
|
|
54
80
|
def read(path)
|
|
@@ -59,6 +85,16 @@ class NameBank
|
|
|
59
85
|
data
|
|
60
86
|
end
|
|
61
87
|
|
|
88
|
+
def variant_countries
|
|
89
|
+
@variant_countries ||= subdirectories(File.join(@data_dir, "variants"))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def subdirectories(dir)
|
|
93
|
+
return [] unless Dir.exist?(dir)
|
|
94
|
+
|
|
95
|
+
Dir.children(dir).select { |child| File.directory?(File.join(dir, child)) }.sort
|
|
96
|
+
end
|
|
97
|
+
|
|
62
98
|
def yml_basenames(dir)
|
|
63
99
|
return [] unless Dir.exist?(dir)
|
|
64
100
|
|
data/lib/name_bank/version.rb
CHANGED
data/lib/name_bank.rb
CHANGED
|
@@ -71,8 +71,9 @@ class NameBank
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# The script forms this country offers, not writing systems — see CONTEXT.md.
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
# With a variant, the forms that variant offers, which may differ.
|
|
75
|
+
def scripts(country:, variant: nil)
|
|
76
|
+
data = @store.pools(country, variant)
|
|
76
77
|
PoolSchema::KEYS.any? { |k| data[PoolSchema.native_key(k)]&.any? } ? %i[latin native] : %i[latin]
|
|
77
78
|
end
|
|
78
79
|
|