divining_rod 0.4.0 → 0.5.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/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