double_agent 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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