focus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 John Nunemaker (for initial structure)
2
+ Copyright (c) 2009 Mark G.
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ = focus
2
+
3
+ A simple interface to the www.hostip.info API.
4
+
5
+ = HostIP.info
6
+
7
+ hostip.info is a "Community Geotarget IP Project" and provides a simple API
8
+ to transform an "IP Address" into a location (including Country, City, and
9
+ coordinates).
10
+
11
+ No API key or sign-up is required to use the service.
12
+
13
+ hostip.info does request that if a lot of databases lookups are being done
14
+ that you download the entire database from them instead.
15
+
16
+ = dependencies
17
+
18
+ - [HTTParty](http://github.com/jnunemaker/httparty/tree/master)
19
+ [version used=0.3.1]
20
+ - fakeweb [version used=1.2.0] [NOTE: only needed to run tests]
21
+
22
+ = example
23
+
24
+ == from the command line
25
+
26
+ # focus 209.85.171.100
27
+
28
+ == from a ruby script
29
+
30
+ require 'rubygems'
31
+ require 'focus'
32
+
33
+ focus = Focus.new("209.85.171.100")
34
+
35
+ puts focus.location.coordinates
36
+
37
+ == from a rails application
38
+
39
+ === in config/environment
40
+
41
+ require 'focus'
42
+
43
+ === in your controller
44
+
45
+ @focus = Focus.new("209.85.171.100")
46
+
47
+ === in your view
48
+
49
+ @focus.location.coordinates
50
+
51
+ == from a rails application with caching
52
+
53
+ Using caching is highly advised if possible. The ip_address to location
54
+ conversion doesn't normally change over time (if ever) so why continuously
55
+ query it?
56
+
57
+ Assumption: caching is already enabled and working.
58
+
59
+ === in config/environment
60
+
61
+ require 'focus'
62
+
63
+ === in your controller
64
+
65
+ ip_address = "209.85.171.100"
66
+ @focus = Rails.cache.fetch(Spotlight.key(ip_address), :expires_in => 1.weeks) do
67
+ Focus.new(ip_address)
68
+ end
69
+
70
+ === in your view
71
+
72
+ @focus.location.coordinates
73
+
74
+ = credits
75
+
76
+ Other then the code written by [me](http://github.com/attack) and the code
77
+ created by ['jeweler'](http://github.com/technicalpickles/jeweler/tree/master)
78
+ for making and maintaining gems, there is strong influence from
79
+ [jnunemaker](http://github.com/jnunemaker) and the gem google-weather.
80
+
81
+ == Copyright
82
+
83
+ Copyright (c) 2009 Mark G. See LICENSE for details.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/focus'
4
+ #require 'rubygems'
5
+ #require 'attack-focus'
6
+
7
+ if ARGV.size == 0
8
+ puts 'Focus [Powered by hostip.info]'
9
+ puts 'USAGE: focus [ip_address]'
10
+ puts 'EXAMPLES:'
11
+ puts ' focus 209.85.171.100'
12
+ exit
13
+ end
14
+
15
+ focus = Focus.new(ARGV[0])
16
+
17
+ puts "location -- name: #{focus.location.name}, latitude: #{focus.location.latitude}, longitude: #{focus.location.longitude}"
18
+ puts "country -- name: #{focus.country.name}, code: #{focus.country.code}"
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ gem 'jnunemaker-httparty'
3
+ require 'httparty'
4
+
5
+ require File.dirname(__FILE__) + '/focus/data'
6
+ require File.dirname(__FILE__) + '/focus/country'
7
+ require File.dirname(__FILE__) + '/focus/location'
8
+
9
+ class Focus
10
+ include HTTParty
11
+ base_uri "api.hostip.info"
12
+
13
+ attr_reader :ip
14
+
15
+ def initialize(ip_address)
16
+ raise unless Focus.valid?(ip_address)
17
+ @ip = ip_address
18
+ self.locate
19
+ self
20
+ end
21
+
22
+ # generate a key from the ip_address
23
+ def self.key(ip_address, prefix="focus")
24
+ raise unless self.valid?(ip_address)
25
+ segments = [prefix]
26
+ # this ip_address does not need to be encoded as it is a valid
27
+ # ip_address and only contains key-friendly chars
28
+ segments << ip_address
29
+ segments.join('-')
30
+ end
31
+
32
+ def country
33
+ @country ||= Country.new(@result)
34
+ end
35
+
36
+ def location
37
+ @location ||= Location.new(@result)
38
+ end
39
+
40
+ protected
41
+
42
+ # actually query HostIP.info with the 'IP address'
43
+ def locate
44
+ @result ||= self.class.get(
45
+ "/",
46
+ :query => {:ip => @ip},
47
+ :format => :xml
48
+ )['HostipLookupResultSet']['gml:featureMember']
49
+ end
50
+
51
+ # validate an ip_address
52
+ # regular expression from http://www.ruby-forum.com/topic/62553
53
+ def self.valid?(ip_address)
54
+ return false unless ip_address.is_a?(String)
55
+ regexp = Regexp.new(/(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)(?:\.(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)){3}/)
56
+ return false unless regexp =~ ip_address
57
+ return true
58
+ end
59
+
60
+ end
@@ -0,0 +1,21 @@
1
+ class Focus
2
+ class Country < Data
3
+
4
+ def name
5
+ begin
6
+ return @data['Hostip']['countryName']
7
+ rescue
8
+ return nil
9
+ end
10
+ end
11
+
12
+ def code
13
+ begin
14
+ return @data['Hostip']['countryAbbrev']
15
+ rescue
16
+ return nil
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ class Focus
2
+ class Data
3
+ attr_reader :data
4
+
5
+ def initialize(data)
6
+ @data = data
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ class Focus
2
+ class Location < Data
3
+
4
+ def name
5
+ begin
6
+ return @data['Hostip']['gml:name']
7
+ rescue
8
+ return nil
9
+ end
10
+ end
11
+
12
+ def coordinates
13
+ begin
14
+ return @data['Hostip']['ipLocation']['gml:PointProperty']['gml:Point']['gml:coordinates']
15
+ rescue
16
+ return nil
17
+ end
18
+ end
19
+
20
+ def longitude
21
+ return unless self.coordinates
22
+ self.coordinates.split(',')[0].to_f
23
+ end
24
+
25
+ def latitude
26
+ return unless self.coordinates
27
+ self.coordinates.split(',')[1].to_f
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1 @@
1
+ <HostipLookupResultSet xsi:schemaLocation='http://www.hostip.info/api/hostip-1.0.0.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:gml='http://www.opengis.net/gml' version='1.0.0' xmlns='http://www.hostip.info/api'><gml:description>This is the Hostip Lookup Service</gml:description><gml:name>hostip</gml:name><gml:boundedBy><gml:Null>inapplicable</gml:Null></gml:boundedBy><gml:featureMember><Hostip><gml:name>TORONTO, ON</gml:name><countryName>CANADA</countryName><countryAbbrev>CA</countryAbbrev><!-- Co-ordinates are available as lng,lat --><ipLocation><gml:PointProperty><gml:Point srsName='http://www.opengis.net/gml/srs/epsg.xml#4326'><gml:coordinates>-79.3833,43.65</gml:coordinates></gml:Point></gml:PointProperty></ipLocation></Hostip></gml:featureMember></HostipLookupResultSet>
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Initialization" do
4
+
5
+ before(:each) do
6
+ FakeWeb.register_uri(:get,
7
+ "http://api.hostip.info/?ip=199.246.67.211",
8
+ :string => File.read(File.join(File.dirname(__FILE__),
9
+ 'fixtures',
10
+ '199_246_67_211.xml')
11
+ )
12
+ )
13
+ end
14
+
15
+ it "should require an ip address" do
16
+ lambda { Focus.new }.should raise_error
17
+
18
+ Focus.new("199.246.67.211").ip.should == "199.246.67.211"
19
+ end
20
+
21
+ it "should require a proper ip address" do
22
+ lambda { Focus.new(199) }.should raise_error
23
+ lambda { Focus.new(199.246) }.should raise_error
24
+ lambda { Focus.new({:test => "test"}) }.should raise_error
25
+ lambda { Focus.new(["test"]) }.should raise_error
26
+ lambda { Focus.new("199") }.should raise_error
27
+ lambda { Focus.new("199.246") }.should raise_error
28
+ lambda { Focus.new("199.246.67") }.should raise_error
29
+ lambda { Focus.new("aaa.246.67.211") }.should raise_error
30
+ lambda { Focus.new("199.aaa.67.211") }.should raise_error
31
+ lambda { Focus.new("199.246.aaa.211") }.should raise_error
32
+ lambda { Focus.new("199.246.67.aaa") }.should raise_error
33
+ lambda { Focus.new("") }.should raise_error
34
+ lambda { Focus.new(" ") }.should raise_error
35
+ lambda { Focus.new("aaa") }.should raise_error
36
+
37
+ # sanity check
38
+ lambda { Focus.new("199.246.67.211") }.should_not raise_error
39
+ end
40
+
41
+ end
42
+
43
+ describe "Focus" do
44
+
45
+ it "should create an ip based key" do
46
+ Focus.key("199.246.67.211").should == "focus-199.246.67.211"
47
+ end
48
+
49
+ it "should create an ip based key with defined prefix" do
50
+ Focus.key("199.246.67.211", "alternate").should == "alternate-199.246.67.211"
51
+ end
52
+
53
+ end
54
+
55
+ describe "Data" do
56
+
57
+ it "should require data" do
58
+ lambda { Focus::Data.new }.should raise_error
59
+
60
+ data = {'foo' => {'data' => 'bar'}}
61
+ Focus::Data.new(data).data.should == data
62
+ end
63
+
64
+ end
65
+
66
+ describe "Location" do
67
+
68
+ it "should require data" do
69
+ lambda { Focus::Location.new }.should raise_error
70
+
71
+ data = {'foo' => {'data' => 'bar'}}
72
+ Focus::Location.new(data).data.should == data
73
+ end
74
+
75
+ it "should return nil when the data does not exist" do
76
+ data = {}
77
+ location = Focus::Location.new(data)
78
+ location.should_not be_nil
79
+
80
+ location.name.should be_nil
81
+ location.coordinates.should be_nil
82
+ location.longitude.should be_nil
83
+ location.latitude.should be_nil
84
+ end
85
+
86
+ # @data['Hostip']['gml:name']
87
+ it "should respond to name" do
88
+ data = {'Hostip' => {'gml:name' => "test_name"}}
89
+ location = Focus::Location.new(data)
90
+ location.should_not be_nil
91
+
92
+ location.name.should == "test_name"
93
+ end
94
+
95
+ # @data['Hostip']['ipLocation']['gml:PointProperty']['gml:Point']['gml:coordinates']
96
+ it "should respond to coordinates" do
97
+ data = {'Hostip' => {'ipLocation' => {'gml:PointProperty' => {'gml:Point' => {'gml:coordinates' => "-79.3833,43.65"}}}}}
98
+ location = Focus::Location.new(data)
99
+ location.should_not be_nil
100
+
101
+ location.coordinates.should == "-79.3833,43.65"
102
+ end
103
+
104
+ # @data['Hostip']['ipLocation']['gml:PointProperty']['gml:Point']['gml:coordinates']
105
+ it "should respond to longitude" do
106
+ data = {'Hostip' => {'ipLocation' => {'gml:PointProperty' => {'gml:Point' => {'gml:coordinates' => "-79.3833,43.65"}}}}}
107
+ location = Focus::Location.new(data)
108
+ location.should_not be_nil
109
+
110
+ location.longitude.should == -79.3833
111
+ end
112
+
113
+ # @data['Hostip']['ipLocation']['gml:PointProperty']['gml:Point']['gml:coordinates']
114
+ it "should respond to latitude" do
115
+ data = {'Hostip' => {'ipLocation' => {'gml:PointProperty' => {'gml:Point' => {'gml:coordinates' => "-79.3833,43.65"}}}}}
116
+ location = Focus::Location.new(data)
117
+ location.should_not be_nil
118
+
119
+ location.latitude.should == 43.65
120
+ end
121
+
122
+ end
123
+
124
+ describe "Country" do
125
+
126
+ it "should require data" do
127
+ lambda { Focus::Country.new }.should raise_error
128
+
129
+ data = {'foo' => {'data' => 'bar'}}
130
+ Focus::Country.new(data).data.should == data
131
+ end
132
+
133
+ it "should return nil when the data does not exist" do
134
+ data = {}
135
+ country = Focus::Country.new(data)
136
+ country.should_not be_nil
137
+
138
+ country.name.should be_nil
139
+ country.code.should be_nil
140
+ end
141
+
142
+ # @data['Hostip']['countryName']
143
+ it "should respond to name" do
144
+ data = {'Hostip' => {'countryName' => "test_name"}}
145
+ country = Focus::Country.new(data)
146
+ country.should_not be_nil
147
+
148
+ country.name.should == "test_name"
149
+ end
150
+
151
+ # @data['Hostip']['countryAbbrev']
152
+ it "should respond to coordinates" do
153
+ data = {'Hostip' => {'countryAbbrev' => "CA"}}
154
+ country = Focus::Country.new(data)
155
+ country.should_not be_nil
156
+
157
+ country.code.should == "CA"
158
+ end
159
+
160
+ end
161
+
162
+ describe "Fetching" do
163
+
164
+ before(:each) do
165
+ FakeWeb.register_uri(:get,
166
+ "http://api.hostip.info/?ip=199.246.67.211",
167
+ :string => File.read(File.join(File.dirname(__FILE__),
168
+ 'fixtures',
169
+ '199_246_67_211.xml')
170
+ )
171
+ )
172
+ @focus = Focus.new("199.246.67.211")
173
+ end
174
+
175
+ it "should have location information" do
176
+ @focus.location.should_not be_nil
177
+ location = @focus.location
178
+ location.name.should == 'TORONTO, ON'
179
+ location.coordinates.should == '-79.3833,43.65'
180
+ location.longitude.should == -79.3833
181
+ location.latitude.should == 43.65
182
+ end
183
+
184
+ it "should have country information" do
185
+ @focus.country.should_not be_nil
186
+ country = @focus.country
187
+ country.name.should == 'CANADA'
188
+ country.code.should == 'CA'
189
+ end
190
+
191
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'fakeweb'
4
+
5
+ FakeWeb.allow_net_connect = false
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+ require 'focus'
10
+
11
+ Spec::Runner.configure do |config|
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: focus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mark G
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-18 00:00:00 -06:00
13
+ default_executable: focus
14
+ dependencies: []
15
+
16
+ description:
17
+ email: focus@attackcorp.com
18
+ executables:
19
+ - focus
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - LICENSE
25
+ files:
26
+ - README.rdoc
27
+ - VERSION.yml
28
+ - bin/focus
29
+ - lib/focus
30
+ - lib/focus/country.rb
31
+ - lib/focus/data.rb
32
+ - lib/focus/location.rb
33
+ - lib/focus.rb
34
+ - spec/fixtures
35
+ - spec/fixtures/199_246_67_211.xml
36
+ - spec/focus_spec.rb
37
+ - spec/spec_helper.rb
38
+ - LICENSE
39
+ has_rdoc: true
40
+ homepage: http://github.com/attack/focus
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --inline-source
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project: focus
62
+ rubygems_version: 1.3.1
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: TODO
66
+ test_files: []
67
+