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