cse 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1eac2bdb36416e89ad8933688ce7bd9f8cdeccbbffb77033f920e238236f4734
4
+ data.tar.gz: 50c23e0c21d26491b280ecc669e0da3de1d168edda3c0a6f0b61d65fd1f721da
5
+ SHA512:
6
+ metadata.gz: 8c06572c54baf8bc4b8d4818e2e5f1d12729d4d0718a7f09317b064842d18fb0ba84897e68298a069514029aeb65170968bdfb25f043123d96d91c9736c9f0bc
7
+ data.tar.gz: 2188639de4d9e2e91d92a74d94be622b6431435c721bb500b676a81af940ff9038c9d15161fb53f4c15164230a29384d28238287ab40312eec787a1226678c83
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
16
+ cities.*
17
+ states.*
18
+ db/countries.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in CityState.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020 Daniel Loureiro
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # CS ruby gem
2
+
3
+ **cs** gem is simple city-state gem with some tweaks. Like for country with no states cities are available now.
4
+
5
+ ### Put this gem at your Gemfile:
6
+ ```ruby
7
+ gem 'cs'
8
+ ```
9
+
10
+ ### Simple Usage:
11
+
12
+ ```ruby
13
+ CS.countries
14
+ # => {:AD=>"Andorra", :AE=>"United Arab Emirates", :AF=>"Afghanistan", :AG=>"Antigua and Barbuda", :AI=>"Anguilla", :AL=>"Albania", :AM=>"Armenia", :AO=>"Angola", :AQ=>"Antarctica", :AR=>"Argentina", :AS=>"American Samoa", :AT=>"Austria", :AU=>"Australia", :AW=>"Aruba", :AX=>"Åland", :AZ=>"Azerbaijanndi", :BJ=>"Benin", :BL=>"Saint-Barthélemy", :BM=>"Bermuda", :BN=>"Brunei", :BO=>"Bolivia", :BQ=>"Bonaire", :BR=>"Brazil", :BS=>"Bahamas", :BT=>"BhutaCOUNTRY_ISO_CODE=>"country_name", :CR=>"Costa Rica", :CU=>"Cuba", :CV=>"Cape Verde", :CW=>"Curaçao", :CX=>"Christmas Island", :CY=>"Cyprus", :CZ=>"Czech Republic", :DE=>"Germany", :DJ=>"Djibouti", :DK=>"Denmark", :DM=>"Dominica", :DO=>"Dominican Republic", :DZ=>"Algeria", :EC=>"Ecuador", :EE=>"Estonia", :EG=>"Egypt", :ER=>"Eritrea", :ES=>"Spain", :ET=>"Ethiopia", :FI=>"Finland", :FJ=>"Fiji", :FK=>"Falkland Islands", :FM=>"Federated States of Micronesia", :FO=>"Faroe Islands", :FR=>"France", :GA=>"Gabon", :GB=>"United Kingdom", :GD=>"Grenada", :GE=>"Georgia", :GF=>"French Guiana", :GG=>"GuernseOcean Territory", :IQ=>"Iraq", :IR=>"Iran", :IS=>"Iceland", :IT=>"Italy", :JE=>"Jersey", :JM=>"Jamaica", :JO=>"Hashemite Kingdom of Jordan", :JP=>"Japic of Korea", :KW=>"Kuwait", :KY=>"Cayman Islands", :KZ=>"Kazakhstan", :LA=>"Laos", :LB=>"Lebanon", :LC=>"Saint Lucia", :LI=>"Liechtenstein", :LK=>"Sri Lanka", :LR=>"Liberia", :LS=>"Lesotho", :LT=>"Republic of Lithuania", :LU=>"Luxembourg", :LV=>"Latvia", :LY=>"Libya", :MA=>"Morocco", :MC=>"Monaco", :MD=>"Republic of Moldova", :ME=>"Montenegro", :MF=>"Saint Martin", :MG=>"Madagascar", :MH=>"Marshall Islands", :MK=>"Macedonia", :ML=>"Mali", :MM=>"ger", :NF=>"Norfolk Island", :NG=>"Nigeria", :NI=>"Nicaragua", :NL=>"Netherlands", :NO=>"Norway", :NP=>"Nepal", :NR=>"Nauru", :NU=>"Niue", :NZ=>"New Zealand", :OM=>"Oman", :PA=>"Panama", :PE=>"Peru", :PF=>"French Polynesia", :PG=>"Papua New Guinea", :PH=>"Philippines", :PK=>"Pakistan", :PL=>"Poland", :PM=>"Saint Pierre and Miquelon", :PN=>"Pitcairn Islands", :PR=>"Puerto Rico", :PS=>"Palestine", :PT=>"Portugal", :PW=>"Palau", :PY=>"Paraguay", :QA=>"Qatar", :RE=>"Réunion", :RO=>"Romania", :RS=>"Serbia", :RU=>"Russia", :RW=>"Rwanda", :SA=>"Saudi Arabia", :SB=>"Solomon Islands", :SC=>"Seychelles", :SD=>"Sudan", :SE=>"Sweden", :SG=>"Singapore", :SH=>"Saint Helena", :SI=>"Slovenia", :SJ=>"Svalbard and Jan Mayen", :SK=>"Slovakia", :SL=>"Sierra Leone", :SM=>"San Marino", :SN=>"Senegal", :SO=>"Somalia", :SR=>"Suriname", :SS=>"South Sudan", :ST=>"São Tomé and Príncipe", :SV=>"El Salvador", :SX=>"Sint Maarten", :SY=>"Syria", :SZ=>"Swaziland", :TC=>"Turks and Caicos Islands", :TD=>"Chad", :TF=>"French Southern Territories", :TG=>"Togo", :TH=>"Thailand", :TJ=>"Tajikistan", :TK=>"Tokelau", :TL=>"East Timor", :TM=>"Turkmenistan", :TN=>"Tunisia", :TO=>"Tonga", :TR=>"Turkey", :TT=>"Trinidad and Tobago", :TV=>"Tuvalu", :TW=>"Taiwan", :TZ=>"Tanzania", :UA=>"Ukraine", :UG=>"Uganda", :UM=>"U.S. Minor Outlying Islands", :US=>"United States", :UY=>"Uruguay", :UZ=>"Uzbekistan", :VA=>"Vatican City", :VC=>"Saint Vincent and the Grenadines", :VE=>"Venezuela", :VG=>"British Virgin Islands", :VI=>"U.S. Virgin Islands", :VN=>"Vietnam", :VU=>"Vanuatu", :WF=>"Wallis and Futuna", :WS=>"Samoa", :XK=>"Kosovo", :YE=>"Yemen", :YT=>"Mayotte", :ZA=>"South Africa", :ZM=>"Zambia", :ZW=>"Zimbabwe"}
15
+ ```
16
+
17
+ ```ruby
18
+ CS.states(:IN)
19
+ # => {:AN=>"Andaman and Nicobar", :AP=>"Andhra Pradesh", :AR=>"Arunachal Pradesh", :AS=>"Assam", :BR=>"Bihar", :CH=>"Chandigarh", :CT=>"Chhattisgarh", :DD=>"Daman and Diu", :DL=>"National Capital Territory of Delhi", :DN=>"Dadra and Nagar Haveli", :GA=>"Goa", :GJ=>"Gujarat", :HP=>"Himachal Pradesh", :HR=>"Haryana", :JH=>"Jharkhand", :JK=>"Jammu and Kashmir", :KA=>"Karnataka", :KL=>"Kerala", :LA=>"Ladakh", :LD=>"Lakshadweep", :MH=>"Maharashtra", :ML=>"Meghalaya", :MN=>"Manipur", :MP=>"Madhya Pradesh", :MZ=>"Mizoram", :NL=>"Nagaland", :OR=>"Odisha", :PB=>"Punjab", :PY=>"Union Territory of Puducherry", :RJ=>"Rajasthan", :SK=>"Sikkim", :TG=>"Telangana", :TN=>"Tamil Nadu", :TR=>"Tripura", :UP=>"Uttar Pradesh", :UT=>"Uttarakhand", :WB=>"West Bengal"}
20
+ ```
21
+
22
+ ```ruby
23
+ CS.cities(:UT, :IN)
24
+ # => ["Almora", "Amsaur", "Araghar", "Bageshwar", "Bagoli", "Bazpur", "Belra", "Bhagwanpur", "Champawat", "Dehradun", "Dwarahat", "Gadarpur", "Garur", "Haldwani", "Haridwar", "Jaspur", "Jhabrera", "Kashipur", "Khatima", "Kichha", "Kotdwara", "Lalkua", "Mussoorie", "Nainital", "Pali", "Pantnagar", "Pauri", "Pithoragarh", "Ranikhet", "Rishikesh", "Roorkee", "Rudarpur", "Rudraprayag", "Sitarganj", "Srinagar", "Tanakpur", "Tehri", "Uttarkashi"]
25
+ ```
26
+
27
+ ### More details about this city-state gem (Original Author)
28
+ https://github.com/loureirorg/city-state
29
+
30
+ # Changelog
31
+ * 0.1.0:
32
+ - [Minor] Added cities with no states for a country
33
+ - [Minor] Can change cities name with empty states
34
+
35
+
36
+ # CityState License
37
+ **cs** is a tweak to the **city-state project by Daniel Loureiro** with a MIT license. Also, it uses MaxMind open source database.
38
+
39
+ # MaxMind License
40
+ Database and Contents Copyright (c) 2020 MaxMind, Inc.
41
+ This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "cse"
7
+ spec.version = "0.0.2"
8
+ spec.authors = ["Gaurav Deou"]
9
+ spec.email = ["gauravdev720@gmail.com"]
10
+ spec.summary = %q{Simple list of cities and states of the world with blank states.}
11
+ spec.description = %q{A simple gem for country, state and cities list}
12
+ spec.homepage = "https://github.com/garv720/cs"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", ">= 1.7"
21
+ spec.add_development_dependency "rake", ">= 10.0"
22
+ spec.add_runtime_dependency "rubyzip", ">= 1.1"
23
+ end
@@ -0,0 +1,349 @@
1
+ require 'yaml'
2
+
3
+ module CS
4
+ # CS constants
5
+ FILES_FOLDER = File.expand_path('../db', __FILE__)
6
+ MAXMIND_DB_FN = File.join(FILES_FOLDER, "GeoLite2-City-Locations-en.csv")
7
+ COUNTRIES_FN = File.join(FILES_FOLDER, "countries.yml")
8
+ DEFAULT_CITIES_LOOKUP_FN = 'db/cities-lookup.yml'
9
+ DEFAULT_STATES_LOOKUP_FN = 'db/states-lookup.yml'
10
+ DEFAULT_COUNTRIES_LOOKUP_FN = 'db/countries-lookup.yml'
11
+
12
+ @countries, @states, @cities = [{}, {}, {}]
13
+ @current_country = nil # :US, :BR, :GB, :JP, ...
14
+ @maxmind_zip_url = nil
15
+ @license_key = nil
16
+
17
+ # lookup tables for state/cities renaming
18
+ @cities_lookup_fn = nil
19
+ @cities_lookup = nil
20
+ @states_lookup_fn = nil
21
+ @states_lookup = nil
22
+ @countries_lookup_fn = nil
23
+ @countries_lookup = nil
24
+
25
+ def self.set_maxmind_zip_url(maxmind_zip_url)
26
+ @maxmind_zip_url = maxmind_zip_url
27
+ end
28
+
29
+ def self.set_license_key(license_key)
30
+ url = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&license_key=#{license_key}&suffix=zip"
31
+ @license_key = license_key
32
+ self.set_maxmind_zip_url(url)
33
+ end
34
+
35
+ def self.update_maxmind
36
+ require "open-uri"
37
+ require "zip"
38
+
39
+ # get zipped file
40
+ return false if !@maxmind_zip_url
41
+ f_zipped = open(@maxmind_zip_url)
42
+
43
+ # unzip file:
44
+ # recursively searches for "GeoLite2-City-Locations-en"
45
+ Zip::File.open(f_zipped) do |zip_file|
46
+ zip_file.each do |entry|
47
+ if self.present?(entry.name["GeoLite2-City-Locations-en"])
48
+ fn = entry.name.split("/").last
49
+ entry.extract(File.join(FILES_FOLDER, fn)) { true } # { true } is to overwrite
50
+ break
51
+ end
52
+ end
53
+ end
54
+ true
55
+ end
56
+
57
+ def self.update
58
+ self.update_maxmind # update via internet
59
+ Dir[File.join(FILES_FOLDER, "states.*")].each do |state_fn|
60
+ self.install(state_fn.split(".").last.upcase.to_sym) # reinstall country
61
+ end
62
+ @countries, @states, @cities = [{}, {}, {}] # invalidades cache
63
+ File.delete COUNTRIES_FN # force countries.yml to be generated at next call of CS.countries
64
+ true
65
+ end
66
+
67
+ # constants: CVS position
68
+ ID = 0
69
+ COUNTRY = 4
70
+ COUNTRY_LONG = 5
71
+ STATE = 6
72
+ STATE_LONG = 7
73
+ CITY = 10
74
+
75
+ def self.install(country)
76
+ # get CSV if doesn't exists
77
+ update_maxmind unless File.exists? MAXMIND_DB_FN
78
+
79
+ # normalize "country"
80
+ country = country.to_s.upcase
81
+
82
+ # some state codes are empty: we'll use "states-replace" in these cases
83
+ states_replace_fn = File.join(FILES_FOLDER, "states-replace.yml")
84
+ states_replace = self.symbolize_keys(YAML::load_file(states_replace_fn))
85
+ states_replace = states_replace[country.to_sym] || {} # we need just this country
86
+ states_replace_inv = states_replace.invert # invert key with value, to ease the search
87
+
88
+ # read CSV line by line
89
+ cities = {}
90
+ states = {}
91
+ File.foreach(MAXMIND_DB_FN) do |line|
92
+ rec = line.split(",")
93
+ next if rec[COUNTRY] != country
94
+ next if (self.blank?(rec[STATE]) && self.blank?(rec[STATE_LONG])) && self.blank?(rec[CITY])
95
+
96
+ # some state codes are empty: we'll use "states-replace" in these cases
97
+ rec[STATE] = states_replace_inv[rec[STATE_LONG]] if self.blank?(rec[STATE])
98
+ rec[STATE] = rec[STATE_LONG] if self.blank?(rec[STATE]) # there's no correspondent in states-replace: we'll use the long name as code
99
+
100
+ # some long names are empty: we'll use "states-replace" to get the code
101
+ rec[STATE_LONG] = states_replace[rec[STATE]] if self.blank?(rec[STATE_LONG])
102
+
103
+ # normalize
104
+ rec[STATE] = rec[STATE].to_sym
105
+ rec[CITY].gsub!(/\"/, "") # sometimes names come with a "\" char
106
+ rec[STATE_LONG].gsub!(/\"/, "") if !self.blank?(rec[STATE_LONG])# sometimes names come with a "\" char
107
+
108
+ # cities list: {TX: ["Texas City", "Another", "Another 2"]}
109
+ cities.merge!({rec[STATE] => []}) if ! states.has_key?(rec[STATE])
110
+ cities[rec[STATE]] << rec[CITY]
111
+
112
+ # states list: {TX: "Texas", CA: "California"}
113
+ if ! states.has_key?(rec[STATE])
114
+ state = {rec[STATE] => rec[STATE_LONG]}
115
+ states.merge!(state)
116
+ end
117
+ end
118
+
119
+ # sort
120
+ cities = Hash[cities.sort]
121
+ states = Hash[states.sort]
122
+ cities.each do |k, v|
123
+ v.reject! {|i| i.empty? }
124
+ cities[k].sort!
125
+ end
126
+
127
+ # save to states.us and cities.us
128
+ states_fn = File.join(FILES_FOLDER, "states.#{country.downcase}")
129
+ cities_fn = File.join(FILES_FOLDER, "cities.#{country.downcase}")
130
+ File.open(states_fn, "w") { |f| f.write states.to_yaml }
131
+ File.open(cities_fn, "w") { |f| f.write cities.to_yaml }
132
+ File.chmod(0666, states_fn, cities_fn) # force permissions to rw_rw_rw_ (issue #3)
133
+ true
134
+ end
135
+
136
+ def self.current_country
137
+ return @current_country if self.present?(@current_country)
138
+
139
+ # we don't have used this method yet: discover by the file extension
140
+ fn = Dir[File.join(FILES_FOLDER, "cities.*")].last
141
+ @current_country = self.blank?(fn) ? nil : fn.split(".").last
142
+
143
+ # there's no files: we'll install and use :US
144
+ if self.blank?(@current_country)
145
+ @current_country = :US
146
+ self.install(@current_country)
147
+
148
+ # we find a file: normalize the extension to something like :US
149
+ else
150
+ @current_country = @current_country.to_s.upcase.to_sym
151
+ end
152
+
153
+ @current_country
154
+ end
155
+
156
+ def self.current_country=(country)
157
+ @current_country = country.to_s.upcase.to_sym
158
+ end
159
+
160
+ def self.cities(state, country = nil)
161
+ self.current_country = country if self.present?(country) # set as current_country
162
+ country = self.current_country
163
+ state = state.to_s.upcase.to_sym
164
+
165
+ # load the country file
166
+ if self.blank?(@cities[country])
167
+ cities_fn = File.join(FILES_FOLDER, "cities.#{country.to_s.downcase}")
168
+ self.install(country) if ! File.exists? cities_fn
169
+ @cities[country] = self.symbolize_keys(YAML::load_file(cities_fn))
170
+
171
+ # Remove duplicated cities
172
+ @cities[country].each do |key, value|
173
+ @cities[country][key] = value.uniq || []
174
+ end
175
+
176
+ # Process lookup table
177
+ lookup = get_cities_lookup(country, state)
178
+ if ! lookup.nil?
179
+ @cities[country][state] = [] if @cities[country][state].nil?
180
+ lookup.each do |old_value, new_value|
181
+ if new_value.nil? || self.blank?(new_value)
182
+ @cities[country][state].delete(old_value)
183
+ else
184
+ index = @cities[country][state].index(old_value)
185
+ if index.nil?
186
+ @cities[country][state] << new_value
187
+ else
188
+ @cities[country][state][index] = new_value
189
+ end
190
+ end
191
+ end
192
+ @cities[country][state] = @cities[country][state].sort # sort it alphabetically
193
+ end
194
+ end
195
+
196
+ # Return list
197
+ @cities[country][state]
198
+ end
199
+
200
+ def self.set_cities_lookup_file(filename)
201
+ @cities_lookup_fn = filename
202
+ @cities_lookup = nil
203
+ end
204
+
205
+ def self.set_states_lookup_file(filename)
206
+ @states_lookup_fn = filename
207
+ @states_lookup = nil
208
+ end
209
+
210
+ def self.set_countries_lookup_file(filename)
211
+ @countries_lookup_fn = filename
212
+ @countries_lookup = nil
213
+ end
214
+
215
+ def self.get_cities_lookup(country, state)
216
+ # lookup file not loaded
217
+ if @cities_lookup.nil?
218
+ @cities_lookup_fn = DEFAULT_CITIES_LOOKUP_FN if @cities_lookup_fn.nil?
219
+ @cities_lookup_fn = File.expand_path(@cities_lookup_fn)
220
+ return nil if ! File.exists?(@cities_lookup_fn)
221
+ @cities_lookup = self.symbolize_keys(YAML::load_file(@cities_lookup_fn)) # force countries to be symbols
222
+ @cities_lookup.each { |key, value| @cities_lookup[key] = self.symbolize_keys(value) } # force states to be symbols
223
+ end
224
+
225
+ return nil if ! @cities_lookup.key?(country) || ! @cities_lookup[country].key?(state)
226
+ @cities_lookup[country][state]
227
+ end
228
+
229
+ def self.get_states_lookup(country)
230
+ # lookup file not loaded
231
+ if @states_lookup.nil?
232
+ @states_lookup_fn = DEFAULT_STATES_LOOKUP_FN if @states_lookup_fn.nil?
233
+ @states_lookup_fn = File.expand_path(@states_lookup_fn)
234
+ return nil if ! File.exists?(@states_lookup_fn)
235
+ @states_lookup = self.symbolize_keys(YAML::load_file(@states_lookup_fn)) # force countries to be symbols
236
+ @states_lookup.each { |key, value| @states_lookup[key] = self.symbolize_keys(value) } # force states to be symbols
237
+ end
238
+
239
+ return nil if ! @states_lookup.key?(country)
240
+ @states_lookup[country]
241
+ end
242
+
243
+ def self.get_countries_lookup
244
+ # lookup file not loaded
245
+ if @countries_lookup.nil?
246
+ @countries_lookup_fn = DEFAULT_COUNTRIES_LOOKUP_FN if @countries_lookup_fn.nil?
247
+ @countries_lookup_fn = File.expand_path(@countries_lookup_fn)
248
+ return nil if ! File.exists?(@countries_lookup_fn)
249
+ @countries_lookup = self.symbolize_keys(YAML::load_file(@countries_lookup_fn)) # force countries to be symbols
250
+ end
251
+
252
+ @countries_lookup
253
+ end
254
+
255
+ def self.states(country)
256
+ return {} if country.nil?
257
+
258
+ # Set it as current_country
259
+ self.current_country = country # set as current_country
260
+ country = self.current_country # normalized
261
+
262
+ # Load the country file
263
+ if self.blank?(@states[country])
264
+ states_fn = File.join(FILES_FOLDER, "states.#{country.to_s.downcase}")
265
+ self.install(country) if ! File.exists? states_fn
266
+ @states[country] = self.symbolize_keys(YAML::load_file(states_fn))
267
+ # Process lookup table
268
+ lookup = get_states_lookup(country)
269
+ if ! lookup.nil?
270
+ lookup.each do |key, value|
271
+ if value.nil? || self.blank?(value)
272
+ @states[country].delete(key)
273
+ else
274
+ @states[country][key] = value
275
+ end
276
+ end
277
+ @states[country] = @states[country].sort.to_h # sort it alphabetically
278
+ end
279
+ end
280
+
281
+ # Return list
282
+ @states[country] || {}
283
+ end
284
+
285
+ # list of all countries of the world (countries.yml)
286
+ def self.countries
287
+ if ! File.exists? COUNTRIES_FN
288
+ # countries.yml doesn't exists, extract from MAXMIND_DB
289
+ update_maxmind unless File.exists? MAXMIND_DB_FN
290
+
291
+ # reads CSV line by line
292
+ File.foreach(MAXMIND_DB_FN) do |line|
293
+ rec = line.split(",")
294
+ next if self.blank?(rec[COUNTRY]) || self.blank?(rec[COUNTRY_LONG]) # jump empty records
295
+ country = rec[COUNTRY].to_s.upcase.to_sym # normalize to something like :US, :BR
296
+ if self.blank?(@countries[country])
297
+ long = rec[COUNTRY_LONG].gsub(/\"/, "") # sometimes names come with a "\" char
298
+ @countries[country] = long
299
+ end
300
+ end
301
+
302
+ # sort and save to "countries.yml"
303
+ @countries = Hash[@countries.sort]
304
+ File.open(COUNTRIES_FN, "w") { |f| f.write @countries.to_yaml }
305
+ File.chmod(0666, COUNTRIES_FN) # force permissions to rw_rw_rw_ (issue #3)
306
+ else
307
+ # countries.yml exists, just read it
308
+ @countries = self.symbolize_keys(YAML::load_file(COUNTRIES_FN))
309
+ end
310
+
311
+ # Applies `countries-lookup.yml` if exists
312
+ lookup = self.get_countries_lookup()
313
+ if ! lookup.nil?
314
+ lookup.each do |key, value|
315
+ if value.nil? || self.blank?(value)
316
+ @countries.delete(key)
317
+ else
318
+ @countries[key] = value
319
+ end
320
+ end
321
+ @countries = @countries.sort.to_h # sort it alphabetically
322
+ end
323
+
324
+ # Return countries list
325
+ @countries
326
+ end
327
+
328
+ # get = countries, get(country) = states(country), get(country, state) = cities(state, country)
329
+ def self.get(country = nil, state = nil)
330
+ return self.countries if country.nil?
331
+ return self.states(country) if state.nil?
332
+ return self.cities(state, country)
333
+ end
334
+
335
+ # Emulates Rails' `blank?` method
336
+ def self.blank?(obj)
337
+ obj.respond_to?(:empty?) ? !!obj.empty? : !obj
338
+ end
339
+
340
+ # Emulates Rails' `present?` method
341
+ def self.present?(obj)
342
+ !self.blank?(obj)
343
+ end
344
+
345
+ # Emulates Rails' `symbolize_keys` method
346
+ def self.symbolize_keys(obj)
347
+ obj.transform_keys { |key| key.to_sym rescue key }
348
+ end
349
+ end