addressing 2.0.1 → 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 +40 -0
- data/README.md +223 -111
- 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/AE.json +1 -1
- 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/CA.json +6 -12
- 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/CO.json +1 -1
- data/data/subdivision/CV.json +9 -9
- data/data/subdivision/GB.json +675 -0
- data/data/subdivision/GT.json +71 -0
- data/data/subdivision/IT.json +13 -1
- data/data/subdivision/KY.json +3 -3
- data/data/subdivision/NG.json +1 -1
- data/data/subdivision/RU.json +4 -4
- data/data/subdivision/TH.json +7 -7
- data/data/subdivision/TR.json +1 -1
- data/data/subdivision/TV.json +1 -1
- data/data/subdivision/VE.json +4 -4
- data/data/subdivision/VN.json +1 -1
- data/lib/addressing/address.rb +7 -3
- data/lib/addressing/address_format.rb +93 -97
- 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 +61 -33
- 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 -79
- data/lib/addressing/postal_label_formatter.rb +16 -15
- data/lib/addressing/subdivision.rb +121 -106
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +6 -1
- metadata +11 -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
|
|
@@ -4,8 +4,10 @@ module Addressing
|
|
|
4
4
|
class DefaultFormatter
|
|
5
5
|
DEFAULT_LOCALE = "en"
|
|
6
6
|
FORMAT_PLACEHOLDER_PATTERN = /%[a-z1-9_]+/
|
|
7
|
+
FORMAT_SPLIT_PATTERN = /(#{FORMAT_PLACEHOLDER_PATTERN})/
|
|
7
8
|
LEADING_TRAILING_PUNCTUATION_PATTERN = /\A[ \-,]+|[ \-,]+\z/
|
|
8
9
|
MULTIPLE_SPACES_PATTERN = /\s\s+/
|
|
10
|
+
HTML_TAG_PATTERN = /\A[a-z][a-z0-9-]*\z/i
|
|
9
11
|
|
|
10
12
|
DEFAULT_OPTIONS = {
|
|
11
13
|
locale: DEFAULT_LOCALE,
|
|
@@ -22,9 +24,8 @@ module Addressing
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def format(address, options = {})
|
|
25
|
-
assert_options(options)
|
|
26
|
-
|
|
27
27
|
options = @default_options.merge(options)
|
|
28
|
+
assert_options(options)
|
|
28
29
|
address_format = AddressFormat.get(address.country_code)
|
|
29
30
|
|
|
30
31
|
# Add the country to the bottom or the top of the format string,
|
|
@@ -38,8 +39,8 @@ module Addressing
|
|
|
38
39
|
view = build_view(address, address_format, options)
|
|
39
40
|
view = render_view(view)
|
|
40
41
|
|
|
41
|
-
replacements = view.
|
|
42
|
-
output = format_string
|
|
42
|
+
replacements = view.transform_keys { |key| "%#{key}" }
|
|
43
|
+
output = insert_values(format_string, replacements)
|
|
43
44
|
output = clean_output(output)
|
|
44
45
|
|
|
45
46
|
if options[:html]
|
|
@@ -56,12 +57,12 @@ module Addressing
|
|
|
56
57
|
# Builds the view for the given address.
|
|
57
58
|
def build_view(address, address_format, options)
|
|
58
59
|
countries = country_list(options[:locale])
|
|
59
|
-
values = values(address, address_format).merge(
|
|
60
|
+
values = values(address, address_format).merge("country" => countries.fetch(address.country_code, address.country_code))
|
|
60
61
|
used_fields = address_format.used_fields + ["country"]
|
|
61
62
|
|
|
62
|
-
used_fields.
|
|
63
|
+
used_fields.to_h do |field|
|
|
63
64
|
[field, {html: options[:html], html_tag: "span", html_attributes: {class: field.tr("_", "-")}, value: values[field]}]
|
|
64
|
-
end
|
|
65
|
+
end
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
# Gets the country list for a locale, with caching.
|
|
@@ -71,16 +72,16 @@ module Addressing
|
|
|
71
72
|
|
|
72
73
|
# Renders the given view.
|
|
73
74
|
def render_view(view)
|
|
74
|
-
view.
|
|
75
|
-
next
|
|
75
|
+
view.transform_values do |element|
|
|
76
|
+
next "" if element[:value].empty?
|
|
76
77
|
|
|
77
78
|
if element[:html]
|
|
78
79
|
element[:value] = CGI.escapeHTML(element[:value])
|
|
79
|
-
next
|
|
80
|
+
next render_html_element(element)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
end
|
|
83
|
+
element[:value].gsub(/<\/?[^>]*>/, "")
|
|
84
|
+
end
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
def render_html_element(element)
|
|
@@ -99,6 +100,39 @@ module Addressing
|
|
|
99
100
|
end.join(" ")
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
# Inserts the rendered address fields into the format string.
|
|
104
|
+
#
|
|
105
|
+
# Empty fields need special handling. When one falls between two values,
|
|
106
|
+
# keep the separator before it and discard the one after it.
|
|
107
|
+
def insert_values(format_string, replacements)
|
|
108
|
+
format_string.split("\n", -1).map do |line|
|
|
109
|
+
rendered = +""
|
|
110
|
+
separator = ""
|
|
111
|
+
skipped = false
|
|
112
|
+
|
|
113
|
+
line.split(FORMAT_SPLIT_PATTERN, -1).each do |part|
|
|
114
|
+
unless replacements.key?(part)
|
|
115
|
+
# A separator before an empty field usually belongs to
|
|
116
|
+
# the value that came before it, so keep that one.
|
|
117
|
+
separator = part unless skipped
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if replacements[part].empty?
|
|
122
|
+
skipped = true
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
rendered << separator if !rendered.empty? || !skipped
|
|
127
|
+
rendered << replacements[part]
|
|
128
|
+
separator = ""
|
|
129
|
+
skipped = false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
rendered
|
|
133
|
+
end.join("\n")
|
|
134
|
+
end
|
|
135
|
+
|
|
102
136
|
# Removes empty lines, leading punctuation, excess whitespace.
|
|
103
137
|
def clean_output(output)
|
|
104
138
|
output.split("\n").map { |line| line.gsub(LEADING_TRAILING_PUNCTUATION_PATTERN, "").strip.gsub(MULTIPLE_SPACES_PATTERN, " ") }.reject(&:empty?).join("\n")
|
|
@@ -113,42 +147,27 @@ module Addressing
|
|
|
113
147
|
|
|
114
148
|
# Extracts all address field values.
|
|
115
149
|
def extract_address_values(address)
|
|
116
|
-
AddressField.all.
|
|
150
|
+
AddressField.all.values.to_h { |field| [field, address.send(field)] }
|
|
117
151
|
end
|
|
118
152
|
|
|
119
|
-
#
|
|
153
|
+
# Replaces the subdivision values with the codes of any predefined ones.
|
|
120
154
|
def resolve_subdivision_values(values, address, address_format)
|
|
121
|
-
|
|
155
|
+
fields = address_format.subdivision_fields
|
|
156
|
+
chain = Subdivision.chain(address.country_code, values.values_at(*fields))
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
subdivision_fields.each_with_index.inject([{}, []]) do |(original_values, parents), (field, index)|
|
|
125
|
-
# This level is empty, so there can be no sublevels.
|
|
126
|
-
break if values[field].nil?
|
|
127
|
-
|
|
128
|
-
parents << ((index > 0) ? original_values[subdivision_fields[index - 1]] : address.country_code)
|
|
129
|
-
|
|
130
|
-
subdivision = Subdivision.get(values[field], parents)
|
|
158
|
+
fields.zip(chain.subdivisions) do |field, subdivision|
|
|
131
159
|
break if subdivision.nil?
|
|
132
160
|
|
|
133
|
-
# Remember the original value so that it can be used for parents.
|
|
134
|
-
original_values[field] = values[field]
|
|
135
|
-
|
|
136
161
|
# Replace the value with the expected code.
|
|
137
162
|
use_local_name = Locale.match_candidates(address.locale, subdivision.locale)
|
|
138
163
|
values[field] = use_local_name ? subdivision.local_code : subdivision.code
|
|
139
|
-
|
|
140
|
-
# The current subdivision has no children, stop.
|
|
141
|
-
break unless subdivision.children?
|
|
142
|
-
|
|
143
|
-
[original_values, parents]
|
|
144
164
|
end
|
|
145
165
|
end
|
|
146
166
|
|
|
147
|
-
private
|
|
148
|
-
|
|
149
167
|
# Validates the provided options.
|
|
150
168
|
#
|
|
151
169
|
# Ensures the absence of unknown keys, correct data types and values.
|
|
170
|
+
# A subclass with more options extends this and calls super.
|
|
152
171
|
def assert_options(options)
|
|
153
172
|
options.each do |option, value|
|
|
154
173
|
unless self.class::DEFAULT_OPTIONS.key?(option)
|
|
@@ -163,6 +182,15 @@ module Addressing
|
|
|
163
182
|
if options.key?(:html_attributes) && !options[:html_attributes].is_a?(Hash)
|
|
164
183
|
raise ArgumentError, "The option `html_attributes` must be a hash."
|
|
165
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
|
|
166
194
|
end
|
|
167
195
|
end
|
|
168
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.
|