focus 0.1.0

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