divining_rod 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,6 +1,7 @@
1
1
  # Divining Rod
2
+ <img src='http://public.mpercival.com.s3.amazonaws.com/images/divining_rod.jpg' />
2
3
 
3
- A tool to help format your sites mobile pages.
4
+ A tool to profile web requests. Especially useful for mobile site development
4
5
 
5
6
  ## Installation
6
7
 
@@ -8,7 +9,7 @@ A tool to help format your sites mobile pages.
8
9
 
9
10
  ## Example
10
11
 
11
- Using the example configuration (found in [example_config.rb](http://github.com/markpercival/divining_rod/blob/master/example_config.rb)])
12
+ Using the example configuration (found in [example_config.rb](http://github.com/markpercival/divining_rod/blob/master/example_config.rb))
12
13
 
13
14
  # For a request with the user agent
14
15
  # "Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20"
@@ -18,21 +19,40 @@ A tool to help format your sites mobile pages.
18
19
  profile.name #=> 'iPhone'
19
20
  profile.youtube_capable? #=> true
20
21
  profile.format #=> :webkit
22
+
23
+ ## Mappings
24
+
25
+ Matches happen in the order they are defined, and then proceed down to the subsequent block. So for example:
26
+
27
+ DiviningRod::Mappings.define do |map|
28
+ map.ua /Apple/, :format => :webkit, :tags => [:apple, :iphone_os] do
29
+ iphone.ua /iPad/, :tags => :ipad, :name => 'iPad', :format => nil
30
+ iphone.ua /iPod/, :tags => :ipod, :name => 'iPod Touch'
31
+ iphone.ua /iPhone/, :tags => :iphone, :name => 'iPhone'
32
+ end
33
+ end
21
34
 
35
+ Will match "Apple iPad" first with the /Apple/ matcher, then with the /iPad/ matcher, and the tags will be
22
36
 
37
+ [:apple, :iphone_os, :ipad] # Notice tags get appended, *not* overridden.
38
+
39
+ And _:format_ will be set to _nil_
40
+
41
+ Why _nil_? Because when :format is set to _nil_ and you ask for it, DiviningRod will return the original _request_ objects format.
42
+
23
43
  ## Usage
24
44
 
25
45
  _initializers/divining\_rod.rb_
26
-
46
+
27
47
  DiviningRod::Mappings.define do |map|
28
48
  # Android based phones
29
49
  map.ua /Android/, :format => :webkit, :name => 'Android', :tags => [:android, :youtube_capable, :google_gears]
30
50
 
31
51
  # Apple iPhone OS
32
52
  map.ua /Apple.*Mobile.*Safari/, :format => :webkit, :tags => [:apple, :iphone_os, :youtube_capable] do |iphone|
33
- iphone.ua /iPad/, :tags => :ipad, :name => 'iPad'
53
+ iphone.ua /iPad/, :tags => :ipad, :name => 'iPad', :format => nil
34
54
  iphone.ua /iPod/, :tags => :ipod, :name => 'iPod Touch'
35
- iphone.ua /iPhone/, :tags => [:iphone], :name => 'iPhone'
55
+ iphone.ua /iPhone/, :tags => :iphone, :name => 'iPhone'
36
56
  end
37
57
 
38
58
  #Blackberry, needs more detail here
@@ -76,10 +96,8 @@ _app/views/mobile/show.webkit.html_
76
96
 
77
97
  ## Note on the development
78
98
 
79
- Tags always merge, while all other hash keys get overridden. Tags also will always allow you to call them as
80
- booleans. Ex @profile.iphone?
81
-
82
- If the :format key isn't available we default to request.format.
99
+ In version 0.3.* it was assumed you always passed in _format_. In 0.4 on, we require _format_ to
100
+ be passed in explicitly with the rest of the options hash.
83
101
 
84
102
  ## Todo
85
103
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
data/divining_rod.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{divining_rod}
8
- s.version = "0.4.0"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Mark Percival"]
12
- s.date = %q{2010-02-23}
12
+ s.date = %q{2010-08-30}
13
13
  s.description = %q{A mobile phone web request profiler using definitions that look like rails routes}
14
14
  s.email = %q{mark@mpercival.com}
15
15
  s.extra_rdoc_files = [
@@ -32,17 +32,21 @@ Gem::Specification.new do |s|
32
32
  "lib/divining_rod/matchers.rb",
33
33
  "lib/divining_rod/murge.rb",
34
34
  "lib/divining_rod/profile.rb",
35
+ "lib/divining_rod/rack.rb",
36
+ "lib/divining_rod/rack/divining_rack.rb",
37
+ "lib/divining_rod/utilities.rb",
35
38
  "spec/definition_spec.rb",
36
39
  "spec/example_mapping_spec.rb",
37
40
  "spec/mapper_spec.rb",
38
41
  "spec/mapping_spec.rb",
39
42
  "spec/profile_spec.rb",
43
+ "spec/rack_spec.rb",
40
44
  "spec/spec_helper.rb"
41
45
  ]
42
46
  s.homepage = %q{http://github.com/markpercival/divining_rod}
43
47
  s.rdoc_options = ["--charset=UTF-8"]
44
48
  s.require_paths = ["lib"]
45
- s.rubygems_version = %q{1.3.5}
49
+ s.rubygems_version = %q{1.3.6}
46
50
  s.summary = %q{A mobile phone web request profiler}
47
51
  s.test_files = [
48
52
  "spec/definition_spec.rb",
@@ -50,6 +54,7 @@ Gem::Specification.new do |s|
50
54
  "spec/mapper_spec.rb",
51
55
  "spec/mapping_spec.rb",
52
56
  "spec/profile_spec.rb",
57
+ "spec/rack_spec.rb",
53
58
  "spec/spec_helper.rb"
54
59
  ]
55
60
 
data/lib/divining_rod.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module DiviningRod; end
2
2
 
3
3
  require 'divining_rod/profile'
4
+ require 'divining_rod/utilities'
4
5
  require 'divining_rod/murge'
5
6
  require 'divining_rod/definition'
6
7
  require 'divining_rod/mappings'
@@ -8,6 +8,7 @@ module DiviningRod
8
8
  def define(opts = {})
9
9
  @root_definition = Definition.new { true }
10
10
  yield Mapper.new(@root_definition, opts)
11
+ @root_definition.freeze
11
12
  end
12
13
 
13
14
  def evaluate(obj)
@@ -1,23 +1,31 @@
1
1
  module DiviningRod
2
2
  class Matchers
3
3
  class << self
4
-
4
+
5
5
  def ua(pattern, opts = {})
6
6
  Definition.new(opts) { |request|
7
- if pattern.match(request.user_agent)
7
+ if pattern.match(request.env['HTTP_USER_AGENT'])
8
8
  true
9
9
  end
10
10
  }
11
11
  end
12
-
12
+
13
13
  def subdomain(pattern, opts={})
14
14
  Definition.new(opts) { |request|
15
- if pattern.match(request.subdomains[0])
15
+ if pattern.match(DiviningRod::Utilities.parse_subdomain(request)[0])
16
16
  true
17
17
  end
18
18
  }
19
19
  end
20
-
20
+
21
+ def ua_prof(pattern, opts ={})
22
+ Definition.new(opts) {|request|
23
+ if pattern.match(request.env['X_WAP_PROFILE'])
24
+ true
25
+ end
26
+ }
27
+ end
28
+
21
29
  end
22
30
  end
23
31
  end
@@ -1,35 +1,36 @@
1
1
  module DiviningRod
2
2
  class Profile
3
3
 
4
- attr_reader :match
5
-
6
4
  def initialize(request)
7
5
  @request = request.clone #Lets not mess with the real one
8
- @match = DiviningRod::Mappings.evaluate(request)
6
+ end
7
+
8
+ def match
9
+ @match ||= DiviningRod::Mappings.evaluate(@request)
9
10
  end
10
11
 
11
12
  def format
12
- if @match && @match.format
13
- @match.format
13
+ if match && match.format
14
+ match.format
14
15
  else
15
16
  @request.format
16
17
  end
17
18
  end
18
19
 
19
20
  def recognized?
20
- @match != DiviningRod::Mappings.root_definition
21
+ match != DiviningRod::Mappings.root_definition
21
22
  end
22
23
 
23
24
  def method_missing(meth)
24
25
  if meth.to_s.match(/(.+)\?$/)
25
26
  tag = $1
26
- if @match
27
- @match.tags.include?(tag.to_s) || @match.tags.include?(tag.to_sym) || @match.tags == tag
27
+ if match
28
+ match.tags.include?(tag.to_s) || match.tags.include?(tag.to_sym) || match.tags == tag
28
29
  else
29
30
  false
30
31
  end
31
- elsif @match.opts.include?(meth.to_sym)
32
- @match.opts[meth]
32
+ elsif match.opts.include?(meth.to_sym)
33
+ match.opts[meth]
33
34
  else
34
35
  super
35
36
  end
@@ -0,0 +1 @@
1
+ require 'divining_rod/rack/divining_rack'
@@ -0,0 +1,16 @@
1
+ module DiviningRod
2
+ class Rack
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ request = ::Rack::Request.new(env)
10
+ profile = DiviningRod::Profile.new(request)
11
+ env['divining_rod.profile'] = profile
12
+ @app.call(env)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module DiviningRod
2
+ class Utilities
3
+
4
+ def self.parse_subdomain(request)
5
+ env = request.env
6
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
7
+ host = forwarded.split(/,\s?/).last
8
+ else
9
+ host = env['HTTP_HOST'] || env['SERVER_NAME'] || env['SERVER_ADDR']
10
+ end
11
+ if host
12
+ host.sub(/\:\d+/, '').split('.')
13
+ else
14
+ []
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -1,13 +1,8 @@
1
1
  require 'spec/spec_helper'
2
2
  require 'example_config'
3
3
 
4
-
5
- def request_mock(ua, subdomain = [])
6
- mock('RailsRequest', :user_agent => ua, :subdomain => subdomain)
7
- end
8
-
9
4
  def profile_ua(ua)
10
- DiviningRod::Profile.new(request_mock(ua))
5
+ DiviningRod::Profile.new(request_mock(:ua => ua))
11
6
  end
12
7
 
13
8
 
data/spec/mapper_spec.rb CHANGED
@@ -13,7 +13,7 @@ describe DiviningRod::Mapper do
13
13
  end
14
14
 
15
15
  it "should map a definition" do
16
- request = mock("rails_request", :user_agent => 'Apple Mobile Safari', :format => :html)
16
+ request = request_mock(:ua => 'Apple Mobile Safari', :format => :html)
17
17
  result = @root_definition.evaluate(request)
18
18
  result.tags.should include(:fuck)
19
19
  end
data/spec/mapping_spec.rb CHANGED
@@ -29,7 +29,7 @@ describe DiviningRod::Mappings do
29
29
  end
30
30
 
31
31
  it "should match a top level user agent" do
32
- request = mock("rails_request", :user_agent => 'Apple Mobile Safari', :format => :html)
32
+ request = request_mock(:ua => 'Apple Mobile Safari', :format => :html)
33
33
  result = DiviningRod::Mappings.root_definition.evaluate(request)
34
34
  result.should_not be_nil
35
35
  result.tags.should include(:apple)
@@ -38,13 +38,13 @@ describe DiviningRod::Mappings do
38
38
  end
39
39
 
40
40
  it "should match a child definition" do
41
- ipad_request = mock("rails_request", :user_agent => 'Apple iPad', :format => :html)
41
+ ipad_request = request_mock(:ua => 'Apple iPad', :format => :html)
42
42
  result = DiviningRod::Mappings.evaluate(ipad_request)
43
43
  result.tags.should include(:ipad)
44
44
  end
45
45
 
46
46
  it "should match a sub child definition" do
47
- ipad_request = mock("rails_request", :user_agent => 'Apple iPad - now powered by Unicorns - OS 3.3', :format => :html)
47
+ ipad_request = request_mock(:ua => 'Apple iPad - now powered by Unicorns - OS 3.3', :format => :html)
48
48
  result = DiviningRod::Mappings.evaluate(ipad_request)
49
49
  result.tags.should include(:ipad)
50
50
  result.tags.should include(:omg_unicorns)
@@ -52,7 +52,7 @@ describe DiviningRod::Mappings do
52
52
  end
53
53
 
54
54
  it "should match a really really deep child definition" do
55
- ipad_request = mock("rails_request", :user_agent => 'Apple iPad - now powered by Unicorns who eat kittens - OS 3.3', :format => :html)
55
+ ipad_request = request_mock(:ua => 'Apple iPad - now powered by Unicorns who eat kittens - OS 3.3', :format => :html)
56
56
  result = DiviningRod::Mappings.evaluate(ipad_request)
57
57
  result.tags.should include(:ipad, :youtube)
58
58
  result.tags.should include(:omg_unicorns)
@@ -61,7 +61,7 @@ describe DiviningRod::Mappings do
61
61
  end
62
62
 
63
63
  it "should match a in order defined" do
64
- ipad_request = mock("rails_request", :user_agent => 'Apple iPad - now powered by Unicorns who eat kittens - OS 2', :format => :html)
64
+ ipad_request = request_mock(:ua => 'Apple iPad - now powered by Unicorns who eat kittens - OS 2', :format => :html)
65
65
  result = DiviningRod::Mappings.evaluate(ipad_request)
66
66
  result.tags.should include(:ipad, :youtube)
67
67
  result.tags.should_not include(:omg_they_eat_kittens, :omg_unicorns)
@@ -69,7 +69,7 @@ describe DiviningRod::Mappings do
69
69
  end
70
70
 
71
71
  it "should match a in order defined" do
72
- ipad_request = mock("rails_request", :user_agent => 'Apple Newton - OS 8', :format => :html)
72
+ ipad_request = request_mock(:ua => 'Apple Newton - OS 8', :format => :html)
73
73
  result = DiviningRod::Mappings.evaluate(ipad_request)
74
74
  result.tags.should_not include(:omg_they_eat_kittens, :omg_unicorns)
75
75
  result.tags.should include(:os8)
data/spec/profile_spec.rb CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe DiviningRod do
4
4
 
5
5
  before :each do
6
- @request = mock("rails_request", :user_agent => 'My iPhone which is actually an iPad')
6
+ @request = request_mock(:ua => 'My iPhone which is actually an iPad')
7
7
 
8
8
  DiviningRod::Mappings.define do |map|
9
9
  map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate] do |iphone|
@@ -30,7 +30,7 @@ describe DiviningRod do
30
30
  describe "without a default route" do
31
31
 
32
32
  before :each do
33
- @request = mock("rails_request", :user_agent => 'My Foo Fone', :format => :html)
33
+ @request = request_mock(:ua => 'My Foo Fone', :format => :html)
34
34
 
35
35
  DiviningRod::Mappings.define do |map|
36
36
  map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
@@ -49,7 +49,7 @@ describe DiviningRod do
49
49
  describe "with a default route" do
50
50
 
51
51
  before :each do
52
- @request = mock("rails_request", :user_agent => 'My Foo Fone')
52
+ @request = request_mock(:ua => 'My Foo Fone')
53
53
 
54
54
  DiviningRod::Mappings.define do |map|
55
55
  map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
@@ -68,7 +68,7 @@ describe DiviningRod do
68
68
  describe "without a default definition" do
69
69
 
70
70
  before :each do
71
- @request = mock("rails_request", :user_agent => 'Foo Fone', :format => :html)
71
+ @request = request_mock(:ua => 'Foo Fone', :format => :html)
72
72
 
73
73
  DiviningRod::Mappings.define do |map|
74
74
  map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
@@ -84,7 +84,7 @@ describe DiviningRod do
84
84
  describe "matching a subdomain" do
85
85
 
86
86
  before :each do
87
- @request = mock("rails_request", :user_agent => 'Foo Fone', :subdomains => ['wap'])
87
+ @request = request_mock(:ua => 'Foo Fone', :host => 'wap.example.com')
88
88
 
89
89
  DiviningRod::Mappings.define do |map|
90
90
  map.subdomain /wap/, :format => :wap, :tags => [:shitty]
@@ -102,7 +102,7 @@ describe DiviningRod do
102
102
  describe "matching the weird requests(no user_agent passed)" do
103
103
 
104
104
  before :each do
105
- @request = mock("rails_request", :user_agent => nil, :subdomains => [])
105
+ @request = request_mock(:ua => nil, :subdomains => [])
106
106
 
107
107
  DiviningRod::Mappings.define do |map|
108
108
  map.ua /iPhone/, :format => :wap, :tags => [:shitty]
data/spec/rack_spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require "divining_rod/rack"
3
+ require "rack/test"
4
+
5
+ describe DiviningRod do
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ DiviningRod::Rack.new(app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['foo']] })
10
+ end
11
+
12
+ before :each do
13
+ DiviningRod::Mappings.define do |map|
14
+ map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate] do |iphone|
15
+ iphone.ua /iPad/, :tags => [:ipad]
16
+ end
17
+ end
18
+ end
19
+
20
+ it "should profile an incoming request" do
21
+ header 'User-Agent', "iPhone Safari"
22
+ get "/"
23
+ last_request.env['divining_rod.profile'].should be_an_instance_of(DiviningRod::Profile)
24
+ last_request.env['divining_rod.profile'].iphone?.should be_true
25
+ end
26
+
27
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,12 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
- require File.expand_path('../../lib/divining_rod', __FILE__)
3
+ require 'rack'
4
+ require File.expand_path('../../lib/divining_rod', __FILE__)
5
+
6
+ def request_mock(opts)
7
+ opts = {
8
+ :host => 'example.com'
9
+ }.merge(opts)
10
+ env = {'HTTP_USER_AGENT' => opts[:ua], 'SERVER_NAME' => opts[:host], 'X_WAP_PROFILE' => opts[:wap_profile]}
11
+ mock('RailsRequest', :env => env, :format => opts[:format])
12
+ end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: divining_rod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Mark Percival
@@ -9,19 +14,21 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-02-23 00:00:00 -05:00
17
+ date: 2010-08-30 00:00:00 -07:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: thoughtbot-shoulda
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
23
29
  version: "0"
24
- version:
30
+ type: :development
31
+ version_requirements: *id001
25
32
  description: A mobile phone web request profiler using definitions that look like rails routes
26
33
  email: mark@mpercival.com
27
34
  executables: []
@@ -47,11 +54,15 @@ files:
47
54
  - lib/divining_rod/matchers.rb
48
55
  - lib/divining_rod/murge.rb
49
56
  - lib/divining_rod/profile.rb
57
+ - lib/divining_rod/rack.rb
58
+ - lib/divining_rod/rack/divining_rack.rb
59
+ - lib/divining_rod/utilities.rb
50
60
  - spec/definition_spec.rb
51
61
  - spec/example_mapping_spec.rb
52
62
  - spec/mapper_spec.rb
53
63
  - spec/mapping_spec.rb
54
64
  - spec/profile_spec.rb
65
+ - spec/rack_spec.rb
55
66
  - spec/spec_helper.rb
56
67
  has_rdoc: true
57
68
  homepage: http://github.com/markpercival/divining_rod
@@ -66,18 +77,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
77
  requirements:
67
78
  - - ">="
68
79
  - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
69
82
  version: "0"
70
- version:
71
83
  required_rubygems_version: !ruby/object:Gem::Requirement
72
84
  requirements:
73
85
  - - ">="
74
86
  - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
75
89
  version: "0"
76
- version:
77
90
  requirements: []
78
91
 
79
92
  rubyforge_project:
80
- rubygems_version: 1.3.5
93
+ rubygems_version: 1.3.6
81
94
  signing_key:
82
95
  specification_version: 3
83
96
  summary: A mobile phone web request profiler
@@ -87,4 +100,5 @@ test_files:
87
100
  - spec/mapper_spec.rb
88
101
  - spec/mapping_spec.rb
89
102
  - spec/profile_spec.rb
103
+ - spec/rack_spec.rb
90
104
  - spec/spec_helper.rb