rack-geo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.gemspec
3
+ *.log
4
+ *.pid
5
+ *.sqlite3
6
+ *.tmproj
7
+ .DS_Store
8
+ log/*
9
+ pkg/*
data/HISTORY.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ === Version 0.1.0 (git)
2
+
3
+ Initial release of Rack::Geo middleware.
data/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright © 2010 Square, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,21 @@
1
+ = Rack::Geo
2
+
3
+ Rack middleware for parsing and serializing IETF draft {Geo-Position}[http://tools.ietf.org/html/draft-daviel-http-geo-header-05] HTTP headers.
4
+
5
+ == Prerequisites
6
+
7
+ * {Rack}[http://rack.rubyforge.org/]
8
+
9
+ == Testing
10
+
11
+ Testing requires the RSpec gem:
12
+
13
+ * {Rspec}[http://rspec.info/]
14
+
15
+ To test, run:
16
+ `rake`
17
+
18
+ == License
19
+
20
+ Copyright © 2010 Square, Inc.
21
+ See LICENSE.txt in this directory.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+
5
+ require File.join(File.dirname(__FILE__), 'lib', 'rack', 'geo', 'version')
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gemspec|
10
+ gemspec.version = Rack::Geo::VERSION::STRING
11
+ gemspec.name = "rack-geo"
12
+ gemspec.summary = "Rack middleware for Geo-Position HTTP headers"
13
+ gemspec.description = "Parse and serialize geospatial HTTP headers."
14
+ gemspec.email = "github@squareup.com"
15
+ gemspec.homepage = "http://github.com/square/rack-geo"
16
+ gemspec.authors = [
17
+ "Randy Reddig",
18
+ "Cameron Walters",
19
+ "Paul McKellar",
20
+ ]
21
+ gemspec.extra_rdoc_files = [
22
+ 'README.rdoc',
23
+ 'HISTORY.rdoc',
24
+ 'LICENSE.txt',
25
+ ]
26
+ gemspec.add_dependency "rack", ">=1.0.0"
27
+ gemspec.add_development_dependency "rack-test", ">=0.5.3"
28
+ gemspec.add_development_dependency "rspec", ">=1.3.0"
29
+ end
30
+ rescue LoadError
31
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
32
+ end
33
+
34
+ desc "Run all specs"
35
+ Spec::Rake::SpecTask.new do |t|
36
+ t.spec_opts = ["--options", "spec/spec.opts"]
37
+ t.spec_files = FileList["spec/**/*_spec.rb"]
38
+ t.rcov = ENV["RCOV"]
39
+ t.rcov_opts = %w{--exclude osx\/objc,gems\/,spec\/}
40
+ t.verbose = true
41
+ end
42
+
43
+ task :spec => :check_dependencies
44
+ task :default => :spec
45
+
46
+ desc "Remove trailing whitespace"
47
+ task :whitespace do
48
+ sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
49
+ end
data/lib/rack-geo.rb ADDED
@@ -0,0 +1 @@
1
+ require 'rack/geo'
data/lib/rack/geo.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'rack/geo/version'
2
+
3
+ module Rack
4
+ class Geo
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ position = Position.new(env["HTTP_GEO_POSITION"] || env["HTTP_X_GEO_POSITION"])
11
+ env["geo.position"] = position if position.valid?
12
+ @app.call(env)
13
+ end
14
+
15
+ class Position
16
+ attr_accessor :latitude, :longitude, :altitude, :uncertainty, :heading, :speed
17
+
18
+ def initialize(value=nil)
19
+ parse! value
20
+ end
21
+
22
+ MATCH_LLA = /\A([+-]?[0-9\.]+);([+-]?[0-9\.]+)(?:;([+-]?[0-9\.]+))?/.freeze
23
+ MATCH_EPU = /\sepu=([0-9\.]+)(?:\s|\z)/i.freeze
24
+ MATCH_HDN = /\shdn=([0-9\.]+)(?:\s|\z)/i.freeze
25
+ MATCH_SPD = /\sspd=([0-9\.]+)(?:\s|\z)/i.freeze
26
+
27
+ # Parse Geo-Position header:
28
+ # http://tools.ietf.org/html/draft-daviel-http-geo-header-05
29
+ def parse!(value)
30
+ reset!
31
+ value = value.to_s.strip
32
+
33
+ if lla = MATCH_LLA.match(value)
34
+ @latitude = lla[1].to_f
35
+ @longitude = lla[2].to_f
36
+ @altitude = lla[3].to_f if lla[3]
37
+ end
38
+
39
+ if epu = MATCH_EPU.match(value)
40
+ @uncertainty = epu[1].to_f
41
+ end
42
+
43
+ if hdn = MATCH_HDN.match(value)
44
+ @heading = hdn[1].to_f
45
+ end
46
+
47
+ if spd = MATCH_SPD.match(value)
48
+ @speed = spd[1].to_f
49
+ end
50
+
51
+ nil
52
+ end
53
+
54
+ def valid?
55
+ (!latitude.nil? && latitude.respond_to?(:to_f) && (-90..90).include?(latitude.to_f)) &&
56
+ (!longitude.nil? && longitude.respond_to?(:to_f) && (-180..180).include?(longitude.to_f)) &&
57
+ (altitude.nil? || altitude.respond_to?(:to_f)) &&
58
+ (uncertainty.nil? || uncertainty.respond_to?(:to_f) && uncertainty.to_f >= 0) &&
59
+ (heading.nil? || heading.respond_to?(:to_f) && (0..360).include?(heading.to_f)) &&
60
+ (speed.nil? || speed.respond_to?(:to_f) && speed.to_f >= 0)
61
+ end
62
+
63
+ def attributes
64
+ {
65
+ :latitude => latitude,
66
+ :longitude => longitude,
67
+ :altitude => altitude,
68
+ :uncertainty => uncertainty,
69
+ :heading => heading,
70
+ :speed => speed,
71
+ }
72
+ end
73
+
74
+ def to_http_header
75
+ value = "%f;%f" % [latitude.to_f, longitude.to_f]
76
+ value += ";%f" % altitude.to_f if altitude
77
+ value += " epu=%f" % uncertainty.to_f if uncertainty
78
+ value += " hdn=%f" % heading.to_f if heading
79
+ value += " spd=%f" % speed.to_f if speed
80
+ value
81
+ end
82
+
83
+ def self.from_http_header(value)
84
+ self.new(value)
85
+ end
86
+
87
+ private
88
+
89
+ def reset!
90
+ @latitude = @longitude = @altitude = @uncertainty = @heading = @speed = nil
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,11 @@
1
+ module Rack
2
+ class Geo
3
+ module VERSION #:nodoc:
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Geo do
4
+ def setup_request(header_name="HTTP_GEO_POSITION")
5
+ @request = Rack::MockRequest.env_for("/", header_name => @coords, :lint => true, :fatal => true)
6
+ end
7
+
8
+ before :each do
9
+ @latitude, @longitude, @uncertainty = 37.0625, -95.677068, 100
10
+ @coords = "#{@latitude};#{@longitude} epu=#{@uncertainty}"
11
+ @app = lambda { |env| [200, {"Content-Type" => "text/plain"}, [""]] }
12
+ end
13
+
14
+ ["HTTP_GEO_POSITION", "HTTP_X_GEO_POSITION"].each do |header_name|
15
+ describe "with invalid geo position header '#{header_name}'" do
16
+ before :each do
17
+ setup_request(header_name)
18
+ Rack::Geo::Position.stub!(:new).and_return(mock(Rack::Geo::Position, :valid? => false))
19
+ @status, @headers, @response = described_class.new(@app).call(@request)
20
+ end
21
+
22
+ it "does not assign a Rack::Geo::Position instance to env['geo.position']" do
23
+ @request['geo.position'].should be_nil
24
+ end
25
+ end
26
+
27
+ describe "with valid geo position header '#{header_name}: #{@coords}'" do
28
+ before :each do
29
+ setup_request(header_name)
30
+ @status, @headers, @response = described_class.new(@app).call(@request)
31
+ end
32
+
33
+ it "assigns a Rack::Geo::Position instance to env['geo.position']" do
34
+ @request['geo.position'].should be_an_instance_of(Rack::Geo::Position)
35
+ end
36
+
37
+ it "the Rack::Geo::Position instance should reflect the header value" do
38
+ @request['geo.position'].latitude.should == @latitude
39
+ @request['geo.position'].longitude.should == @longitude
40
+ @request['geo.position'].uncertainty.should == @uncertainty
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+
47
+ describe Rack::Geo::Position do
48
+
49
+ ZEROES = %w[0 -0 +0 0.0 -0.0 +0.0]
50
+ LATITUDES = %w[-90 -45 0 45 +45 90 +90]
51
+ LONGITUDES = %w[-180 -90 -45 0 45 +45 90 +90 180 +180]
52
+ ALTITUDES = %w[-85.5 -34 0 15 1337 8848 23300.0 +23300.0]
53
+ UNCERTAINTIES = %w[0 0.0 0.00 1.25 32.1 455 600.123]
54
+ HEADINGS = %w[0 0.0 0.00 1.25 32.1 180 359.123 360]
55
+ SPEEDS = %w[0 0.0 0.00 1.25 32.1 999 9999.99 12345.6456]
56
+
57
+ describe "#new" do
58
+ it "creates an instance of #{described_class}" do
59
+ instance = described_class.new
60
+ instance.should be_an_instance_of(described_class)
61
+ end
62
+
63
+ it "calls #parse!" do
64
+ class GeoPositionParseTest < described_class
65
+ def parse!(arg)
66
+ @parsed = arg
67
+ end
68
+ end
69
+
70
+ instance = GeoPositionParseTest.new "TEST"
71
+ instance.instance_variable_get(:@parsed).should == "TEST"
72
+ end
73
+ end
74
+
75
+ describe "#from_http_header" do
76
+ it "creates an instance of #{described_class}" do
77
+ instance = described_class.from_http_header nil
78
+ instance.should be_an_instance_of(described_class)
79
+ end
80
+
81
+ it "calls #new with supplied argument" do
82
+ described_class.should_receive(:new).with("TEST")
83
+ described_class.from_http_header "TEST"
84
+ end
85
+ end
86
+
87
+ describe "instance method" do
88
+ before :each do
89
+ @instance = described_class.new
90
+ end
91
+
92
+ describe "#attributes" do
93
+ it "returns a hash" do
94
+ @instance.attributes.should be_an_instance_of(Hash)
95
+ end
96
+
97
+ it "returns a hash containing specific keys" do
98
+ h = @instance.attributes
99
+ [:latitude, :longitude, :altitude, :uncertainty, :heading, :speed].each do |key|
100
+ h.should have_key(key)
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#parse!" do
106
+ it "nil sets all attributes to nil" do
107
+ @instance.parse! nil
108
+ @instance.latitude.should == nil
109
+ @instance.longitude.should == nil
110
+ @instance.altitude.should == nil
111
+ @instance.uncertainty.should == nil
112
+ @instance.heading.should == nil
113
+ @instance.speed.should == nil
114
+ end
115
+
116
+ it "\"0;0\" parses latitude and longitude" do
117
+ @instance.parse! "0;0"
118
+ @instance.latitude.should == 0.0
119
+ @instance.longitude.should == 0.0
120
+ @instance.altitude.should == nil
121
+ @instance.uncertainty.should == nil
122
+ @instance.heading.should == nil
123
+ @instance.speed.should == nil
124
+ end
125
+
126
+ it "\"0;0;0\" parses latitude, longitude and altitude" do
127
+ @instance.parse! "0;0;0"
128
+ @instance.latitude.should == 0.0
129
+ @instance.longitude.should == 0.0
130
+ @instance.altitude.should == 0.0
131
+ @instance.uncertainty.should == nil
132
+ @instance.heading.should == nil
133
+ @instance.speed.should == nil
134
+ end
135
+
136
+ ZEROES.each do |latitude|
137
+ ZEROES.each do |longitude|
138
+ value = "#{latitude};#{longitude}"
139
+ it "\"#{value}\" should return a new instance with latitude == 0 and longitude == 0" do
140
+ @instance.parse! value
141
+ @instance.latitude.should == 0.0
142
+ @instance.longitude.should == 0.0
143
+ end
144
+ end
145
+ end
146
+
147
+ LATITUDES.each do |latitude|
148
+ LONGITUDES.each do |longitude|
149
+ value = "#{latitude};#{longitude}"
150
+ latitude_f = latitude.to_f
151
+ longitude_f = longitude.to_f
152
+ it "#{value} should return a new instance with latitude == #{latitude_f} and longitude == #{longitude_f}" do
153
+ @instance.parse! value
154
+ @instance.latitude.should == latitude_f
155
+ @instance.longitude.should == longitude_f
156
+ end
157
+ end
158
+ end
159
+
160
+ LATITUDES.each do |latitude|
161
+ LONGITUDES.each do |longitude|
162
+ ALTITUDES.each do |altitude|
163
+ value = "#{latitude};#{longitude};#{altitude}"
164
+ latitude_f = latitude.to_f
165
+ longitude_f = longitude.to_f
166
+ altitude_f = altitude.to_f
167
+ it "#{value} should return a new instance with latitude == #{latitude_f}, longitude == #{longitude_f} and altitude == #{altitude_f}" do
168
+ @instance.parse! value
169
+ @instance.latitude.should == latitude_f
170
+ @instance.longitude.should == longitude_f
171
+ @instance.altitude.should == altitude_f
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ UNCERTAINTIES.each do |uncertainty|
178
+ value = "0;0;0 epu=#{uncertainty}"
179
+ uncertainty_f = uncertainty.to_f
180
+ it "\"#{value}\" should return a new instance with uncertainty = #{uncertainty_f}" do
181
+ @instance.parse! value
182
+ @instance.uncertainty.should == uncertainty_f
183
+ end
184
+ end
185
+
186
+ HEADINGS.each do |heading|
187
+ value = "0;0;0 hdn=#{heading}"
188
+ heading_f = heading.to_f
189
+ it "\"#{value}\" should return a new instance with heading = #{heading_f}" do
190
+ @instance.parse! value
191
+ @instance.heading.should == heading_f
192
+ end
193
+ end
194
+
195
+ SPEEDS.each do |speed|
196
+ value = "0;0;0 spd=#{speed}"
197
+ speed_f = speed.to_f
198
+ it "\"#{value}\" should return a new instance with speed = #{speed_f}" do
199
+ @instance.parse! value
200
+ @instance.speed.should == speed_f
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "#valid?" do
206
+ before :each do
207
+ @instance = described_class.new "0;0;0 epu=0 hdn=0 spd=0"
208
+ @instance.should be_valid
209
+ end
210
+
211
+ it "returns false for instances initialized with no arguments" do
212
+ described_class.new.should_not be_valid
213
+ end
214
+
215
+ [nil, false, [], {}, -91, 90.0001, "-90.0001", "91"].each do |latitude|
216
+ it "returns false when latitude = #{latitude.inspect}" do
217
+ @instance.latitude = latitude
218
+ @instance.should_not be_valid
219
+ end
220
+ end
221
+
222
+ [nil, false, [], {}, -181, 180.0001, "-180.0001", "181"].each do |longitude|
223
+ it "returns false when longitude = #{longitude.inspect}" do
224
+ @instance.longitude = longitude
225
+ @instance.should_not be_valid
226
+ end
227
+ end
228
+
229
+ [false, [], {}].each do |altitude|
230
+ it "returns false when altitude = #{altitude.inspect}" do
231
+ @instance.altitude = altitude
232
+ @instance.should_not be_valid
233
+ end
234
+ end
235
+
236
+ [false, [], {}, -1, -0.0001, "-0.0001"].each do |uncertainty|
237
+ it "returns false when uncertainty = #{uncertainty.inspect}" do
238
+ @instance.uncertainty = uncertainty
239
+ @instance.should_not be_valid
240
+ end
241
+ end
242
+
243
+ [false, [], {}, -1, -0.0001, 360.01, 361, "-0.0001", "360.01", "361"].each do |heading|
244
+ it "returns false when heading = #{heading.inspect}" do
245
+ @instance.heading = heading
246
+ @instance.should_not be_valid
247
+ end
248
+ end
249
+
250
+ [false, [], {}, -1, -0.0001, "-0.0001"].each do |speed|
251
+ it "returns false when speed = #{speed.inspect}" do
252
+ @instance.speed = speed
253
+ @instance.should_not be_valid
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
4
+
5
+ require 'rack-geo'
6
+ require 'spec/expectations'
7
+ require 'rack/test'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-geo
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Randy Reddig
14
+ - Cameron Walters
15
+ - Paul McKellar
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2010-07-18 00:00:00 -07:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: rack
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ hash: 23
32
+ segments:
33
+ - 1
34
+ - 0
35
+ - 0
36
+ version: 1.0.0
37
+ type: :runtime
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: rack-test
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ hash: 13
48
+ segments:
49
+ - 0
50
+ - 5
51
+ - 3
52
+ version: 0.5.3
53
+ type: :development
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 27
64
+ segments:
65
+ - 1
66
+ - 3
67
+ - 0
68
+ version: 1.3.0
69
+ type: :development
70
+ version_requirements: *id003
71
+ description: Parse and serialize geospatial HTTP headers.
72
+ email: github@squareup.com
73
+ executables: []
74
+
75
+ extensions: []
76
+
77
+ extra_rdoc_files:
78
+ - HISTORY.rdoc
79
+ - LICENSE.txt
80
+ - README.rdoc
81
+ files:
82
+ - .gitignore
83
+ - HISTORY.rdoc
84
+ - LICENSE.txt
85
+ - README.rdoc
86
+ - Rakefile
87
+ - lib/rack-geo.rb
88
+ - lib/rack/geo.rb
89
+ - lib/rack/geo/version.rb
90
+ - spec/rack/geo_spec.rb
91
+ - spec/spec.opts
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/square/rack-geo
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --charset=UTF-8
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.7
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Rack middleware for Geo-Position HTTP headers
127
+ test_files:
128
+ - spec/rack/geo_spec.rb
129
+ - spec/spec_helper.rb