geolocal 0.5

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development do
5
+ gem 'guard'
6
+ gem 'guard-rspec'
7
+ gem 'guard-bundler'
8
+ end
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']
@@ -0,0 +1,5 @@
1
+ require 'geolocal'
2
+
3
+ Geolocal.configure do |config|
4
+ config.countries = { us: 'US' }
5
+ end
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,2 @@
1
+ class Test
2
+ end
@@ -0,0 +1,12 @@
1
+ require 'geolocal'
2
+ require 'rails'
3
+
4
+ module Geolocal
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :geolocal
7
+
8
+ rake_tasks do
9
+ load "tasks/geolocal.rake"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Geolocal
2
+ VERSION = "0.5"
3
+ 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
@@ -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