geolocal 0.5
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 +22 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +12 -0
- data/config/geolocal.rb +5 -0
- data/geolocal.gemspec +27 -0
- data/lib/geolocal.rb +32 -0
- data/lib/geolocal/configuration.rb +45 -0
- data/lib/geolocal/provider/base.rb +112 -0
- data/lib/geolocal/provider/db_ip.rb +86 -0
- data/lib/geolocal/provider/test.rb +2 -0
- data/lib/geolocal/railtie.rb +12 -0
- data/lib/geolocal/version.rb +3 -0
- data/lib/tasks/geolocal.rake +16 -0
- data/spec/configuration_spec.rb +91 -0
- data/spec/data/dbip-country.csv.gz +0 -0
- data/spec/provider/db_ip_spec.rb +99 -0
- data/spec/spec_helper.rb +15 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 42ed22b2aefba01694af9ec4f3fb0f969f307ef8
|
4
|
+
data.tar.gz: 969d5d208bb9fd73e83d363c2b628a370af600b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 319338e43dac19ca42ef0e7d6d3ff59e4862a71f95b1e6f34f17f1b96675bbf966e4d190c68b26c9342ff3cc1bd1f3efc3c1287ea053f316e57b4e9653948490
|
7
|
+
data.tar.gz: c36302f7fa49e21a064410754030276cc2e71398618eca05dad5d0bfacfd11285a61b173627f10c6547a9ad57c0cb01b4aaaaa8c3e1205ef32883afdbb833b25
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
guard :bundler do
|
2
|
+
watch('Gemfile')
|
3
|
+
watch(/^.+\.gemspec/)
|
4
|
+
end
|
5
|
+
|
6
|
+
guard :rspec, cmd: 'rspec' do
|
7
|
+
# run all specs if any Ruby file is modified
|
8
|
+
watch(%r{^lib/.+\.rb}) { "spec" }
|
9
|
+
# watch(%r{.*\.rb})
|
10
|
+
# watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
11
|
+
|
12
|
+
# re-run just the spec
|
13
|
+
watch(%r{^spec/.+_spec\.rb$})
|
14
|
+
|
15
|
+
# run all specs if the helper is modified
|
16
|
+
watch(%r{spec/spec_helper\.rb}) { "spec" }
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014-2015 Scott Bronson
|
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,103 @@
|
|
1
|
+
# Geolocal
|
2
|
+
|
3
|
+
Allows IP addresses to geocoded with a single Ruby if statement.
|
4
|
+
No network access, no context switches, no delay. Just one low-calorie local lookup.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
The usual method, add this line to your Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'geolocal'
|
13
|
+
```
|
14
|
+
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
First create a config file that describes the ranges you're interested in.
|
19
|
+
Here's an example:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Geolocal.configure do
|
23
|
+
config.countries = {
|
24
|
+
us: 'US',
|
25
|
+
central_america: %w[ BZ CR SV GT HN NI PA ]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Now run `rake geolocal:update`. It will download the geocoding data
|
31
|
+
from the default provider (see the [providers](#providers) section) and
|
32
|
+
create `lib/geolocal.rb`.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Geolocal.in_us?(request.remote_ip)
|
36
|
+
Geolocal.in_central_america?(IPAddr.new('200.16.66.0'))
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
## Config
|
41
|
+
|
42
|
+
Here are the most common config keys. See the docs for the provider
|
43
|
+
you're using for more.
|
44
|
+
|
45
|
+
* **provider**: Where to download the geocoding data. Default: DB_IP.
|
46
|
+
* **module**: The name of the module to receive the `in_*` methods. Default: 'Geolocal'.
|
47
|
+
* **file**: Path to the file to contain the generated code. Default: `lib/#{module}.rb`.
|
48
|
+
* **tmpdir**: the directory to contain intermediate files. They will require tens of megabytes
|
49
|
+
for countries, hundreds for cities). Default: `./tmp/geolocal`
|
50
|
+
* **expires**: the amount of time to consider downloaded data valid. Default: 1.month
|
51
|
+
* **countries**: the ISO-codes of the countries to include in the lookup.
|
52
|
+
* **ipv6**: whether the ranges should support ipv6 addresses.
|
53
|
+
|
54
|
+
|
55
|
+
## Examples
|
56
|
+
|
57
|
+
This uses the [Countries](https://github.com/hexorx/countries) gem
|
58
|
+
to discover if an address is in the European Union:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
require 'countries'
|
62
|
+
|
63
|
+
eu_codes = Country.find_all_by_eu_member(true).map(&:first)
|
64
|
+
|
65
|
+
Geolocal.configure do |config|
|
66
|
+
config.countries = { us: 'US', eu: eu_codes }
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Now you can call `Geolocal.in_eu?(ip)`. If the European Union ever changes,
|
71
|
+
run `bundle update countries` and then `rake geolocal`.
|
72
|
+
|
73
|
+
## Providers
|
74
|
+
|
75
|
+
This gem currently only supoports the [DB-IP](https://db-ip.com/about/) database.
|
76
|
+
There are lots of other databases available and this gem is organized to support them.
|
77
|
+
Patches welcome.
|
78
|
+
|
79
|
+
|
80
|
+
## Alternatives
|
81
|
+
|
82
|
+
The [Geocoder gem](https://github.com/alexreisner/geocoder) offers
|
83
|
+
[local database services](https://github.com/alexreisner/geocoder#ip-address-local-database-services).
|
84
|
+
Geolocal is simpler, faster, and production ready, but the Geocoder gem offers more options and more providers.
|
85
|
+
Geolocal also doesn't add any dependencies to your deploy, potentially making it easier to get working with oddball
|
86
|
+
environments like Heroku.
|
87
|
+
|
88
|
+
|
89
|
+
## TODO
|
90
|
+
|
91
|
+
- [ ] performance information? benchmarks. space saving by going ipv4-only?
|
92
|
+
- [ ] include a Rails generator for the config file?
|
93
|
+
- [ ] write a command that takes the config on the command line and writes the result to stdout?
|
94
|
+
- [ ] Add support for cities
|
95
|
+
- [ ] replace Nokogiri dependency with a single regex? Shame to force that dependency on all clients.
|
96
|
+
- [ ] other sources for this data? [MainFacts](http://mainfacts.com/ip-address-space-addresses), [NirSoft](http://www.nirsoft.net/countryip/)
|
97
|
+
- [ ] Add support for for-pay features like lat/lon and timezones?
|
98
|
+
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
To make this gem less imperfect, please submit your issues and patches on
|
103
|
+
[GitHub](https://github.com/bronson/geolocal/).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
Dir.glob('lib/tasks/*.rake').each { |r| load r}
|
5
|
+
|
6
|
+
Geolocal.configure do |config|
|
7
|
+
config.file = 'tmp/geolocal.rb' # 'lib/geolocal.rb' default would overwrite our source code
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
|
+
task :default => ['spec']
|
12
|
+
task :test => ['spec']
|
data/config/geolocal.rb
ADDED
data/geolocal.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'geolocal/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "geolocal"
|
7
|
+
spec.version = Geolocal::VERSION
|
8
|
+
spec.authors = ["Scott Bronson"]
|
9
|
+
spec.email = ["brons_geolo@rinspin.com"]
|
10
|
+
spec.summary = "Generates plain Ruby if statements to geocode IP addresses"
|
11
|
+
spec.description = "Geocode an IP address with a single if statement. No network access, no context switches, no waiting."
|
12
|
+
spec.homepage = "http://github.com/bronson/geolocal"
|
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
|
+
# hm... this is only required when running 'rake download'.
|
21
|
+
spec.add_runtime_dependency "nokogiri"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "webmock"
|
27
|
+
end
|
data/lib/geolocal.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'geolocal/version'
|
2
|
+
require 'geolocal/configuration'
|
3
|
+
require 'geolocal/provider/base'
|
4
|
+
|
5
|
+
|
6
|
+
module Geolocal
|
7
|
+
require 'geolocal/railtie' if defined? Rails
|
8
|
+
|
9
|
+
def self.configuration
|
10
|
+
@configuration ||= Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configure
|
14
|
+
yield(configuration)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.reset_configuration
|
18
|
+
@configuration = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.provider
|
22
|
+
@provider ||= configuration.load_provider.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.download
|
26
|
+
provider.download
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.update
|
30
|
+
provider.update
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Configuration
|
2
|
+
OPTIONS = [ :provider, :module, :file, :tmpdir, :expires, :ipv6, :quiet, :countries ]
|
3
|
+
attr_accessor(*OPTIONS)
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# configuration defaults
|
7
|
+
@provider = 'Geolocal::Provider::DB_IP'
|
8
|
+
@module = 'Geolocal'
|
9
|
+
@file = nil # default is computed
|
10
|
+
@tmpdir = 'tmp/geolocal'
|
11
|
+
@expires = nil # provider chooses the most sensible
|
12
|
+
@ipv6 = true
|
13
|
+
@quiet = false
|
14
|
+
@countries = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# if not set, defaults to lib/module-name
|
18
|
+
def file
|
19
|
+
@file || "lib/#{module_file @module}.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
def require_provider_file
|
23
|
+
begin
|
24
|
+
# normal ruby/gem load path
|
25
|
+
Kernel.require module_file(@provider)
|
26
|
+
rescue LoadError
|
27
|
+
# used when running source code locally
|
28
|
+
Kernel.require "./lib/#{module_file(@provider)}.rb"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_provider
|
33
|
+
require_provider_file
|
34
|
+
@provider.split('::').reduce(Module, :const_get)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
# returned keys will always be symbols
|
39
|
+
OPTIONS.reduce({}) { |a,v| a.merge! v => self.send(v) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def module_file modname
|
43
|
+
modname.gsub('::', '/').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
|
4
|
+
module Geolocal
|
5
|
+
module Provider
|
6
|
+
class Base
|
7
|
+
def initialize params={}
|
8
|
+
@config = params.merge(Geolocal.configuration.to_hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
def config
|
12
|
+
@config
|
13
|
+
end
|
14
|
+
|
15
|
+
def download
|
16
|
+
# TODO: skip download if local files are new enough
|
17
|
+
# TODO: provide a FORCE argument to force download anyway
|
18
|
+
download_files
|
19
|
+
end
|
20
|
+
|
21
|
+
def update
|
22
|
+
countries = config[:countries].merge(config[:countries]) { |name, country_codes|
|
23
|
+
Array(country_codes).map(&:upcase).to_set
|
24
|
+
}
|
25
|
+
|
26
|
+
results = countries.merge(countries) { "" }
|
27
|
+
|
28
|
+
read_ranges(countries) do |name,lo,hi|
|
29
|
+
results[name] << "#{IPAddr.new(lo).to_i}..#{IPAddr.new(hi).to_i},\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
File.open(config[:file], 'w') do |file|
|
33
|
+
output(file, results)
|
34
|
+
end
|
35
|
+
|
36
|
+
status "done, result in #{config[:file]}\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
def output file, results
|
40
|
+
names = results.keys
|
41
|
+
modname = config[:module]
|
42
|
+
|
43
|
+
write_header file, names, modname
|
44
|
+
|
45
|
+
file.write "module #{modname}\n"
|
46
|
+
names.each do |name|
|
47
|
+
write_method file, name
|
48
|
+
end
|
49
|
+
file.write "end\n\n"
|
50
|
+
|
51
|
+
status " writing "
|
52
|
+
results.each do |name, body|
|
53
|
+
status "#{name} "
|
54
|
+
write_ranges file, modname, name, body
|
55
|
+
end
|
56
|
+
status "\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def write_header file, names, modname
|
61
|
+
file.write <<EOL
|
62
|
+
# This file is autogenerated
|
63
|
+
|
64
|
+
# Defines #{names.map { |n| "#{modname}.in_#{n}?" }.join(', ')}
|
65
|
+
# and #{names.map { |n| "#{modname}::#{n.upcase}" }.join(', ')}
|
66
|
+
|
67
|
+
EOL
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_method file, name
|
71
|
+
file.write <<EOL
|
72
|
+
def self.in_#{name}? addr
|
73
|
+
num = addr.to_i
|
74
|
+
#{name.upcase}.bsearch { |range| num > range.max ? 1 : num < range.min ? -1 : 0 }
|
75
|
+
end
|
76
|
+
EOL
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_ranges file, modname, name, body
|
80
|
+
file.write <<EOL
|
81
|
+
#{modname}::#{name.upcase} = [
|
82
|
+
#{body}]
|
83
|
+
|
84
|
+
EOL
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# random utilities
|
92
|
+
module Geolocal
|
93
|
+
module Provider
|
94
|
+
class Base
|
95
|
+
# returns elapsed time of block in seconds
|
96
|
+
def time_block
|
97
|
+
start = Time.now
|
98
|
+
yield
|
99
|
+
stop = Time.now
|
100
|
+
stop - start + 0.0000001 # fudge to prevent division by zero
|
101
|
+
end
|
102
|
+
|
103
|
+
def status *args
|
104
|
+
unless config[:quiet]
|
105
|
+
Kernel.print(*args)
|
106
|
+
$stdout.flush unless args.last.end_with?("\n")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'net/http'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
require 'nokogiri'
|
7
|
+
|
8
|
+
|
9
|
+
class Geolocal::Provider::DB_IP < Geolocal::Provider::Base
|
10
|
+
START_URL = 'https://db-ip.com/db/download/country'
|
11
|
+
|
12
|
+
# TODO: refactor progress and download code into a mixin?
|
13
|
+
|
14
|
+
def update_download_status size, length
|
15
|
+
@current_byte ||= 0
|
16
|
+
@previous_print ||= 0
|
17
|
+
@current_byte += size
|
18
|
+
|
19
|
+
if length
|
20
|
+
pct = @current_byte * 100 / length
|
21
|
+
pct = (pct / 5) * 5
|
22
|
+
|
23
|
+
if pct != @previous_print
|
24
|
+
@previous_print = pct
|
25
|
+
status pct.to_s + '% '
|
26
|
+
end
|
27
|
+
else
|
28
|
+
# server didn't supply a length, display running byte count?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def csv_file
|
33
|
+
"#{config[:tmpdir]}/dbip-country.csv.gz"
|
34
|
+
end
|
35
|
+
|
36
|
+
def download_files
|
37
|
+
page = Net::HTTP.get(URI START_URL)
|
38
|
+
doc = Nokogiri::HTML(page)
|
39
|
+
href = URI.parse doc.css('a.btn-primary').attr('href').to_s
|
40
|
+
|
41
|
+
# stream result because it's large
|
42
|
+
FileUtils.mkdir_p(config[:tmpdir])
|
43
|
+
status "downloading #{href} to #{csv_file}\n"
|
44
|
+
|
45
|
+
elapsed = time_block do
|
46
|
+
File.open(csv_file, 'wb') do |file|
|
47
|
+
Net::HTTP.new(href.host, href.port).request_get(href.path) do |response|
|
48
|
+
total_length = response['Content-Length'].to_i
|
49
|
+
status " reading #{(total_length/1024.0/1024).round(1)} MB: "
|
50
|
+
|
51
|
+
response.read_body do |chunk|
|
52
|
+
file.write chunk
|
53
|
+
update_download_status(chunk.length, total_length)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
status "\n read #{@current_byte} bytes in #{elapsed.round(2)} seconds at " +
|
60
|
+
"#{(@current_byte/1024/elapsed).round(1)} KB/sec\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_ranges countries
|
64
|
+
status "computing ranges\n"
|
65
|
+
|
66
|
+
row_count = 0
|
67
|
+
match_count = 0
|
68
|
+
|
69
|
+
elapsed = time_block do
|
70
|
+
File.open(csv_file, 'r') do |file|
|
71
|
+
gz = Zlib::GzipReader.new(file)
|
72
|
+
CSV.new(gz, headers: false).each do |row|
|
73
|
+
row_count += 1
|
74
|
+
countries.each do |name, country_codes|
|
75
|
+
if country_codes.include?(row[2])
|
76
|
+
match_count += 1
|
77
|
+
yield name, row[0], row[1]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
status " matched #{match_count} of #{row_count} ranges in #{elapsed.round(2)} seconds\n"
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require './config/geolocal'
|
2
|
+
|
3
|
+
namespace :geolocal do
|
4
|
+
desc "Downloads the most recent geocoding information"
|
5
|
+
task :download do
|
6
|
+
Geolocal.download
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Updates your geocoding statements to use new data."
|
10
|
+
task :update => :download do
|
11
|
+
Geolocal.update
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "shorthand for running geolocal:update"
|
16
|
+
task geolocal: 'geolocal:update'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "configuration" do
|
5
|
+
let(:config) { Geolocal.configuration }
|
6
|
+
|
7
|
+
it "has the right defaults" do
|
8
|
+
# reset the configuration so we get the default configuration,
|
9
|
+
# not the test configuration that the spec_helper has set up.
|
10
|
+
Geolocal.reset_configuration
|
11
|
+
defaults = Geolocal.configuration
|
12
|
+
|
13
|
+
# these need to match the description in the readme
|
14
|
+
expect(defaults.provider).to eq 'Geolocal::Provider::DB_IP'
|
15
|
+
expect(defaults.module).to eq 'Geolocal'
|
16
|
+
expect(defaults.file).to eq 'lib/geolocal.rb'
|
17
|
+
expect(defaults.tmpdir).to eq 'tmp/geolocal'
|
18
|
+
expect(defaults.expires).to eq nil
|
19
|
+
expect(defaults.ipv6).to eq true
|
20
|
+
expect(defaults.quiet).to eq false
|
21
|
+
expect(defaults.countries).to eq({})
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
describe "reset method" do
|
26
|
+
before :each do
|
27
|
+
Geolocal.configure do |conf|
|
28
|
+
conf.module = "Changed"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "resets the configuration" do
|
33
|
+
expect(Geolocal.configuration.module).to eq "Changed"
|
34
|
+
Geolocal.reset_configuration
|
35
|
+
expect(Geolocal.configuration.module).to eq "Geolocal"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
describe "module names" do
|
41
|
+
it "can handle typical module names" do
|
42
|
+
module_name = "GeoRangeFinderID::QuickLook"
|
43
|
+
Geolocal.configure do |g|
|
44
|
+
g.module = module_name
|
45
|
+
end
|
46
|
+
|
47
|
+
expect(config.module).to eq module_name
|
48
|
+
expect(config.file).to eq "lib/geo_range_finder_id/quick_look.rb"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "can handle a pathological module name" do
|
52
|
+
module_name = "Geolocal::Provider::DB_IP"
|
53
|
+
Geolocal.configure do |g|
|
54
|
+
g.module = module_name
|
55
|
+
end
|
56
|
+
|
57
|
+
expect(config.module).to eq module_name
|
58
|
+
expect(config.file).to eq "lib/geolocal/provider/db_ip.rb"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
describe "to_hash" do
|
64
|
+
it "can do the conversion" do
|
65
|
+
expect(config.to_hash).to eq({
|
66
|
+
provider: 'Geolocal::Provider::DB_IP',
|
67
|
+
module: 'Geolocal',
|
68
|
+
file: 'lib/geolocal.rb',
|
69
|
+
tmpdir: 'tmp/geolocal',
|
70
|
+
expires: nil,
|
71
|
+
ipv6: true,
|
72
|
+
quiet: true,
|
73
|
+
countries: {}
|
74
|
+
})
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
describe "module paths" do
|
80
|
+
it "includes the correct provider when running as a gem" do
|
81
|
+
expect(Kernel).to receive(:require).with('geolocal/provider/db_ip')
|
82
|
+
config.require_provider_file
|
83
|
+
end
|
84
|
+
|
85
|
+
it "includes the correct provider when running locally" do
|
86
|
+
expect(Kernel).to receive(:require).with('geolocal/provider/db_ip') { raise LoadError }
|
87
|
+
expect(Kernel).to receive(:require).with('./lib/geolocal/provider/db_ip.rb')
|
88
|
+
config.require_provider_file
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
Binary file
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'geolocal/provider/db_ip'
|
3
|
+
|
4
|
+
describe Geolocal::Provider::DB_IP do
|
5
|
+
let(:it) { described_class }
|
6
|
+
let(:provider) { it.new }
|
7
|
+
|
8
|
+
|
9
|
+
describe 'network operation' do
|
10
|
+
let(:country_page) {
|
11
|
+
<<-eol
|
12
|
+
<div class="container">
|
13
|
+
<h3>Free database download</h3>
|
14
|
+
<a href='http://download.db-ip.com/free/dbip-country-2015-02.csv.gz' class='btn btn-primary'>Download free IP-country database</a> (CSV, February 2015)
|
15
|
+
</div>
|
16
|
+
eol
|
17
|
+
}
|
18
|
+
|
19
|
+
# todo: would be nice to test returning lots of little chunks
|
20
|
+
let(:country_csv) {
|
21
|
+
<<-eol.gsub(/^\s*/, '')
|
22
|
+
"0.0.0.0","0.255.255.255","US"
|
23
|
+
"1.0.0.0","1.0.0.255","AU"
|
24
|
+
"1.0.1.0","1.0.3.255","CN"
|
25
|
+
eol
|
26
|
+
}
|
27
|
+
|
28
|
+
before do
|
29
|
+
stub_request(:get, 'https://db-ip.com/db/download/country').
|
30
|
+
with(headers: {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
|
31
|
+
to_return(status: 200, body: country_page, headers: {})
|
32
|
+
|
33
|
+
stub_request(:get, "http://download.db-ip.com/free/dbip-country-2015-02.csv.gz").
|
34
|
+
with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
|
35
|
+
to_return(:status => 200, :body => country_csv, :headers => {'Content-Length' => country_csv.length})
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can download the csv' do
|
39
|
+
# wow!! can't do this in an around hook because it gets the ordering completely wrong.
|
40
|
+
# since around hooks wrap ALL before hooks, they end up using the previous test's config.
|
41
|
+
if File.exist?(provider.csv_file)
|
42
|
+
File.delete(provider.csv_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
provider.download
|
46
|
+
expect(File.read provider.csv_file).to eq country_csv
|
47
|
+
|
48
|
+
File.delete(provider.csv_file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
describe 'generating' do
|
54
|
+
let(:example_output) {
|
55
|
+
<<EOL
|
56
|
+
# This file is autogenerated
|
57
|
+
|
58
|
+
# Defines Geolocal.in_us?, Geolocal.in_au?
|
59
|
+
# and Geolocal::US, Geolocal::AU
|
60
|
+
|
61
|
+
module Geolocal
|
62
|
+
def self.in_us? addr
|
63
|
+
num = addr.to_i
|
64
|
+
US.bsearch { |range| num > range.max ? 1 : num < range.min ? -1 : 0 }
|
65
|
+
end
|
66
|
+
def self.in_au? addr
|
67
|
+
num = addr.to_i
|
68
|
+
AU.bsearch { |range| num > range.max ? 1 : num < range.min ? -1 : 0 }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Geolocal::US = [
|
73
|
+
0..16777215,
|
74
|
+
]
|
75
|
+
|
76
|
+
Geolocal::AU = [
|
77
|
+
16777216..16777471,
|
78
|
+
]
|
79
|
+
|
80
|
+
EOL
|
81
|
+
}
|
82
|
+
|
83
|
+
it 'can generate countries from a csv' do
|
84
|
+
outfile = 'tmp/geolocal.rb'
|
85
|
+
if File.exist?(outfile)
|
86
|
+
File.delete(outfile)
|
87
|
+
end
|
88
|
+
|
89
|
+
Geolocal.configure do |config|
|
90
|
+
config.tmpdir = 'spec/data'
|
91
|
+
config.file = outfile
|
92
|
+
config.countries = { us: 'US', au: 'AU' }
|
93
|
+
end
|
94
|
+
provider.update
|
95
|
+
expect(File.read outfile).to eq example_output
|
96
|
+
File.delete(outfile)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'geolocal'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
|
4
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.order = 'random'
|
8
|
+
|
9
|
+
config.before :each do
|
10
|
+
Geolocal.reset_configuration
|
11
|
+
Geolocal.configure do |geoconf|
|
12
|
+
geoconf.quiet = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geolocal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Bronson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Geocode an IP address with a single if statement. No network access,
|
84
|
+
no context switches, no waiting.
|
85
|
+
email:
|
86
|
+
- brons_geolo@rinspin.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- Gemfile
|
94
|
+
- Guardfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- config/geolocal.rb
|
99
|
+
- geolocal.gemspec
|
100
|
+
- lib/geolocal.rb
|
101
|
+
- lib/geolocal/configuration.rb
|
102
|
+
- lib/geolocal/provider/base.rb
|
103
|
+
- lib/geolocal/provider/db_ip.rb
|
104
|
+
- lib/geolocal/provider/test.rb
|
105
|
+
- lib/geolocal/railtie.rb
|
106
|
+
- lib/geolocal/version.rb
|
107
|
+
- lib/tasks/geolocal.rake
|
108
|
+
- spec/configuration_spec.rb
|
109
|
+
- spec/data/dbip-country.csv.gz
|
110
|
+
- spec/provider/db_ip_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
homepage: http://github.com/bronson/geolocal
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.4.5
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Generates plain Ruby if statements to geocode IP addresses
|
136
|
+
test_files:
|
137
|
+
- spec/configuration_spec.rb
|
138
|
+
- spec/data/dbip-country.csv.gz
|
139
|
+
- spec/provider/db_ip_spec.rb
|
140
|
+
- spec/spec_helper.rb
|