trifle 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in messages.gemspec
4
+ gemspec
5
+
6
+ gem "fakeredis", github: "caius/fakeredis", branch: "redis-rb-3"
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ GIT
2
+ remote: git://github.com/caius/fakeredis.git
3
+ revision: ab336ff1edf50c259b557559f42316fe15a4feb7
4
+ branch: redis-rb-3
5
+ specs:
6
+ fakeredis (0.3.1)
7
+ redis (>= 3.0.0)
8
+
9
+ PATH
10
+ remote: .
11
+ specs:
12
+ trifle (0.0.1)
13
+
14
+ GEM
15
+ remote: http://rubygems.org/
16
+ specs:
17
+ diff-lcs (1.1.3)
18
+ redis (3.0.1)
19
+ rspec (2.10.0)
20
+ rspec-core (~> 2.10.0)
21
+ rspec-expectations (~> 2.10.0)
22
+ rspec-mocks (~> 2.10.0)
23
+ rspec-core (2.10.1)
24
+ rspec-expectations (2.10.0)
25
+ diff-lcs (~> 1.1.3)
26
+ rspec-mocks (2.10.1)
27
+ rujitsu (0.3.3)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ fakeredis!
34
+ rspec
35
+ rujitsu
36
+ trifle!
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Trifle
2
+
3
+ Stores the GeoIP databases in Redis and gives it a simple way to lookup IPs and map them to countries/country codes.
4
+
5
+ Get your GeoIP country CSVs from http://www.maxmind.com/app/geolite.
6
+
7
+ Trifle supports both the IPV4 and IPV6 databases.
8
+
9
+ ## Installation
10
+
11
+ Install the gem
12
+
13
+ ```
14
+ gem install trifle
15
+ ```
16
+
17
+ or add it to your Gemfile and bundle
18
+
19
+ ```ruby
20
+ gem "trifle"
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Loading
26
+
27
+ ```ruby
28
+ # Initialize
29
+ trifle = Trifle.new(Redis.new)
30
+ # Load data from file
31
+ trifle.load filename: "file.csv"
32
+ # or files
33
+ trifle.load filenames: ["file1.csv", "file2.csv"]
34
+ # or directly as an array
35
+ trifle.load data: preloaded_array
36
+ ```
37
+
38
+ ### Lookup
39
+
40
+ ```ruby
41
+ # Initialize
42
+ trifle = Trifle.new(Redis.new)
43
+
44
+ # Lookup for existing data
45
+ trifle.find "223.255.128.0"
46
+ # => ["HK", "Hong Kong"]
47
+
48
+ # Lookup for missing data
49
+ trifle.find "192.168.1.1"
50
+ # => nil
51
+ ```
52
+
53
+
54
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/trifle.rb ADDED
@@ -0,0 +1,29 @@
1
+ # Trifle is a simple storage and lookup gem for GeoIP data
2
+ # using Redis as storage
3
+ require "trifle/loader"
4
+ require "trifle/finder"
5
+ require "trifle/initialize_with_redis"
6
+
7
+ class Trifle
8
+ include InitializeWithRedis
9
+
10
+ KEY = "trifle".freeze
11
+
12
+ def load options = {}
13
+ loader.handle options
14
+ end
15
+
16
+ def find ip
17
+ finder.handle ip
18
+ end
19
+
20
+ protected
21
+
22
+ def loader
23
+ @loader = Loader.new(redis)
24
+ end
25
+
26
+ def finder
27
+ @finder = Finder.new(redis)
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require_relative "initialize_with_redis"
2
+ require 'ipaddr'
3
+
4
+ class Trifle
5
+ class Finder
6
+ include InitializeWithRedis
7
+
8
+ def handle ip
9
+ ip_i = IPAddr.new(ip).to_i
10
+ find(ip_i, 0, max)
11
+ end
12
+
13
+ protected
14
+
15
+ def find ip_i, lower, upper
16
+ return nil if lower > upper
17
+
18
+ index = (lower+upper)/2
19
+
20
+ current = entry_for(index)
21
+
22
+ if in_range(current, ip_i)
23
+ current.last(2)
24
+ elsif ip_i < current[0]
25
+ find ip_i, lower, index-1
26
+ else
27
+ find ip_i, index+1, upper
28
+ end
29
+ end
30
+
31
+ def max
32
+ redis.llen Trifle::KEY
33
+ end
34
+
35
+ def entry_for index
36
+ entry = redis.lindex(Trifle::KEY, index).split(":")
37
+ entry[0] = entry[0].to_i
38
+ entry[1] = entry[1].to_i
39
+ entry
40
+ end
41
+
42
+ def in_range range, ip_i
43
+ ip_i >= range[0] && ip_i <= range[1]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ class Trifle
2
+ module InitializeWithRedis
3
+ attr_accessor :redis
4
+
5
+ def initialize redis
6
+ raise ArgumentError.new("Redis object expected") unless redis.is_a?(Redis)
7
+ self.redis = redis
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,69 @@
1
+ require_relative "initialize_with_redis"
2
+ require "csv"
3
+
4
+ class Trifle
5
+ class Loader
6
+ include InitializeWithRedis
7
+
8
+ def handle options = {}
9
+ if options[:filename]
10
+ load_files [options[:filename]]
11
+ elsif options[:filenames]
12
+ load_files options[:filenames]
13
+ elsif options[:data]
14
+ load_data options[:data]
15
+ else
16
+ raise ArgumentError.new("Please provide a :filename, :filenames or an array of :data as an argument.")
17
+ end
18
+ end
19
+
20
+ def load_files filenames
21
+ raise ArgumentError.new("filenames must be an array of strings") unless filenames.is_a?(Array) && !filenames.map{|element| element.is_a?(String)}.include?(false)
22
+
23
+ data = []
24
+ filenames.each do |filename|
25
+ contents = File.open(filename, "rb").read
26
+ data += parse(contents)
27
+ end
28
+ load_data data
29
+ end
30
+
31
+ def load_data data
32
+ raise ArgumentError.new("data must be an array as loaded from a GeoIP data set") unless valid?(data)
33
+ clear
34
+ sort(data)
35
+ data.each {|row| append(row) }
36
+ end
37
+
38
+ def append row
39
+ entry = row.values_at(2,3,4,5).join(":")
40
+ redis.rpush Trifle::KEY, entry
41
+ end
42
+
43
+ def sort data
44
+ data.sort {|a,b| a[2] <=> b[2] }
45
+ end
46
+
47
+ def parse contents
48
+ contents.gsub!('", "', '","')
49
+ CSV.parse(contents)
50
+ end
51
+
52
+ def valid? data
53
+ if data.is_a?(Array) && data.count > 0
54
+ return data.map do |row|
55
+ is_number(row[2]) && is_number(row[3])
56
+ end.select{|valid| !valid}.count == 0
57
+ end
58
+ false
59
+ end
60
+
61
+ def is_number field
62
+ field.is_a?(Numeric) || field =~ /^\d+$/
63
+ end
64
+
65
+ def clear
66
+ redis.del(Trifle::KEY)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ class Trifle
2
+ VERSION = "0.0.1" unless defined? Trifle::VERSION
3
+ end
@@ -0,0 +1,3 @@
1
+ require "trifle"
2
+ require "rujitsu"
3
+ require "fakeredis/rspec"
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe Trifle::Finder do
4
+
5
+ before do
6
+ @valid_data = [
7
+ ["223.255.128.0","223.255.191.255","3758063616","3758079999","HK","Hong Kong"],
8
+ ["223.255.192.0","223.255.223.255","3758080000","3758088191","KR","Korea, Republic of"],
9
+ ["223.255.224.0","223.255.231.255","3758088192","3758090239","ID","Indonesia"],
10
+ ["223.255.232.0","223.255.235.255","3758090240","3758091263","AU","Australia"],
11
+ ["223.255.236.0","223.255.239.255","3758091264","3758092287","CN","China"],
12
+ ["223.255.240.0","223.255.243.255","3758092288","3758093311","HK","Hong Kong"],
13
+ ["223.255.244.0","223.255.247.255","3758093312","3758094335","IN","India"],
14
+ ["223.255.248.0","223.255.251.255","3758094336","3758095359","AU","Australia"],
15
+ ["223.255.252.0","223.255.253.255","3758095360","3758095871","CN","China"],
16
+ ["223.255.254.0","223.255.254.255","3758095872","3758096127","SG","Singapore"],
17
+ ["223.255.255.0","223.255.255.255","3758096128","3758096383","AU","Australia"]
18
+ ]
19
+ @redis = Redis.new
20
+ trifle = Trifle.new(@redis)
21
+ @finder = Trifle::Finder.new(@redis)
22
+ trifle.load(data: @valid_data)
23
+ end
24
+
25
+ describe "#handle" do
26
+ it "should find the right entry" do
27
+ @finder.handle("223.255.128.0").should be == ["HK", "Hong Kong"]
28
+ @finder.handle("223.255.244.10").should be == ["IN", "India"]
29
+ @finder.handle("223.255.255.255").should be == ["AU","Australia"]
30
+ end
31
+
32
+ it "should return nil if the entry wasn't found" do
33
+ @finder.handle("127.0.0.1").should be_nil
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Trifle::InitializeWithRedis do
4
+
5
+ describe "#initialize" do
6
+ before do
7
+ @klass = Class.new do
8
+ include Trifle::InitializeWithRedis
9
+ end
10
+ end
11
+
12
+ it "should accept a redis instance" do
13
+ @klass.new(Redis.new).should be_a(@klass)
14
+ end
15
+
16
+ it "should fail without a redis instance" do
17
+ -> { @klass.new(nil) }.should raise_error(ArgumentError)
18
+ -> { @klass.new("http://127.0.0.1:3000/") }.should raise_error(ArgumentError)
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,169 @@
1
+ require "spec_helper"
2
+
3
+ describe Trifle::Loader do
4
+
5
+ before do
6
+ @loader = Trifle::Loader.new(Redis.new)
7
+ @valid_data = [
8
+ ["223.255.254.0","223.255.254.255","3758095872","3758096127","SG","Singapore"],
9
+ ["223.255.255.0","223.255.255.255","3758096128","3758096383","AU","Australia"]
10
+ ]
11
+ @csv = <<-EOD
12
+ "223.255.254.0","223.255.254.255","3758095872","3758096127","SG","Singapore"
13
+ "223.255.255.0","223.255.255.255","3758096128","3758096383","AU","Australia"
14
+ EOD
15
+ end
16
+
17
+ it "should extend InitializeWithRedis" do
18
+ Trifle::Loader.included_modules.should include(Trifle::InitializeWithRedis)
19
+ end
20
+
21
+ describe "#handle" do
22
+
23
+ context "when given the :filename option" do
24
+ it "should pass it on to load_files" do
25
+ filename = 'data.csv'
26
+ @loader.should_receive(:load_files).with([filename])
27
+ @loader.handle(filename: filename)
28
+ end
29
+ end
30
+
31
+ context "when given the :filenames option" do
32
+ it "should pass it on to load_files" do
33
+ filenames = ['data1.csv', 'data2.csv']
34
+ @loader.should_receive(:load_files).with(filenames)
35
+ @loader.handle(filenames: filenames)
36
+ end
37
+ end
38
+
39
+ context "when given the :data option" do
40
+ it "should pass it on to load_data" do
41
+ data = [:foo, :bar]
42
+ @loader.should_receive(:load_data).with(data)
43
+ @loader.handle(data: data)
44
+ end
45
+ end
46
+
47
+ context "when given anything else" do
48
+ it "should raise an error" do
49
+ -> { @loader.handle(foo: :bar) }.should raise_error(ArgumentError)
50
+ -> { @loader.handle }.should raise_error(ArgumentError)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "#load_files" do
56
+ context "when given a valid list of files" do
57
+ before do
58
+ @filenames = ['foo.csv', 'bar.csv']
59
+ file = mock File
60
+ file.should_receive(:read).twice.and_return(@csv)
61
+ File.should_receive(:open).twice.and_return(file)
62
+ end
63
+
64
+ it "should read the files" do
65
+ @loader.load_files @filenames
66
+ @loader.redis.llen(Trifle::KEY).should be == 4
67
+ end
68
+ end
69
+
70
+ context "when given anything but an array of strings" do
71
+ it "should raise an error" do
72
+ -> { @loader.load_files "" }.should raise_error(ArgumentError)
73
+ -> { @loader.load_files [:foo] }.should raise_error(ArgumentError)
74
+ end
75
+ end
76
+
77
+ context "when given a filename for a file that's missing" do
78
+ it "should raise an error" do
79
+ -> { @loader.load_files ["foobar.csv"] }.should raise_error(/No such file or directory - foobar.csv/)
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "#load_data" do
85
+
86
+ context "when given valid data" do
87
+ it "should clear out existing data" do
88
+ @loader.should_receive(:clear)
89
+ @loader.load_data @valid_data
90
+ end
91
+
92
+ it "should validate the data" do
93
+ @loader.should_receive(:valid?).and_return(true)
94
+ @loader.load_data @valid_data
95
+ end
96
+
97
+ it "should load it in redis" do
98
+ @loader.load_data @valid_data
99
+ @loader.redis.llen(Trifle::KEY).should be == 2
100
+ end
101
+ end
102
+
103
+ context "when given invalid data" do
104
+ it "should raise an error" do
105
+ @loader.should_receive(:valid?).and_return(false)
106
+ -> { @loader.load_data @valid_data }.should raise_error(ArgumentError)
107
+ end
108
+ end
109
+
110
+ context "when given anything but an array" do
111
+ it "should raise an error" do
112
+ -> { @loader.load_data :rubbish }.should raise_error(ArgumentError)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "#parse" do
118
+ it "should parse valid csv data" do
119
+ @loader.parse(@csv).should be == @valid_data
120
+ end
121
+
122
+ it "should raise an error for invalid csv data" do
123
+ -> { @loader.parse('"foo",","#') }.should raise_error(CSV::MalformedCSVError)
124
+ end
125
+
126
+ it "should handle ipv6 data" do
127
+ @ipv6csv = <<-CSV
128
+ "2c0f:ffe8::", "2c0f:ffe8:ffff:ffff:ffff:ffff:ffff:ffff", "58569106662796955307479896348547874816", "58569106742025117821744233942091825151", "NG", "Nigeria"
129
+ "2c0f:fff0::", "2c0f:fff0:ffff:ffff:ffff:ffff:ffff:ffff", "58569107296622255421594597096899477504", "58569107375850417935858934690443427839", "NG", "Nigeria"
130
+ CSV
131
+ -> { @loader.parse(@ipv6csv) }.should_not raise_error
132
+ end
133
+ end
134
+
135
+ describe "#sort" do
136
+ it "should sort the data" do
137
+ @loader.sort(@valid_data.reverse).should be == @valid_data
138
+ end
139
+ end
140
+
141
+
142
+ describe "#valid?" do
143
+ it "should mark valid data as such" do
144
+ @loader.valid?(@valid_data).should be_true
145
+ end
146
+
147
+ it "should mark invalid data as such" do
148
+ invalid_data = []
149
+ @loader.valid?(invalid_data).should be_false
150
+ invalid_data = [
151
+ ["223.255.254.0","223.255.254.255","not_a_number","3758096127","SG","Singapore"]
152
+ ]
153
+ @loader.valid?(invalid_data).should be_false
154
+ invalid_data = [
155
+ ["223.255.254.0","223.255.254.255","3758095872","not_a_number","SG","Singapore"]
156
+ ]
157
+ @loader.valid?(invalid_data).should be_false
158
+ end
159
+ end
160
+
161
+ describe "#clear" do
162
+ it "should clear existing data" do
163
+ @loader.load_data @valid_data
164
+ @loader.clear
165
+ @loader.redis.llen(Trifle::KEY).should be == 0
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe Trifle do
4
+
5
+ it "should extend InitializeWithRedis" do
6
+ Trifle.included_modules.should include(Trifle::InitializeWithRedis)
7
+ end
8
+
9
+ describe "#load" do
10
+ before do
11
+ @trifle = Trifle.new(Redis.new)
12
+ @options = { filenames: "foobar.csv" }
13
+ end
14
+
15
+ it "should pass this to the loader" do
16
+ loader = mock Trifle::Loader
17
+ Trifle::Loader.should_receive(:new).and_return(loader)
18
+ loader.should_receive(:handle).with(@options)
19
+ @trifle.load @options
20
+ end
21
+ end
22
+
23
+ describe "#find" do
24
+ before do
25
+ @trifle = Trifle.new(Redis.new)
26
+ @ip = "127.0.0.1"
27
+ end
28
+
29
+ it "should pass this to the finder" do
30
+ finder = mock Trifle::Finder
31
+ Trifle::Finder.should_receive(:new).and_return(finder)
32
+ finder.should_receive(:handle).with(@ip)
33
+ @trifle.find @ip
34
+ end
35
+ end
36
+
37
+ end
data/trifle.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "trifle/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "trifle"
7
+ s.version = Trifle::VERSION
8
+ s.authors = ["Cristiano Betta"]
9
+ s.email = ["cristiano@emberads.com"]
10
+ s.homepage = "http://emberads.com"
11
+ s.summary = "A GeoIP lookup in Redis"
12
+ s.description = "Stores the GeoIP databases in Redis and gives it a simple way to lookup IPs and map them to countries"
13
+
14
+ s.rubyforge_project = "trifle"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency 'rspec'
22
+ s.add_development_dependency 'rujitsu'
23
+ s.add_development_dependency "fakeredis", "~> 0.3.0"
24
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trifle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Cristiano Betta
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70353203055680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70353203055680
25
+ - !ruby/object:Gem::Dependency
26
+ name: rujitsu
27
+ requirement: &70353203079000 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70353203079000
36
+ - !ruby/object:Gem::Dependency
37
+ name: fakeredis
38
+ requirement: &70353203078460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.3.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70353203078460
47
+ description: Stores the GeoIP databases in Redis and gives it a simple way to lookup
48
+ IPs and map them to countries
49
+ email:
50
+ - cristiano@emberads.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - .rspec
57
+ - Gemfile
58
+ - Gemfile.lock
59
+ - README.md
60
+ - Rakefile
61
+ - lib/trifle.rb
62
+ - lib/trifle/finder.rb
63
+ - lib/trifle/initialize_with_redis.rb
64
+ - lib/trifle/loader.rb
65
+ - lib/trifle/version.rb
66
+ - spec/spec_helper.rb
67
+ - spec/trifle/finder_spec.rb
68
+ - spec/trifle/initialize_with_redis_spec.rb
69
+ - spec/trifle/loader_spec.rb
70
+ - spec/trifle_spec.rb
71
+ - trifle.gemspec
72
+ homepage: http://emberads.com
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project: trifle
92
+ rubygems_version: 1.8.10
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: A GeoIP lookup in Redis
96
+ test_files:
97
+ - spec/spec_helper.rb
98
+ - spec/trifle/finder_spec.rb
99
+ - spec/trifle/initialize_with_redis_spec.rb
100
+ - spec/trifle/loader_spec.rb
101
+ - spec/trifle_spec.rb