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 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 XP
27
- :sym: :windows_xp
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 5\.1
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 7
39
- :sym: :windows_7
56
+ - :name: Windows XP
57
+ :sym: :windows_xp
40
58
  :family_sym: :windows
41
- :regex: windows nt 6\.1
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
- - :family: Unknown
66
+ - :name: BlackBerry
67
+ :sym: :blackberry
68
+ :regex: blackberry
69
+
70
+ - :name: Unknown
50
71
  :sym: :unknown
51
- :name: Unknown
52
72
  :regex: .+
@@ -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
- else
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
- def icon
39
- @icon || @sym
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
- # Get a browser parser with either a user agent or symbol
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
- # Get an OS parser with either a user agent or symbol
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"
@@ -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 which returns a User Agent string
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
@@ -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. The *_sym methods
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
- # => [['Firefox', 50.4], ['Chrome', 19.6], ['Internet Explorer', 15], ['Safari', 10], ['Unknown', 5]]
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
- # => [[:firefox_4, :windows_7, 50.4], [:firefox_3, :osx, 19.6], [:msie, :windows_xp, 15], [:safari, :osx, 10], [:other, :other, 5]]
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
@@ -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
@@ -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
@@ -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
- hash: 29
5
- prerelease: false
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 -04: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
- - data/browsers.yml
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
- has_rdoc: true
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.3.7
67
+ rubygems_version: 1.7.2
71
68
  signing_key:
72
69
  specification_version: 3
73
70
  summary: Browser User Agent string parser