agent_orange 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/README.rdoc +3 -1
- data/Rakefile +1 -1
- data/lib/agent_orange/browser.rb +9 -0
- data/lib/agent_orange/device.rb +13 -5
- data/lib/agent_orange/version.rb +1 -1
- data/spec/long_user_agent_spec.rb +58 -0
- data/spec/spec_helper.rb +2 -11
- data/spec/user_agent_spec.rb +56 -66
- metadata +32 -9
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -78,7 +78,7 @@ If you're going to use it with Rails then add the gem to your Gemfile.
|
|
78
78
|
|
79
79
|
=== Looking at the operating system
|
80
80
|
|
81
|
-
>> os = ua.device.
|
81
|
+
>> os = ua.device.operating_system
|
82
82
|
=> "iPhone OS 4.3"
|
83
83
|
>> os.type
|
84
84
|
=> "iphone_os"
|
@@ -163,6 +163,8 @@ A warm thank you to all contributors to the project, who have put effort and tim
|
|
163
163
|
* David Rice (@davidjrice) - RSpec tests and bug fixes.
|
164
164
|
* Joshua Siler (@eatenbyagrue) - Bot detection, initial test, and bug fixes.
|
165
165
|
* Alexander Rozumiy (@brain-geek) - Rspec testing for 1.9, updated travis-ci config, updated useragents to test against.
|
166
|
+
* Joe Lencioni (@lencioni) - Improved bot detection.
|
167
|
+
* Todd Huss (@thuss) of Two Bit Labs (@twobitlabs) - Google Mobile crawler detection as mobile and bot.
|
166
168
|
|
167
169
|
== License
|
168
170
|
|
data/Rakefile
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/dsl_definition'
|
2
3
|
Dir.glob('./lib/tasks/*.rake').each { |r| import r }
|
3
4
|
|
4
5
|
begin
|
@@ -9,6 +10,5 @@ begin
|
|
9
10
|
# t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
10
11
|
# Put spec opts in a file named .rspec in root
|
11
12
|
end
|
12
|
-
|
13
13
|
task :default => :spec
|
14
14
|
end
|
data/lib/agent_orange/browser.rb
CHANGED
@@ -10,9 +10,12 @@ module AgentOrange
|
|
10
10
|
:ie => 'MSIE|Internet Explorer|IE',
|
11
11
|
:firefox => 'Firefox',
|
12
12
|
:opera => 'Opera',
|
13
|
+
:chrome => 'Chrome',
|
13
14
|
:safari => 'Safari'
|
14
15
|
}
|
15
16
|
|
17
|
+
BROWSER_TITLES = BROWSERS.merge :ie => 'MSIE'
|
18
|
+
|
16
19
|
def parse(user_agent)
|
17
20
|
AgentOrange.debug "BROWSER PARSING", 2
|
18
21
|
|
@@ -21,6 +24,7 @@ module AgentOrange
|
|
21
24
|
if content[:name] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
|
22
25
|
# Matched group against name
|
23
26
|
self.populate(content)
|
27
|
+
break
|
24
28
|
elsif content[:comment] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
|
25
29
|
# Matched group against comment
|
26
30
|
chosen_content = { :name => nil, :version => nil }
|
@@ -28,6 +32,11 @@ module AgentOrange
|
|
28
32
|
additional_groups.each do |additional_content|
|
29
33
|
if additional_content[:name] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
|
30
34
|
chosen_content = additional_content
|
35
|
+
|
36
|
+
#Additional checking for chromeframe, iemobile, etc.
|
37
|
+
BROWSERS.each do |cat, regex|
|
38
|
+
chosen_content[:name] = BROWSER_TITLES[cat] if additional_content[:name] =~ /(#{regex})/i
|
39
|
+
end
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
data/lib/agent_orange/device.rb
CHANGED
@@ -6,15 +6,18 @@ require 'agent_orange/version'
|
|
6
6
|
|
7
7
|
module AgentOrange
|
8
8
|
class Device < Base
|
9
|
-
attr_accessor :type, :name, :version
|
9
|
+
attr_accessor :type, :name, :version, :bot
|
10
10
|
attr_accessor :platform
|
11
11
|
attr_accessor :operating_system
|
12
12
|
attr_accessor :engine
|
13
13
|
|
14
14
|
DEVICES = {
|
15
15
|
:computer => 'windows|macintosh|x11|linux',
|
16
|
-
:mobile => 'ipod|ipad|iphone|palm|android|opera mini|hiptop|windows ce|smartphone|mobile|treo|psp'
|
17
|
-
|
16
|
+
:mobile => 'ipod|ipad|iphone|palm|android|opera mini|hiptop|windows ce|smartphone|mobile|treo|psp'
|
17
|
+
}
|
18
|
+
|
19
|
+
BOTS = {
|
20
|
+
:bot => 'alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex'
|
18
21
|
}
|
19
22
|
|
20
23
|
def parse(user_agent)
|
@@ -22,7 +25,8 @@ module AgentOrange
|
|
22
25
|
|
23
26
|
groups = parse_user_agent_string_into_groups(user_agent)
|
24
27
|
groups.each_with_index do |content,i|
|
25
|
-
|
28
|
+
devices_and_bots = DEVICES.merge(BOTS)
|
29
|
+
if content[:comment] =~ /(#{devices_and_bots.collect{|cat,regex| regex}.join(')|(')})/i
|
26
30
|
# Matched group against name
|
27
31
|
self.populate(content)
|
28
32
|
end
|
@@ -40,6 +44,10 @@ module AgentOrange
|
|
40
44
|
AgentOrange.debug "", 2
|
41
45
|
|
42
46
|
self.type = self.determine_type(DEVICES, content[:comment])
|
47
|
+
self.bot = self.determine_type(BOTS, content[:comment]) == :bot
|
48
|
+
if (self.bot && self.type == "other")
|
49
|
+
self.type = :bot # backwards compatability
|
50
|
+
end
|
43
51
|
self.name = self.type.to_s.capitalize
|
44
52
|
self.version = nil
|
45
53
|
self
|
@@ -86,7 +94,7 @@ module AgentOrange
|
|
86
94
|
return self.name.downcase.include?(name.to_s.downcase)
|
87
95
|
end
|
88
96
|
else
|
89
|
-
|
97
|
+
self.bot
|
90
98
|
end
|
91
99
|
end
|
92
100
|
|
data/lib/agent_orange/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module AgentOrange
|
2
|
-
VERSION = "0.1.
|
2
|
+
VERSION = "0.1.4" # This is for the gem and does not conflict with the rest of the functionality
|
3
3
|
|
4
4
|
class Version
|
5
5
|
attr_accessor :major, :minor, :patch_level, :build_number
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'net/http'
|
3
|
+
require 'iconv'
|
4
|
+
|
5
|
+
describe AgentOrange::UserAgent do
|
6
|
+
describe 'checking with real browsers list' do
|
7
|
+
before do
|
8
|
+
@lists = [Iconv.new('UTF-8//IGNORE', 'UTF-8').iconv( File.open("spec/fixtures/browser_ids.htm").read)]
|
9
|
+
|
10
|
+
begin
|
11
|
+
@lists.push Net::HTTP.get('www.zytrax.com', '/tech/web/browser_ids.htm')
|
12
|
+
rescue
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should parse browsers from list" do
|
17
|
+
[
|
18
|
+
{
|
19
|
+
:a_name => 'chrome',
|
20
|
+
:browser_names => ['Chrome', 'Safari']
|
21
|
+
},
|
22
|
+
{
|
23
|
+
:a_name => 'safari',
|
24
|
+
:browser_names => ['Safari']
|
25
|
+
},
|
26
|
+
{
|
27
|
+
:a_name => 'firefox',
|
28
|
+
:browser_names => ['Firefox']
|
29
|
+
},
|
30
|
+
{
|
31
|
+
:a_name => 'msie',
|
32
|
+
#chromeframe!
|
33
|
+
:browser_names => ['MSIE', 'Chrome']
|
34
|
+
},
|
35
|
+
{
|
36
|
+
:a_name => 'opera',
|
37
|
+
:browser_names => ['Opera']
|
38
|
+
}
|
39
|
+
].each do |params|
|
40
|
+
part = ''
|
41
|
+
|
42
|
+
@lists.each do |list|
|
43
|
+
part += list.scan(/<a name="#{params[:a_name]}">.+?<a name="/m).first.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
part.scan(/<p class="g-c-s">([^<]+)<\/p>/).each do |q|
|
47
|
+
unless params[:browser_names]== ['Opera'] && q.first == 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
48
|
+
ua = AgentOrange::UserAgent.new(q.first)
|
49
|
+
br_name = ua.device.engine.browser.name
|
50
|
+
|
51
|
+
puts "Test failing at user id: '#{q}'" unless params[:browser_names].include?(br_name)
|
52
|
+
params[:browser_names].should include(br_name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
|
11
|
-
require File.join(File.dirname(__FILE__), "user_agent_matcher")
|
12
|
-
|
2
|
+
require 'bundler/setup'
|
3
|
+
require File.join(File.dirname(__FILE__), 'user_agent_matcher')
|
13
4
|
|
14
5
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
6
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
data/spec/user_agent_spec.rb
CHANGED
@@ -9,6 +9,7 @@ describe AgentOrange::UserAgent do
|
|
9
9
|
ua.device.type.should == :computer
|
10
10
|
ua.device.name.should == "Computer"
|
11
11
|
ua.device.version.should == nil
|
12
|
+
ua.device.bot.should == false
|
12
13
|
|
13
14
|
ua.device.operating_system.name.should == "Mac OS X"
|
14
15
|
ua.device.operating_system.version.to_s.should == "10.6.8"
|
@@ -22,24 +23,6 @@ describe AgentOrange::UserAgent do
|
|
22
23
|
ua.device.engine.browser.name.should == "Safari"
|
23
24
|
ua.device.engine.browser.version.to_s.should == "533.21.1"
|
24
25
|
end
|
25
|
-
|
26
|
-
detect "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/535.5 (KHTML, like Gecko) Chrome/16.0.891.1 Safari/535.5" do |ua|
|
27
|
-
ua.device.type.should == :computer
|
28
|
-
ua.device.name.should == "Computer"
|
29
|
-
ua.device.version.should == nil
|
30
|
-
|
31
|
-
ua.device.operating_system.name.should == "Mac OS X"
|
32
|
-
ua.device.operating_system.version.to_s.should == "10.7.1"
|
33
|
-
|
34
|
-
ua.device.platform.name.should == "Macintosh"
|
35
|
-
ua.device.platform.version.should == nil
|
36
|
-
|
37
|
-
ua.device.engine.name.should == "AppleWebKit"
|
38
|
-
ua.device.engine.version.to_s.should == "535.5"
|
39
|
-
|
40
|
-
ua.device.engine.browser.name.should == "Safari"
|
41
|
-
ua.device.engine.browser.version.to_s.should == "535.5"
|
42
|
-
end
|
43
26
|
end
|
44
27
|
|
45
28
|
describe "Firefox" do
|
@@ -47,6 +30,7 @@ describe AgentOrange::UserAgent do
|
|
47
30
|
ua.device.type.should == :computer
|
48
31
|
ua.device.name.should == "Computer"
|
49
32
|
ua.device.version.should == nil
|
33
|
+
ua.device.bot.should == false
|
50
34
|
|
51
35
|
ua.device.operating_system.name.should == "Linux"
|
52
36
|
ua.device.operating_system.version.to_s.should == "i686"
|
@@ -68,6 +52,7 @@ describe AgentOrange::UserAgent do
|
|
68
52
|
ua.device.type.should == :computer
|
69
53
|
ua.device.name.should == "Computer"
|
70
54
|
ua.device.version.should == nil
|
55
|
+
ua.device.bot.should == false
|
71
56
|
|
72
57
|
ua.device.operating_system.name.should == "Windows"
|
73
58
|
ua.device.operating_system.version.to_s.should == "5.1"
|
@@ -78,61 +63,66 @@ describe AgentOrange::UserAgent do
|
|
78
63
|
ua.device.engine.name.should == "AppleWebKit"
|
79
64
|
ua.device.engine.version.to_s.should == "535.2"
|
80
65
|
|
81
|
-
|
82
|
-
ua.device.engine.browser.
|
83
|
-
ua.device.engine.browser.version.to_s.should == "535.2"
|
66
|
+
ua.device.engine.browser.name.should == "Chrome"
|
67
|
+
ua.device.engine.browser.version.to_s.should == "15.0.872.0"
|
84
68
|
end
|
85
|
-
end
|
86
69
|
|
87
|
-
|
88
|
-
|
89
|
-
|
70
|
+
detect "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/535.5 (KHTML, like Gecko) Chrome/16.0.891.1 Safari/535.5" do |ua|
|
71
|
+
ua.device.type.should == :computer
|
72
|
+
ua.device.name.should == "Computer"
|
73
|
+
ua.device.version.should == nil
|
74
|
+
ua.device.bot.should == false
|
75
|
+
|
76
|
+
ua.device.operating_system.name.should == "Mac OS X"
|
77
|
+
ua.device.operating_system.version.to_s.should == "10.7.1"
|
90
78
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
79
|
+
ua.device.platform.name.should == "Macintosh"
|
80
|
+
ua.device.platform.version.should == nil
|
81
|
+
|
82
|
+
ua.device.engine.name.should == "AppleWebKit"
|
83
|
+
ua.device.engine.version.to_s.should == "535.5"
|
96
84
|
|
97
|
-
|
98
|
-
|
99
|
-
{
|
100
|
-
:a_name => 'chrome',
|
101
|
-
:browser_names => ['Safari']
|
102
|
-
},
|
103
|
-
{
|
104
|
-
:a_name => 'safari',
|
105
|
-
:browser_names => ['Safari']
|
106
|
-
},
|
107
|
-
{
|
108
|
-
:a_name => 'firefox',
|
109
|
-
:browser_names => ['Firefox']
|
110
|
-
},
|
111
|
-
{
|
112
|
-
:a_name => 'msie',
|
113
|
-
:browser_names => ['MSIE','IEMobile']
|
114
|
-
},
|
115
|
-
{
|
116
|
-
:a_name => 'opera',
|
117
|
-
:browser_names => ['Opera']
|
118
|
-
}
|
119
|
-
].each do |params|
|
120
|
-
part = ''
|
121
|
-
|
122
|
-
@lists.each do |list|
|
123
|
-
part += list.scan(/<a name="#{params[:a_name]}">.+?<a name="/m).first.to_s
|
124
|
-
end
|
125
|
-
|
126
|
-
part.scan(/<p class="g-c-s">([^<]+)<\/p>/).each do |q|
|
127
|
-
ua = AgentOrange::UserAgent.new(q.first)
|
128
|
-
br_name = ua.device.engine.browser.name
|
129
|
-
|
130
|
-
params[:browser_names].should include(br_name)
|
131
|
-
end
|
132
|
-
end
|
85
|
+
ua.device.engine.browser.name.should == "Chrome"
|
86
|
+
ua.device.engine.browser.version.to_s.should == "16.0.891.1"
|
133
87
|
end
|
134
88
|
|
89
|
+
detect "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; chromeframe/13.0.782.218; chromeframe; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)" do |ua|
|
90
|
+
ua.device.type.should == :computer
|
91
|
+
ua.device.name.should == "Computer"
|
92
|
+
ua.device.version.should == nil
|
93
|
+
ua.device.bot.should == false
|
94
|
+
|
95
|
+
ua.device.engine.browser.name.should == "Chrome"
|
96
|
+
ua.device.engine.browser.version.to_s.should == "13.0.782.218"
|
97
|
+
end
|
135
98
|
end
|
99
|
+
|
100
|
+
describe "Google User Agents" do
|
101
|
+
detect "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_1 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7 (compatible; Googlebot-Mobile/2.1; +http://www.google.com/bot.html)" do |ua|
|
102
|
+
ua.device.type.should == :mobile
|
103
|
+
ua.device.name.should == "Mobile"
|
104
|
+
ua.device.version.should == nil
|
105
|
+
ua.device.bot.should == true
|
106
|
+
ua.is_mobile?.should == true
|
107
|
+
ua.is_computer?.should == false
|
108
|
+
ua.is_bot?.should == true
|
136
109
|
|
110
|
+
ua.device.engine.browser.name.should == "Safari"
|
111
|
+
ua.device.engine.browser.version.to_s.should == "6531.22.7"
|
112
|
+
end
|
113
|
+
|
114
|
+
detect "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" do |ua|
|
115
|
+
ua.device.type.should == :bot
|
116
|
+
ua.device.name.should == "Bot"
|
117
|
+
ua.device.version.should == nil
|
118
|
+
ua.device.bot.should == true
|
119
|
+
ua.is_mobile?.should == false
|
120
|
+
ua.is_computer?.should == false
|
121
|
+
ua.is_bot?.should == true
|
137
122
|
|
123
|
+
ua.device.engine.browser.name.should == nil
|
124
|
+
ua.device.engine.browser.version.to_s.should == ""
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
138
128
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: agent_orange
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-10-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rspec
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: bundler
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,7 +53,12 @@ dependencies:
|
|
43
53
|
version: '0'
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
47
62
|
description: Parse and process User Agents like a secret one
|
48
63
|
email:
|
49
64
|
- kevin@welikeinc.com
|
@@ -71,6 +86,7 @@ files:
|
|
71
86
|
- lib/tasks/allagents.xml
|
72
87
|
- lib/tasks/bot_agent_test.rake
|
73
88
|
- spec/fixtures/browser_ids.htm
|
89
|
+
- spec/long_user_agent_spec.rb
|
74
90
|
- spec/spec_helper.rb
|
75
91
|
- spec/user_agent_matcher.rb
|
76
92
|
- spec/user_agent_spec.rb
|
@@ -86,20 +102,27 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
102
|
- - ! '>='
|
87
103
|
- !ruby/object:Gem::Version
|
88
104
|
version: '0'
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
hash: 3447996973066074909
|
89
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
109
|
none: false
|
91
110
|
requirements:
|
92
111
|
- - ! '>='
|
93
112
|
- !ruby/object:Gem::Version
|
94
113
|
version: '0'
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
hash: 3447996973066074909
|
95
117
|
requirements: []
|
96
118
|
rubyforge_project: agent_orange
|
97
|
-
rubygems_version: 1.8.
|
119
|
+
rubygems_version: 1.8.24
|
98
120
|
signing_key:
|
99
121
|
specification_version: 3
|
100
122
|
summary: Parse and process User Agents like a secret one
|
101
123
|
test_files:
|
102
124
|
- spec/fixtures/browser_ids.htm
|
125
|
+
- spec/long_user_agent_spec.rb
|
103
126
|
- spec/spec_helper.rb
|
104
127
|
- spec/user_agent_matcher.rb
|
105
128
|
- spec/user_agent_spec.rb
|