double_agent 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +13 -0
- data/data/browsers.yml +57 -0
- data/data/oses.yml +52 -0
- data/lib/double_agent.rb +3 -0
- data/lib/double_agent/all.rb +2 -0
- data/lib/double_agent/core.rb +156 -0
- data/lib/double_agent/logs.rb +28 -0
- data/lib/double_agent/resources.rb +65 -0
- data/lib/double_agent/stats.rb +48 -0
- metadata +75 -0
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: .+
|
data/lib/double_agent.rb
ADDED
@@ -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
|
+
|