addressing 2.1.0 → 2.2.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 +32 -0
- data/README.md +223 -112
- data/data/UPSTREAM_VERSION +1 -0
- data/data/address_formats.json +2256 -206
- data/data/countries.json +1282 -0
- data/data/locale.json +222 -0
- data/data/subdivision/BR-AC.json +22 -22
- data/data/subdivision/BR-AL.json +102 -102
- data/data/subdivision/BR-AM.json +62 -62
- data/data/subdivision/BR-AP.json +16 -16
- data/data/subdivision/BR-BA.json +417 -417
- data/data/subdivision/BR-CE.json +184 -184
- data/data/subdivision/BR-DF.json +1 -1
- data/data/subdivision/BR-ES.json +79 -79
- data/data/subdivision/BR-GO.json +246 -246
- data/data/subdivision/BR-MA.json +217 -217
- data/data/subdivision/BR-MG.json +853 -853
- data/data/subdivision/BR-MS.json +78 -78
- data/data/subdivision/BR-MT.json +141 -141
- data/data/subdivision/BR-PA.json +144 -144
- data/data/subdivision/BR-PB.json +223 -223
- data/data/subdivision/BR-PE.json +185 -185
- data/data/subdivision/BR-PI.json +223 -223
- data/data/subdivision/BR-PR.json +400 -400
- data/data/subdivision/BR-RJ.json +93 -93
- data/data/subdivision/BR-RN.json +166 -166
- data/data/subdivision/BR-RO.json +52 -52
- data/data/subdivision/BR-RR.json +15 -15
- data/data/subdivision/BR-RS.json +497 -497
- data/data/subdivision/BR-SC.json +295 -295
- data/data/subdivision/BR-SE.json +75 -75
- data/data/subdivision/BR-SP.json +645 -645
- data/data/subdivision/BR-TO.json +139 -139
- data/data/subdivision/CL-AI.json +10 -10
- data/data/subdivision/CL-AN.json +9 -9
- data/data/subdivision/CL-AP.json +4 -4
- data/data/subdivision/CL-AR.json +32 -32
- data/data/subdivision/CL-AT.json +9 -9
- data/data/subdivision/CL-BI.json +34 -34
- data/data/subdivision/CL-CO.json +15 -15
- data/data/subdivision/CL-LI.json +33 -33
- data/data/subdivision/CL-LL.json +30 -30
- data/data/subdivision/CL-LR.json +12 -12
- data/data/subdivision/CL-MA.json +11 -11
- data/data/subdivision/CL-ML.json +30 -30
- data/data/subdivision/CL-NB.json +21 -21
- data/data/subdivision/CL-RM.json +52 -52
- data/data/subdivision/CL-TA.json +7 -7
- data/data/subdivision/CL-VS.json +38 -38
- data/data/subdivision/CV.json +9 -9
- data/data/subdivision/KY.json +3 -3
- data/data/subdivision/TV.json +1 -1
- data/lib/addressing/address.rb +7 -3
- data/lib/addressing/address_format.rb +55 -81
- data/lib/addressing/address_validator.rb +98 -0
- data/lib/addressing/blank.rb +32 -0
- data/lib/addressing/country.rb +28 -314
- data/lib/addressing/data_source.rb +75 -0
- data/lib/addressing/default_formatter.rb +25 -25
- data/lib/addressing/enum.rb +2 -6
- data/lib/addressing/field_violation.rb +17 -0
- data/lib/addressing/lazy_subdivisions.rb +16 -15
- data/lib/addressing/locale.rb +20 -269
- data/lib/addressing/model.rb +20 -78
- data/lib/addressing/postal_label_formatter.rb +16 -15
- data/lib/addressing/subdivision.rb +113 -116
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +6 -1
- metadata +9 -3
- data/data/address_formats.dump +0 -0
data/lib/addressing/country.rb
CHANGED
|
@@ -17,26 +17,6 @@ module Addressing
|
|
|
17
17
|
# list = Addressing::Country.list('en')
|
|
18
18
|
class Country
|
|
19
19
|
class << self
|
|
20
|
-
AVAILABLE_LOCALES = [
|
|
21
|
-
"af", "ak", "am", "ar", "ar-LY", "ar-SA", "as", "az", "ba", "be", "bg",
|
|
22
|
-
"bn", "bn-IN", "bs", "ca", "chr", "cs", "cv", "cy", "da", "de",
|
|
23
|
-
"de-AT", "de-CH", "dsb", "el", "el-polyton", "en", "en-001", "en-AU",
|
|
24
|
-
"en-CA", "en-EE", "en-GE", "en-LT", "en-LV", "en-UA", "es", "es-419",
|
|
25
|
-
"es-AR", "es-BO", "es-CL", "es-CO", "es-CR", "es-DO", "es-EC", "es-GT",
|
|
26
|
-
"es-HN", "es-MX", "es-NI", "es-PA", "es-PE", "es-PR", "es-PY", "es-SV",
|
|
27
|
-
"es-US", "es-VE", "et", "eu", "fa", "fa-AF", "fi", "fil", "fr",
|
|
28
|
-
"fr-BE", "fr-CA", "ga", "gd", "gl", "gu", "he", "hi", "hi-Latn", "hr",
|
|
29
|
-
"hsb", "hu", "hy", "id", "ig", "is", "it", "ja", "ka", "kk", "kk-Arab",
|
|
30
|
-
"km", "ko", "ko-KP", "kok", "ky", "lo", "lt", "lv", "mk", "ml", "mn",
|
|
31
|
-
"mr", "ms", "my", "ne", "nl", "nn", "no", "or", "pa", "pl", "ps",
|
|
32
|
-
"ps-PK", "pt", "pt-PT", "qu", "rm", "ro", "ro-MD", "ru", "ru-UA",
|
|
33
|
-
"shn", "si", "sk", "sl", "so", "sq", "sr", "sr-Cyrl-BA", "sr-Cyrl-ME",
|
|
34
|
-
"sr-Cyrl-XK", "sr-Latn", "sr-Latn-BA", "sr-Latn-ME", "sr-Latn-XK",
|
|
35
|
-
"sv", "sw", "sw-CD", "sw-KE", "ta", "te", "th", "tk", "tr", "uk", "ur",
|
|
36
|
-
"ur-IN", "uz", "vi", "yue", "yue-Hans", "zh", "zh-Hant", "zh-Hant-HK",
|
|
37
|
-
"zu"
|
|
38
|
-
]
|
|
39
|
-
|
|
40
20
|
# Gets a Country instance for the provided country code.
|
|
41
21
|
#
|
|
42
22
|
# @param country_code [String] ISO 3166-1 alpha-2 country code
|
|
@@ -49,17 +29,9 @@ module Addressing
|
|
|
49
29
|
|
|
50
30
|
raise UnknownCountryError.new(country_code) unless base_definitions.key?(country_code)
|
|
51
31
|
|
|
52
|
-
locale = Locale.resolve(
|
|
53
|
-
definitions = load_definitions(locale)
|
|
32
|
+
locale = Locale.resolve(available_locales, locale, fallback_locale)
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
country_code: country_code,
|
|
57
|
-
name: definitions[country_code],
|
|
58
|
-
three_letter_code: base_definitions[country_code][0],
|
|
59
|
-
numeric_code: base_definitions[country_code][1],
|
|
60
|
-
currency_code: base_definitions[country_code][2],
|
|
61
|
-
locale: locale
|
|
62
|
-
)
|
|
34
|
+
build(country_code, load_definitions(locale)[country_code], locale)
|
|
63
35
|
end
|
|
64
36
|
|
|
65
37
|
# Gets all Country instances.
|
|
@@ -68,21 +40,11 @@ module Addressing
|
|
|
68
40
|
# @param fallback_locale [String] Fallback locale if requested locale is unavailable
|
|
69
41
|
# @return [Hash<String, Country>] Hash of country code => Country instance
|
|
70
42
|
def all(locale = "en", fallback_locale = "en")
|
|
71
|
-
locale = Locale.resolve(
|
|
72
|
-
definitions = load_definitions(locale)
|
|
73
|
-
|
|
74
|
-
definitions.map do |country_code, country_name|
|
|
75
|
-
country = new(
|
|
76
|
-
country_code: country_code,
|
|
77
|
-
name: country_name,
|
|
78
|
-
three_letter_code: base_definitions[country_code][0],
|
|
79
|
-
numeric_code: base_definitions[country_code][1],
|
|
80
|
-
currency_code: base_definitions[country_code][2],
|
|
81
|
-
locale: locale
|
|
82
|
-
)
|
|
43
|
+
locale = Locale.resolve(available_locales, locale, fallback_locale)
|
|
83
44
|
|
|
84
|
-
|
|
85
|
-
|
|
45
|
+
load_definitions(locale).to_h do |country_code, country_name|
|
|
46
|
+
[country_code, build(country_code, country_name, locale)]
|
|
47
|
+
end
|
|
86
48
|
end
|
|
87
49
|
|
|
88
50
|
# Gets a list of country codes and names.
|
|
@@ -91,289 +53,41 @@ module Addressing
|
|
|
91
53
|
# @param fallback_locale [String] Fallback locale if requested locale is unavailable
|
|
92
54
|
# @return [Hash<String, String>] Hash of country code => country name
|
|
93
55
|
def list(locale = "en", fallback_locale = "en")
|
|
94
|
-
locale = Locale.resolve(
|
|
95
|
-
definitions = load_definitions(locale)
|
|
56
|
+
locale = Locale.resolve(available_locales, locale, fallback_locale)
|
|
96
57
|
|
|
97
|
-
|
|
98
|
-
[country_code, country_name]
|
|
99
|
-
end.to_h
|
|
58
|
+
load_definitions(locale).dup
|
|
100
59
|
end
|
|
101
60
|
|
|
102
61
|
protected
|
|
103
62
|
|
|
63
|
+
def build(country_code, name, locale)
|
|
64
|
+
base_definition = base_definitions[country_code]
|
|
65
|
+
|
|
66
|
+
new(
|
|
67
|
+
country_code: country_code,
|
|
68
|
+
name: name,
|
|
69
|
+
three_letter_code: base_definition["three_letter_code"],
|
|
70
|
+
numeric_code: base_definition["numeric_code"],
|
|
71
|
+
currency_code: base_definition["currency_code"],
|
|
72
|
+
locale: locale
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
104
76
|
# Loads the country definitions for the provided locale.
|
|
105
77
|
def load_definitions(locale)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
filename = File.join(File.expand_path("../../../data/country", __FILE__).to_s, "#{locale}.json")
|
|
109
|
-
@definitions[locale] = JSON.parse(File.read(filename))
|
|
110
|
-
end
|
|
78
|
+
Addressing.data_source.fetch("country/#{locale}")
|
|
79
|
+
end
|
|
111
80
|
|
|
112
|
-
|
|
81
|
+
# Gets the locales that have country names, one data file each.
|
|
82
|
+
def available_locales
|
|
83
|
+
Addressing.data_source.names("country")
|
|
113
84
|
end
|
|
114
85
|
|
|
115
86
|
# Gets the base country definitions.
|
|
116
87
|
#
|
|
117
|
-
# Contains data common to all locales: three letter code, numeric code.
|
|
88
|
+
# Contains data common to all locales: three letter code, numeric code, currency code.
|
|
118
89
|
def base_definitions
|
|
119
|
-
|
|
120
|
-
"AC" => ["ASC", nil, "SHP"],
|
|
121
|
-
"AD" => ["AND", "020", "EUR"],
|
|
122
|
-
"AE" => ["ARE", "784", "AED"],
|
|
123
|
-
"AF" => ["AFG", "004", "AFN"],
|
|
124
|
-
"AG" => ["ATG", "028", "XCD"],
|
|
125
|
-
"AI" => ["AIA", "660", "XCD"],
|
|
126
|
-
"AL" => ["ALB", "008", "ALL"],
|
|
127
|
-
"AM" => ["ARM", "051", "AMD"],
|
|
128
|
-
"AO" => ["AGO", "024", "AOA"],
|
|
129
|
-
"AQ" => ["ATA", "010", nil],
|
|
130
|
-
"AR" => ["ARG", "032", "ARS"],
|
|
131
|
-
"AS" => ["ASM", "016", "USD"],
|
|
132
|
-
"AT" => ["AUT", "040", "EUR"],
|
|
133
|
-
"AU" => ["AUS", "036", "AUD"],
|
|
134
|
-
"AW" => ["ABW", "533", "AWG"],
|
|
135
|
-
"AX" => ["ALA", "248", "EUR"],
|
|
136
|
-
"AZ" => ["AZE", "031", "AZN"],
|
|
137
|
-
"BA" => ["BIH", "070", "BAM"],
|
|
138
|
-
"BB" => ["BRB", "052", "BBD"],
|
|
139
|
-
"BD" => ["BGD", "050", "BDT"],
|
|
140
|
-
"BE" => ["BEL", "056", "EUR"],
|
|
141
|
-
"BF" => ["BFA", "854", "XOF"],
|
|
142
|
-
"BG" => ["BGR", "100", "EUR"],
|
|
143
|
-
"BH" => ["BHR", "048", "BHD"],
|
|
144
|
-
"BI" => ["BDI", "108", "BIF"],
|
|
145
|
-
"BJ" => ["BEN", "204", "XOF"],
|
|
146
|
-
"BL" => ["BLM", "652", "EUR"],
|
|
147
|
-
"BM" => ["BMU", "060", "BMD"],
|
|
148
|
-
"BN" => ["BRN", "096", "BND"],
|
|
149
|
-
"BO" => ["BOL", "068", "BOB"],
|
|
150
|
-
"BQ" => ["BES", "535", "USD"],
|
|
151
|
-
"BR" => ["BRA", "076", "BRL"],
|
|
152
|
-
"BS" => ["BHS", "044", "BSD"],
|
|
153
|
-
"BT" => ["BTN", "064", "BTN"],
|
|
154
|
-
"BV" => ["BVT", "074", "NOK"],
|
|
155
|
-
"BW" => ["BWA", "072", "BWP"],
|
|
156
|
-
"BY" => ["BLR", "112", "BYN"],
|
|
157
|
-
"BZ" => ["BLZ", "084", "BZD"],
|
|
158
|
-
"CA" => ["CAN", "124", "CAD"],
|
|
159
|
-
"CC" => ["CCK", "166", "AUD"],
|
|
160
|
-
"CD" => ["COD", "180", "CDF"],
|
|
161
|
-
"CF" => ["CAF", "140", "XAF"],
|
|
162
|
-
"CG" => ["COG", "178", "XAF"],
|
|
163
|
-
"CH" => ["CHE", "756", "CHF"],
|
|
164
|
-
"CI" => ["CIV", "384", "XOF"],
|
|
165
|
-
"CK" => ["COK", "184", "NZD"],
|
|
166
|
-
"CL" => ["CHL", "152", "CLP"],
|
|
167
|
-
"CM" => ["CMR", "120", "XAF"],
|
|
168
|
-
"CN" => ["CHN", "156", "CNY"],
|
|
169
|
-
"CO" => ["COL", "170", "COP"],
|
|
170
|
-
"CP" => ["CPT", nil, nil],
|
|
171
|
-
"CR" => ["CRI", "188", "CRC"],
|
|
172
|
-
"CU" => ["CUB", "192", "CUP"],
|
|
173
|
-
"CV" => ["CPV", "132", "CVE"],
|
|
174
|
-
"CW" => ["CUW", "531", "XCG"],
|
|
175
|
-
"CX" => ["CXR", "162", "AUD"],
|
|
176
|
-
"CY" => ["CYP", "196", "EUR"],
|
|
177
|
-
"CZ" => ["CZE", "203", "CZK"],
|
|
178
|
-
"DE" => ["DEU", "276", "EUR"],
|
|
179
|
-
"DG" => ["DGA", nil, "USD"],
|
|
180
|
-
"DJ" => ["DJI", "262", "DJF"],
|
|
181
|
-
"DK" => ["DNK", "208", "DKK"],
|
|
182
|
-
"DM" => ["DMA", "212", "XCD"],
|
|
183
|
-
"DO" => ["DOM", "214", "DOP"],
|
|
184
|
-
"DZ" => ["DZA", "012", "DZD"],
|
|
185
|
-
"EA" => [nil, nil, "EUR"],
|
|
186
|
-
"EC" => ["ECU", "218", "USD"],
|
|
187
|
-
"EE" => ["EST", "233", "EUR"],
|
|
188
|
-
"EG" => ["EGY", "818", "EGP"],
|
|
189
|
-
"EH" => ["ESH", "732", "MAD"],
|
|
190
|
-
"ER" => ["ERI", "232", "ERN"],
|
|
191
|
-
"ES" => ["ESP", "724", "EUR"],
|
|
192
|
-
"ET" => ["ETH", "231", "ETB"],
|
|
193
|
-
"FI" => ["FIN", "246", "EUR"],
|
|
194
|
-
"FJ" => ["FJI", "242", "FJD"],
|
|
195
|
-
"FK" => ["FLK", "238", "FKP"],
|
|
196
|
-
"FM" => ["FSM", "583", "USD"],
|
|
197
|
-
"FO" => ["FRO", "234", "DKK"],
|
|
198
|
-
"FR" => ["FRA", "250", "EUR"],
|
|
199
|
-
"GA" => ["GAB", "266", "XAF"],
|
|
200
|
-
"GB" => ["GBR", "826", "GBP"],
|
|
201
|
-
"GD" => ["GRD", "308", "XCD"],
|
|
202
|
-
"GE" => ["GEO", "268", "GEL"],
|
|
203
|
-
"GF" => ["GUF", "254", "EUR"],
|
|
204
|
-
"GG" => ["GGY", "831", "GBP"],
|
|
205
|
-
"GH" => ["GHA", "288", "GHS"],
|
|
206
|
-
"GI" => ["GIB", "292", "GIP"],
|
|
207
|
-
"GL" => ["GRL", "304", "DKK"],
|
|
208
|
-
"GM" => ["GMB", "270", "GMD"],
|
|
209
|
-
"GN" => ["GIN", "324", "GNF"],
|
|
210
|
-
"GP" => ["GLP", "312", "EUR"],
|
|
211
|
-
"GQ" => ["GNQ", "226", "XAF"],
|
|
212
|
-
"GR" => ["GRC", "300", "EUR"],
|
|
213
|
-
"GS" => ["SGS", "239", "GBP"],
|
|
214
|
-
"GT" => ["GTM", "320", "GTQ"],
|
|
215
|
-
"GU" => ["GUM", "316", "USD"],
|
|
216
|
-
"GW" => ["GNB", "624", "XOF"],
|
|
217
|
-
"GY" => ["GUY", "328", "GYD"],
|
|
218
|
-
"HK" => ["HKG", "344", "HKD"],
|
|
219
|
-
"HM" => ["HMD", "334", "AUD"],
|
|
220
|
-
"HN" => ["HND", "340", "HNL"],
|
|
221
|
-
"HR" => ["HRV", "191", "EUR"],
|
|
222
|
-
"HT" => ["HTI", "332", "USD"],
|
|
223
|
-
"HU" => ["HUN", "348", "HUF"],
|
|
224
|
-
"IC" => [nil, nil, "EUR"],
|
|
225
|
-
"ID" => ["IDN", "360", "IDR"],
|
|
226
|
-
"IE" => ["IRL", "372", "EUR"],
|
|
227
|
-
"IL" => ["ISR", "376", "ILS"],
|
|
228
|
-
"IM" => ["IMN", "833", "GBP"],
|
|
229
|
-
"IN" => ["IND", "356", "INR"],
|
|
230
|
-
"IO" => ["IOT", "086", "USD"],
|
|
231
|
-
"IQ" => ["IRQ", "368", "IQD"],
|
|
232
|
-
"IR" => ["IRN", "364", "IRR"],
|
|
233
|
-
"IS" => ["ISL", "352", "ISK"],
|
|
234
|
-
"IT" => ["ITA", "380", "EUR"],
|
|
235
|
-
"JE" => ["JEY", "832", "GBP"],
|
|
236
|
-
"JM" => ["JAM", "388", "JMD"],
|
|
237
|
-
"JO" => ["JOR", "400", "JOD"],
|
|
238
|
-
"JP" => ["JPN", "392", "JPY"],
|
|
239
|
-
"KE" => ["KEN", "404", "KES"],
|
|
240
|
-
"KG" => ["KGZ", "417", "KGS"],
|
|
241
|
-
"KH" => ["KHM", "116", "KHR"],
|
|
242
|
-
"KI" => ["KIR", "296", "AUD"],
|
|
243
|
-
"KM" => ["COM", "174", "KMF"],
|
|
244
|
-
"KN" => ["KNA", "659", "XCD"],
|
|
245
|
-
"KP" => ["PRK", "408", "KPW"],
|
|
246
|
-
"KR" => ["KOR", "410", "KRW"],
|
|
247
|
-
"KW" => ["KWT", "414", "KWD"],
|
|
248
|
-
"KY" => ["CYM", "136", "KYD"],
|
|
249
|
-
"KZ" => ["KAZ", "398", "KZT"],
|
|
250
|
-
"LA" => ["LAO", "418", "LAK"],
|
|
251
|
-
"LB" => ["LBN", "422", "LBP"],
|
|
252
|
-
"LC" => ["LCA", "662", "XCD"],
|
|
253
|
-
"LI" => ["LIE", "438", "CHF"],
|
|
254
|
-
"LK" => ["LKA", "144", "LKR"],
|
|
255
|
-
"LR" => ["LBR", "430", "LRD"],
|
|
256
|
-
"LS" => ["LSO", "426", "LSL"],
|
|
257
|
-
"LT" => ["LTU", "440", "EUR"],
|
|
258
|
-
"LU" => ["LUX", "442", "EUR"],
|
|
259
|
-
"LV" => ["LVA", "428", "EUR"],
|
|
260
|
-
"LY" => ["LBY", "434", "LYD"],
|
|
261
|
-
"MA" => ["MAR", "504", "MAD"],
|
|
262
|
-
"MC" => ["MCO", "492", "EUR"],
|
|
263
|
-
"MD" => ["MDA", "498", "MDL"],
|
|
264
|
-
"ME" => ["MNE", "499", "EUR"],
|
|
265
|
-
"MF" => ["MAF", "663", "EUR"],
|
|
266
|
-
"MG" => ["MDG", "450", "MGA"],
|
|
267
|
-
"MH" => ["MHL", "584", "USD"],
|
|
268
|
-
"MK" => ["MKD", "807", "MKD"],
|
|
269
|
-
"ML" => ["MLI", "466", nil],
|
|
270
|
-
"MM" => ["MMR", "104", "MMK"],
|
|
271
|
-
"MN" => ["MNG", "496", "MNT"],
|
|
272
|
-
"MO" => ["MAC", "446", "MOP"],
|
|
273
|
-
"MP" => ["MNP", "580", "USD"],
|
|
274
|
-
"MQ" => ["MTQ", "474", "EUR"],
|
|
275
|
-
"MR" => ["MRT", "478", "MRU"],
|
|
276
|
-
"MS" => ["MSR", "500", "XCD"],
|
|
277
|
-
"MT" => ["MLT", "470", "EUR"],
|
|
278
|
-
"MU" => ["MUS", "480", "MUR"],
|
|
279
|
-
"MV" => ["MDV", "462", "MVR"],
|
|
280
|
-
"MW" => ["MWI", "454", "MWK"],
|
|
281
|
-
"MX" => ["MEX", "484", "MXN"],
|
|
282
|
-
"MY" => ["MYS", "458", "MYR"],
|
|
283
|
-
"MZ" => ["MOZ", "508", "MZN"],
|
|
284
|
-
"NA" => ["NAM", "516", "NAD"],
|
|
285
|
-
"NC" => ["NCL", "540", "XPF"],
|
|
286
|
-
"NE" => ["NER", "562", "XOF"],
|
|
287
|
-
"NF" => ["NFK", "574", "AUD"],
|
|
288
|
-
"NG" => ["NGA", "566", "NGN"],
|
|
289
|
-
"NI" => ["NIC", "558", "NIO"],
|
|
290
|
-
"NL" => ["NLD", "528", "EUR"],
|
|
291
|
-
"NO" => ["NOR", "578", "NOK"],
|
|
292
|
-
"NP" => ["NPL", "524", "NPR"],
|
|
293
|
-
"NR" => ["NRU", "520", "AUD"],
|
|
294
|
-
"NU" => ["NIU", "570", "NZD"],
|
|
295
|
-
"NZ" => ["NZL", "554", "NZD"],
|
|
296
|
-
"OM" => ["OMN", "512", "OMR"],
|
|
297
|
-
"PA" => ["PAN", "591", "USD"],
|
|
298
|
-
"PE" => ["PER", "604", "PEN"],
|
|
299
|
-
"PF" => ["PYF", "258", "XPF"],
|
|
300
|
-
"PG" => ["PNG", "598", "PGK"],
|
|
301
|
-
"PH" => ["PHL", "608", "PHP"],
|
|
302
|
-
"PK" => ["PAK", "586", "PKR"],
|
|
303
|
-
"PL" => ["POL", "616", "PLN"],
|
|
304
|
-
"PM" => ["SPM", "666", "EUR"],
|
|
305
|
-
"PN" => ["PCN", "612", "NZD"],
|
|
306
|
-
"PR" => ["PRI", "630", "USD"],
|
|
307
|
-
"PS" => ["PSE", "275", "ILS"],
|
|
308
|
-
"PT" => ["PRT", "620", "EUR"],
|
|
309
|
-
"PW" => ["PLW", "585", "USD"],
|
|
310
|
-
"PY" => ["PRY", "600", "PYG"],
|
|
311
|
-
"QA" => ["QAT", "634", "QAR"],
|
|
312
|
-
"RE" => ["REU", "638", "EUR"],
|
|
313
|
-
"RO" => ["ROU", "642", "RON"],
|
|
314
|
-
"RS" => ["SRB", "688", "RSD"],
|
|
315
|
-
"RU" => ["RUS", "643", "RUB"],
|
|
316
|
-
"RW" => ["RWA", "646", "RWF"],
|
|
317
|
-
"SA" => ["SAU", "682", "SAR"],
|
|
318
|
-
"SB" => ["SLB", "090", "SBD"],
|
|
319
|
-
"SC" => ["SYC", "690", "SCR"],
|
|
320
|
-
"SD" => ["SDN", "729", "SDG"],
|
|
321
|
-
"SE" => ["SWE", "752", "SEK"],
|
|
322
|
-
"SG" => ["SGP", "702", "SGD"],
|
|
323
|
-
"SH" => ["SHN", "654", "SHP"],
|
|
324
|
-
"SI" => ["SVN", "705", "EUR"],
|
|
325
|
-
"SJ" => ["SJM", "744", "NOK"],
|
|
326
|
-
"SK" => ["SVK", "703", "EUR"],
|
|
327
|
-
"SL" => ["SLE", "694", "SLE"],
|
|
328
|
-
"SM" => ["SMR", "674", "EUR"],
|
|
329
|
-
"SN" => ["SEN", "686", "XOF"],
|
|
330
|
-
"SO" => ["SOM", "706", "SOS"],
|
|
331
|
-
"SR" => ["SUR", "740", "SRD"],
|
|
332
|
-
"SS" => ["SSD", "728", "SSP"],
|
|
333
|
-
"ST" => ["STP", "678", "STN"],
|
|
334
|
-
"SV" => ["SLV", "222", "USD"],
|
|
335
|
-
"SX" => ["SXM", "534", "XCG"],
|
|
336
|
-
"SY" => ["SYR", "760", "SYP"],
|
|
337
|
-
"SZ" => ["SWZ", "748", "SZL"],
|
|
338
|
-
"TA" => ["TAA", nil, "GBP"],
|
|
339
|
-
"TC" => ["TCA", "796", "USD"],
|
|
340
|
-
"TD" => ["TCD", "148", "XAF"],
|
|
341
|
-
"TF" => ["ATF", "260", "EUR"],
|
|
342
|
-
"TG" => ["TGO", "768", "XOF"],
|
|
343
|
-
"TH" => ["THA", "764", "THB"],
|
|
344
|
-
"TJ" => ["TJK", "762", "TJS"],
|
|
345
|
-
"TK" => ["TKL", "772", "NZD"],
|
|
346
|
-
"TL" => ["TLS", "626", "USD"],
|
|
347
|
-
"TM" => ["TKM", "795", "TMT"],
|
|
348
|
-
"TN" => ["TUN", "788", "TND"],
|
|
349
|
-
"TO" => ["TON", "776", "TOP"],
|
|
350
|
-
"TR" => ["TUR", "792", "TRY"],
|
|
351
|
-
"TT" => ["TTO", "780", "TTD"],
|
|
352
|
-
"TV" => ["TUV", "798", "AUD"],
|
|
353
|
-
"TW" => ["TWN", "158", "TWD"],
|
|
354
|
-
"TZ" => ["TZA", "834", "TZS"],
|
|
355
|
-
"UA" => ["UKR", "804", "UAH"],
|
|
356
|
-
"UG" => ["UGA", "800", "UGX"],
|
|
357
|
-
"UM" => ["UMI", "581", "USD"],
|
|
358
|
-
"US" => ["USA", "840", "USD"],
|
|
359
|
-
"UY" => ["URY", "858", "UYU"],
|
|
360
|
-
"UZ" => ["UZB", "860", "UZS"],
|
|
361
|
-
"VA" => ["VAT", "336", "EUR"],
|
|
362
|
-
"VC" => ["VCT", "670", "XCD"],
|
|
363
|
-
"VE" => ["VEN", "862", "VES"],
|
|
364
|
-
"VG" => ["VGB", "092", "USD"],
|
|
365
|
-
"VI" => ["VIR", "850", "USD"],
|
|
366
|
-
"VN" => ["VNM", "704", "VND"],
|
|
367
|
-
"VU" => ["VUT", "548", "VUV"],
|
|
368
|
-
"WF" => ["WLF", "876", "XPF"],
|
|
369
|
-
"WS" => ["WSM", "882", "WST"],
|
|
370
|
-
"XK" => ["XKK", "983", "EUR"],
|
|
371
|
-
"YE" => ["YEM", "887", "YER"],
|
|
372
|
-
"YT" => ["MYT", "175", "EUR"],
|
|
373
|
-
"ZA" => ["ZAF", "710", "ZAR"],
|
|
374
|
-
"ZM" => ["ZMB", "894", "ZMW"],
|
|
375
|
-
"ZW" => ["ZWE", "716", "ZWG"]
|
|
376
|
-
}
|
|
90
|
+
Addressing.data_source.fetch("countries")
|
|
377
91
|
end
|
|
378
92
|
end
|
|
379
93
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Addressing
|
|
4
|
+
class << self
|
|
5
|
+
# The data source that the reference data is read from.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
attr_writer :data_source
|
|
9
|
+
|
|
10
|
+
# @api private
|
|
11
|
+
# @return [DataSource]
|
|
12
|
+
def data_source
|
|
13
|
+
@data_source ||= DataSource.new(DataSource::DEFAULT_DIR)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Reads the reference data: the JSON files that the data sync writes.
|
|
18
|
+
#
|
|
19
|
+
# Each instance has its own cache, so a new instance reads the files again.
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
class DataSource
|
|
23
|
+
# The data directory that ships with the gem.
|
|
24
|
+
DEFAULT_DIR = File.expand_path("../../data", __dir__)
|
|
25
|
+
|
|
26
|
+
def initialize(dir)
|
|
27
|
+
@dir = dir
|
|
28
|
+
@cache = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Gets the dataset with the provided name.
|
|
32
|
+
#
|
|
33
|
+
# The parsed JSON is passed to the block, and the result of the block is
|
|
34
|
+
# cached, so each dataset is read and processed once per data source.
|
|
35
|
+
# A missing dataset gives nil, which is cached as well. Malformed JSON
|
|
36
|
+
# raises, because the data files are never edited by hand.
|
|
37
|
+
#
|
|
38
|
+
# @param name [String] Path relative to the data directory, without extension (e.g. "country/en")
|
|
39
|
+
# @yieldparam json [Hash, Array] The parsed dataset
|
|
40
|
+
# @return [Object, nil] The result of the block, or the parsed dataset without a block
|
|
41
|
+
# @raise [JSON::ParserError] if the dataset is malformed
|
|
42
|
+
def fetch(name)
|
|
43
|
+
return @cache[name] if @cache.key?(name)
|
|
44
|
+
|
|
45
|
+
dir, basename = File.split(name)
|
|
46
|
+
return @cache[name] = nil unless filenames(dir).include?("#{basename}.json")
|
|
47
|
+
|
|
48
|
+
json = JSON.parse(File.read(File.join(@dir, "#{name}.json"), encoding: "UTF-8"))
|
|
49
|
+
@cache[name] = block_given? ? yield(json) : json
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Gets the names of the datasets in a directory, without the directory.
|
|
53
|
+
#
|
|
54
|
+
# @param dir [String] Directory relative to the data directory (e.g. "country")
|
|
55
|
+
# @return [Array<String>] Sorted names (e.g. ["af", "ak", ...])
|
|
56
|
+
def names(dir)
|
|
57
|
+
(@names ||= {})[dir] ||= filenames(dir).filter_map { |filename| File.basename(filename, ".json") if filename.end_with?(".json") }.sort.freeze
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Gets the file names in a directory of the data directory.
|
|
63
|
+
#
|
|
64
|
+
# A dataset exists only when its name matches a file name exactly, so
|
|
65
|
+
# that a lookup gives the same result on a case-insensitive file system.
|
|
66
|
+
# The listing is cached like the datasets, so a file added later is not
|
|
67
|
+
# seen by this instance.
|
|
68
|
+
def filenames(dir)
|
|
69
|
+
(@filenames ||= {})[dir] ||= begin
|
|
70
|
+
path = File.join(@dir, dir)
|
|
71
|
+
Dir.exist?(path) ? Dir.children(path).to_set : Set.new
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -7,6 +7,7 @@ module Addressing
|
|
|
7
7
|
FORMAT_SPLIT_PATTERN = /(#{FORMAT_PLACEHOLDER_PATTERN})/
|
|
8
8
|
LEADING_TRAILING_PUNCTUATION_PATTERN = /\A[ \-,]+|[ \-,]+\z/
|
|
9
9
|
MULTIPLE_SPACES_PATTERN = /\s\s+/
|
|
10
|
+
HTML_TAG_PATTERN = /\A[a-z][a-z0-9-]*\z/i
|
|
10
11
|
|
|
11
12
|
DEFAULT_OPTIONS = {
|
|
12
13
|
locale: DEFAULT_LOCALE,
|
|
@@ -23,9 +24,8 @@ module Addressing
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def format(address, options = {})
|
|
26
|
-
assert_options(options)
|
|
27
|
-
|
|
28
27
|
options = @default_options.merge(options)
|
|
28
|
+
assert_options(options)
|
|
29
29
|
address_format = AddressFormat.get(address.country_code)
|
|
30
30
|
|
|
31
31
|
# Add the country to the bottom or the top of the format string,
|
|
@@ -39,7 +39,7 @@ module Addressing
|
|
|
39
39
|
view = build_view(address, address_format, options)
|
|
40
40
|
view = render_view(view)
|
|
41
41
|
|
|
42
|
-
replacements = view.
|
|
42
|
+
replacements = view.transform_keys { |key| "%#{key}" }
|
|
43
43
|
output = insert_values(format_string, replacements)
|
|
44
44
|
output = clean_output(output)
|
|
45
45
|
|
|
@@ -57,12 +57,12 @@ module Addressing
|
|
|
57
57
|
# Builds the view for the given address.
|
|
58
58
|
def build_view(address, address_format, options)
|
|
59
59
|
countries = country_list(options[:locale])
|
|
60
|
-
values = values(address, address_format).merge(
|
|
60
|
+
values = values(address, address_format).merge("country" => countries.fetch(address.country_code, address.country_code))
|
|
61
61
|
used_fields = address_format.used_fields + ["country"]
|
|
62
62
|
|
|
63
|
-
used_fields.
|
|
63
|
+
used_fields.to_h do |field|
|
|
64
64
|
[field, {html: options[:html], html_tag: "span", html_attributes: {class: field.tr("_", "-")}, value: values[field]}]
|
|
65
|
-
end
|
|
65
|
+
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Gets the country list for a locale, with caching.
|
|
@@ -72,16 +72,16 @@ module Addressing
|
|
|
72
72
|
|
|
73
73
|
# Renders the given view.
|
|
74
74
|
def render_view(view)
|
|
75
|
-
view.
|
|
76
|
-
next
|
|
75
|
+
view.transform_values do |element|
|
|
76
|
+
next "" if element[:value].empty?
|
|
77
77
|
|
|
78
78
|
if element[:html]
|
|
79
79
|
element[:value] = CGI.escapeHTML(element[:value])
|
|
80
|
-
next
|
|
80
|
+
next render_html_element(element)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
end
|
|
83
|
+
element[:value].gsub(/<\/?[^>]*>/, "")
|
|
84
|
+
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def render_html_element(element)
|
|
@@ -147,36 +147,27 @@ module Addressing
|
|
|
147
147
|
|
|
148
148
|
# Extracts all address field values.
|
|
149
149
|
def extract_address_values(address)
|
|
150
|
-
AddressField.all.
|
|
150
|
+
AddressField.all.values.to_h { |field| [field, address.send(field)] }
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
# Replaces the subdivision values with the codes of any predefined ones.
|
|
154
154
|
def resolve_subdivision_values(values, address, address_format)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
address_format.subdivision_fields.each do |field|
|
|
158
|
-
# This level is empty, so there can be no sublevels.
|
|
159
|
-
break if values[field].nil? || values[field].empty?
|
|
155
|
+
fields = address_format.subdivision_fields
|
|
156
|
+
chain = Subdivision.chain(address.country_code, values.values_at(*fields))
|
|
160
157
|
|
|
161
|
-
|
|
158
|
+
fields.zip(chain.subdivisions) do |field, subdivision|
|
|
162
159
|
break if subdivision.nil?
|
|
163
160
|
|
|
164
|
-
parents << values[field]
|
|
165
|
-
|
|
166
161
|
# Replace the value with the expected code.
|
|
167
162
|
use_local_name = Locale.match_candidates(address.locale, subdivision.locale)
|
|
168
163
|
values[field] = use_local_name ? subdivision.local_code : subdivision.code
|
|
169
|
-
|
|
170
|
-
# The current subdivision has no children, stop.
|
|
171
|
-
break unless subdivision.children?
|
|
172
164
|
end
|
|
173
165
|
end
|
|
174
166
|
|
|
175
|
-
private
|
|
176
|
-
|
|
177
167
|
# Validates the provided options.
|
|
178
168
|
#
|
|
179
169
|
# Ensures the absence of unknown keys, correct data types and values.
|
|
170
|
+
# A subclass with more options extends this and calls super.
|
|
180
171
|
def assert_options(options)
|
|
181
172
|
options.each do |option, value|
|
|
182
173
|
unless self.class::DEFAULT_OPTIONS.key?(option)
|
|
@@ -191,6 +182,15 @@ module Addressing
|
|
|
191
182
|
if options.key?(:html_attributes) && !options[:html_attributes].is_a?(Hash)
|
|
192
183
|
raise ArgumentError, "The option `html_attributes` must be a hash."
|
|
193
184
|
end
|
|
185
|
+
|
|
186
|
+
# The tag is written into the markup as is, so only allow a tag name.
|
|
187
|
+
if options.key?(:html_tag) && !HTML_TAG_PATTERN.match?(options[:html_tag].to_s)
|
|
188
|
+
raise ArgumentError, "The option `html_tag` must be an HTML tag name."
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
if options.key?(:locale) && !options[:locale].is_a?(String)
|
|
192
|
+
raise ArgumentError, "The option `locale` must be a string."
|
|
193
|
+
end
|
|
194
194
|
end
|
|
195
195
|
end
|
|
196
196
|
end
|
data/lib/addressing/enum.rb
CHANGED
|
@@ -5,12 +5,8 @@ module Addressing
|
|
|
5
5
|
class << self
|
|
6
6
|
# Gets all available values.
|
|
7
7
|
def all
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@values[name] = constants.map { |constant| [constant, const_get(constant)] }.to_h
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
@values[name]
|
|
8
|
+
# Each subclass has its own @all.
|
|
9
|
+
@all ||= constants.to_h { |constant| [constant, const_get(constant)] }
|
|
14
10
|
end
|
|
15
11
|
|
|
16
12
|
# Gets the key of the provided value.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Addressing
|
|
4
|
+
# One field of an address that breaks a rule of the address format for its
|
|
5
|
+
# country, together with the kind of rule it breaks. A blank country code is
|
|
6
|
+
# also a field violation.
|
|
7
|
+
#
|
|
8
|
+
# field is the field as a symbol, such as :postal_code. kind is one of:
|
|
9
|
+
#
|
|
10
|
+
# - :blank, the field must not be blank.
|
|
11
|
+
# - :present, the field must be blank.
|
|
12
|
+
# - :invalid, the value matches no predefined subdivision or does not match
|
|
13
|
+
# the postal code pattern.
|
|
14
|
+
#
|
|
15
|
+
# The kinds match the error types of Rails.
|
|
16
|
+
FieldViolation = Data.define(:field, :kind)
|
|
17
|
+
end
|