addressing 0.7.0 → 1.1.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 +22 -0
- data/README.md +1 -1
- data/data/address_formats.dump +0 -0
- data/data/address_formats.json +10 -10
- data/data/country/hi-Latn.json +1 -1
- data/data/subdivision/UA.json +31 -31
- data/lib/addressing/address.rb +66 -78
- data/lib/addressing/address_format.rb +19 -8
- data/lib/addressing/country.rb +43 -13
- data/lib/addressing/default_formatter.rb +33 -19
- data/lib/addressing/enum.rb +4 -5
- data/lib/addressing/exceptions.rb +29 -0
- data/lib/addressing/field_override.rb +6 -13
- data/lib/addressing/locale.rb +1 -1
- data/lib/addressing/model.rb +0 -6
- data/lib/addressing/postal_label_formatter.rb +2 -2
- data/lib/addressing/subdivision.rb +36 -22
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +1 -8
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25d057964fdb1ec9c73f9652e2ce82974abf5309b52047e0927a51d51aed78da
|
4
|
+
data.tar.gz: 2c6e90b8c9db02dfb442e65a2bd11d5e6fa690f9e9772d30545a9feba6c9a9c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cd5595a0644d74eafcb7e859576ab24e9aaf4e684981cbe5d4ec9039fd425cb5659c6c33c5b9111f22c7e9ba6d8cc5d46821341c4124f22670c15cb54e4df69
|
7
|
+
data.tar.gz: 844a1e91ec57d11b867aa8c9d381661e6e560bc5d4f1b797bf363eb66528fbf345f9ff324214a1bcafd50b125aa789c5961bda732e7d0c056fd5e4fe7738700e
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,28 @@
|
|
2
2
|
|
3
3
|
All notable changes to `addressing` will be documented in this file.
|
4
4
|
|
5
|
+
## 1.1.0 (2025-10-03)
|
6
|
+
|
7
|
+
- Add formatter-level caching for Country.list
|
8
|
+
- Refactor FieldOverrides initialization to be more idiomatic
|
9
|
+
- Add equality methods to Address class
|
10
|
+
- Refactor complex values method in DefaultFormatter
|
11
|
+
- Extract magic strings to constants
|
12
|
+
- Simplify boolean validation in formatter options
|
13
|
+
- use .new() for UnknownLocaleError
|
14
|
+
- Added RDoc documentation
|
15
|
+
- Replaced class variables with class instance variables
|
16
|
+
- Add custom exception classes for better error handling
|
17
|
+
- Refactor Address class
|
18
|
+
- Fix test bug in address_test.rb
|
19
|
+
- Remove duplicate code in Model validation
|
20
|
+
- add missing % to generic address format
|
21
|
+
|
22
|
+
## 1.0.0 (2024-10-21)
|
23
|
+
|
24
|
+
- Sync data with commerceguys repository (v2.2.2)
|
25
|
+
- Drop support for Ruby 3.0
|
26
|
+
|
5
27
|
## 0.7.0 (2024-01-30)
|
6
28
|
|
7
29
|
- Update subdivisions for Philippines (PH)
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
A Ruby addressing library, powered by CLDR and Google's address data.
|
4
4
|
|
5
|
-
- Countries, with translations for over 250 locales. Powered by [CLDR](http://cldr.unicode.org)
|
5
|
+
- Countries, with translations for over 250 locales. Powered by [CLDR](http://cldr.unicode.org) v45.
|
6
6
|
- Address formats for over 200 countries.
|
7
7
|
- Subdivisions (administrative areas, localities, dependent localities) for 60 countries.
|
8
8
|
- Both latin and local subdivision names, when relevant (e.g: Okinawa / 沖縄県).
|
data/data/address_formats.dump
CHANGED
Binary file
|
data/data/address_formats.json
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
{"country_code":"AX","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"22\\d{3}","postal_code_prefix":"AX-"}
|
14
14
|
{"country_code":"AZ","format":"%given_name %additional_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}","postal_code_prefix":"AZ "}
|
15
15
|
{"country_code":"BA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
|
16
|
-
{"country_code":"BB","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area %postal_code","administrative_area_type":"parish","postal_code_pattern":"BB\\d{5}","subdivision_depth":1}
|
16
|
+
{"country_code":"BB","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"parish","postal_code_pattern":"BB\\d{5}","subdivision_depth":1}
|
17
17
|
{"country_code":"BD","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality - %postal_code","postal_code_pattern":"\\d{4}"}
|
18
18
|
{"country_code":"BE","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"\\d{4}"}
|
19
19
|
{"country_code":"BF","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %sorting_code"}
|
@@ -32,12 +32,12 @@
|
|
32
32
|
{"country_code":"CC","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","uppercase_fields":["locality"],"postal_code_pattern":"6799"}
|
33
33
|
{"country_code":"CH","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":[],"postal_code_pattern":"\\d{4}"}
|
34
34
|
{"country_code":"CI","format":"%given_name %family_name\n%organization\n%sorting_code %address_line1\n%address_line2\n%address_line3 %locality %sorting_code"}
|
35
|
-
{"country_code":"CL","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","postal_code_pattern":"\\d{7}","subdivision_depth":2}
|
35
|
+
{"country_code":"CL","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"region","postal_code_pattern":"\\d{7}","subdivision_depth":2}
|
36
36
|
{"country_code":"CN","locale":"zh-Hans","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%locality\n%administrative_area, %postal_code","local_format":"%postal_code\n%administrative_area%locality%dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%organization\n%family_name %given_name","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["administrative_area"],"dependent_locality_type":"district","postal_code_pattern":"\\d{6}","subdivision_depth":3}
|
37
37
|
{"country_code":"CO","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area, %postal_code","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"department","postal_code_pattern":"\\d{6}","subdivision_depth":1}
|
38
38
|
{"country_code":"CR","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area, %locality\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{4,5}|\\d{3}-\\d{4}","subdivision_depth":1}
|
39
39
|
{"country_code":"CU","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %administrative_area\n%postal_code","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
40
|
-
{"country_code":"CV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","administrative_area_type":"island","postal_code_pattern":"\\d{4}","subdivision_depth":1}
|
40
|
+
{"country_code":"CV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"island","postal_code_pattern":"\\d{4}","subdivision_depth":1}
|
41
41
|
{"country_code":"CX","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","uppercase_fields":["locality"],"postal_code_pattern":"6798"}
|
42
42
|
{"country_code":"CY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
|
43
43
|
{"country_code":"CZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"\\d{3} ?\\d{2}"}
|
@@ -47,7 +47,7 @@
|
|
47
47
|
{"country_code":"DZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
|
48
48
|
{"country_code":"EC","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code\n%locality","uppercase_fields":["locality","postal_code"],"postal_code_pattern":"\\d{6}"}
|
49
49
|
{"country_code":"EE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","postal_code"],"administrative_area_type":"county","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
50
|
-
{"country_code":"EG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area\n%postal_code","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
50
|
+
{"country_code":"EG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
51
51
|
{"country_code":"EH","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
|
52
52
|
{"country_code":"ES","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
53
53
|
{"country_code":"ET","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
|
@@ -82,7 +82,7 @@
|
|
82
82
|
{"country_code":"IN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"administrative_area_type":"state","postal_code_type":"pin","postal_code_pattern":"\\d{6}","subdivision_depth":1}
|
83
83
|
{"country_code":"IO","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"BBND 1ZZ"}
|
84
84
|
{"country_code":"IQ","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}"}
|
85
|
-
{"country_code":"IR","format":"%organization\n%given_name %family_name\n%administrative_area\n%locality, %dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%postal_code","dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}-?\\d{5}","subdivision_depth":1}
|
85
|
+
{"country_code":"IR","format":"%organization\n%given_name %family_name\n%administrative_area\n%locality, %dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}-?\\d{5}","subdivision_depth":1}
|
86
86
|
{"country_code":"IS","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{3}"}
|
87
87
|
{"country_code":"IT","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
88
88
|
{"country_code":"JE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}"}
|
@@ -92,7 +92,7 @@
|
|
92
92
|
{"country_code":"KE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"\\d{5}"}
|
93
93
|
{"country_code":"KG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
|
94
94
|
{"country_code":"KH","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","postal_code_pattern":"\\d{5,6}"}
|
95
|
-
{"country_code":"KI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area\n%locality","uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
|
95
|
+
{"country_code":"KI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area\n%locality","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
|
96
96
|
{"country_code":"KM","uppercase_fields":["address_line1","address_line2","address_line3","locality"]}
|
97
97
|
{"country_code":"KN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
|
98
98
|
{"country_code":"KP","locale":"ko","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area, %postal_code","local_format":"%postal_code\n%administrative_area\n%locality\n%address_line1\n%address_line2\n%address_line3\n%organization\n%given_name %family_name","subdivision_depth":1}
|
@@ -128,7 +128,7 @@
|
|
128
128
|
{"country_code":"MV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","postal_code_pattern":"\\d{5}"}
|
129
129
|
{"country_code":"MW","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %sorting_code"}
|
130
130
|
{"country_code":"MX","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality, %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area","postal_code"],"administrative_area_type":"state","dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
131
|
-
{"country_code":"MY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","dependent_locality_type":"village_township","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
131
|
+
{"country_code":"MY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","dependent_locality_type":"village_township","postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
132
132
|
{"country_code":"MZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality%administrative_area","postal_code_pattern":"\\d{4}","subdivision_depth":1}
|
133
133
|
{"country_code":"NA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"\\d{5}"}
|
134
134
|
{"country_code":"NC","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"988\\d{2}"}
|
@@ -143,7 +143,7 @@
|
|
143
143
|
{"country_code":"NZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%locality %postal_code","required_fields":["address_line1","locality","postal_code"],"locality_type":"town_city","postal_code_pattern":"\\d{4}"}
|
144
144
|
{"country_code":"OM","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code\n%locality","postal_code_pattern":"(?:PC )?\\d{3}"}
|
145
145
|
{"country_code":"PA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area","uppercase_fields":["locality","administrative_area"],"subdivision_depth":1}
|
146
|
-
{"country_code":"PE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","locality_type":"district","postal_code_pattern":"
|
146
|
+
{"country_code":"PE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"locality_type":"district","postal_code_pattern":"[0-2]\\d{4}","subdivision_depth":1}
|
147
147
|
{"country_code":"PF","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"island","postal_code_pattern":"987\\d{2}"}
|
148
148
|
{"country_code":"PG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code %administrative_area","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{3}","subdivision_depth":1}
|
149
149
|
{"country_code":"PH","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%postal_code %administrative_area","postal_code_pattern":"\\d{4}","subdivision_depth":1}
|
@@ -179,7 +179,7 @@
|
|
179
179
|
{"country_code":"SZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","uppercase_fields":["address_line1","address_line2","address_line3","locality","postal_code"],"postal_code_pattern":"[HLMS]\\d{3}"}
|
180
180
|
{"country_code":"TA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"TDCU 1ZZ"}
|
181
181
|
{"country_code":"TC","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"TKCA 1ZZ"}
|
182
|
-
{"country_code":"TH","locale":"th","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%administrative_area %postal_code","local_format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality %locality\n%administrative_area %postal_code","uppercase_fields":["administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
182
|
+
{"country_code":"TH","locale":"th","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%administrative_area %postal_code","local_format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality %locality\n%administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
|
183
183
|
{"country_code":"TJ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
|
184
184
|
{"country_code":"TM","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
|
185
185
|
{"country_code":"TN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
|
@@ -197,7 +197,7 @@
|
|
197
197
|
{"country_code":"VE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code, %administrative_area","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","postal_code_pattern":"\\d{4}","subdivision_depth":1}
|
198
198
|
{"country_code":"VG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1"],"postal_code_pattern":"VG\\d{4}"}
|
199
199
|
{"country_code":"VI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization"],"postal_code_type":"zip","postal_code_pattern":"(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?"}
|
200
|
-
{"country_code":"VN","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area %postal_code","postal_code_pattern":"\\d{5}\\d?","subdivision_depth":1}
|
200
|
+
{"country_code":"VN","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{5}\\d?","subdivision_depth":1}
|
201
201
|
{"country_code":"WF","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"986\\d{2}"}
|
202
202
|
{"country_code":"XK","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"[1-7]\\d{4}"}
|
203
203
|
{"country_code":"YT","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"976\\d{2}"}
|
data/data/country/hi-Latn.json
CHANGED
@@ -25,6 +25,7 @@
|
|
25
25
|
"BZ": "Belize",
|
26
26
|
"BJ": "Benin",
|
27
27
|
"BM": "Bermuda",
|
28
|
+
"IN": "Bharat",
|
28
29
|
"BT": "Bhutan",
|
29
30
|
"BO": "Bolivia",
|
30
31
|
"BA": "Bosnia & Herzegovina",
|
@@ -107,7 +108,6 @@
|
|
107
108
|
"HK": "Hong Kong SAR China",
|
108
109
|
"HU": "Hungary",
|
109
110
|
"IS": "Iceland",
|
110
|
-
"IN": "India",
|
111
111
|
"ID": "Indonesia",
|
112
112
|
"IR": "Iran",
|
113
113
|
"IQ": "Iraq",
|
data/data/subdivision/UA.json
CHANGED
@@ -3,115 +3,115 @@
|
|
3
3
|
"locale": "uk",
|
4
4
|
"subdivisions": {
|
5
5
|
"43": {
|
6
|
-
"name": "
|
6
|
+
"name": "Avtonomna Respublika Krym",
|
7
7
|
"local_name": "Автономна Республіка Крим"
|
8
8
|
},
|
9
9
|
"05": {
|
10
|
-
"name": "
|
10
|
+
"name": "Vinnytska oblast",
|
11
11
|
"local_name": "Вінницька область"
|
12
12
|
},
|
13
13
|
"07": {
|
14
|
-
"name": "
|
14
|
+
"name": "Volynska oblast",
|
15
15
|
"local_name": "Волинська область"
|
16
16
|
},
|
17
17
|
"12": {
|
18
|
-
"name": "
|
18
|
+
"name": "Dnipropetrovska oblast",
|
19
19
|
"local_name": "Дніпропетровська область"
|
20
20
|
},
|
21
21
|
"14": {
|
22
|
-
"name": "
|
22
|
+
"name": "Donetska oblast",
|
23
23
|
"local_name": "Донецька область"
|
24
24
|
},
|
25
25
|
"18": {
|
26
|
-
"name": "
|
26
|
+
"name": "Zhytomyrska oblast",
|
27
27
|
"local_name": "Житомирська область"
|
28
28
|
},
|
29
29
|
"21": {
|
30
|
-
"name": "
|
30
|
+
"name": "Zakarpatska oblast",
|
31
31
|
"local_name": "Закарпатська область"
|
32
32
|
},
|
33
33
|
"23": {
|
34
|
-
"name": "
|
34
|
+
"name": "Zaporizka oblast",
|
35
35
|
"local_name": "Запорізька область"
|
36
36
|
},
|
37
37
|
"26": {
|
38
|
-
"name": "Ivano-
|
38
|
+
"name": "Ivano-Frankivska oblast",
|
39
39
|
"local_name": "Івано-Франківська область"
|
40
40
|
},
|
41
41
|
"30": {
|
42
|
-
"code": "Kyiv
|
43
|
-
"local_code": "
|
44
|
-
"name": "Kyiv
|
42
|
+
"code": "Misto Kyiv",
|
43
|
+
"local_code": "Місто Київ",
|
44
|
+
"name": "Kyiv",
|
45
45
|
"local_name": "Київ"
|
46
46
|
},
|
47
47
|
"32": {
|
48
|
-
"name": "
|
48
|
+
"name": "Kyivska oblast",
|
49
49
|
"local_name": "Київська область"
|
50
50
|
},
|
51
51
|
"35": {
|
52
|
-
"name": "
|
52
|
+
"name": "Kirovohradska oblast",
|
53
53
|
"local_name": "Кіровоградська область"
|
54
54
|
},
|
55
55
|
"09": {
|
56
|
-
"name": "
|
56
|
+
"name": "Luhanska oblast",
|
57
57
|
"local_name": "Луганська область"
|
58
58
|
},
|
59
59
|
"46": {
|
60
|
-
"name": "
|
60
|
+
"name": "Lvivska oblast",
|
61
61
|
"local_name": "Львівська область"
|
62
62
|
},
|
63
63
|
"48": {
|
64
|
-
"name": "
|
64
|
+
"name": "Mykolaivska oblast",
|
65
65
|
"local_name": "Миколаївська область"
|
66
66
|
},
|
67
67
|
"51": {
|
68
|
-
"name": "
|
68
|
+
"name": "Odeska oblast",
|
69
69
|
"local_name": "Одеська область"
|
70
70
|
},
|
71
71
|
"53": {
|
72
|
-
"name": "
|
72
|
+
"name": "Poltavska oblast",
|
73
73
|
"local_name": "Полтавська область"
|
74
74
|
},
|
75
75
|
"56": {
|
76
|
-
"name": "
|
76
|
+
"name": "Rivnenska oblast",
|
77
77
|
"local_name": "Рівненська область"
|
78
78
|
},
|
79
79
|
"40": {
|
80
|
-
"code": "Sevastopol
|
81
|
-
"local_code": "
|
82
|
-
"name": "Sevastopol
|
80
|
+
"code": "Misto Sevastopol",
|
81
|
+
"local_code": "Місто Севастополь",
|
82
|
+
"name": "Sevastopol",
|
83
83
|
"local_name": "Севастополь"
|
84
84
|
},
|
85
85
|
"59": {
|
86
|
-
"name": "
|
86
|
+
"name": "Sumska oblast",
|
87
87
|
"local_name": "Сумська область"
|
88
88
|
},
|
89
89
|
"61": {
|
90
|
-
"name": "
|
90
|
+
"name": "Ternopilska oblast",
|
91
91
|
"local_name": "Тернопільська область"
|
92
92
|
},
|
93
93
|
"63": {
|
94
|
-
"name": "
|
94
|
+
"name": "Kharkivska oblast",
|
95
95
|
"local_name": "Харківська область"
|
96
96
|
},
|
97
97
|
"65": {
|
98
|
-
"name": "
|
98
|
+
"name": "Khersonska oblast",
|
99
99
|
"local_name": "Херсонська область"
|
100
100
|
},
|
101
101
|
"68": {
|
102
|
-
"name": "
|
102
|
+
"name": "Khmelnytska oblast",
|
103
103
|
"local_name": "Хмельницька область"
|
104
104
|
},
|
105
105
|
"71": {
|
106
|
-
"name": "
|
106
|
+
"name": "Cherkaska oblast",
|
107
107
|
"local_name": "Черкаська область"
|
108
108
|
},
|
109
109
|
"77": {
|
110
|
-
"name": "
|
110
|
+
"name": "Chernivetska oblast",
|
111
111
|
"local_name": "Чернівецька область"
|
112
112
|
},
|
113
113
|
"74": {
|
114
|
-
"name": "
|
114
|
+
"name": "Chernihivska oblast",
|
115
115
|
"local_name": "Чернігівська область"
|
116
116
|
}
|
117
117
|
}
|
data/lib/addressing/address.rb
CHANGED
@@ -1,9 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Addressing
|
4
|
+
# Represents a postal address with attributes for country, administrative areas,
|
5
|
+
# postal code, address lines, and recipient information.
|
6
|
+
#
|
7
|
+
# Address objects are immutable - use the +with_*+ methods to create modified copies.
|
8
|
+
#
|
9
|
+
# @example Creating an address
|
10
|
+
# address = Addressing::Address.new(
|
11
|
+
# country_code: "US",
|
12
|
+
# administrative_area: "CA",
|
13
|
+
# locality: "Mountain View",
|
14
|
+
# postal_code: "94043",
|
15
|
+
# address_line1: "1600 Amphitheatre Parkway"
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# @example Modifying an address
|
19
|
+
# updated = address.with_postal_code("94044")
|
4
20
|
class Address
|
5
|
-
|
6
|
-
|
21
|
+
FIELDS = %i[
|
22
|
+
country_code administrative_area locality dependent_locality
|
23
|
+
postal_code sorting_code address_line1 address_line2 address_line3
|
24
|
+
organization given_name additional_name family_name locale
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
attr_reader(*FIELDS)
|
28
|
+
|
29
|
+
# Creates a new Address instance.
|
30
|
+
#
|
31
|
+
# @param country_code [String] ISO 3166-1 alpha-2 country code
|
32
|
+
# @param administrative_area [String] Top-level administrative subdivision (state, province, etc.)
|
33
|
+
# @param locality [String] City or locality
|
34
|
+
# @param dependent_locality [String] Dependent locality (neighborhood, suburb, district, etc.)
|
35
|
+
# @param postal_code [String] Postal code
|
36
|
+
# @param sorting_code [String] Sorting code (used in some countries)
|
37
|
+
# @param address_line1 [String] First line of the street address
|
38
|
+
# @param address_line2 [String] Second line of the street address
|
39
|
+
# @param address_line3 [String] Third line of the street address
|
40
|
+
# @param organization [String] Organization name
|
41
|
+
# @param given_name [String] Given name (first name)
|
42
|
+
# @param additional_name [String] Additional name (middle name, patronymic)
|
43
|
+
# @param family_name [String] Family name (last name)
|
44
|
+
# @param locale [String] Locale code for the address
|
7
45
|
def initialize(country_code: "", administrative_area: "", locality: "", dependent_locality: "", postal_code: "", sorting_code: "", address_line1: "", address_line2: "", address_line3: "", organization: "", given_name: "", additional_name: "", family_name: "", locale: "und")
|
8
46
|
@country_code = country_code
|
9
47
|
@administrative_area = administrative_area
|
@@ -21,92 +59,42 @@ module Addressing
|
|
21
59
|
@locale = locale
|
22
60
|
end
|
23
61
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
address = clone
|
32
|
-
address.administrative_area = administrative_area
|
33
|
-
address
|
34
|
-
end
|
35
|
-
|
36
|
-
def with_locality(locality)
|
37
|
-
address = clone
|
38
|
-
address.locality = locality
|
39
|
-
address
|
40
|
-
end
|
41
|
-
|
42
|
-
def with_dependent_locality(dependent_locality)
|
43
|
-
address = clone
|
44
|
-
address.dependent_locality = dependent_locality
|
45
|
-
address
|
46
|
-
end
|
47
|
-
|
48
|
-
def with_postal_code(postal_code)
|
49
|
-
address = clone
|
50
|
-
address.postal_code = postal_code
|
51
|
-
address
|
52
|
-
end
|
53
|
-
|
54
|
-
def with_sorting_code(sorting_code)
|
55
|
-
address = clone
|
56
|
-
address.sorting_code = sorting_code
|
57
|
-
address
|
58
|
-
end
|
59
|
-
|
60
|
-
def with_address_line1(address_line1)
|
61
|
-
address = clone
|
62
|
-
address.address_line1 = address_line1
|
63
|
-
address
|
64
|
-
end
|
65
|
-
|
66
|
-
def with_address_line2(address_line2)
|
67
|
-
address = clone
|
68
|
-
address.address_line2 = address_line2
|
69
|
-
address
|
62
|
+
# Define with_* methods for all fields
|
63
|
+
FIELDS.each do |field|
|
64
|
+
define_method(:"with_#{field}") do |value|
|
65
|
+
address = clone
|
66
|
+
address.send(:"#{field}=", value)
|
67
|
+
address
|
68
|
+
end
|
70
69
|
end
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
def with_organization(organization)
|
79
|
-
address = clone
|
80
|
-
address.organization = organization
|
81
|
-
address
|
82
|
-
end
|
83
|
-
|
84
|
-
def with_given_name(given_name)
|
85
|
-
address = clone
|
86
|
-
address.given_name = given_name
|
87
|
-
address
|
88
|
-
end
|
71
|
+
# Compares two addresses for equality based on all field values.
|
72
|
+
#
|
73
|
+
# @param other [Object] The object to compare with
|
74
|
+
# @return [Boolean] true if all fields are equal
|
75
|
+
def ==(other)
|
76
|
+
return false unless other.is_a?(Address)
|
89
77
|
|
90
|
-
|
91
|
-
address = clone
|
92
|
-
address.additional_name = additional_name
|
93
|
-
address
|
78
|
+
FIELDS.all? { |field| send(field) == other.send(field) }
|
94
79
|
end
|
95
80
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
81
|
+
# Compares two addresses for equality (alias for ==).
|
82
|
+
#
|
83
|
+
# @param other [Object] The object to compare with
|
84
|
+
# @return [Boolean] true if all fields are equal
|
85
|
+
def eql?(other)
|
86
|
+
self == other
|
100
87
|
end
|
101
88
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
89
|
+
# Generates a hash code for the address based on all field values.
|
90
|
+
#
|
91
|
+
# @return [Integer] hash code
|
92
|
+
def hash
|
93
|
+
FIELDS.map { |field| send(field) }.hash
|
106
94
|
end
|
107
95
|
|
108
96
|
protected
|
109
97
|
|
110
|
-
attr_writer
|
98
|
+
attr_writer(*FIELDS)
|
111
99
|
end
|
112
100
|
end
|
@@ -1,20 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Addressing
|
4
|
+
# Provides address format information for countries.
|
5
|
+
#
|
6
|
+
# Address formats define which fields are used, their order, requirements,
|
7
|
+
# and formatting rules for postal addresses in different countries.
|
8
|
+
#
|
9
|
+
# @example Get address format for Brazil
|
10
|
+
# format = Addressing::AddressFormat.get('BR')
|
11
|
+
# format.used_fields # => ["given_name", "family_name", ...]
|
12
|
+
# format.required_fields # => ["address_line1", "locality", ...]
|
4
13
|
class AddressFormat
|
5
14
|
class << self
|
6
|
-
#
|
7
|
-
|
8
|
-
|
15
|
+
# Gets the address format for the provided country code.
|
16
|
+
#
|
17
|
+
# @param country_code [String] ISO 3166-1 alpha-2 country code
|
18
|
+
# @return [AddressFormat] Address format instance
|
9
19
|
def get(country_code)
|
10
20
|
country_code = country_code.upcase
|
21
|
+
@address_formats ||= {}
|
11
22
|
|
12
|
-
unless
|
23
|
+
unless @address_formats.key?(country_code)
|
13
24
|
definition = process_definition(definitions[country_code] || {country_code: country_code})
|
14
|
-
|
25
|
+
@address_formats[country_code] = new(definition)
|
15
26
|
end
|
16
27
|
|
17
|
-
|
28
|
+
@address_formats[country_code]
|
18
29
|
end
|
19
30
|
|
20
31
|
def all
|
@@ -27,7 +38,7 @@ module Addressing
|
|
27
38
|
private
|
28
39
|
|
29
40
|
def definitions
|
30
|
-
|
41
|
+
@definitions ||= Marshal.load(File.read(File.expand_path("../../../data/address_formats.dump", __FILE__).to_s))
|
31
42
|
end
|
32
43
|
|
33
44
|
def process_definition(definition)
|
@@ -42,7 +53,7 @@ module Addressing
|
|
42
53
|
|
43
54
|
def generic_definition
|
44
55
|
{
|
45
|
-
format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n
|
56
|
+
format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality",
|
46
57
|
required_fields: [
|
47
58
|
"address_line1", "locality"
|
48
59
|
],
|
data/lib/addressing/country.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Addressing
|
4
|
+
# Provides country information including names, codes, currency, and timezones.
|
5
|
+
#
|
6
|
+
# Country names are available in over 250 locales powered by CLDR data.
|
7
|
+
#
|
8
|
+
# @example Get a country by code
|
9
|
+
# brazil = Addressing::Country.get('BR')
|
10
|
+
# brazil.name # => "Brazil"
|
11
|
+
# brazil.currency_code # => "BRL"
|
12
|
+
#
|
13
|
+
# @example Get all countries
|
14
|
+
# countries = Addressing::Country.all('fr-FR')
|
15
|
+
#
|
16
|
+
# @example Get a simple list of countries
|
17
|
+
# list = Addressing::Country.list('en')
|
4
18
|
class Country
|
5
19
|
class << self
|
6
|
-
|
7
|
-
|
8
|
-
@@available_locales = [
|
20
|
+
AVAILABLE_LOCALES = [
|
9
21
|
"af", "am", "ar", "ar-LY", "ar-SA", "as", "az", "be", "bg", "bn",
|
10
22
|
"bn-IN", "bs", "ca", "chr", "cs", "cy", "da", "de", "de-AT", "de-CH",
|
11
23
|
"dsb", "el", "el-polyton", "en", "en-001", "en-AU", "en-CA", "en-ID",
|
@@ -23,12 +35,19 @@ module Addressing
|
|
23
35
|
"uz", "vi", "yue", "yue-Hans", "zh", "zh-Hant", "zh-Hant-HK", "zu"
|
24
36
|
]
|
25
37
|
|
38
|
+
# Gets a Country instance for the provided country code.
|
39
|
+
#
|
40
|
+
# @param country_code [String] ISO 3166-1 alpha-2 country code
|
41
|
+
# @param locale [String] Locale for the country name (default: "en")
|
42
|
+
# @param fallback_locale [String] Fallback locale if requested locale is unavailable
|
43
|
+
# @return [Country] Country instance
|
44
|
+
# @raise [UnknownCountryError] if the country code is not recognized
|
26
45
|
def get(country_code, locale = "en", fallback_locale = "en")
|
27
46
|
country_code = country_code.upcase
|
28
47
|
|
29
|
-
raise UnknownCountryError
|
48
|
+
raise UnknownCountryError.new(country_code) unless base_definitions.key?(country_code)
|
30
49
|
|
31
|
-
locale = Locale.resolve(
|
50
|
+
locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
|
32
51
|
definitions = load_definitions(locale)
|
33
52
|
|
34
53
|
new(
|
@@ -41,8 +60,13 @@ module Addressing
|
|
41
60
|
)
|
42
61
|
end
|
43
62
|
|
63
|
+
# Gets all Country instances.
|
64
|
+
#
|
65
|
+
# @param locale [String] Locale for country names (default: "en")
|
66
|
+
# @param fallback_locale [String] Fallback locale if requested locale is unavailable
|
67
|
+
# @return [Hash<String, Country>] Hash of country code => Country instance
|
44
68
|
def all(locale = "en", fallback_locale = "en")
|
45
|
-
locale = Locale.resolve(
|
69
|
+
locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
|
46
70
|
definitions = load_definitions(locale)
|
47
71
|
|
48
72
|
definitions.map do |country_code, country_name|
|
@@ -59,8 +83,13 @@ module Addressing
|
|
59
83
|
end.to_h
|
60
84
|
end
|
61
85
|
|
86
|
+
# Gets a list of country codes and names.
|
87
|
+
#
|
88
|
+
# @param locale [String] Locale for country names (default: "en")
|
89
|
+
# @param fallback_locale [String] Fallback locale if requested locale is unavailable
|
90
|
+
# @return [Hash<String, String>] Hash of country code => country name
|
62
91
|
def list(locale = "en", fallback_locale = "en")
|
63
|
-
locale = Locale.resolve(
|
92
|
+
locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
|
64
93
|
definitions = load_definitions(locale)
|
65
94
|
|
66
95
|
definitions.map do |country_code, country_name|
|
@@ -72,19 +101,20 @@ module Addressing
|
|
72
101
|
|
73
102
|
# Loads the country definitions for the provided locale.
|
74
103
|
def load_definitions(locale)
|
75
|
-
|
104
|
+
@definitions ||= {}
|
105
|
+
unless @definitions.key?(locale)
|
76
106
|
filename = File.join(File.expand_path("../../../data/country", __FILE__).to_s, "#{locale}.json")
|
77
|
-
|
107
|
+
@definitions[locale] = JSON.parse(File.read(filename))
|
78
108
|
end
|
79
109
|
|
80
|
-
|
110
|
+
@definitions[locale]
|
81
111
|
end
|
82
112
|
|
83
113
|
# Gets the base country definitions.
|
84
114
|
#
|
85
115
|
# Contains data common to all locales: three letter code, numeric code.
|
86
116
|
def base_definitions
|
87
|
-
|
117
|
+
@base_definitions ||= {
|
88
118
|
"AC" => ["ASC", nil, "SHP"],
|
89
119
|
"AD" => ["AND", "020", "EUR"],
|
90
120
|
"AE" => ["ARE", "784", "AED"],
|
@@ -139,7 +169,7 @@ module Addressing
|
|
139
169
|
"CR" => ["CRI", "188", "CRC"],
|
140
170
|
"CU" => ["CUB", "192", "CUP"],
|
141
171
|
"CV" => ["CPV", "132", "CVE"],
|
142
|
-
"CW" => ["CUW", "531", "
|
172
|
+
"CW" => ["CUW", "531", "XCG"],
|
143
173
|
"CX" => ["CXR", "162", "AUD"],
|
144
174
|
"CY" => ["CYP", "196", "EUR"],
|
145
175
|
"CZ" => ["CZE", "203", "CZK"],
|
@@ -300,7 +330,7 @@ module Addressing
|
|
300
330
|
"SS" => ["SSD", "728", "SSP"],
|
301
331
|
"ST" => ["STP", "678", "STN"],
|
302
332
|
"SV" => ["SLV", "222", "USD"],
|
303
|
-
"SX" => ["SXM", "534", "
|
333
|
+
"SX" => ["SXM", "534", "XCG"],
|
304
334
|
"SY" => ["SYR", "760", "SYP"],
|
305
335
|
"SZ" => ["SWZ", "748", "SZL"],
|
306
336
|
"TA" => ["TAA", nil, "GBP"],
|
@@ -2,8 +2,13 @@
|
|
2
2
|
|
3
3
|
module Addressing
|
4
4
|
class DefaultFormatter
|
5
|
+
DEFAULT_LOCALE = "en"
|
6
|
+
FORMAT_PLACEHOLDER_PATTERN = /%[a-z1-9_]+/
|
7
|
+
LEADING_TRAILING_PUNCTUATION_PATTERN = /\A[ \-,]+|[ \-,]+\z/
|
8
|
+
MULTIPLE_SPACES_PATTERN = /\s\s+/
|
9
|
+
|
5
10
|
DEFAULT_OPTIONS = {
|
6
|
-
locale:
|
11
|
+
locale: DEFAULT_LOCALE,
|
7
12
|
html: true,
|
8
13
|
html_tag: "p",
|
9
14
|
html_attributes: {translate: "no"}
|
@@ -13,6 +18,7 @@ module Addressing
|
|
13
18
|
assert_options(default_options)
|
14
19
|
|
15
20
|
@default_options = self.class::DEFAULT_OPTIONS.merge(default_options)
|
21
|
+
@country_list_cache = {}
|
16
22
|
end
|
17
23
|
|
18
24
|
def format(address, options = {})
|
@@ -33,7 +39,7 @@ module Addressing
|
|
33
39
|
view = render_view(view)
|
34
40
|
|
35
41
|
replacements = view.map { |key, element| ["%#{key}", element] }.to_h
|
36
|
-
output = format_string.gsub(
|
42
|
+
output = format_string.gsub(FORMAT_PLACEHOLDER_PATTERN) { |m| replacements[m] }
|
37
43
|
output = clean_output(output)
|
38
44
|
|
39
45
|
if options[:html]
|
@@ -49,7 +55,7 @@ module Addressing
|
|
49
55
|
|
50
56
|
# Builds the view for the given address.
|
51
57
|
def build_view(address, address_format, options)
|
52
|
-
countries =
|
58
|
+
countries = country_list(options[:locale])
|
53
59
|
values = values(address, address_format).merge({"country" => countries.key?(address.country_code) ? countries[address.country_code] : address.country_code})
|
54
60
|
used_fields = address_format.used_fields + ["country"]
|
55
61
|
|
@@ -58,6 +64,11 @@ module Addressing
|
|
58
64
|
end.to_h
|
59
65
|
end
|
60
66
|
|
67
|
+
# Gets the country list for a locale, with caching.
|
68
|
+
def country_list(locale)
|
69
|
+
@country_list_cache[locale] ||= Country.list(locale)
|
70
|
+
end
|
71
|
+
|
61
72
|
# Renders the given view.
|
62
73
|
def render_view(view)
|
63
74
|
view.map do |key, element|
|
@@ -90,27 +101,34 @@ module Addressing
|
|
90
101
|
|
91
102
|
# Removes empty lines, leading punctuation, excess whitespace.
|
92
103
|
def clean_output(output)
|
93
|
-
output.split("\n").map { |line| line.gsub(
|
104
|
+
output.split("\n").map { |line| line.gsub(LEADING_TRAILING_PUNCTUATION_PATTERN, "").strip.gsub(MULTIPLE_SPACES_PATTERN, " ") }.reject(&:empty?).join("\n")
|
94
105
|
end
|
95
106
|
|
96
107
|
# Gets the address values used to build the view.
|
97
108
|
def values(address, address_format)
|
98
|
-
values =
|
109
|
+
values = extract_address_values(address)
|
110
|
+
resolve_subdivision_values(values, address, address_format)
|
111
|
+
values
|
112
|
+
end
|
113
|
+
|
114
|
+
# Extracts all address field values.
|
115
|
+
def extract_address_values(address)
|
116
|
+
AddressField.all.map { |_, field| [field, address.send(field)] }.to_h
|
117
|
+
end
|
118
|
+
|
119
|
+
# Resolves subdivision values to their display codes.
|
120
|
+
def resolve_subdivision_values(values, address, address_format)
|
99
121
|
subdivision_fields = address_format.used_subdivision_fields
|
100
122
|
|
101
123
|
# Replace the subdivision values with the names of any predefined ones.
|
102
124
|
subdivision_fields.each_with_index.inject([{}, []]) do |(original_values, parents), (field, index)|
|
103
|
-
|
104
|
-
|
105
|
-
break
|
106
|
-
end
|
125
|
+
# This level is empty, so there can be no sublevels.
|
126
|
+
break if values[field].nil?
|
107
127
|
|
108
128
|
parents << ((index > 0) ? original_values[subdivision_fields[index - 1]] : address.country_code)
|
109
129
|
|
110
130
|
subdivision = Subdivision.get(values[field], parents)
|
111
|
-
if subdivision.nil?
|
112
|
-
break
|
113
|
-
end
|
131
|
+
break if subdivision.nil?
|
114
132
|
|
115
133
|
# Remember the original value so that it can be used for parents.
|
116
134
|
original_values[field] = values[field]
|
@@ -119,15 +137,11 @@ module Addressing
|
|
119
137
|
use_local_name = Locale.match_candidates(address.locale, subdivision.locale)
|
120
138
|
values[field] = use_local_name ? subdivision.local_code : subdivision.code
|
121
139
|
|
122
|
-
|
123
|
-
|
124
|
-
break
|
125
|
-
end
|
140
|
+
# The current subdivision has no children, stop.
|
141
|
+
break unless subdivision.children?
|
126
142
|
|
127
143
|
[original_values, parents]
|
128
144
|
end
|
129
|
-
|
130
|
-
values
|
131
145
|
end
|
132
146
|
|
133
147
|
private
|
@@ -142,7 +156,7 @@ module Addressing
|
|
142
156
|
end
|
143
157
|
end
|
144
158
|
|
145
|
-
if options.key?(:html) && !
|
159
|
+
if options.key?(:html) && ![true, false].include?(options[:html])
|
146
160
|
raise ArgumentError, "The option `html` must be a boolean."
|
147
161
|
end
|
148
162
|
|
data/lib/addressing/enum.rb
CHANGED
@@ -3,15 +3,14 @@
|
|
3
3
|
module Addressing
|
4
4
|
class Enum
|
5
5
|
class << self
|
6
|
-
@@values = {}
|
7
|
-
|
8
6
|
# Gets all available values.
|
9
7
|
def all
|
10
|
-
|
11
|
-
|
8
|
+
@values ||= {}
|
9
|
+
if !@values.key?(name)
|
10
|
+
@values[name] = constants.map { |constant| [constant, const_get(constant)] }.to_h
|
12
11
|
end
|
13
12
|
|
14
|
-
|
13
|
+
@values[name]
|
15
14
|
end
|
16
15
|
|
17
16
|
# Gets the key of the provided value.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Addressing
|
4
|
+
# Base error class for all Addressing errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Raised when an unknown country code is provided
|
8
|
+
class UnknownCountryError < Error
|
9
|
+
def initialize(country_code)
|
10
|
+
super("Unknown country code: #{country_code}")
|
11
|
+
@country_code = country_code
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :country_code
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when an unknown locale is provided
|
18
|
+
class UnknownLocaleError < Error
|
19
|
+
def initialize(locale)
|
20
|
+
super("Unknown locale: #{locale}")
|
21
|
+
@locale = locale
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :locale
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when an invalid argument is provided
|
28
|
+
class InvalidArgumentError < Error; end
|
29
|
+
end
|
@@ -14,20 +14,13 @@ module Addressing
|
|
14
14
|
AddressField.assert_all_exist(definition.keys)
|
15
15
|
FieldOverride.assert_all_exist(definition.values)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# Group fields by their override type
|
18
|
+
grouped = definition.group_by { |_field, override| override }
|
19
|
+
.transform_values { |pairs| pairs.map(&:first) }
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@hidden_fields << field
|
25
|
-
when FieldOverride::OPTIONAL
|
26
|
-
@optional_fields << field
|
27
|
-
when FieldOverride::REQUIRED
|
28
|
-
@required_fields << field
|
29
|
-
end
|
30
|
-
end
|
21
|
+
@hidden_fields = grouped[FieldOverride::HIDDEN] || []
|
22
|
+
@optional_fields = grouped[FieldOverride::OPTIONAL] || []
|
23
|
+
@required_fields = grouped[FieldOverride::REQUIRED] || []
|
31
24
|
end
|
32
25
|
end
|
33
26
|
end
|
data/lib/addressing/locale.rb
CHANGED
data/lib/addressing/model.rb
CHANGED
@@ -20,12 +20,6 @@ module Addressing
|
|
20
20
|
return unless address.country_code.present?
|
21
21
|
|
22
22
|
address_format = AddressFormat.get(address.country_code)
|
23
|
-
address_format.used_fields
|
24
|
-
|
25
|
-
return unless address.country_code.present?
|
26
|
-
|
27
|
-
address_format = AddressFormat.get(address.country_code)
|
28
|
-
address_format.used_fields
|
29
23
|
|
30
24
|
# Validate the presence of required fields.
|
31
25
|
AddressFormatHelper.required_fields(address_format, field_overrides).each do |required_field|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Addressing
|
4
4
|
class PostalLabelFormatter < DefaultFormatter
|
5
5
|
DEFAULT_OPTIONS = {
|
6
|
-
locale:
|
6
|
+
locale: DEFAULT_LOCALE,
|
7
7
|
html: false,
|
8
8
|
html_tag: "p",
|
9
9
|
html_attributes: {translate: "no"},
|
@@ -15,7 +15,7 @@ module Addressing
|
|
15
15
|
def build_view(address, address_format, options)
|
16
16
|
raise ArgumentError, "The origin_country option cannot be empty." if options[:origin_country].empty?
|
17
17
|
|
18
|
-
view = super
|
18
|
+
view = super
|
19
19
|
view = view.map do |key, element|
|
20
20
|
# Uppercase fields where required by the format.
|
21
21
|
element[:value] = element[:value].upcase if address_format.uppercase_fields.include?(key)
|
@@ -1,25 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Addressing
|
4
|
+
# Represents administrative subdivisions within countries.
|
5
|
+
#
|
6
|
+
# Subdivisions can be hierarchical with up to three levels:
|
7
|
+
# Administrative Area -> Locality -> Dependent Locality
|
8
|
+
#
|
9
|
+
# @example Get subdivisions for Brazil
|
10
|
+
# states = Addressing::Subdivision.all(['BR'])
|
11
|
+
# states.each do |code, state|
|
12
|
+
# puts "#{code}: #{state.name}"
|
13
|
+
# municipalities = state.children
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Get subdivisions for a Brazilian state
|
17
|
+
# municipalities = Addressing::Subdivision.all(['BR', 'CE'])
|
4
18
|
class Subdivision
|
5
19
|
class << self
|
6
|
-
# Subdivision
|
7
|
-
@@definitions = {}
|
8
|
-
|
9
|
-
# Parent subdivisions.
|
20
|
+
# Gets a Subdivision instance by ID and parent hierarchy.
|
10
21
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# memory usage.
|
15
|
-
@@parents = {}
|
16
|
-
|
22
|
+
# @param id [String] Subdivision ID
|
23
|
+
# @param parents [Array<String>] Parent hierarchy (e.g., ['BR'] or ['BR', 'CE'])
|
24
|
+
# @return [Subdivision, nil] Subdivision instance or nil if not found
|
17
25
|
def get(id, parents)
|
18
26
|
definitions = load_definitions(parents)
|
19
27
|
create_subdivision_from_definitions(id, definitions)
|
20
28
|
end
|
21
29
|
|
22
30
|
# Returns all subdivision instances for the provided parents.
|
31
|
+
#
|
32
|
+
# @param parents [Array<String>] Parent hierarchy (e.g., ['BR'] or ['BR', 'CE'])
|
33
|
+
# @return [Hash<String, Subdivision>] Hash of subdivision ID => Subdivision instance
|
23
34
|
def all(parents)
|
24
35
|
definitions = load_definitions(parents)
|
25
36
|
return {} if definitions.empty?
|
@@ -57,9 +68,10 @@ module Addressing
|
|
57
68
|
grandparents = parents.dup
|
58
69
|
parent_id = grandparents.pop
|
59
70
|
parent_group = build_group(grandparents.dup)
|
71
|
+
@definitions ||= {}
|
60
72
|
|
61
|
-
if
|
62
|
-
definition =
|
73
|
+
if @definitions.dig(parent_group, "subdivisions", parent_id)
|
74
|
+
definition = @definitions[parent_group]["subdivisions"][parent_id]
|
63
75
|
return !!definition["has_children"]
|
64
76
|
else
|
65
77
|
# The parent definition wasn't loaded previously, fallback to guessing based on depth.
|
@@ -73,12 +85,13 @@ module Addressing
|
|
73
85
|
|
74
86
|
# Loads the subdivision definitions for the provided parents.
|
75
87
|
def load_definitions(parents)
|
88
|
+
@definitions ||= {}
|
76
89
|
group = build_group(parents.dup)
|
77
|
-
if
|
78
|
-
return
|
90
|
+
if @definitions.key?(group)
|
91
|
+
return @definitions[group]
|
79
92
|
end
|
80
93
|
|
81
|
-
|
94
|
+
@definitions[group] = {}
|
82
95
|
|
83
96
|
# If there are predefined subdivisions at this level, try to load them.
|
84
97
|
if has_data(parents)
|
@@ -86,12 +99,12 @@ module Addressing
|
|
86
99
|
|
87
100
|
if File.exist?(filename)
|
88
101
|
raw_definition = File.read(filename)
|
89
|
-
|
90
|
-
|
102
|
+
@definitions[group] = JSON.parse(raw_definition)
|
103
|
+
@definitions[group] = process_definitions(@definitions[group])
|
91
104
|
end
|
92
105
|
end
|
93
106
|
|
94
|
-
|
107
|
+
@definitions[group]
|
95
108
|
end
|
96
109
|
|
97
110
|
# Processes the loaded definitions.
|
@@ -164,13 +177,14 @@ module Addressing
|
|
164
177
|
grandparents = parents.dup
|
165
178
|
parent_id = grandparents.pop
|
166
179
|
parent_group = build_group(grandparents.dup)
|
180
|
+
@parents ||= {}
|
167
181
|
|
168
|
-
if
|
169
|
-
|
170
|
-
|
182
|
+
if !@parents.dig(parent_group, parent_id)
|
183
|
+
@parents[parent_group] ||= {}
|
184
|
+
@parents[parent_group][parent_id] = get(parent_id, grandparents)
|
171
185
|
end
|
172
186
|
|
173
|
-
definition["parent"] =
|
187
|
+
definition["parent"] = @parents[parent_group][parent_id]
|
174
188
|
end
|
175
189
|
|
176
190
|
# Prepare children.
|
data/lib/addressing/version.rb
CHANGED
data/lib/addressing.rb
CHANGED
@@ -6,6 +6,7 @@ require "digest"
|
|
6
6
|
require "json"
|
7
7
|
|
8
8
|
# modules
|
9
|
+
require "addressing/exceptions"
|
9
10
|
require "addressing/enum"
|
10
11
|
require "addressing/address"
|
11
12
|
require "addressing/address_field"
|
@@ -23,14 +24,6 @@ require "addressing/postal_label_formatter"
|
|
23
24
|
require "addressing/subdivision"
|
24
25
|
require "addressing/version"
|
25
26
|
|
26
|
-
module Addressing
|
27
|
-
class Error < StandardError; end
|
28
|
-
|
29
|
-
class UnknownCountryError < Error; end
|
30
|
-
|
31
|
-
class UnknownLocaleError < Error; end
|
32
|
-
end
|
33
|
-
|
34
27
|
if defined?(ActiveSupport.on_load)
|
35
28
|
ActiveSupport.on_load(:active_record) do
|
36
29
|
require "addressing/model"
|
metadata
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: addressing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robin van der Vleuten
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
|
-
description:
|
14
12
|
email: robinvdvleuten@gmail.com
|
15
13
|
executables: []
|
16
14
|
extensions: []
|
@@ -712,6 +710,7 @@ files:
|
|
712
710
|
- lib/addressing/default_formatter.rb
|
713
711
|
- lib/addressing/dependent_locality_type.rb
|
714
712
|
- lib/addressing/enum.rb
|
713
|
+
- lib/addressing/exceptions.rb
|
715
714
|
- lib/addressing/field_override.rb
|
716
715
|
- lib/addressing/lazy_subdivisions.rb
|
717
716
|
- lib/addressing/locale.rb
|
@@ -724,8 +723,9 @@ files:
|
|
724
723
|
homepage: https://github.com/robinvdvleuten/addressing
|
725
724
|
licenses:
|
726
725
|
- MIT
|
727
|
-
metadata:
|
728
|
-
|
726
|
+
metadata:
|
727
|
+
bug_tracker_uri: https://github.com/robinvdvleuten/addressing/issues
|
728
|
+
changelog_uri: https://github.com/robinvdvleuten/addressing/blob/main/CHANGELOG.md
|
729
729
|
rdoc_options: []
|
730
730
|
require_paths:
|
731
731
|
- lib
|
@@ -733,15 +733,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
733
733
|
requirements:
|
734
734
|
- - ">="
|
735
735
|
- !ruby/object:Gem::Version
|
736
|
-
version: '3.
|
736
|
+
version: '3.1'
|
737
737
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
738
738
|
requirements:
|
739
739
|
- - ">="
|
740
740
|
- !ruby/object:Gem::Version
|
741
741
|
version: '0'
|
742
742
|
requirements: []
|
743
|
-
rubygems_version: 3.
|
744
|
-
signing_key:
|
743
|
+
rubygems_version: 3.6.8
|
745
744
|
specification_version: 4
|
746
745
|
summary: Addressing library powered by CLDR and Google's address data
|
747
746
|
test_files: []
|