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 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