cse 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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