double_agent 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +17 -0
- data/README.rdoc +118 -0
- data/data/browsers.yml +22 -14
- data/data/oses.yml +31 -11
- data/lib/double_agent/core.rb +51 -19
- data/lib/double_agent/logs.rb +13 -0
- data/lib/double_agent/resources.rb +15 -1
- data/lib/double_agent/stats.rb +5 -9
- data/spec/core_spec.rb +81 -0
- data/spec/data/httpd.access.log +20 -0
- data/spec/data/httpd.access.log.1.gz +0 -0
- data/spec/parser_spec.rb +100 -0
- data/spec/resources_spec.rb +78 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/stats_spec.rb +22 -0
- metadata +20 -23
data/CHANGELOG
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
== Release 0.0.3 (May 6, 2011)
|
2
|
+
|
3
|
+
* Fixed bug from 0.0.2 in Ruby 1.8 where browser versions never got parsed
|
4
|
+
|
5
|
+
== Release 0.0.2 (May 6, 2011)
|
6
|
+
|
7
|
+
* Fixed detection for some versions of Windows XP and Opera
|
8
|
+
|
9
|
+
* Added detection for Windows 8, Fedora, Slackware, FreeBSD, BlackBerry, Konqueror, Epiphany
|
10
|
+
|
11
|
+
* Added parsing tests for each detectable OS and browser
|
12
|
+
|
13
|
+
* Misc. minor internal refactoring and bug fixes
|
14
|
+
|
15
|
+
== Release 0.0.1 (April 30, 2011) **
|
16
|
+
|
17
|
+
* Initial release
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= Double Agent
|
2
|
+
|
3
|
+
double_agent is a library for parsing browser and operating system info out of user
|
4
|
+
agent strings. It is designed for parsing large sets for review or analysis.
|
5
|
+
|
6
|
+
# Load core, resources and stats
|
7
|
+
require 'double_agent'
|
8
|
+
|
9
|
+
# Load pieces individually
|
10
|
+
require 'double_agent/core'
|
11
|
+
require 'double_agent/resources'
|
12
|
+
require 'double_agent/stats'
|
13
|
+
require 'double_agent/logs'
|
14
|
+
|
15
|
+
# Load everything at once
|
16
|
+
require 'double_agent/all'
|
17
|
+
|
18
|
+
= Core
|
19
|
+
|
20
|
+
The core parser.
|
21
|
+
|
22
|
+
ua_string = "pretent I'm a user agent string for Firefox 4 on Ubuntu"
|
23
|
+
|
24
|
+
DoubleAgent.browser(ua_string)
|
25
|
+
=> "Firefox 4"
|
26
|
+
|
27
|
+
DoubleAgent.browser_family(ua_string)
|
28
|
+
=> "Firefox"
|
29
|
+
|
30
|
+
DoubleAgent.browser_sym(ua_string)
|
31
|
+
=> :firefox
|
32
|
+
|
33
|
+
DoubleAgent.browser_family_sym(ua_string)
|
34
|
+
=> :firefox
|
35
|
+
|
36
|
+
DoubleAgent.os(ua_string)
|
37
|
+
=> "Ubuntu"
|
38
|
+
|
39
|
+
DoubleAgent.os_family(ua_string)
|
40
|
+
=> "GNU/Linux"
|
41
|
+
|
42
|
+
DoubleAgent.os_sym(ua_string)
|
43
|
+
=> :ubuntu
|
44
|
+
|
45
|
+
DoubleAgent.os_family_sym(ua_string)
|
46
|
+
=> :linux
|
47
|
+
|
48
|
+
= Resources
|
49
|
+
|
50
|
+
Say you're saving a record of each user's login so you can determine their favorite browser. Just make sure
|
51
|
+
the user agent is available through user_agent, and presto!
|
52
|
+
|
53
|
+
class Login
|
54
|
+
include DoubleAgent::Resource
|
55
|
+
|
56
|
+
def user_agent
|
57
|
+
#returns this object's user agent string
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
login = Login.find(76)
|
62
|
+
|
63
|
+
login.browser
|
64
|
+
=> "Firefox 4"
|
65
|
+
|
66
|
+
login.os_family
|
67
|
+
=> "OS X"
|
68
|
+
|
69
|
+
= Stats
|
70
|
+
|
71
|
+
Figure out what percent use which browser, browser family, os, etc.
|
72
|
+
|
73
|
+
== Example 1
|
74
|
+
|
75
|
+
logins = Login.all
|
76
|
+
stats = DoubleAgent.percentages_for(logins, :browser)
|
77
|
+
|
78
|
+
p stats
|
79
|
+
=> [["Firefox 4", 20.0], ["Internet Explorer 8", 18.0], ...]
|
80
|
+
|
81
|
+
stats.each do |browser, percent|
|
82
|
+
puts "#{browser} - #{percent}%"
|
83
|
+
end
|
84
|
+
|
85
|
+
== Example 2
|
86
|
+
|
87
|
+
logins = Login.all
|
88
|
+
stats = DoubleAgent.percentages_for(logins, :browser_family, :os_family)
|
89
|
+
|
90
|
+
p stats
|
91
|
+
=> [["Firefox", "Windows", 50.0], ["Internet Explorer", "Windows", 20.0], ["Safari", "OS X", 20.0], ["Firefox", "GNU/Linux", 10.0]]
|
92
|
+
|
93
|
+
stats.each do |browser_family, os_family, percent|
|
94
|
+
puts "#{browser_family} on #{os_family} - #{percent}%"
|
95
|
+
end
|
96
|
+
|
97
|
+
= Logs
|
98
|
+
|
99
|
+
DoubleAgent#log_entries parses through Apache and Nginx access logs, instantiating each log line into a LogEntry
|
100
|
+
object. It even reads gzipped logs (requires zlib)! The LogEntry class mixes in DoubleAgent::Resource, so this is
|
101
|
+
a great way to generate browser and OS breakdowns of who's visiting your site.
|
102
|
+
|
103
|
+
require 'double_agent'
|
104
|
+
require 'double_agent/logs'
|
105
|
+
|
106
|
+
entries = DoubleAgent.log_entries("/var/log/nginx/my-site.access.log*")
|
107
|
+
entries.each do |entry|
|
108
|
+
puts entry.browser
|
109
|
+
end
|
110
|
+
|
111
|
+
Firefox 3
|
112
|
+
Internet Explorer 8
|
113
|
+
Internet Explorer 9
|
114
|
+
Firefox 4
|
115
|
+
Internet Explorer
|
116
|
+
Safari
|
117
|
+
Chrome
|
118
|
+
...
|
data/data/browsers.yml
CHANGED
@@ -3,18 +3,10 @@
|
|
3
3
|
|
4
4
|
- :name: Internet Explorer %s
|
5
5
|
:sym: :msie
|
6
|
-
:family_sym: :msie
|
7
6
|
:regex: 'msie \d+'
|
8
7
|
:version: '(?<=msie )[0-9]+'
|
9
8
|
:safe_version: ["msie [0-9]+", "[0-9]+"]
|
10
9
|
|
11
|
-
- :name: Chromium %s
|
12
|
-
:sym: :chromium
|
13
|
-
:family_sym: :chromium
|
14
|
-
:regex: 'chromium/\d+'
|
15
|
-
:version: '(?<=chromium/)[0-9]+'
|
16
|
-
:safe_version: ["chromium/[0-9]+", "[0-9]+"]
|
17
|
-
|
18
10
|
- :name: Chrome %s
|
19
11
|
:sym: :chrome
|
20
12
|
:family_sym: :chromium
|
@@ -22,6 +14,12 @@
|
|
22
14
|
:version: '(?<=chrome/)[0-9]+'
|
23
15
|
:safe_version: ["chrome/[0-9]+", "[0-9]+"]
|
24
16
|
|
17
|
+
- :name: Chromium %s
|
18
|
+
:sym: :chromium
|
19
|
+
:regex: 'chromium/\d+'
|
20
|
+
:version: '(?<=chromium/)[0-9]+'
|
21
|
+
:safe_version: ["chromium/[0-9]+", "[0-9]+"]
|
22
|
+
|
25
23
|
- :name: Android %s
|
26
24
|
:sym: :android
|
27
25
|
:family_sym: :chromium
|
@@ -29,29 +27,39 @@
|
|
29
27
|
:version: '(?<=android )[0-9]+(\.[0-9]+)?'
|
30
28
|
:safe_version: ["android [0-9]+(\.[0-9]+)?", "[0-9]+(\.[0-9]+)?"]
|
31
29
|
|
30
|
+
- :name: BlackBerry
|
31
|
+
:sym: :blackberry
|
32
|
+
:regex: blackberry
|
33
|
+
|
34
|
+
- :name: Epiphany
|
35
|
+
:sym: :epiphany
|
36
|
+
:regex: epiphany
|
37
|
+
:version: '(?<=epiphany/)[0-9]+'
|
38
|
+
:safe_version: ["epiphany/[0-9]+", "[0-9]+"]
|
39
|
+
|
32
40
|
- :name: Safari %s
|
33
41
|
:sym: :safari
|
34
|
-
:family_sym: :safari
|
35
42
|
:regex: safari
|
36
43
|
:version: '(?<=version/)[0-9]+'
|
37
44
|
:safe_version: ["version/[0-9]+", "[0-9]+"]
|
38
45
|
|
39
46
|
- :name: Opera %s
|
40
47
|
:sym: :opera
|
41
|
-
:family_sym: :opera
|
42
48
|
:regex: opera
|
43
|
-
:version: '(?<=version/)[0-9]+'
|
44
|
-
:safe_version: ["version/[0-9]+", "[0-9]+"]
|
49
|
+
:version: '((?<=version/)[0-9]+)|((?<=opera )[0-9]+)'
|
50
|
+
:safe_version: ["(version/[0-9]+)|(opera [0-9]+)", "[0-9]+"]
|
51
|
+
|
52
|
+
- :name: Konqueror
|
53
|
+
:sym: :konqueror
|
54
|
+
:regex: konqueror
|
45
55
|
|
46
56
|
- :name: Firefox %s
|
47
57
|
:sym: :firefox
|
48
|
-
:family_sym: :firefox
|
49
58
|
:regex: firefox
|
50
59
|
:version: '(?<=firefox/)[0-9]+'
|
51
60
|
:safe_version: ["firefox/[0-9]+", "[0-9]+"]
|
52
61
|
|
53
62
|
- :name: Unknown
|
54
63
|
:sym: :unknown
|
55
|
-
:family_sym: :unkown
|
56
64
|
:regex: .+
|
57
65
|
:version: .+
|
data/data/oses.yml
CHANGED
@@ -8,11 +8,24 @@
|
|
8
8
|
:family_sym: :linux
|
9
9
|
:regex: ubuntu
|
10
10
|
|
11
|
+
- :name: Fedora
|
12
|
+
:sym: :fedora
|
13
|
+
:family_sym: :linux
|
14
|
+
:regex: fedora
|
15
|
+
|
16
|
+
- :name: Slackware
|
17
|
+
:sym: :slackware
|
18
|
+
:family_sym: :linux
|
19
|
+
:regex: slackware
|
20
|
+
|
11
21
|
- :name: GNU/Linux
|
12
22
|
:sym: :linux
|
13
|
-
:family_sym: :linux
|
14
23
|
:regex: linux
|
15
24
|
|
25
|
+
- :name: FreeBSD
|
26
|
+
:sym: :freebsd
|
27
|
+
:regex: freebsd
|
28
|
+
|
16
29
|
- :name: iOS
|
17
30
|
:sym: :ios
|
18
31
|
:family_sym: :osx
|
@@ -20,13 +33,18 @@
|
|
20
33
|
|
21
34
|
- :name: OS X
|
22
35
|
:sym: :osx
|
23
|
-
:family_sym: :osx
|
24
36
|
:regex: macintosh
|
25
37
|
|
26
|
-
- :name: Windows
|
27
|
-
:sym: :
|
38
|
+
- :name: Windows 8
|
39
|
+
:sym: :windows_8
|
40
|
+
:family_sym: :windows
|
41
|
+
:regex: windows nt 6\.2
|
42
|
+
:icon: windows
|
43
|
+
|
44
|
+
- :name: Windows 7
|
45
|
+
:sym: :windows_7
|
28
46
|
:family_sym: :windows
|
29
|
-
:regex: windows nt
|
47
|
+
:regex: windows nt 6\.1
|
30
48
|
:icon: windows
|
31
49
|
|
32
50
|
- :name: Windows Vista
|
@@ -35,18 +53,20 @@
|
|
35
53
|
:regex: windows nt 6\.0
|
36
54
|
:icon: windows
|
37
55
|
|
38
|
-
- :name: Windows
|
39
|
-
:sym: :
|
56
|
+
- :name: Windows XP
|
57
|
+
:sym: :windows_xp
|
40
58
|
:family_sym: :windows
|
41
|
-
:regex: windows nt
|
59
|
+
:regex: windows nt 5\.[12]
|
42
60
|
:icon: windows
|
43
61
|
|
44
62
|
- :name: Windows
|
45
63
|
:sym: :windows
|
46
|
-
:family_sym: :windows
|
47
64
|
:regex: windows
|
48
65
|
|
49
|
-
- :
|
66
|
+
- :name: BlackBerry
|
67
|
+
:sym: :blackberry
|
68
|
+
:regex: blackberry
|
69
|
+
|
70
|
+
- :name: Unknown
|
50
71
|
:sym: :unknown
|
51
|
-
:name: Unknown
|
52
72
|
:regex: .+
|
data/lib/double_agent/core.rb
CHANGED
@@ -1,50 +1,60 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module DoubleAgent
|
4
|
-
#
|
5
|
-
# Map browser/os ids to names, families and icons
|
6
|
-
#
|
4
|
+
# An array of "browser knowledge hashes," the basis of browser parsing. You may edit this data and call load_browsers! to customize parsing.
|
7
5
|
BROWSER_DATA = YAML.load_file(File.expand_path('../../../data/browsers.yml', __FILE__))
|
6
|
+
|
7
|
+
# An array of "OS knowledge hashes," the basis of OS parsing. You may edit this data and call load_oses! to customize parsing.
|
8
8
|
OS_DATA = YAML.load_file(File.expand_path('../../../data/oses.yml', __FILE__))
|
9
9
|
|
10
|
+
# An array of BrowserParser objects created from the data in BROWSER_DATA.
|
10
11
|
BROWSERS = {}
|
12
|
+
|
13
|
+
# An array of OSParser objects created from the data in OS_DATA.
|
11
14
|
OSES = {}
|
12
15
|
|
16
|
+
# Each browser in BROWSER_DATA gets its own BrowserParser object. These
|
17
|
+
# parser objects are then used to parse specific data out of a user agent string.
|
18
|
+
|
13
19
|
class BrowserParser
|
14
20
|
BLANK = ''
|
15
21
|
MIN_VERSION = '1.9.2'
|
16
22
|
attr_reader :sym, :family_sym, :icon
|
17
23
|
|
24
|
+
# Instantiate a new BrowserParser using a "browser family" element from BROWSER_DATA
|
18
25
|
def initialize(attrs={})
|
19
|
-
@family_sym = attrs[:family_sym]
|
20
|
-
@name = attrs[:name]
|
21
26
|
@sym = attrs[:sym]
|
27
|
+
@family_sym = attrs[:family_sym] || @sym
|
28
|
+
@name = attrs[:name]
|
22
29
|
@icon = attrs[:icon] || @sym
|
23
30
|
if RUBY_VERSION < MIN_VERSION and attrs[:safe_version]
|
24
31
|
@safe_version = attrs[:safe_version].map { |r| Regexp.new r, Regexp::IGNORECASE }
|
25
|
-
|
32
|
+
elsif attrs[:version]
|
26
33
|
@version = Regexp.new(attrs[:version], Regexp::IGNORECASE)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
37
|
+
# Returns the browser's name. If you provide an user agent string as an argument,
|
38
|
+
# it will attempt to also return the major version number. E.g. "Firefox 4".
|
30
39
|
def browser(ua=nil)
|
31
|
-
if ua
|
40
|
+
if ua and (@version or @safe_version)
|
32
41
|
@name % version(ua)
|
33
42
|
else
|
34
43
|
(@name % BLANK).rstrip
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
47
|
+
# Returns the BrowserParser for this BrowserParser object's Family. E.g. the Chrome
|
48
|
+
# BrowserParser would return the Chromium BrowserParser. For browsers that are their
|
49
|
+
# own family (e.g. Firefox, IE) it will end up returning itself.
|
42
50
|
def family
|
43
51
|
BROWSERS[family_sym]
|
44
52
|
end
|
45
53
|
|
46
54
|
private
|
47
55
|
|
56
|
+
# Attempts to parse and return the browser's version from a user agent string. Returns
|
57
|
+
# nil if nothing is found.
|
48
58
|
def version(ua)
|
49
59
|
if @safe_version and RUBY_VERSION < MIN_VERSION
|
50
60
|
ua.slice(@safe_version[0]).slice(@safe_version[1])
|
@@ -54,81 +64,101 @@ module DoubleAgent
|
|
54
64
|
end
|
55
65
|
end
|
56
66
|
|
67
|
+
# Each OS in OS_DATA gets its own OSParser object. In theory, these parser
|
68
|
+
# objects can then be used to grab further info from user agent strings, though
|
69
|
+
# that is not currently happening.
|
70
|
+
|
57
71
|
class OSParser
|
58
72
|
attr_reader :os, :sym, :family_sym, :icon
|
59
73
|
|
74
|
+
# Instantiate a new OSParser using an "OS family" element from OS_DATA
|
60
75
|
def initialize(attrs={})
|
61
|
-
@family_sym = attrs[:family_sym]
|
62
|
-
@os = attrs[:name]
|
63
76
|
@sym = attrs[:sym]
|
77
|
+
@family_sym = attrs[:family_sym] || @sym
|
78
|
+
@os = attrs[:name]
|
64
79
|
@icon = attrs[:icon] || @sym
|
65
80
|
end
|
66
81
|
|
82
|
+
# Returns the OSParser for this OSParser object's Family. E.g. the Ubuntu
|
83
|
+
# OSParser would return the GNU/Linux OSerParser. For OSes that are their own
|
84
|
+
# family (e.g. OS X) it will end up returning itself.
|
67
85
|
def family
|
68
86
|
OSES[family_sym]
|
69
87
|
end
|
70
88
|
end
|
71
89
|
|
72
|
-
#
|
73
|
-
# Methods for getting browser/os names, families, and icons either by passing a user agent string.
|
74
|
-
#
|
75
|
-
|
90
|
+
# Returns the browser's name, possibly including the version number, e.g. "Chrome 12"
|
76
91
|
def self.browser(ua)
|
77
92
|
browser_parser(ua).browser(ua)
|
78
93
|
end
|
79
94
|
|
95
|
+
# Returns the browser's symbol name, e.g. :chrome
|
80
96
|
def self.browser_sym(user_agent)
|
97
|
+
# This method is overwitten by load_browsers!
|
81
98
|
end
|
82
99
|
|
100
|
+
# Returns the browser's icon name, e.g. :chrome
|
83
101
|
def self.browser_icon(ua)
|
84
102
|
browser_parser(ua).icon
|
85
103
|
end
|
86
104
|
|
105
|
+
# Returns the browser's family name, e.g. "Chromium"
|
87
106
|
def self.browser_family(ua)
|
88
107
|
browser_parser(ua).family.browser
|
89
108
|
end
|
90
109
|
|
110
|
+
# Returns the browser's family's symbol name, e.g. :chromium
|
91
111
|
def self.browser_family_sym(ua)
|
92
112
|
browser_parser(ua).family_sym
|
93
113
|
end
|
94
114
|
|
115
|
+
# Returns the browser's family's icon name, e.g. :chromium
|
95
116
|
def self.browser_family_icon(ua)
|
96
117
|
browser_parser(ua).family.icon
|
97
118
|
end
|
98
119
|
|
120
|
+
# Returns the OS's name, e.g. "Ubuntu"
|
99
121
|
def self.os(ua)
|
100
122
|
os_parser(ua).os
|
101
123
|
end
|
102
124
|
|
125
|
+
# Returns the OS's symbol name, e.g. :ubuntu
|
103
126
|
def self.os_sym(user_agent)
|
127
|
+
# This method is overwitten by load_oses!
|
104
128
|
end
|
105
129
|
|
130
|
+
# Returns the OS's icon name, e.g. :ubuntu
|
106
131
|
def self.os_icon(ua)
|
107
132
|
os_parser(ua).icon
|
108
133
|
end
|
109
134
|
|
135
|
+
# Returns the OS's family, e.g. "GNU/Linux"
|
110
136
|
def self.os_family(ua)
|
111
137
|
os_parser(ua).family.os
|
112
138
|
end
|
113
139
|
|
140
|
+
# Returns the OS's family's symbol name, e.g. :linux
|
114
141
|
def self.os_family_sym(ua)
|
115
142
|
os_parser(ua).family_sym
|
116
143
|
end
|
117
144
|
|
145
|
+
# Returns the OS's family's symbol icon, e.g. :linux
|
118
146
|
def self.os_family_icon(ua)
|
119
147
|
os_parser(ua).family.icon
|
120
148
|
end
|
121
149
|
|
122
|
-
#
|
150
|
+
# Returns the correct BrowerParser for the given user agent or symbol
|
123
151
|
def self.browser_parser(ua_or_sym)
|
124
152
|
BROWSERS[ua_or_sym.is_a?(Symbol) ? ua_or_sym : browser_sym(ua_or_sym)]
|
125
153
|
end
|
126
154
|
|
127
|
-
#
|
155
|
+
# Returns the correct OSParser for the given user agent or symbol
|
128
156
|
def self.os_parser(ua_or_sym)
|
129
157
|
OSES[ua_or_sym.is_a?(Symbol) ? ua_or_sym : os_sym(ua_or_sym)]
|
130
158
|
end
|
131
159
|
|
160
|
+
# Parses BROWSER_DATA into BROWSERS, a hash of BrowserParser objects indexed by their symbol names.
|
161
|
+
# Parses and evals BROWSER_DATA into a case statement inside of the browser_sym method.
|
132
162
|
def self.load_browsers!
|
133
163
|
BROWSERS.clear
|
134
164
|
str = "case user_agent\n"
|
@@ -140,6 +170,8 @@ module DoubleAgent
|
|
140
170
|
module_eval "def self.browser_sym(user_agent); #{str}; end"
|
141
171
|
end
|
142
172
|
|
173
|
+
# Parses OS_DATA into OSES, a hash of OSParser objects indexed by their symbol names.
|
174
|
+
# Parses and evals OS_DATA into a case statement inside of the os_sym method.
|
143
175
|
def self.load_oses!
|
144
176
|
OSES.clear
|
145
177
|
str = "case user_agent\n"
|
data/lib/double_agent/logs.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'zlib'
|
2
2
|
|
3
3
|
module DoubleAgent
|
4
|
+
# Accepts a glob path like /var/logs/apache/my-site.access.log*,
|
5
|
+
# parses all matching files into an array of LegEntry objects, and returns them.
|
6
|
+
#
|
7
|
+
# If a Regexp is passed as the second argument, lines which do not match
|
8
|
+
# it will be ignored.
|
4
9
|
def self.log_entries(glob_str, regex=nil)
|
5
10
|
entries, gz_regexp = [], /\.gz\Z/i
|
6
11
|
Dir.glob(glob_str).each do |f|
|
@@ -14,12 +19,20 @@ module DoubleAgent
|
|
14
19
|
entries
|
15
20
|
end
|
16
21
|
|
22
|
+
# This class represents a line in an Apache or Nginx access log.
|
23
|
+
# The user agent string is parsed out and available through the
|
24
|
+
# user_agent attribute, making it available to the mixed-in DoubleAgent::Resource.
|
25
|
+
|
17
26
|
class LogEntry
|
27
|
+
# Regular expression for pulling a user agent string out of a log entry
|
18
28
|
USER_AGENT_REGEXP = /[^"]+(?="$)/
|
19
29
|
include DoubleAgent::Resource
|
20
30
|
|
31
|
+
# Returns the user agent string
|
21
32
|
attr_reader :user_agent
|
22
33
|
|
34
|
+
# Initializes a new LogEntry object. An Apache or Nginx log line should be
|
35
|
+
# passed to it.
|
23
36
|
def initialize(line)
|
24
37
|
#@line = line
|
25
38
|
@user_agent = line.slice(USER_AGENT_REGEXP)
|
@@ -1,63 +1,77 @@
|
|
1
1
|
module DoubleAgent
|
2
2
|
#
|
3
|
-
# Any class with a "user_agent" method
|
3
|
+
# Any class with a "user_agent" method returning a User Agent string
|
4
4
|
# may include this module to easily parse out Browser and OS info.
|
5
5
|
#
|
6
6
|
module Resource
|
7
|
+
# Return's this object's browser name
|
7
8
|
def browser
|
8
9
|
_browser_parser.browser(user_agent)
|
9
10
|
end
|
10
11
|
|
12
|
+
# Return's this object's browser symbol name
|
11
13
|
def browser_sym
|
12
14
|
_browser_parser.sym
|
13
15
|
end
|
14
16
|
|
17
|
+
# Return's this object's browser icon name
|
15
18
|
def browser_icon
|
16
19
|
_browser_parser.icon
|
17
20
|
end
|
18
21
|
|
22
|
+
# Return's this object's browser family name
|
19
23
|
def browser_family
|
20
24
|
_browser_parser.family.browser
|
21
25
|
end
|
22
26
|
|
27
|
+
# Return's this object's browser family symbol name
|
23
28
|
def browser_family_sym
|
24
29
|
_browser_parser.family_sym
|
25
30
|
end
|
26
31
|
|
32
|
+
# Return's this object's browser family icon name
|
27
33
|
def browser_family_icon
|
28
34
|
_browser_parser.family.icon
|
29
35
|
end
|
30
36
|
|
37
|
+
# Return's this object's OS name
|
31
38
|
def os
|
32
39
|
_os_parser.os
|
33
40
|
end
|
34
41
|
|
42
|
+
# Return's this object's OS symbol name
|
35
43
|
def os_sym
|
36
44
|
_os_parser.sym
|
37
45
|
end
|
38
46
|
|
47
|
+
# Return's this object's OS icon name
|
39
48
|
def os_icon
|
40
49
|
_os_parser.icon
|
41
50
|
end
|
42
51
|
|
52
|
+
# Return's this object's OS family name
|
43
53
|
def os_family
|
44
54
|
_os_parser.family.os
|
45
55
|
end
|
46
56
|
|
57
|
+
# Return's this object's OS family symbol name
|
47
58
|
def os_family_sym
|
48
59
|
_os_parser.family_sym
|
49
60
|
end
|
50
61
|
|
62
|
+
# Return's this object's OS family icon name
|
51
63
|
def os_family_icon
|
52
64
|
_os_parser.family.icon
|
53
65
|
end
|
54
66
|
|
55
67
|
private
|
56
68
|
|
69
|
+
# Returns and caches a BrowserParser for this object
|
57
70
|
def _browser_parser
|
58
71
|
@browser_parser ||= DoubleAgent.browser_parser(user_agent)
|
59
72
|
end
|
60
73
|
|
74
|
+
# Returns and caches an OSParser for this object
|
61
75
|
def _os_parser
|
62
76
|
@os_parser ||= DoubleAgent.os_parser(user_agent)
|
63
77
|
end
|
data/lib/double_agent/stats.rb
CHANGED
@@ -1,33 +1,29 @@
|
|
1
|
-
# Time for some monkey patching!
|
2
|
-
|
3
1
|
module DoubleAgent
|
2
|
+
# True if running under less than Ruby 1.9
|
4
3
|
BAD_RUBY = RUBY_VERSION < '1.9.0'
|
5
4
|
|
6
5
|
if BAD_RUBY
|
7
6
|
require 'bigdecimal'
|
7
|
+
# If BAD_RUBY, this is used in lieu of the native round method
|
8
8
|
def self.better_round(f, n)
|
9
9
|
d = BigDecimal.new f.to_s
|
10
10
|
d.round(n).to_f
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
15
14
|
# For the given "things", returns the share of the group that each attr has.
|
16
15
|
#
|
17
16
|
# "things" is an array of objects who's classes "include DoubleAgent::Resource".
|
18
17
|
#
|
19
|
-
# "attrs" is one or more method symbols from DoubleAgent::Resource.
|
20
|
-
# are probably the most useful here, as you can use the symbol results to query DoubleAgent
|
21
|
-
# methods and get the browser/os name, family and icons.
|
18
|
+
# "attrs" is one or more method symbols from DoubleAgent::Resource.
|
22
19
|
#
|
23
20
|
# Example, Browser Family share:
|
24
21
|
# DoubleAgent.percentages_for(logins, :browser_family)
|
25
|
-
#
|
22
|
+
# [['Firefox', 50.4], ['Chrome', 19.6], ['Internet Explorer', 15], ['Safari', 10], ['Unknown', 5]]
|
26
23
|
#
|
27
24
|
# Example, Browser/OS share, asking for symbols back:
|
28
25
|
# DoubleAgent.percentages_for(server_log_entries, :browser_sym, :os_sym)
|
29
|
-
#
|
30
|
-
#
|
26
|
+
# [[:firefox, :windows_7, 50.4], [:chrome, :osx, 19.6], [:msie, :windows_xp, 15], [:safari, :osx, 10], [:other, :other, 5]]
|
31
27
|
def self.percentages_for(things, *attrs)
|
32
28
|
p = {}
|
33
29
|
things.each do |h|
|
data/spec/core_spec.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe DoubleAgent do
|
4
|
+
context 'Core' do
|
5
|
+
before do
|
6
|
+
@ua_string = 'Mozilla/5.0 (X11; Ubuntu Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0'
|
7
|
+
end
|
8
|
+
|
9
|
+
#browser
|
10
|
+
it 'returns Firefox 4 for browser' do
|
11
|
+
DoubleAgent.browser(@ua_string).should == 'Firefox 4'
|
12
|
+
end
|
13
|
+
it 'returns Unknown for browser' do
|
14
|
+
DoubleAgent.browser('froofroo').should == 'Unknown'
|
15
|
+
end
|
16
|
+
|
17
|
+
#browser_sym
|
18
|
+
it 'returns :firefox for browser_sym' do
|
19
|
+
DoubleAgent.browser_sym(@ua_string).should == :firefox
|
20
|
+
end
|
21
|
+
it 'returns :unknown for browser_sym' do
|
22
|
+
DoubleAgent.browser_sym('froofroo').should == :unknown
|
23
|
+
end
|
24
|
+
|
25
|
+
#browser_family
|
26
|
+
it 'returns Firefox for browser family' do
|
27
|
+
DoubleAgent.browser_family(@ua_string).should == 'Firefox'
|
28
|
+
end
|
29
|
+
|
30
|
+
#browser_family_sym
|
31
|
+
it 'returns :firefox for browser_family_sym' do
|
32
|
+
DoubleAgent.browser_family_sym(@ua_string).should == :firefox
|
33
|
+
end
|
34
|
+
|
35
|
+
#browser_icon
|
36
|
+
it 'returns :firefox for browser_sym' do
|
37
|
+
DoubleAgent.browser_icon(@ua_string).should == :firefox
|
38
|
+
end
|
39
|
+
|
40
|
+
#browser_family_icon
|
41
|
+
it 'returns :firefox for browser_family_sym' do
|
42
|
+
DoubleAgent.browser_family_icon(@ua_string).should == :firefox
|
43
|
+
end
|
44
|
+
|
45
|
+
#os
|
46
|
+
it 'returns Ubuntua for OS' do
|
47
|
+
DoubleAgent.os(@ua_string).should == 'Ubuntu'
|
48
|
+
end
|
49
|
+
it 'returns Unknowna for OS' do
|
50
|
+
DoubleAgent.os('froofroo').should == 'Unknown'
|
51
|
+
end
|
52
|
+
|
53
|
+
#os_sym
|
54
|
+
it 'returns :ubuntu for os_sym' do
|
55
|
+
DoubleAgent.os_sym(@ua_string).should == :ubuntu
|
56
|
+
end
|
57
|
+
it 'returns :unknown for os_sym' do
|
58
|
+
DoubleAgent.os_sym('froofroo').should == :unknown
|
59
|
+
end
|
60
|
+
|
61
|
+
#os_family
|
62
|
+
it 'returns GNU/Linux OS family' do
|
63
|
+
DoubleAgent.os_family(@ua_string).should == 'GNU/Linux'
|
64
|
+
end
|
65
|
+
|
66
|
+
#os_family_sym
|
67
|
+
it 'returns :linux for os_family_sym' do
|
68
|
+
DoubleAgent.os_family_sym(@ua_string).should == :linux
|
69
|
+
end
|
70
|
+
|
71
|
+
#os_icon
|
72
|
+
it 'returns :ubuntu for os_sym' do
|
73
|
+
DoubleAgent.os_icon(@ua_string).should == :ubuntu
|
74
|
+
end
|
75
|
+
|
76
|
+
#os_family_icon
|
77
|
+
it 'returns :linux for os_family_sym' do
|
78
|
+
DoubleAgent.os_family_icon(@ua_string).should == :linux
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
68.52.99.211 - - [05/May/2011:08:21:04 -0400] "GET / HTTP/1.1" 200 1312 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17"
|
2
|
+
68.52.99.211 - - [05/May/2011:08:25:47 -0400] "POST /entries HTTP/1.1" 200 7889 "http://www.example.com/entries/new" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17"
|
3
|
+
216.84.130.30 - - [04/May/2011:09:45:42 -0400] "GET /dashboard HTTP/1.1" 302 104 "-" "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_6 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8E200 Safari/6533.18.5"
|
4
|
+
173.190.91.2 - - [04/May/2011:10:51:09 -0400] "GET /images/icons/opera_browser.png?1295928127 HTTP/1.1" 304 0 "http://cur8.cur8.net/sbin/sites/2/page_hit_stats" "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
|
5
|
+
173.190.91.2 - - [04/May/2011:10:51:09 -0400] "GET /images/icons/ubuntu_os.png?1295928127 HTTP/1.1" 304 0 "http://cur8.cur8.net/sbin/sites/2/page_hit_stats" "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
|
6
|
+
173.190.91.2 - - [04/May/2011:10:52:29 -0400] "GET /sbin/sites/2/login_stats HTTP/1.1" 200 10131 "http://cur8.cur8.net/sbin/sites/2/page_hit_stats" "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
|
7
|
+
173.190.91.2 - - [04/May/2011:10:52:34 -0400] "GET /sbin/sites/2/page_hit_stats HTTP/1.1" 200 12460 "http://cur8.cur8.net/sbin/sites/2/login_stats" "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
|
8
|
+
bad log line!
|
9
|
+
173.190.91.2 - - [04/May/2011:10:53:10 -0400] "GET /sbin/sites/2 HTTP/1.1" 304 0 "http://cur8.cur8.net/sbin/sites/2/page_hit_stats" "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
|
10
|
+
216.84.130.30 - - [04/May/2011:11:11:39 -0400] "GET /login HTTP/1.1" 200 1104 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
|
11
|
+
216.84.130.30 - - [04/May/2011:11:11:39 -0400] "GET /stylesheets/styles.css?1303352447 HTTP/1.1" 304 0 "http://www.example.com/login" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
|
12
|
+
216.84.130.30 - - [04/May/2011:11:11:39 -0400] "GET /javascripts/lib.js?1303099416 HTTP/1.1" 304 0 "http://www.example.com/login" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
|
13
|
+
216.84.130.30 - - [04/May/2011:11:11:39 -0400] "GET /stylesheets/print.css?1290464093 HTTP/1.1" 304 0 "http://www.example.com/login" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
|
14
|
+
bad log line!
|
15
|
+
216.84.130.30 - - [04/May/2011:11:11:39 -0400] "GET /javascripts/application.js?1303352447 HTTP/1.1" 304 0 "http://www.example.com/login" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
|
16
|
+
75.201.10.91 - - [04/May/2011:18:37:10 -0400] "GET /entries/new HTTP/1.1" 302 104 "-" "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; ADR6400L 4G Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
|
17
|
+
75.201.10.91 - - [04/May/2011:18:37:10 -0400] "GET /login HTTP/1.1" 200 1107 "-" "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; ADR6400L 4G Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
|
18
|
+
75.201.10.91 - - [04/May/2011:18:43:33 -0400] "GET /dashboard HTTP/1.1" 200 8098 "http://www.example.com/login" "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; ADR6400L 4G Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
|
19
|
+
75.201.10.91 - - [04/May/2011:18:43:55 -0400] "GET /search HTTP/1.1" 200 4070 "http://www.example.com/dashboard" "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; ADR6400L 4G Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
|
20
|
+
bad log line!
|
Binary file
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
DA = DoubleAgent
|
4
|
+
|
5
|
+
describe DoubleAgent do
|
6
|
+
# Internet Explorer
|
7
|
+
it 'should be Internet Explorer 10 on Windows 8' do
|
8
|
+
ua = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/5.0"
|
9
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Internet Explorer 10 on Windows 8'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be Internet Explorer 9 on Windows 7' do
|
13
|
+
ua = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0"
|
14
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Internet Explorer 9 on Windows 7'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should be Internet Explorer 8 on Windows Vista' do
|
18
|
+
ua = "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Win64; x64; Trident/5.0"
|
19
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Internet Explorer 8 on Windows Vista'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be Internet Explorer 7 on Windows XP' do
|
23
|
+
ua = "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.2; Win64; x64; Trident/5.0"
|
24
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Internet Explorer 7 on Windows XP'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should be Internet Explorer 7 on Windows XP' do
|
28
|
+
ua = "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.1; Win64; x64; Trident/5.0"
|
29
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Internet Explorer 7 on Windows XP'
|
30
|
+
end
|
31
|
+
|
32
|
+
# Chrome
|
33
|
+
it 'should be Chrome 12 on Windows XP' do
|
34
|
+
ua = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.706.0 Safari/534.25"
|
35
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Chrome 12 on Windows XP'
|
36
|
+
end
|
37
|
+
|
38
|
+
# Chromium
|
39
|
+
it 'should be Chrome 12 on Ubuntu' do
|
40
|
+
ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24"
|
41
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Chrome 12 on Ubuntu'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Android
|
45
|
+
it 'should be Android 2.3 on Android' do
|
46
|
+
ua = "Mozilla/5.0 (Linux; U; Android 2.3.3; zh-tw; HTC_Pyramid Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
|
47
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Android 2.3 on Android'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Safari
|
51
|
+
it 'should be Safari 5 on OS X' do
|
52
|
+
ua = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; zh-cn) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27"
|
53
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Safari 5 on OS X'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Opera
|
57
|
+
it 'should be Opera 11 on GNU/Linux' do
|
58
|
+
ua = "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00"
|
59
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Opera 11 on GNU/Linux'
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should be Opera 11 on Windows 7' do
|
63
|
+
ua = "Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01"
|
64
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Opera 11 on Windows 7'
|
65
|
+
end
|
66
|
+
|
67
|
+
# Firefox
|
68
|
+
it 'should be Firefox 4 on GNU/Linux' do
|
69
|
+
ua = "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:2.0) Gecko/20110307 Firefox/4.0"
|
70
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Firefox 4 on GNU/Linux'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Epiphany
|
74
|
+
it 'should be Epiphany on GNU/Linux' do
|
75
|
+
ua = "Mozilla/5.0 (X11; U; Linux x86_64; fr-FR) AppleWebKit/534.7 (KHTML, like Gecko) Epiphany/2.30.6 Safari/534.7"
|
76
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Epiphany on GNU/Linux'
|
77
|
+
end
|
78
|
+
|
79
|
+
# Konqueror
|
80
|
+
it 'should be Konqueror on FreeBSD' do
|
81
|
+
ua = "Mozilla/5.0 (compatible; Konqueror/4.5; FreeBSD) KHTML/4.5.4 (like Gecko)"
|
82
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Konqueror on FreeBSD'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should be Konqueror on Fedora' do
|
86
|
+
ua = "Mozilla/5.0 (compatible; Konqueror/4.4; Linux) KHTML/4.4.1 (like Gecko) Fedora/4.4.1-1.fc12"
|
87
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Konqueror on Fedora'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should be Konqueror on Slackware' do
|
91
|
+
ua = "Mozilla/5.0 (compatible; Konqueror/4.2; Linux) KHTML/4.2.4 (like Gecko) Slackware/13.0"
|
92
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'Konqueror on Slackware'
|
93
|
+
end
|
94
|
+
|
95
|
+
# BlackBerry
|
96
|
+
it 'should be BlackBerry on BlackBerry' do
|
97
|
+
ua = "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; zh-TW) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.448 Mobile Safari/534.8+"
|
98
|
+
"#{DA.browser ua} on #{DA.os ua}".should == 'BlackBerry on BlackBerry'
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Login
|
4
|
+
include DoubleAgent::Resource
|
5
|
+
attr_accessor :user_agent
|
6
|
+
|
7
|
+
def initialize(ua)
|
8
|
+
@user_agent = ua
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe DoubleAgent do
|
13
|
+
context 'Resource' do
|
14
|
+
before do
|
15
|
+
@login = Login.new 'Mozilla/5.0 (X11; Ubuntu Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0'
|
16
|
+
end
|
17
|
+
|
18
|
+
#browser
|
19
|
+
it 'returns Firefox 4 for browser' do
|
20
|
+
@login.browser.should == 'Firefox 4'
|
21
|
+
end
|
22
|
+
|
23
|
+
#browser_sym
|
24
|
+
it 'returns :firefox for browser_sym' do
|
25
|
+
@login.browser_sym.should == :firefox
|
26
|
+
end
|
27
|
+
|
28
|
+
#browser_family
|
29
|
+
it 'returns Firefox for browser family' do
|
30
|
+
@login.browser_family.should == 'Firefox'
|
31
|
+
end
|
32
|
+
|
33
|
+
#browser_family_sym
|
34
|
+
it 'returns :firefox for browser_family_sym' do
|
35
|
+
@login.browser_family_sym.should == :firefox
|
36
|
+
end
|
37
|
+
|
38
|
+
#browser_icon
|
39
|
+
it 'returns :firefox for browser_sym' do
|
40
|
+
@login.browser_icon.should == :firefox
|
41
|
+
end
|
42
|
+
|
43
|
+
#browser_family_icon
|
44
|
+
it 'returns :firefox for browser_family_sym' do
|
45
|
+
@login.browser_family_icon.should == :firefox
|
46
|
+
end
|
47
|
+
|
48
|
+
#os
|
49
|
+
it 'returns Ubuntua for OS' do
|
50
|
+
@login.os.should == 'Ubuntu'
|
51
|
+
end
|
52
|
+
|
53
|
+
#os_sym
|
54
|
+
it 'returns :ubuntu for os_sym' do
|
55
|
+
@login.os_sym.should == :ubuntu
|
56
|
+
end
|
57
|
+
|
58
|
+
#os_family
|
59
|
+
it 'returns GNU/Linux OS family' do
|
60
|
+
@login.os_family.should == 'GNU/Linux'
|
61
|
+
end
|
62
|
+
|
63
|
+
#os_family_sym
|
64
|
+
it 'returns :linux for os_family_sym' do
|
65
|
+
@login.os_family_sym.should == :linux
|
66
|
+
end
|
67
|
+
|
68
|
+
#os_icon
|
69
|
+
it 'returns :ubuntu for os_sym' do
|
70
|
+
@login.os_icon.should == :ubuntu
|
71
|
+
end
|
72
|
+
|
73
|
+
#os_family_icon
|
74
|
+
it 'returns :linux for os_family_sym' do
|
75
|
+
@login.os_family_icon.should == :linux
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/double_agent/core'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/double_agent/resources'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/double_agent/stats'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/double_agent/logs'
|
6
|
+
|
7
|
+
Rspec.configure do |c|
|
8
|
+
c.mock_with :rspec
|
9
|
+
end
|
data/spec/stats_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
log_glob = File.dirname(__FILE__) + '/data/*.access.log*'
|
4
|
+
entries = DoubleAgent::log_entries(log_glob, /^\d/)
|
5
|
+
|
6
|
+
describe DoubleAgent do
|
7
|
+
context 'Logs' do
|
8
|
+
it 'should have loaded n log entries' do
|
9
|
+
entries.size.should == 47
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'Stats' do
|
14
|
+
stats = DoubleAgent.percentages_for entries, :browser_family, :os_family
|
15
|
+
answer = [["Internet Explorer", "Windows", 42.55],
|
16
|
+
["Chromium", "GNU/Linux", 40.43],
|
17
|
+
["Firefox", "GNU/Linux", 10.64],
|
18
|
+
["Firefox", "OS X", 4.26],
|
19
|
+
["Safari", "OS X", 2.13]]
|
20
|
+
stats.should == answer
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: double_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.3
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Jordan Hollinger
|
@@ -15,8 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-04-30 00:00:00
|
19
|
-
default_executable:
|
13
|
+
date: 2011-04-30 00:00:00 Z
|
20
14
|
dependencies: []
|
21
15
|
|
22
16
|
description: Browser User Agent string parser with resource, stats, and a log reader
|
@@ -25,19 +19,28 @@ executables: []
|
|
25
19
|
|
26
20
|
extensions: []
|
27
21
|
|
28
|
-
extra_rdoc_files:
|
29
|
-
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- CHANGELOG
|
30
25
|
files:
|
31
|
-
- lib/double_agent.rb
|
32
26
|
- lib/double_agent/all.rb
|
33
|
-
- lib/double_agent/stats.rb
|
34
|
-
- lib/double_agent/resources.rb
|
35
27
|
- lib/double_agent/core.rb
|
28
|
+
- lib/double_agent/resources.rb
|
36
29
|
- lib/double_agent/logs.rb
|
37
|
-
-
|
30
|
+
- lib/double_agent/stats.rb
|
31
|
+
- lib/double_agent.rb
|
38
32
|
- data/oses.yml
|
33
|
+
- data/browsers.yml
|
34
|
+
- spec/resources_spec.rb
|
35
|
+
- spec/core_spec.rb
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
- spec/parser_spec.rb
|
38
|
+
- spec/data/httpd.access.log
|
39
|
+
- spec/data/httpd.access.log.1.gz
|
40
|
+
- spec/stats_spec.rb
|
41
|
+
- README.rdoc
|
39
42
|
- LICENSE
|
40
|
-
|
43
|
+
- CHANGELOG
|
41
44
|
homepage: http://github.com/jhollinger/double_agent
|
42
45
|
licenses: []
|
43
46
|
|
@@ -51,23 +54,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
54
|
requirements:
|
52
55
|
- - ">="
|
53
56
|
- !ruby/object:Gem::Version
|
54
|
-
hash: 3
|
55
|
-
segments:
|
56
|
-
- 0
|
57
57
|
version: "0"
|
58
58
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
59
|
none: false
|
60
60
|
requirements:
|
61
61
|
- - ">="
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
hash: 3
|
64
|
-
segments:
|
65
|
-
- 0
|
66
63
|
version: "0"
|
67
64
|
requirements: []
|
68
65
|
|
69
66
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.
|
67
|
+
rubygems_version: 1.7.2
|
71
68
|
signing_key:
|
72
69
|
specification_version: 3
|
73
70
|
summary: Browser User Agent string parser
|