divining_rod 0.3.1 → 0.4.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 +32 -9
- data/VERSION +1 -1
- data/divining_rod.gemspec +19 -4
- data/example_config.rb +31 -0
- data/lib/divining_rod.rb +8 -107
- data/lib/divining_rod/definition.rb +53 -0
- data/lib/divining_rod/mapper.rb +55 -0
- data/lib/divining_rod/mappings.rb +20 -0
- data/lib/divining_rod/matchers.rb +23 -0
- data/lib/divining_rod/murge.rb +16 -0
- data/lib/divining_rod/profile.rb +39 -0
- data/spec/definition_spec.rb +40 -0
- data/spec/example_mapping_spec.rb +65 -0
- data/spec/mapper_spec.rb +21 -0
- data/spec/mapping_spec.rb +78 -0
- data/spec/{basic_spec.rb → profile_spec.rb} +23 -56
- metadata +19 -4
data/README.markdown
CHANGED
@@ -5,20 +5,43 @@ A tool to help format your sites mobile pages.
|
|
5
5
|
## Installation
|
6
6
|
|
7
7
|
gem install divining_rod
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
Using the example configuration (found in [example_config.rb](http://github.com/markpercival/divining_rod/blob/master/example_config.rb)])
|
12
|
+
|
13
|
+
# For a request with the user agent
|
14
|
+
# "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"
|
15
|
+
|
16
|
+
profile = DiviningRod::Profile.new(request)
|
17
|
+
profile.iphone? #=> true
|
18
|
+
profile.name #=> 'iPhone'
|
19
|
+
profile.youtube_capable? #=> true
|
20
|
+
profile.format #=> :webkit
|
21
|
+
|
8
22
|
|
9
23
|
## Usage
|
10
24
|
|
11
25
|
_initializers/divining\_rod.rb_
|
12
26
|
|
13
|
-
DiviningRod::
|
14
|
-
#
|
15
|
-
map.ua /
|
16
|
-
|
17
|
-
|
27
|
+
DiviningRod::Mappings.define do |map|
|
28
|
+
# Android based phones
|
29
|
+
map.ua /Android/, :format => :webkit, :name => 'Android', :tags => [:android, :youtube_capable, :google_gears]
|
30
|
+
|
31
|
+
# Apple iPhone OS
|
32
|
+
map.ua /Apple.*Mobile.*Safari/, :format => :webkit, :tags => [:apple, :iphone_os, :youtube_capable] do |iphone|
|
33
|
+
iphone.ua /iPad/, :tags => :ipad, :name => 'iPad'
|
34
|
+
iphone.ua /iPod/, :tags => :ipod, :name => 'iPod Touch'
|
35
|
+
iphone.ua /iPhone/, :tags => [:iphone], :name => 'iPhone'
|
36
|
+
end
|
37
|
+
|
38
|
+
#Blackberry, needs more detail here
|
39
|
+
map.ua /BlackBerry/, :tags => :blackberry, :name => 'BlackBerry'
|
40
|
+
map.subdomain /wap/, :format => :wap, :tags => [:crappy_old_phone]
|
18
41
|
|
19
42
|
# Enable this to forces a default format if unmatched
|
20
43
|
# otherwise it will return the request.format
|
21
|
-
# map.default :html
|
44
|
+
# map.default :format => :html
|
22
45
|
end
|
23
46
|
|
24
47
|
_initializers/mime\_types.rb_
|
@@ -53,10 +76,10 @@ _app/views/mobile/show.webkit.html_
|
|
53
76
|
|
54
77
|
## Note on the development
|
55
78
|
|
56
|
-
|
57
|
-
|
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?
|
58
81
|
|
59
|
-
|
82
|
+
If the :format key isn't available we default to request.format.
|
60
83
|
|
61
84
|
## Todo
|
62
85
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.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.
|
8
|
+
s.version = "0.4.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-
|
12
|
+
s.date = %q{2010-02-23}
|
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 = [
|
@@ -24,8 +24,19 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"divining_rod.gemspec",
|
27
|
+
"example_config.rb",
|
27
28
|
"lib/divining_rod.rb",
|
28
|
-
"
|
29
|
+
"lib/divining_rod/definition.rb",
|
30
|
+
"lib/divining_rod/mapper.rb",
|
31
|
+
"lib/divining_rod/mappings.rb",
|
32
|
+
"lib/divining_rod/matchers.rb",
|
33
|
+
"lib/divining_rod/murge.rb",
|
34
|
+
"lib/divining_rod/profile.rb",
|
35
|
+
"spec/definition_spec.rb",
|
36
|
+
"spec/example_mapping_spec.rb",
|
37
|
+
"spec/mapper_spec.rb",
|
38
|
+
"spec/mapping_spec.rb",
|
39
|
+
"spec/profile_spec.rb",
|
29
40
|
"spec/spec_helper.rb"
|
30
41
|
]
|
31
42
|
s.homepage = %q{http://github.com/markpercival/divining_rod}
|
@@ -34,7 +45,11 @@ Gem::Specification.new do |s|
|
|
34
45
|
s.rubygems_version = %q{1.3.5}
|
35
46
|
s.summary = %q{A mobile phone web request profiler}
|
36
47
|
s.test_files = [
|
37
|
-
"spec/
|
48
|
+
"spec/definition_spec.rb",
|
49
|
+
"spec/example_mapping_spec.rb",
|
50
|
+
"spec/mapper_spec.rb",
|
51
|
+
"spec/mapping_spec.rb",
|
52
|
+
"spec/profile_spec.rb",
|
38
53
|
"spec/spec_helper.rb"
|
39
54
|
]
|
40
55
|
|
data/example_config.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
DiviningRod::Mappings.define do |map|
|
2
|
+
# Android based phones
|
3
|
+
map.ua /Android/, :format => :webkit, :name => 'Android', :tags => [:android, :youtube_capable, :google_gears]
|
4
|
+
|
5
|
+
# Apple iPhone OS
|
6
|
+
map.ua /Apple.*Mobile.*Safari/, :format => :webkit, :tags => [:apple, :iphone_os, :youtube_capable] do |iphone|
|
7
|
+
iphone.ua /iPad/, :tags => :ipad, :name => 'iPad'
|
8
|
+
iphone.ua /iPod/, :tags => :ipod, :name => 'iPod Touch'
|
9
|
+
iphone.ua /iPhone/, :tags => [:iphone], :name => 'iPhone'
|
10
|
+
end
|
11
|
+
|
12
|
+
#Blackberry, needs more detail here
|
13
|
+
map.ua /BlackBerry/, :tags => :blackberry, :name => 'BlackBerry'
|
14
|
+
|
15
|
+
# The desktop browsers, we don't set a format on these to let it pass through
|
16
|
+
map.with_options :tags => :desktop do |desktop|
|
17
|
+
desktop.ua /Chrome/, :tags => :chrome, :name => 'Chrome'
|
18
|
+
desktop.ua /Firefox/, :tags => :firefox, :name => 'Firefox'
|
19
|
+
desktop.ua /Safari/, :tags => :safari, :name => 'Safari'
|
20
|
+
desktop.ua /Opera/, :tags => :opera, :name => 'Opera'
|
21
|
+
desktop.ua /MSIE/, :tags => :ie, :name => 'Internet Explorer' do |msie|
|
22
|
+
msie.ua /MSIE 6/, :version => 6
|
23
|
+
msie.ua /MSIE 7/, :version => 7
|
24
|
+
msie.ua /MSIE 8/, :version => 8
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Enable this to forces a default format if unmatched
|
29
|
+
# otherwise it will return the request.format
|
30
|
+
# map.default :format => :html
|
31
|
+
end
|
data/lib/divining_rod.rb
CHANGED
@@ -1,107 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(request)
|
10
|
-
@request = request.clone #Lets not mess with the real one
|
11
|
-
matchers.each do |matcher|
|
12
|
-
@match = matcher if matcher.matches?(request)
|
13
|
-
break if @match
|
14
|
-
end
|
15
|
-
nil
|
16
|
-
end
|
17
|
-
|
18
|
-
def group
|
19
|
-
if @match
|
20
|
-
@match.group
|
21
|
-
else
|
22
|
-
@request.format
|
23
|
-
end
|
24
|
-
end
|
25
|
-
alias_method :format, :group
|
26
|
-
|
27
|
-
def recognized?
|
28
|
-
!!@match
|
29
|
-
end
|
30
|
-
|
31
|
-
def method_missing(meth)
|
32
|
-
if meth.to_s.match(/(.+)\?$/)
|
33
|
-
tag = $1
|
34
|
-
if @match
|
35
|
-
@match.tags.include?(tag.to_s) || @match.tags.include?(tag.to_sym) || @match.tags == tag
|
36
|
-
else
|
37
|
-
false
|
38
|
-
end
|
39
|
-
else
|
40
|
-
super
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def matchers
|
45
|
-
DiviningRod::Matchers.definitions || []
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
class Matchers
|
51
|
-
|
52
|
-
class << self
|
53
|
-
|
54
|
-
attr_accessor :definitions
|
55
|
-
|
56
|
-
def define
|
57
|
-
yield(self)
|
58
|
-
end
|
59
|
-
|
60
|
-
def clear_definitions
|
61
|
-
@definitions = []
|
62
|
-
end
|
63
|
-
|
64
|
-
def ua(pattern, group, opts={})
|
65
|
-
@definitions ||= []
|
66
|
-
@definitions << Definition.new(group, opts) { |request|
|
67
|
-
if pattern.match(request.user_agent)
|
68
|
-
true
|
69
|
-
end
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
def subdomain(pattern, group, opts={})
|
74
|
-
@definitions ||= []
|
75
|
-
@definitions << Definition.new(group, opts) { |request|
|
76
|
-
if pattern.match(request.subdomains[0])
|
77
|
-
true
|
78
|
-
end
|
79
|
-
}
|
80
|
-
end
|
81
|
-
|
82
|
-
def default(group, opts = {})
|
83
|
-
@definitions ||= []
|
84
|
-
@definitions << Definition.new(group, opts) { |request| true }
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
class Definition
|
92
|
-
|
93
|
-
attr_accessor :prc, :tags, :group
|
94
|
-
|
95
|
-
def initialize(group, opts={}, &blk)
|
96
|
-
@group = group
|
97
|
-
@tags = opts[:tags] || []
|
98
|
-
@prc = blk
|
99
|
-
end
|
100
|
-
|
101
|
-
def matches?(request)
|
102
|
-
!!@prc.call(request)
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
1
|
+
module DiviningRod; end
|
2
|
+
|
3
|
+
require 'divining_rod/profile'
|
4
|
+
require 'divining_rod/murge'
|
5
|
+
require 'divining_rod/definition'
|
6
|
+
require 'divining_rod/mappings'
|
7
|
+
require 'divining_rod/mapper'
|
8
|
+
require 'divining_rod/matchers'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
class Definition
|
3
|
+
include Murge
|
4
|
+
|
5
|
+
attr_accessor :prc, :group, :opts, :parent
|
6
|
+
attr_writer :children
|
7
|
+
|
8
|
+
def initialize(opts={}, &blk)
|
9
|
+
@prc = blk
|
10
|
+
@opts = opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(request)
|
14
|
+
result = nil
|
15
|
+
child_result = nil
|
16
|
+
if @prc.call(request)
|
17
|
+
result = self
|
18
|
+
unless self.children.empty?
|
19
|
+
self.children.each do |child|
|
20
|
+
child_result = child.evaluate(request)
|
21
|
+
break if child_result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
child_result || result
|
26
|
+
end
|
27
|
+
|
28
|
+
def tags
|
29
|
+
opts[:tags] || []
|
30
|
+
end
|
31
|
+
|
32
|
+
def opts
|
33
|
+
if parent
|
34
|
+
@murged_opts ||= murge(parent.opts, @opts)
|
35
|
+
else
|
36
|
+
@opts
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def format
|
41
|
+
opts[:format]
|
42
|
+
end
|
43
|
+
|
44
|
+
def children
|
45
|
+
@children ||= []
|
46
|
+
@children.each do |child|
|
47
|
+
child.parent ||= self
|
48
|
+
end
|
49
|
+
@children
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
class Mapper
|
3
|
+
include Murge
|
4
|
+
|
5
|
+
def initialize(parent, default_opts = {})
|
6
|
+
@parent = parent
|
7
|
+
@default_opts = default_opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def pattern(type, pattern, opts = {})
|
11
|
+
opts = murge(default_options, opts)
|
12
|
+
definition = Matchers.send(type.to_sym, pattern, opts)
|
13
|
+
append_to_parent(definition)
|
14
|
+
if block_given?
|
15
|
+
yield self.class.new(definition)
|
16
|
+
end
|
17
|
+
definition
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_options(opts)
|
21
|
+
yield self.class.new(@parent, opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def default(opts = {})
|
25
|
+
definition = Definition.new(opts) { true }
|
26
|
+
append_to_parent(definition)
|
27
|
+
definition
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(meth, *args, &blk)
|
31
|
+
# Lets us use map.ua instead of map.pattern :ua
|
32
|
+
if Matchers.respond_to?(meth.to_sym)
|
33
|
+
self.pattern(meth, args[0], args[1,], &blk)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def default_options
|
42
|
+
@default_opts || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def append_to_parent(definition)
|
46
|
+
if @parent
|
47
|
+
@parent.children << definition
|
48
|
+
end
|
49
|
+
definition
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
class Mappings
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
attr_accessor :root_definition
|
7
|
+
|
8
|
+
def define(opts = {})
|
9
|
+
@root_definition = Definition.new { true }
|
10
|
+
yield Mapper.new(@root_definition, opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(obj)
|
14
|
+
@root_definition.evaluate(obj)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
class Matchers
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def ua(pattern, opts = {})
|
6
|
+
Definition.new(opts) { |request|
|
7
|
+
if pattern.match(request.user_agent)
|
8
|
+
true
|
9
|
+
end
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def subdomain(pattern, opts={})
|
14
|
+
Definition.new(opts) { |request|
|
15
|
+
if pattern.match(request.subdomains[0])
|
16
|
+
true
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
module Murge
|
3
|
+
|
4
|
+
# This is tacky, but I'm preserving tags in the option hash
|
5
|
+
# and only want to have to write this once
|
6
|
+
#
|
7
|
+
def murge(old_opts, new_opts)
|
8
|
+
old_opts = old_opts || {}
|
9
|
+
tags = Array(old_opts[:tags]) | Array(new_opts[:tags])
|
10
|
+
opts = old_opts.merge(new_opts)
|
11
|
+
opts[:tags] = tags
|
12
|
+
opts
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DiviningRod
|
2
|
+
class Profile
|
3
|
+
|
4
|
+
attr_reader :match
|
5
|
+
|
6
|
+
def initialize(request)
|
7
|
+
@request = request.clone #Lets not mess with the real one
|
8
|
+
@match = DiviningRod::Mappings.evaluate(request)
|
9
|
+
end
|
10
|
+
|
11
|
+
def format
|
12
|
+
if @match && @match.format
|
13
|
+
@match.format
|
14
|
+
else
|
15
|
+
@request.format
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def recognized?
|
20
|
+
@match != DiviningRod::Mappings.root_definition
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(meth)
|
24
|
+
if meth.to_s.match(/(.+)\?$/)
|
25
|
+
tag = $1
|
26
|
+
if @match
|
27
|
+
@match.tags.include?(tag.to_s) || @match.tags.include?(tag.to_sym) || @match.tags == tag
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
elsif @match.opts.include?(meth.to_sym)
|
32
|
+
@match.opts[meth]
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DiviningRod::Definition do
|
4
|
+
|
5
|
+
it "should work with no children not match" do
|
6
|
+
definition = DiviningRod::Definition.new(:format => :iphone, :tags => [:overpriced, :easy_to_break]) { |request|
|
7
|
+
request == 'iphone'
|
8
|
+
}
|
9
|
+
definition.evaluate('foo').should be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should work with no children and match" do
|
13
|
+
definition = DiviningRod::Definition.new(:format => :iphone, :tags => [:overpriced, :easy_to_break]) { |request|
|
14
|
+
request == 'iphone'
|
15
|
+
}
|
16
|
+
definition.evaluate('iphone').should eql(definition)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "with children" do
|
20
|
+
|
21
|
+
it "should work with children that match" do
|
22
|
+
definition = DiviningRod::Definition.new(:format => :iphone, :tags => [:overpriced, :easy_to_break]) { |request|
|
23
|
+
request.match(/iphone/)
|
24
|
+
}
|
25
|
+
definition.children << DiviningRod::Definition.new(:format => :ipod, :tags => [:big, :lacks_a_camera]) { |request|
|
26
|
+
request.match(/ipod/)
|
27
|
+
}
|
28
|
+
definition.children << DiviningRod::Definition.new(:format => :ipad, :tags => [:big, :lacks_a_camera]) { |request|
|
29
|
+
request.match(/ipad/)
|
30
|
+
}
|
31
|
+
result = definition.evaluate('iphone ipad')
|
32
|
+
result.should_not eql(definition)
|
33
|
+
result.format.should eql(:ipad)
|
34
|
+
result.tags.should include(:overpriced)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
require 'example_config'
|
3
|
+
|
4
|
+
|
5
|
+
def request_mock(ua, subdomain = [])
|
6
|
+
mock('RailsRequest', :user_agent => ua, :subdomain => subdomain)
|
7
|
+
end
|
8
|
+
|
9
|
+
def profile_ua(ua)
|
10
|
+
DiviningRod::Profile.new(request_mock(ua))
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
describe 'the example config' do
|
15
|
+
|
16
|
+
describe "recognizing Apple devices" do
|
17
|
+
|
18
|
+
it "should recognize an iPad" do
|
19
|
+
profile = profile_ua("Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10")
|
20
|
+
profile.ipad?.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should recognize an iPod touch 2.2.1" do
|
24
|
+
profile = profile_ua("Mozilla/5.0 (iPod; 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")
|
25
|
+
profile.ipod?.should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should recognize an iPod touch 3.1.2" do
|
29
|
+
profile = profile_ua("Mozilla/5.0 (iPod; U; CPU iPhone OS 3_1_2 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7D11 Safari/528.16")
|
30
|
+
profile.ipod?.should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should recognize an iPhone 3.1.2" do
|
34
|
+
profile = profile_ua("Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7D11 Safari/528.16")
|
35
|
+
profile.iphone?.should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should recognize an iPhone 2.1.2" do
|
39
|
+
profile = profile_ua("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")
|
40
|
+
profile.iphone?.should be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "recognizing Android devices" do
|
46
|
+
|
47
|
+
it "should recognize a basic android" do
|
48
|
+
profile = profile_ua("Mozilla/5.0 (Linux; U; Android 1.0; en-us; dream) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2")
|
49
|
+
profile.android?.should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "recognizing desktop browsers" do
|
55
|
+
|
56
|
+
it "should recognize Internet Explorer 7" do
|
57
|
+
profile = profile_ua("Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0)")
|
58
|
+
profile.desktop?.should be_true
|
59
|
+
profile.version.should eql(7)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
data/spec/mapper_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DiviningRod::Mapper do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@root_definition = DiviningRod::Definition.new { true }
|
7
|
+
mapper = DiviningRod::Mapper.new(@root_definition, {:tags => [:fuck], :foo => true})
|
8
|
+
mapper.ua /Safari/, :tags => [:baz] do |map|
|
9
|
+
map.with_options :tags => :awsome do |awesome|
|
10
|
+
awesome.ua /Apple/, :tags => [:foo]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should map a definition" do
|
16
|
+
request = mock("rails_request", :user_agent => 'Apple Mobile Safari', :format => :html)
|
17
|
+
result = @root_definition.evaluate(request)
|
18
|
+
result.tags.should include(:fuck)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DiviningRod::Mappings do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
DiviningRod::Mappings.define(:tags => :mobile) do |map|
|
7
|
+
map.ua /Apple/, :format => :webkit, :tags => [:apple] do |apple|
|
8
|
+
|
9
|
+
# iPod, iPhone, and iPad
|
10
|
+
apple.with_options :tags => [:youtube, :geolocate, :iphone] do |advanced|
|
11
|
+
advanced.ua /iPad/, :tags => :ipad do |ipad|
|
12
|
+
ipad.ua /OS\s4/, :tags => :version4
|
13
|
+
ipad.ua /OS\s3/, :tags => :version3 do |v3|
|
14
|
+
v3.ua /Unicorns/, :tags => :omg_unicorns do |unicorns|
|
15
|
+
unicorns.ua /eat kittens/, :tags => [:omg_they_eat_kittens]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
ipad.ua /OS\s2/, :tags => :version2
|
19
|
+
end
|
20
|
+
advanced.ua /iPod/, :tags => [:ipod, :ipod_touch]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Also an apple but actualy a newton
|
24
|
+
apple.ua /Newton/, :tags => [:apple] do |newton|
|
25
|
+
newton.ua /OS 8/, :tags => :os8
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should match a top level user agent" do
|
32
|
+
request = mock("rails_request", :user_agent => 'Apple Mobile Safari', :format => :html)
|
33
|
+
result = DiviningRod::Mappings.root_definition.evaluate(request)
|
34
|
+
result.should_not be_nil
|
35
|
+
result.tags.should include(:apple)
|
36
|
+
result.tags.should_not include(:ipad)
|
37
|
+
result.tags.should include(:mobile)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should match a child definition" do
|
41
|
+
ipad_request = mock("rails_request", :user_agent => 'Apple iPad', :format => :html)
|
42
|
+
result = DiviningRod::Mappings.evaluate(ipad_request)
|
43
|
+
result.tags.should include(:ipad)
|
44
|
+
end
|
45
|
+
|
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)
|
48
|
+
result = DiviningRod::Mappings.evaluate(ipad_request)
|
49
|
+
result.tags.should include(:ipad)
|
50
|
+
result.tags.should include(:omg_unicorns)
|
51
|
+
result.tags.should include(:version3)
|
52
|
+
end
|
53
|
+
|
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)
|
56
|
+
result = DiviningRod::Mappings.evaluate(ipad_request)
|
57
|
+
result.tags.should include(:ipad, :youtube)
|
58
|
+
result.tags.should include(:omg_unicorns)
|
59
|
+
result.tags.should include(:omg_they_eat_kittens)
|
60
|
+
result.tags.should include(:version3)
|
61
|
+
end
|
62
|
+
|
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)
|
65
|
+
result = DiviningRod::Mappings.evaluate(ipad_request)
|
66
|
+
result.tags.should include(:ipad, :youtube)
|
67
|
+
result.tags.should_not include(:omg_they_eat_kittens, :omg_unicorns)
|
68
|
+
result.tags.should include(:version2)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should match a in order defined" do
|
72
|
+
ipad_request = mock("rails_request", :user_agent => 'Apple Newton - OS 8', :format => :html)
|
73
|
+
result = DiviningRod::Mappings.evaluate(ipad_request)
|
74
|
+
result.tags.should_not include(:omg_they_eat_kittens, :omg_unicorns)
|
75
|
+
result.tags.should include(:os8)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -3,10 +3,12 @@ require 'spec_helper'
|
|
3
3
|
describe DiviningRod do
|
4
4
|
|
5
5
|
before :each do
|
6
|
-
@request = mock("rails_request", :user_agent => 'My iPhone')
|
7
|
-
|
8
|
-
DiviningRod::
|
9
|
-
map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
|
6
|
+
@request = mock("rails_request", :user_agent => 'My iPhone which is actually an iPad')
|
7
|
+
|
8
|
+
DiviningRod::Mappings.define do |map|
|
9
|
+
map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate] do |iphone|
|
10
|
+
iphone.ua /iPad/, :tags => [:ipad]
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
@@ -17,7 +19,7 @@ describe DiviningRod do
|
|
17
19
|
|
18
20
|
it "should know if it belongs to a category tag" do
|
19
21
|
profile = DiviningRod::Profile.new(@request)
|
20
|
-
profile.
|
22
|
+
profile.ipad?.should be_true
|
21
23
|
end
|
22
24
|
|
23
25
|
it "should know if it does not belongs to a category" do
|
@@ -29,9 +31,9 @@ describe DiviningRod do
|
|
29
31
|
|
30
32
|
before :each do
|
31
33
|
@request = mock("rails_request", :user_agent => 'My Foo Fone', :format => :html)
|
32
|
-
|
33
|
-
DiviningRod::
|
34
|
-
map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
|
34
|
+
|
35
|
+
DiviningRod::Mappings.define do |map|
|
36
|
+
map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -48,10 +50,10 @@ describe DiviningRod do
|
|
48
50
|
|
49
51
|
before :each do
|
50
52
|
@request = mock("rails_request", :user_agent => 'My Foo Fone')
|
51
|
-
|
52
|
-
DiviningRod::
|
53
|
-
map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
|
54
|
-
map.default :html
|
53
|
+
|
54
|
+
DiviningRod::Mappings.define do |map|
|
55
|
+
map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
|
56
|
+
map.default :format => :html
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -67,9 +69,9 @@ describe DiviningRod do
|
|
67
69
|
|
68
70
|
before :each do
|
69
71
|
@request = mock("rails_request", :user_agent => 'Foo Fone', :format => :html)
|
70
|
-
|
71
|
-
DiviningRod::
|
72
|
-
map.ua /iPhone/, :webkit, :tags => [:iphone, :youtube, :geolocate]
|
72
|
+
|
73
|
+
DiviningRod::Mappings.define do |map|
|
74
|
+
map.ua /iPhone/, :format => :webkit, :tags => [:iphone, :youtube, :geolocate]
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
@@ -83,9 +85,9 @@ describe DiviningRod do
|
|
83
85
|
|
84
86
|
before :each do
|
85
87
|
@request = mock("rails_request", :user_agent => 'Foo Fone', :subdomains => ['wap'])
|
86
|
-
|
87
|
-
DiviningRod::
|
88
|
-
map.subdomain /wap/, :wap, :tags => [:shitty]
|
88
|
+
|
89
|
+
DiviningRod::Mappings.define do |map|
|
90
|
+
map.subdomain /wap/, :format => :wap, :tags => [:shitty]
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
@@ -101,9 +103,9 @@ describe DiviningRod do
|
|
101
103
|
|
102
104
|
before :each do
|
103
105
|
@request = mock("rails_request", :user_agent => nil, :subdomains => [])
|
104
|
-
|
105
|
-
DiviningRod::
|
106
|
-
map.ua /iPhone/, :wap, :tags => [:shitty]
|
106
|
+
|
107
|
+
DiviningRod::Mappings.define do |map|
|
108
|
+
map.ua /iPhone/, :format => :wap, :tags => [:shitty]
|
107
109
|
end
|
108
110
|
end
|
109
111
|
|
@@ -114,38 +116,3 @@ describe DiviningRod do
|
|
114
116
|
end
|
115
117
|
|
116
118
|
end
|
117
|
-
|
118
|
-
|
119
|
-
describe DiviningRod::Matchers do
|
120
|
-
|
121
|
-
before :each do
|
122
|
-
@request = mock("rails_request", :user_agent => 'iPhone Foo')
|
123
|
-
DiviningRod::Matchers.clear_definitions
|
124
|
-
DiviningRod::Matchers.define do |map|
|
125
|
-
map.ua /iPhone/, :iphone, :tags => [:iphone, :youtube]
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
it "should recognize an iPhone" do
|
130
|
-
DiviningRod::Matchers.definitions.first.matches?(@request).should be_true
|
131
|
-
DiviningRod::Matchers.definitions.first.group.should eql(:iphone)
|
132
|
-
end
|
133
|
-
|
134
|
-
describe "defining a default definition" do
|
135
|
-
|
136
|
-
before :each do
|
137
|
-
@request = mock("rails_request", :user_agent => 'Foo Fone')
|
138
|
-
DiviningRod::Matchers.clear_definitions
|
139
|
-
DiviningRod::Matchers.define do |map|
|
140
|
-
map.default :unknown, :tags => [:html]
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
it "should use the default route if no other match is found" do
|
145
|
-
DiviningRod::Matchers.definitions.first.matches?(@request).should be_true
|
146
|
-
DiviningRod::Matchers.definitions.first.group.should eql(:unknown)
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: divining_rod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Percival
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-23 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,8 +39,19 @@ files:
|
|
39
39
|
- Rakefile
|
40
40
|
- VERSION
|
41
41
|
- divining_rod.gemspec
|
42
|
+
- example_config.rb
|
42
43
|
- lib/divining_rod.rb
|
43
|
-
-
|
44
|
+
- lib/divining_rod/definition.rb
|
45
|
+
- lib/divining_rod/mapper.rb
|
46
|
+
- lib/divining_rod/mappings.rb
|
47
|
+
- lib/divining_rod/matchers.rb
|
48
|
+
- lib/divining_rod/murge.rb
|
49
|
+
- lib/divining_rod/profile.rb
|
50
|
+
- spec/definition_spec.rb
|
51
|
+
- spec/example_mapping_spec.rb
|
52
|
+
- spec/mapper_spec.rb
|
53
|
+
- spec/mapping_spec.rb
|
54
|
+
- spec/profile_spec.rb
|
44
55
|
- spec/spec_helper.rb
|
45
56
|
has_rdoc: true
|
46
57
|
homepage: http://github.com/markpercival/divining_rod
|
@@ -71,5 +82,9 @@ signing_key:
|
|
71
82
|
specification_version: 3
|
72
83
|
summary: A mobile phone web request profiler
|
73
84
|
test_files:
|
74
|
-
- spec/
|
85
|
+
- spec/definition_spec.rb
|
86
|
+
- spec/example_mapping_spec.rb
|
87
|
+
- spec/mapper_spec.rb
|
88
|
+
- spec/mapping_spec.rb
|
89
|
+
- spec/profile_spec.rb
|
75
90
|
- spec/spec_helper.rb
|