double_agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2011 Jordan Hollinger
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/data/browsers.yml ADDED
@@ -0,0 +1,57 @@
1
+ # The ordering is very important; matches are performed in the specified order.
2
+ # This allows for greatly simplified regexes.
3
+
4
+ - :name: Internet Explorer %s
5
+ :sym: :msie
6
+ :family_sym: :msie
7
+ :regex: 'msie \d+'
8
+ :version: '(?<=msie )[0-9]+'
9
+ :safe_version: ["msie [0-9]+", "[0-9]+"]
10
+
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
+ - :name: Chrome %s
19
+ :sym: :chrome
20
+ :family_sym: :chromium
21
+ :regex: 'chrome/\d+'
22
+ :version: '(?<=chrome/)[0-9]+'
23
+ :safe_version: ["chrome/[0-9]+", "[0-9]+"]
24
+
25
+ - :name: Android %s
26
+ :sym: :android
27
+ :family_sym: :chromium
28
+ :regex: android
29
+ :version: '(?<=android )[0-9]+(\.[0-9]+)?'
30
+ :safe_version: ["android [0-9]+(\.[0-9]+)?", "[0-9]+(\.[0-9]+)?"]
31
+
32
+ - :name: Safari %s
33
+ :sym: :safari
34
+ :family_sym: :safari
35
+ :regex: safari
36
+ :version: '(?<=version/)[0-9]+'
37
+ :safe_version: ["version/[0-9]+", "[0-9]+"]
38
+
39
+ - :name: Opera %s
40
+ :sym: :opera
41
+ :family_sym: :opera
42
+ :regex: opera
43
+ :version: '(?<=version/)[0-9]+'
44
+ :safe_version: ["version/[0-9]+", "[0-9]+"]
45
+
46
+ - :name: Firefox %s
47
+ :sym: :firefox
48
+ :family_sym: :firefox
49
+ :regex: firefox
50
+ :version: '(?<=firefox/)[0-9]+'
51
+ :safe_version: ["firefox/[0-9]+", "[0-9]+"]
52
+
53
+ - :name: Unknown
54
+ :sym: :unknown
55
+ :family_sym: :unkown
56
+ :regex: .+
57
+ :version: .+
data/data/oses.yml ADDED
@@ -0,0 +1,52 @@
1
+ - :name: Android
2
+ :sym: :android
3
+ :family_sym: :linux
4
+ :regex: android
5
+
6
+ - :name: Ubuntu
7
+ :sym: :ubuntu
8
+ :family_sym: :linux
9
+ :regex: ubuntu
10
+
11
+ - :name: GNU/Linux
12
+ :sym: :linux
13
+ :family_sym: :linux
14
+ :regex: linux
15
+
16
+ - :name: iOS
17
+ :sym: :ios
18
+ :family_sym: :osx
19
+ :regex: (iphone)|(ipad)
20
+
21
+ - :name: OS X
22
+ :sym: :osx
23
+ :family_sym: :osx
24
+ :regex: macintosh
25
+
26
+ - :name: Windows XP
27
+ :sym: :windows_xp
28
+ :family_sym: :windows
29
+ :regex: windows nt 5\.1
30
+ :icon: windows
31
+
32
+ - :name: Windows Vista
33
+ :sym: :windows_vista
34
+ :family_sym: :windows
35
+ :regex: windows nt 6\.0
36
+ :icon: windows
37
+
38
+ - :name: Windows 7
39
+ :sym: :windows_7
40
+ :family_sym: :windows
41
+ :regex: windows nt 6\.1
42
+ :icon: windows
43
+
44
+ - :name: Windows
45
+ :sym: :windows
46
+ :family_sym: :windows
47
+ :regex: windows
48
+
49
+ - :family: Unknown
50
+ :sym: :unknown
51
+ :name: Unknown
52
+ :regex: .+
@@ -0,0 +1,3 @@
1
+ require 'double_agent/core'
2
+ require 'double_agent/resources'
3
+ require 'double_agent/stats'
@@ -0,0 +1,2 @@
1
+ require 'double_agent'
2
+ require 'double_agent/logs'
@@ -0,0 +1,156 @@
1
+ require 'yaml'
2
+
3
+ module DoubleAgent
4
+ #
5
+ # Map browser/os ids to names, families and icons
6
+ #
7
+ BROWSER_DATA = YAML.load_file(File.expand_path('../../../data/browsers.yml', __FILE__))
8
+ OS_DATA = YAML.load_file(File.expand_path('../../../data/oses.yml', __FILE__))
9
+
10
+ BROWSERS = {}
11
+ OSES = {}
12
+
13
+ class BrowserParser
14
+ BLANK = ''
15
+ MIN_VERSION = '1.9.2'
16
+ attr_reader :sym, :family_sym, :icon
17
+
18
+ def initialize(attrs={})
19
+ @family_sym = attrs[:family_sym]
20
+ @name = attrs[:name]
21
+ @sym = attrs[:sym]
22
+ @icon = attrs[:icon] || @sym
23
+ if RUBY_VERSION < MIN_VERSION and attrs[:safe_version]
24
+ @safe_version = attrs[:safe_version].map { |r| Regexp.new r, Regexp::IGNORECASE }
25
+ else
26
+ @version = Regexp.new(attrs[:version], Regexp::IGNORECASE)
27
+ end
28
+ end
29
+
30
+ def browser(ua=nil)
31
+ if ua
32
+ @name % version(ua)
33
+ else
34
+ (@name % BLANK).rstrip
35
+ end
36
+ end
37
+
38
+ def icon
39
+ @icon || @sym
40
+ end
41
+
42
+ def family
43
+ BROWSERS[family_sym]
44
+ end
45
+
46
+ private
47
+
48
+ def version(ua)
49
+ if @safe_version and RUBY_VERSION < MIN_VERSION
50
+ ua.slice(@safe_version[0]).slice(@safe_version[1])
51
+ else
52
+ ua.slice(@version)
53
+ end
54
+ end
55
+ end
56
+
57
+ class OSParser
58
+ attr_reader :os, :sym, :family_sym, :icon
59
+
60
+ def initialize(attrs={})
61
+ @family_sym = attrs[:family_sym]
62
+ @os = attrs[:name]
63
+ @sym = attrs[:sym]
64
+ @icon = attrs[:icon] || @sym
65
+ end
66
+
67
+ def family
68
+ OSES[family_sym]
69
+ end
70
+ end
71
+
72
+ #
73
+ # Methods for getting browser/os names, families, and icons either by passing a user agent string.
74
+ #
75
+
76
+ def self.browser(ua)
77
+ browser_parser(ua).browser(ua)
78
+ end
79
+
80
+ def self.browser_sym(user_agent)
81
+ end
82
+
83
+ def self.browser_icon(ua)
84
+ browser_parser(ua).icon
85
+ end
86
+
87
+ def self.browser_family(ua)
88
+ browser_parser(ua).family.browser
89
+ end
90
+
91
+ def self.browser_family_sym(ua)
92
+ browser_parser(ua).family_sym
93
+ end
94
+
95
+ def self.browser_family_icon(ua)
96
+ browser_parser(ua).family.icon
97
+ end
98
+
99
+ def self.os(ua)
100
+ os_parser(ua).os
101
+ end
102
+
103
+ def self.os_sym(user_agent)
104
+ end
105
+
106
+ def self.os_icon(ua)
107
+ os_parser(ua).icon
108
+ end
109
+
110
+ def self.os_family(ua)
111
+ os_parser(ua).family.os
112
+ end
113
+
114
+ def self.os_family_sym(ua)
115
+ os_parser(ua).family_sym
116
+ end
117
+
118
+ def self.os_family_icon(ua)
119
+ os_parser(ua).family.icon
120
+ end
121
+
122
+ # Get a browser parser with either a user agent or symbol
123
+ def self.browser_parser(ua_or_sym)
124
+ BROWSERS[ua_or_sym.is_a?(Symbol) ? ua_or_sym : browser_sym(ua_or_sym)]
125
+ end
126
+
127
+ # Get an OS parser with either a user agent or symbol
128
+ def self.os_parser(ua_or_sym)
129
+ OSES[ua_or_sym.is_a?(Symbol) ? ua_or_sym : os_sym(ua_or_sym)]
130
+ end
131
+
132
+ def self.load_browsers!
133
+ BROWSERS.clear
134
+ str = "case user_agent\n"
135
+ BROWSER_DATA.each do |data|
136
+ BROWSERS[data[:sym]] = BrowserParser.new(data)
137
+ str << " when %r{#{data[:regex]}}i then :#{data[:sym]}\n"
138
+ end
139
+ str << 'end'
140
+ module_eval "def self.browser_sym(user_agent); #{str}; end"
141
+ end
142
+
143
+ def self.load_oses!
144
+ OSES.clear
145
+ str = "case user_agent\n"
146
+ OS_DATA.each do |data|
147
+ OSES[data[:sym]] = OSParser.new(data)
148
+ str << " when %r{#{data[:regex]}}i then :#{data[:sym]}\n"
149
+ end
150
+ str << 'end'
151
+ module_eval "def self.os_sym(user_agent); #{str}; end"
152
+ end
153
+ end
154
+
155
+ DoubleAgent.load_browsers!
156
+ DoubleAgent.load_oses!
@@ -0,0 +1,28 @@
1
+ require 'zlib'
2
+
3
+ module DoubleAgent
4
+ def self.log_entries(glob_str, regex=nil)
5
+ entries, gz_regexp = [], /\.gz\Z/i
6
+ Dir.glob(glob_str).each do |f|
7
+ File.open(f) do |file|
8
+ handle = f =~ gz_regexp ? Zlib::GzipReader.new(file) : file
9
+ while ( line = handle.gets )
10
+ entries << LogEntry.new(line) if regex.nil? or line =~ regex
11
+ end
12
+ end
13
+ end
14
+ entries
15
+ end
16
+
17
+ class LogEntry
18
+ USER_AGENT_REGEXP = /[^"]+(?="$)/
19
+ include DoubleAgent::Resource
20
+
21
+ attr_reader :user_agent
22
+
23
+ def initialize(line)
24
+ #@line = line
25
+ @user_agent = line.slice(USER_AGENT_REGEXP)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ module DoubleAgent
2
+ #
3
+ # Any class with a "user_agent" method which returns a User Agent string
4
+ # may include this module to easily parse out Browser and OS info.
5
+ #
6
+ module Resource
7
+ def browser
8
+ _browser_parser.browser(user_agent)
9
+ end
10
+
11
+ def browser_sym
12
+ _browser_parser.sym
13
+ end
14
+
15
+ def browser_icon
16
+ _browser_parser.icon
17
+ end
18
+
19
+ def browser_family
20
+ _browser_parser.family.browser
21
+ end
22
+
23
+ def browser_family_sym
24
+ _browser_parser.family_sym
25
+ end
26
+
27
+ def browser_family_icon
28
+ _browser_parser.family.icon
29
+ end
30
+
31
+ def os
32
+ _os_parser.os
33
+ end
34
+
35
+ def os_sym
36
+ _os_parser.sym
37
+ end
38
+
39
+ def os_icon
40
+ _os_parser.icon
41
+ end
42
+
43
+ def os_family
44
+ _os_parser.family.os
45
+ end
46
+
47
+ def os_family_sym
48
+ _os_parser.family_sym
49
+ end
50
+
51
+ def os_family_icon
52
+ _os_parser.family.icon
53
+ end
54
+
55
+ private
56
+
57
+ def _browser_parser
58
+ @browser_parser ||= DoubleAgent.browser_parser(user_agent)
59
+ end
60
+
61
+ def _os_parser
62
+ @os_parser ||= DoubleAgent.os_parser(user_agent)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,48 @@
1
+ # Time for some monkey patching!
2
+
3
+ module DoubleAgent
4
+ BAD_RUBY = RUBY_VERSION < '1.9.0'
5
+
6
+ if BAD_RUBY
7
+ require 'bigdecimal'
8
+ def self.better_round(f, n)
9
+ d = BigDecimal.new f.to_s
10
+ d.round(n).to_f
11
+ end
12
+ end
13
+
14
+ #
15
+ # For the given "things", returns the share of the group that each attr has.
16
+ #
17
+ # "things" is an array of objects who's classes "include DoubleAgent::Resource".
18
+ #
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.
22
+ #
23
+ # Example, Browser Family share:
24
+ # DoubleAgent.percentages_for(logins, :browser_family)
25
+ # => [['Firefox', 50.4], ['Chrome', 19.6], ['Internet Explorer', 15], ['Safari', 10], ['Unknown', 5]]
26
+ #
27
+ # Example, Browser/OS share, asking for symbols back:
28
+ # 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
+ #
31
+ def self.percentages_for(things, *attrs)
32
+ p = {}
33
+ things.each do |h|
34
+ syms = attrs.map { |attr| h.send attr }
35
+ p[syms] = 0 unless p.has_key? syms
36
+ p[syms] += 1
37
+ end
38
+ size = things.size.to_f
39
+ p = p.to_a
40
+ if BAD_RUBY
41
+ p.collect! { |k,n| [*k.<<(better_round(((n * 100) / size), 2))] }
42
+ else
43
+ p.collect! { |k,n| [*k.<<(((n * 100) / size).round(2))] }
44
+ end
45
+ p.sort! { |a,b| b.last <=> a.last }
46
+ p
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: double_agent
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
11
+ platform: ruby
12
+ authors:
13
+ - Jordan Hollinger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-30 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Browser User Agent string parser with resource, stats, and a log reader
23
+ email: jordan@jordanhollinger.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/double_agent.rb
32
+ - lib/double_agent/all.rb
33
+ - lib/double_agent/stats.rb
34
+ - lib/double_agent/resources.rb
35
+ - lib/double_agent/core.rb
36
+ - lib/double_agent/logs.rb
37
+ - data/browsers.yml
38
+ - data/oses.yml
39
+ - LICENSE
40
+ has_rdoc: true
41
+ homepage: http://github.com/jhollinger/double_agent
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Browser User Agent string parser
74
+ test_files: []
75
+