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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +2 -0
- data/cs.gemspec +23 -0
- data/lib/cs.rb +349 -0
- data/lib/db/GeoLite2-City-Locations-en.csv +119237 -0
- data/lib/db/countries.yml +249 -0
- data/lib/db/states-replace.yml +3 -0
- metadata +95 -0
checksums.yaml
ADDED
|
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -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.
|
data/README.md
ADDED
|
@@ -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/.
|
data/Rakefile
ADDED
data/cs.gemspec
ADDED
|
@@ -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
|
data/lib/cs.rb
ADDED
|
@@ -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
|