brauser 1.0.0
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/.gitignore +6 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -0
- data/README.md +27 -0
- data/Rakefile +18 -0
- data/brauser.gemspec +34 -0
- data/doc/Brauser.html +129 -0
- data/doc/Brauser/Browser.html +4392 -0
- data/doc/Brauser/Hooks.html +125 -0
- data/doc/Brauser/Hooks/RubyOnRails.html +332 -0
- data/doc/Brauser/Query.html +1315 -0
- data/doc/Brauser/Version.html +189 -0
- data/doc/_index.html +177 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +328 -0
- data/doc/file.README.html +102 -0
- data/doc/file_list.html +55 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +102 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +412 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/brauser.rb +14 -0
- data/lib/brauser/browser.rb +794 -0
- data/lib/brauser/hooks.rb +31 -0
- data/lib/brauser/query.rb +118 -0
- data/lib/brauser/version.rb +24 -0
- data/spec/brauser/browser_spec.rb +685 -0
- data/spec/brauser/hooks_spec.rb +27 -0
- data/spec/brauser/query_spec.rb +123 -0
- data/spec/brauser_spec.rb +6 -0
- data/spec/coverage_helper.rb +20 -0
- data/spec/frameworks_helper.rb +24 -0
- data/spec/spec_helper.rb +18 -0
- metadata +226 -0
data/lib/brauser.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the brauser gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
require "lazier"
|
8
|
+
|
9
|
+
Lazier.load!(:object)
|
10
|
+
|
11
|
+
require "brauser/version" if !defined?(Brauser::Version)
|
12
|
+
require "brauser/query"
|
13
|
+
require "brauser/browser"
|
14
|
+
require "brauser/hooks"
|
@@ -0,0 +1,794 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the brauser gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
|
4
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
|
+
#
|
6
|
+
|
7
|
+
# A framework agnostic browser detection and querying helper.
|
8
|
+
module Brauser
|
9
|
+
# This class represents a detection of the current user browser.
|
10
|
+
class Browser
|
11
|
+
# The raw User-Agent HTTP header.
|
12
|
+
attr_accessor :agent
|
13
|
+
|
14
|
+
# The raw Accept-Language HTTP header.
|
15
|
+
attr_accessor :accept_language
|
16
|
+
|
17
|
+
# The accepted languages.
|
18
|
+
attr_accessor :languages
|
19
|
+
|
20
|
+
# The current browser name.
|
21
|
+
attr_accessor :name
|
22
|
+
|
23
|
+
# The current browser version.
|
24
|
+
attr_accessor :version
|
25
|
+
|
26
|
+
# The current browser platform.
|
27
|
+
attr_accessor :platform
|
28
|
+
|
29
|
+
# Aliases
|
30
|
+
alias :ua :agent
|
31
|
+
alias :ua= :agent=
|
32
|
+
|
33
|
+
# Returns the list of browser that can be recognized.
|
34
|
+
#
|
35
|
+
# The keys are the browser name, the values are arrays of the name matcher, the version match and the label.
|
36
|
+
#
|
37
|
+
# @return [Hash] The list of browser that can be recognized.
|
38
|
+
def self.browsers
|
39
|
+
rv = ActiveSupport::OrderedHash.new
|
40
|
+
|
41
|
+
@browsers.each do |browser|
|
42
|
+
rv[browser[0]] = browser[1, browser.length]
|
43
|
+
end
|
44
|
+
|
45
|
+
rv
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the list of platforms that can be recognized.
|
49
|
+
#
|
50
|
+
# The keys are the platform name, values are arrays of the matcher and the label.
|
51
|
+
#
|
52
|
+
# @return [Hash] The list of platform that can be recognized.
|
53
|
+
def self.platforms
|
54
|
+
rv = ActiveSupport::OrderedHash.new
|
55
|
+
|
56
|
+
@platforms.each do |platform|
|
57
|
+
rv[platform[0]] = platform[1, platform.length]
|
58
|
+
end
|
59
|
+
|
60
|
+
rv
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the list of languages that can be recognized.
|
64
|
+
#
|
65
|
+
# The keys are the languages code, the values the labels.
|
66
|
+
#
|
67
|
+
# @return [Hash] The list of languages that can be recognized.
|
68
|
+
def self.languages
|
69
|
+
@languages
|
70
|
+
end
|
71
|
+
|
72
|
+
# Registers the default list of browsers that can be recognized.
|
73
|
+
#
|
74
|
+
# @return [Boolean] `true` if at least one browser has been added, `false` otherwise.
|
75
|
+
def self.register_default_browsers
|
76
|
+
@browsers = nil
|
77
|
+
|
78
|
+
self.register_browser([
|
79
|
+
[:coremedia, /coremedia/i, /.+CoreMedia v([a-z0-9.]+)/i, "Apple CoreMedia"],
|
80
|
+
|
81
|
+
[:opera_mobile, /opera mobi/i, /.+Opera Mobi.+((.+Opera )|(Version\/))([a-z0-9.]+)/i, "Opera Mobile"],
|
82
|
+
[:opera, /opera/i, Proc.new{ |name, agent|
|
83
|
+
regexp = (agent !~ /wii/i) ? /((.+Opera )|(Version\/))([a-z0-9.]+)/i : /(.+Nintendo Wii; U; ; )([a-z0-9.]+)/i
|
84
|
+
|
85
|
+
version = regexp.match(agent)
|
86
|
+
version = version.to_a.last if version.is_a?(MatchData)
|
87
|
+
version
|
88
|
+
}, "Opera"],
|
89
|
+
|
90
|
+
[:android, /android/i, /(.+Android )([a-z0-9.]+)/i, "Android"],
|
91
|
+
[:blackberry, /blackberry/i, /(.+Version\/)([a-z0-9.]+)/i, "RIM BlackBerry"],
|
92
|
+
[:kindle, /(kindle)/i, /(.+(Kindle|Silk)\/)([a-z0-9.]+)/i, "Amazon Kindle"],
|
93
|
+
[:psp, /psp/i, /(.+PlayStation Portable\); )([a-z0-9.]+)/i, "Sony Playstation Portable"],
|
94
|
+
[:ps3, /playstation 3/i, /(.+PLAYSTATION 3; )([a-z0-9.]+)/i, "Sony Playstation 3"],
|
95
|
+
[:windows_phone, /windows phone/i, /(.+IEMobile\/)([a-z0-9.]+)/i, "Microsoft Windows Phone"],
|
96
|
+
[:wii, /nintendo wii/, /(.+Nintendo Wii; U; ; )([a-z0-9.]+)/i, "Nintendo Wii"],
|
97
|
+
|
98
|
+
[:ipod, /ipod/i, /(.+Version\/)([a-z0-9.]+)/i, "Apple iPod"],
|
99
|
+
[:iphone, /iphone/i, /(.+Version\/)([a-z0-9.]+)/i, "Apple iPhone"],
|
100
|
+
[:ipad, /ipad/i, /(.+Version\/)([a-z0-9.]+)/i, "Apple iPad"],
|
101
|
+
|
102
|
+
[:mobile, /(mobile|symbian|midp|windows ce)/i, /.+\/([a-z0-9.]+)/i, "Other Mobile Browser"],
|
103
|
+
|
104
|
+
[:chrome, /((chrome)|(chromium))/i, /(.+Chrom[a-z]+\/)([a-z0-9.]+)/i, "Google Chrome"],
|
105
|
+
[:netscape, /(netscape|navigator)\//i, /((Netscape|Navigator)\/)([a-z0-9.]+)/i, "Netscape Navigator"],
|
106
|
+
[:firefox, /firefox/i, /(.+Firefox\/)([a-z0-9.]+)/i, "Mozilla Firefox"],
|
107
|
+
[:safari, Proc.new{ |agent| agent =~ /safari/i && agent !~ /((chrome)|(chromium))/i }, /(.+Version\/)([a-z0-9.]+)/i, "Apple Safari"],
|
108
|
+
|
109
|
+
[:msie_compatibility, /trident/i, Proc.new { |name, agent|
|
110
|
+
version = /(.+Trident\/)([a-z0-9.]+)/i.match(agent)
|
111
|
+
|
112
|
+
if version.is_a?(::MatchData) then
|
113
|
+
v = version.to_a.last.split(".")
|
114
|
+
v[0] = v[0].to_integer + 4
|
115
|
+
version = v.join(".")
|
116
|
+
end
|
117
|
+
|
118
|
+
version
|
119
|
+
}, "Microsoft Internet Explorer (Compatibility View)"],
|
120
|
+
[:msie, Proc.new{ |agent| agent =~ /msie/i && agent !~ /opera/i }, /(.+MSIE )([a-z0-9.]+)/i, "Microsoft Internet Explorer"],
|
121
|
+
|
122
|
+
[:quicktime, /quicktime/i, /(.+((QuickTime\/)|(qtver=)))([a-z0-9.]+)/i, "Apple QuickTime"],
|
123
|
+
|
124
|
+
[:webkit, /webkit/i, /(.+WebKit\/)([a-z0-9.]+)/i, "WebKit Browser"],
|
125
|
+
[:gecko, /gecko/i, /(.+rv:|Gecko\/)([a-z0-9.]+)/i, "Gecko Browser"],
|
126
|
+
])
|
127
|
+
|
128
|
+
@browsers.present? ? true : false
|
129
|
+
end
|
130
|
+
|
131
|
+
# Registers the default list of platforms that can be recognized.
|
132
|
+
#
|
133
|
+
# @return [Boolean] `true` if at least one platform has been added, `false` otherwise.
|
134
|
+
def self.register_default_platforms
|
135
|
+
@platforms = nil
|
136
|
+
|
137
|
+
self.register_platform([
|
138
|
+
[:symbian, /s60|symb/i, "Symbian"],
|
139
|
+
[:windows_phone, /windows phone/i, "Microsoft Windows Phone"],
|
140
|
+
[:kindle, Proc.new { |name, agent| name == :kindle }, "Nokia Symbian"],
|
141
|
+
[:ios, Proc.new { |name, agent| [:iphone, :ipad, :ipod].include?(name) || agent =~ /ipad|iphone|ipod/i }, "Apple iOS"],
|
142
|
+
[:android, /android/i, "Android"],
|
143
|
+
[:blackberry, /blackberry/i, "RIM BlackBerry"],
|
144
|
+
[:psp, /psp/i, "Sony Playstation Portable"],
|
145
|
+
[:ps3, /playstation 3/i, "Sony Playstation 3"],
|
146
|
+
[:wii, /wii/i, "Nintendo Wii"],
|
147
|
+
|
148
|
+
[:linux, /linux/i, "Linux"],
|
149
|
+
[:osx, /mac|macintosh|mac os x/i, "Apple MacOS X"],
|
150
|
+
[:windows, /windows/i, "Microsoft Windows"]
|
151
|
+
])
|
152
|
+
|
153
|
+
@platforms.present? ? true : false
|
154
|
+
end
|
155
|
+
|
156
|
+
# Registers the default list of languages that can be recognized.
|
157
|
+
#
|
158
|
+
# @return [Boolean] `true` if at least one language has been added, `false` otherwise.
|
159
|
+
def self.register_default_languages
|
160
|
+
@languages = nil
|
161
|
+
|
162
|
+
self.register_language({
|
163
|
+
"af" => "Afrikaans",
|
164
|
+
"sq" => "Albanian",
|
165
|
+
"eu" => "Basque",
|
166
|
+
"bg" => "Bulgarian",
|
167
|
+
"be" => "Byelorussian",
|
168
|
+
"ca" => "Catalan",
|
169
|
+
"zh" => "Chinese",
|
170
|
+
"zh-cn" => "Chinese/China",
|
171
|
+
"zh-tw" => "Chinese/Taiwan",
|
172
|
+
"zh-hk" => "Chinese/Hong Kong",
|
173
|
+
"zh-sg" => "Chinese/singapore",
|
174
|
+
"hr" => "Croatian",
|
175
|
+
"cs" => "Czech",
|
176
|
+
"da" => "Danish",
|
177
|
+
"nl" => "Dutch",
|
178
|
+
"nl-nl" => "Dutch/Netherlands",
|
179
|
+
"nl-be" => "Dutch/Belgium",
|
180
|
+
"en" => "English",
|
181
|
+
"en-gb" => "English/United Kingdom",
|
182
|
+
"en-us" => "English/United States",
|
183
|
+
"en-au" => "English/Australian",
|
184
|
+
"en-ca" => "English/Canada",
|
185
|
+
"en-nz" => "English/New Zealand",
|
186
|
+
"en-ie" => "English/Ireland",
|
187
|
+
"en-za" => "English/South Africa",
|
188
|
+
"en-jm" => "English/Jamaica",
|
189
|
+
"en-bz" => "English/Belize",
|
190
|
+
"en-tt" => "English/Trinidad",
|
191
|
+
"et" => "Estonian",
|
192
|
+
"fo" => "Faeroese",
|
193
|
+
"fa" => "Farsi",
|
194
|
+
"fi" => "Finnish",
|
195
|
+
"fr" => "French",
|
196
|
+
"fr-be" => "French/Belgium",
|
197
|
+
"fr-fr" => "French/France",
|
198
|
+
"fr-ch" => "French/Switzerland",
|
199
|
+
"fr-ca" => "French/Canada",
|
200
|
+
"fr-lu" => "French/Luxembourg",
|
201
|
+
"gd" => "Gaelic",
|
202
|
+
"gl" => "Galician",
|
203
|
+
"de" => "German",
|
204
|
+
"de-at" => "German/Austria",
|
205
|
+
"de-de" => "German/Germany",
|
206
|
+
"de-ch" => "German/Switzerland",
|
207
|
+
"de-lu" => "German/Luxembourg",
|
208
|
+
"de-li" => "German/Liechtenstein",
|
209
|
+
"el" => "Greek",
|
210
|
+
"he" => "Hebrew",
|
211
|
+
"he-il" => "Hebrew/Israel",
|
212
|
+
"hi" => "Hindi",
|
213
|
+
"hu" => "Hungarian",
|
214
|
+
"ie-ee" => "Internet Explorer/Easter Egg",
|
215
|
+
"is" => "Icelandic",
|
216
|
+
"id" => "Indonesian",
|
217
|
+
"in" => "Indonesian",
|
218
|
+
"ga" => "Irish",
|
219
|
+
"it" => "Italian",
|
220
|
+
"it-ch" => "Italian/ Switzerland",
|
221
|
+
"ja" => "Japanese",
|
222
|
+
"km" => "Khmer",
|
223
|
+
"km-kh" => "Khmer/Cambodia",
|
224
|
+
"ko" => "Korean",
|
225
|
+
"lv" => "Latvian",
|
226
|
+
"lt" => "Lithuanian",
|
227
|
+
"mk" => "Macedonian",
|
228
|
+
"ms" => "Malaysian",
|
229
|
+
"mt" => "Maltese",
|
230
|
+
"no" => "Norwegian",
|
231
|
+
"pl" => "Polish",
|
232
|
+
"pt" => "Portuguese",
|
233
|
+
"pt-br" => "Portuguese/Brazil",
|
234
|
+
"rm" => "Rhaeto-Romanic",
|
235
|
+
"ro" => "Romanian",
|
236
|
+
"ro-mo" => "Romanian/Moldavia",
|
237
|
+
"ru" => "Russian",
|
238
|
+
"ru-mo" => "Russian /Moldavia",
|
239
|
+
"gd" => "Scots Gaelic",
|
240
|
+
"sr" => "Serbian",
|
241
|
+
"sk" => "Slovack",
|
242
|
+
"sl" => "Slovenian",
|
243
|
+
"sb" => "Sorbian",
|
244
|
+
"es" => "Spanish",
|
245
|
+
"es-do" => "Spanish",
|
246
|
+
"es-ar" => "Spanish/Argentina",
|
247
|
+
"es-co" => "Spanish/Colombia",
|
248
|
+
"es-mx" => "Spanish/Mexico",
|
249
|
+
"es-es" => "Spanish/Spain",
|
250
|
+
"es-gt" => "Spanish/Guatemala",
|
251
|
+
"es-cr" => "Spanish/Costa Rica",
|
252
|
+
"es-pa" => "Spanish/Panama",
|
253
|
+
"es-ve" => "Spanish/Venezuela",
|
254
|
+
"es-pe" => "Spanish/Peru",
|
255
|
+
"es-ec" => "Spanish/Ecuador",
|
256
|
+
"es-cl" => "Spanish/Chile",
|
257
|
+
"es-uy" => "Spanish/Uruguay",
|
258
|
+
"es-py" => "Spanish/Paraguay",
|
259
|
+
"es-bo" => "Spanish/Bolivia",
|
260
|
+
"es-sv" => "Spanish/El salvador",
|
261
|
+
"es-hn" => "Spanish/Honduras",
|
262
|
+
"es-ni" => "Spanish/Nicaragua",
|
263
|
+
"es-pr" => "Spanish/Puerto Rico",
|
264
|
+
"sx" => "Sutu",
|
265
|
+
"sv" => "Swedish",
|
266
|
+
"sv-se" => "Swedish/Sweden",
|
267
|
+
"sv-fi" => "Swedish/Finland",
|
268
|
+
"ts" => "Thai",
|
269
|
+
"tn" => "Tswana",
|
270
|
+
"tr" => "Turkish",
|
271
|
+
"uk" => "Ukrainian",
|
272
|
+
"ur" => "Urdu",
|
273
|
+
"vi" => "Vietnamese",
|
274
|
+
"xh" => "Xshosa",
|
275
|
+
"ji" => "Yiddish",
|
276
|
+
"zu" => "Zulu"
|
277
|
+
})
|
278
|
+
|
279
|
+
@languages.present? ? true : false
|
280
|
+
end
|
281
|
+
|
282
|
+
# Registers a new browser that can be recognized.
|
283
|
+
#
|
284
|
+
# @param name [Symbol|Array] The browser name or a list of browser (a list of array with `[name, name_match, version_match, label]` entries).
|
285
|
+
# @param name_match [String|Regexp|Block] The matcher for the name. If a block, it will be yield with the user agent and must return `true` if the name was recognized.
|
286
|
+
# @param version_match [String|Regexp|Block] The match for the version. If a block, it will be yield with the browser name and the user agent and must return the browser version.
|
287
|
+
# @param label [String] A human readable name of the browser.
|
288
|
+
# @return [Boolean] `true` if at least one browser has been added, `false` otherwise.
|
289
|
+
def self.register_browser(name, name_match = nil, version_match = nil, label = nil)
|
290
|
+
if !@browsers then
|
291
|
+
@browsers = []
|
292
|
+
@browsers_indexes = {}
|
293
|
+
end
|
294
|
+
|
295
|
+
rv = false
|
296
|
+
name = [[name.ensure_string.to_sym, name_match, version_match, label]] if !name.is_a?(Array)
|
297
|
+
|
298
|
+
name.each do |browser|
|
299
|
+
browser[0] = browser[0].to_sym
|
300
|
+
|
301
|
+
index = @browsers.index do |item|
|
302
|
+
item[0] == browser[0]
|
303
|
+
end
|
304
|
+
|
305
|
+
# Replace a previous entry
|
306
|
+
if index then
|
307
|
+
@browsers[index] = browser
|
308
|
+
else
|
309
|
+
@browsers << browser
|
310
|
+
@browsers_indexes[browser[0]] = @browsers.length - 1
|
311
|
+
end
|
312
|
+
|
313
|
+
rv = true
|
314
|
+
end
|
315
|
+
|
316
|
+
rv
|
317
|
+
end
|
318
|
+
|
319
|
+
# Registers a new platform that can be recognized.
|
320
|
+
#
|
321
|
+
# @param name [Symbol|Array] The platform name or a list of platforms (a list of array with `[name, matcher, label]` entries).
|
322
|
+
# @param matcher [StringRegexp|Block] The matcher for the platform. If a block, it will be yielded with the browser name and the user agent and must return `true` if the platform was recognized.
|
323
|
+
# @param label [String] A human readable name of the platform.
|
324
|
+
# @return [Boolean] `true` if at least one platform has been added, `false` otherwise.
|
325
|
+
def self.register_platform(name, matcher = nil, label = nil)
|
326
|
+
if !@platforms then
|
327
|
+
@platforms = []
|
328
|
+
@platforms_indexes = {}
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
rv = false
|
333
|
+
name = [[name.ensure_string.to_sym, matcher, label]] if !name.is_a?(Array)
|
334
|
+
|
335
|
+
name.each do |platform|
|
336
|
+
platform[0] = platform[0].to_sym
|
337
|
+
|
338
|
+
index = @platforms.index do |item|
|
339
|
+
item[0] == platform[0]
|
340
|
+
end
|
341
|
+
|
342
|
+
# Replace a previous entry
|
343
|
+
if index then
|
344
|
+
@platforms[index] = platform
|
345
|
+
else
|
346
|
+
@platforms << platform
|
347
|
+
@platforms_indexes[platform[0]] = @platforms.length - 1
|
348
|
+
end
|
349
|
+
|
350
|
+
rv = true
|
351
|
+
end
|
352
|
+
|
353
|
+
rv
|
354
|
+
end
|
355
|
+
|
356
|
+
# Registers a new language that can be recognized.
|
357
|
+
#
|
358
|
+
# @param code [String|Hash] The language code or an hash with codes as keys and label as values.
|
359
|
+
# @param label [String] The language name. Ignored if code is an Hash.
|
360
|
+
# @return [Boolean] `true` if at least one language has been added, `false` otherwise.
|
361
|
+
def self.register_language(code, label = nil)
|
362
|
+
@languages ||= {}
|
363
|
+
rv = false
|
364
|
+
code = {code.ensure_string => label.ensure_string} if !code.is_a?(Hash)
|
365
|
+
|
366
|
+
code.each_pair do |c, l|
|
367
|
+
if c.present? && l.present? then
|
368
|
+
@languages[c] = l
|
369
|
+
rv = true
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
rv
|
374
|
+
end
|
375
|
+
|
376
|
+
# Compares two versions.
|
377
|
+
#
|
378
|
+
# @param v1 [String] The first versions to compare.
|
379
|
+
# @param operator [Symbol] The operator to use for comparison, can be one of `[:lt, :lte, :eq, :gte, :gt]`.
|
380
|
+
# @param v2 [Symbol] The second version to compare.
|
381
|
+
# @return [Boolean] true if comparison is valid, `false` otherwise.
|
382
|
+
def self.compare_versions(v1 = "", operator = :eq, v2 = "")
|
383
|
+
rv = true
|
384
|
+
|
385
|
+
if [:lt, :lte, :eq, :gte, :gt].include?(operator) && v1.ensure_string.present? then
|
386
|
+
# At first, split versions
|
387
|
+
vv1 = v1.ensure_string.split(".").reverse
|
388
|
+
vv2 = v2.ensure_string.split(".").reverse
|
389
|
+
|
390
|
+
left = vv1.pop
|
391
|
+
right = vv2.pop
|
392
|
+
|
393
|
+
# Start comparison, picking from length
|
394
|
+
rv = catch(:result) do
|
395
|
+
while true do
|
396
|
+
left = (left || "0").ensure_string
|
397
|
+
right = (right || "0").ensure_string
|
398
|
+
|
399
|
+
# Adjust for alpha, beta, etc
|
400
|
+
if !left.is_integer? then
|
401
|
+
ll = left.length
|
402
|
+
left = right + left
|
403
|
+
right = right + ("z" * ll)
|
404
|
+
end
|
405
|
+
|
406
|
+
# Pad arguments to make correct string comparisons
|
407
|
+
len = [left.length, right.length].max
|
408
|
+
left = left.rjust(len, "0")
|
409
|
+
right = right.rjust(len, "0")
|
410
|
+
|
411
|
+
if left == right then # If we have still an equal version
|
412
|
+
# Goto next tokens
|
413
|
+
left = vv1.pop
|
414
|
+
right = vv2.pop
|
415
|
+
|
416
|
+
# We end the left version, so until now all tokens were equal. Check what to do.
|
417
|
+
if left.blank? then
|
418
|
+
case operator
|
419
|
+
when :lt then throw(:result, right.present? && right.to_integer > 0) # The comparison is true if there is at least another right version number greater than 0.
|
420
|
+
when :lte then throw(:result, right.blank? || right.is_integer?) # The comparison is true if there is no other right version or that is a integer (so it means no alpha, beta etc).
|
421
|
+
when :eq then throw(:result, right.blank? || ([right] + vv2).uniq.first == "0") # The comparison is true if also right version ends or only zero are left.
|
422
|
+
when :gte then throw(:result, right.blank? || !right.is_integer?) # The comparison is true if there is no other right version or that is not a integer (so it means alpha, beta etc).
|
423
|
+
when :gt then throw(:result, right.present? && !right.is_integer?) # The comparison is true if there is at least another right version not a integer (so it means alpha, beta etc).
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
throw(:result, [:lte, :eq, :gte].include?(operator)) if left.blank? # If we end the left version, it means that all tokens were equal. Return accordingly
|
428
|
+
else # We can compare a different token
|
429
|
+
case operator
|
430
|
+
when :lt, :lte then throw(:result, left < right)
|
431
|
+
when :eq then throw(:result, false)
|
432
|
+
when :gt, :gte then throw(:result, left > right)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
else
|
438
|
+
rv = false
|
439
|
+
end
|
440
|
+
|
441
|
+
rv
|
442
|
+
end
|
443
|
+
|
444
|
+
# Creates a new browser.
|
445
|
+
#
|
446
|
+
# @param agent [String] The User-Agent HTTP header.
|
447
|
+
# @param accept_language [String] The Accept-Language HTTP header.
|
448
|
+
def initialize(agent = "", accept_language = "")
|
449
|
+
self.class.register_default_browsers
|
450
|
+
self.class.register_default_platforms
|
451
|
+
self.class.register_default_languages
|
452
|
+
|
453
|
+
@agent = agent
|
454
|
+
@accept_language = accept_language
|
455
|
+
|
456
|
+
@languages = self.parse_accept_language(@accept_language) if @accept_language
|
457
|
+
self.parse_agent(@agent) if @agent
|
458
|
+
end
|
459
|
+
|
460
|
+
# Parses the User-Agent header.
|
461
|
+
# @param agent [String] The User-Agent header.
|
462
|
+
# @return [Boolean] `true` if the browser was detected, `false` otherwise.
|
463
|
+
def parse_agent(agent = nil)
|
464
|
+
agent = agent.ensure_string
|
465
|
+
|
466
|
+
# At first, detect name and version. Tries order is important to avoid false positives.
|
467
|
+
@name = catch(:name) do
|
468
|
+
self.class.browsers.each do |name, definitions|
|
469
|
+
matched = false
|
470
|
+
|
471
|
+
if definitions[0].is_a?(::Regexp) then
|
472
|
+
matched = definitions[0].match(agent) ? true : false
|
473
|
+
elsif definitions[0].respond_to?(:call) then
|
474
|
+
matched = (definitions[0].call(agent) ? true : false)
|
475
|
+
else
|
476
|
+
matched = (agent == definitions[0].ensure_string)
|
477
|
+
end
|
478
|
+
|
479
|
+
if matched then # Found a match, go through version
|
480
|
+
if definitions[1].is_a?(::Regexp) then
|
481
|
+
@version = definitions[1].match(agent)
|
482
|
+
@version = @version.to_a.last if @version.is_a?(::MatchData)
|
483
|
+
elsif @version = definitions[1].respond_to?(:call) then
|
484
|
+
@version = definitions[1].call(name, agent).ensure_string
|
485
|
+
else
|
486
|
+
@version = definitions[1].ensure_string
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
throw(:name, name) if matched
|
491
|
+
end
|
492
|
+
|
493
|
+
:unknown
|
494
|
+
end
|
495
|
+
|
496
|
+
# Adjust version
|
497
|
+
@version = "0.0" if @version.blank?
|
498
|
+
|
499
|
+
# At last, detect platform
|
500
|
+
@platform = catch(:platform) do
|
501
|
+
self.class.platforms.each do |platform, definitions|
|
502
|
+
if definitions[0].is_a?(::Regexp) then
|
503
|
+
matched = definitions[0].match(agent) ? true : false
|
504
|
+
elsif definitions[0].respond_to?(:call) then
|
505
|
+
matched = (definitions[0].call(@name, agent) ? true : false)
|
506
|
+
else
|
507
|
+
matched = (agent == definitions[0].ensure_string)
|
508
|
+
end
|
509
|
+
|
510
|
+
throw(:platform, platform) if matched
|
511
|
+
end
|
512
|
+
|
513
|
+
:unknown
|
514
|
+
end
|
515
|
+
|
516
|
+
(@name != :unknown) ? true : false
|
517
|
+
end
|
518
|
+
|
519
|
+
# Parses the Accept-Language header.
|
520
|
+
#
|
521
|
+
# @param accept_language [String] The Accept-Language header.
|
522
|
+
# @return [Array] The list of accepted languages.
|
523
|
+
def parse_accept_language(accept_language = nil)
|
524
|
+
accept_language.ensure_string.gsub(/;q=[\d.]+/, "").split(",").collect {|l| l.downcase.strip }.select{|l| l.present? }
|
525
|
+
end
|
526
|
+
|
527
|
+
# Gets a human-readable browser name.
|
528
|
+
#
|
529
|
+
# @return [String] A human-readable browser name.
|
530
|
+
def readable_name
|
531
|
+
self.parse_agent(@agent) if !@name
|
532
|
+
self.class.browsers.fetch(@name, ["Unknown Browser"]).last.ensure_string
|
533
|
+
end
|
534
|
+
|
535
|
+
# Gets a human-readable platform name.
|
536
|
+
#
|
537
|
+
# @return [String] A readable platform name.
|
538
|
+
def platform_name
|
539
|
+
self.parse_agent(@agent) if !@platform
|
540
|
+
self.class.platforms.fetch(@platform, ["Unknown Platform"]).last.ensure_string
|
541
|
+
end
|
542
|
+
|
543
|
+
# Checks if the browser is a specific name and optionally of a specific version and platform.
|
544
|
+
#
|
545
|
+
# @see #v?
|
546
|
+
# @see #on?
|
547
|
+
#
|
548
|
+
# @param names [Symbol|Array] A list of specific names to match. Also, this meta-names are supported: `:capable` and `:tablet`.
|
549
|
+
# @param versions [Hash] An hash with specific version to match against. Need to be in form `{:operator => version}`, where operator is one of `:lt, :lte, :eq, :gt, :gte`.
|
550
|
+
# @param platforms [Symbol|Array] A list of specific platform to match. Valid values are all those possible for the platform attribute.
|
551
|
+
# @return [Query] A query which can evaluated for concatenation or result.
|
552
|
+
def is(names = [], versions = {}, platforms = [])
|
553
|
+
self.parse_agent(@agent) if !@name
|
554
|
+
versions = {} if !versions.is_a?(::Hash)
|
555
|
+
platforms = platforms.ensure_array
|
556
|
+
|
557
|
+
# Adjust names
|
558
|
+
names = names.ensure_array
|
559
|
+
names << [:msie] if names.include?(:ie)
|
560
|
+
names << [:chromium] if names.include?(:chrome)
|
561
|
+
|
562
|
+
if names.delete(:capable) then
|
563
|
+
names += [:chrome, :firefox, :safari, :opera, :msie]
|
564
|
+
|
565
|
+
# Add version 9 as minimum for IE if capable is specified
|
566
|
+
versions[:gte] = 9 if @name == :msie
|
567
|
+
end
|
568
|
+
|
569
|
+
names << [:ipad, :android, :kindle] if names.delete(:tablet)
|
570
|
+
names.compact!
|
571
|
+
|
572
|
+
rv = names.blank? || names.include?(@name)
|
573
|
+
|
574
|
+
# Look also for version
|
575
|
+
rv = rv && self.v?(versions) if rv && versions.present?
|
576
|
+
|
577
|
+
# Look also for platforms
|
578
|
+
rv = rv && self.on?(platforms) if rv && platforms.present?
|
579
|
+
|
580
|
+
::Brauser::Query.new(self, rv)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Checks if the browser is a specific name and optionally of a specific version and platform.
|
584
|
+
#
|
585
|
+
# @see #v?
|
586
|
+
# @see #on?
|
587
|
+
#
|
588
|
+
# @param names [Symbol|Array] A list of specific names to match. Also, this meta-names are supported: `:capable` and `:tablet`.
|
589
|
+
# @param versions [Hash] An hash with specific version to match against. Need to be in form `{:operator => version}`, where operator is one of `:lt, :lte, :eq, :gt, :gte`.
|
590
|
+
# @param platforms [Symbol|Array] A list of specific platform to match. Valid values are all those possible for the platform attribute.
|
591
|
+
# @return [Boolean] `true` if current browser matches, `false` otherwise.
|
592
|
+
def is?(names = [], versions = {}, platforms = [])
|
593
|
+
self.is(names, versions, platforms).result
|
594
|
+
end
|
595
|
+
|
596
|
+
# Checks if the brower is a specific version.
|
597
|
+
#
|
598
|
+
# @param versions [String|Hash] A string in the form `operator version && ...` (example: `>= 7 && < 4`) or an hash with specific version to match against, in form `{:operator => version}`, where operator is one of `:lt, :lte, :eq, :gt, :gte`.
|
599
|
+
# @return [Query] A query which can evaluated for concatenation or result.
|
600
|
+
def v(versions = {})
|
601
|
+
self.parse_agent(@agent) if !@version
|
602
|
+
rv = true
|
603
|
+
|
604
|
+
if versions.is_a?(String) then
|
605
|
+
versions_s = versions
|
606
|
+
versions = {}
|
607
|
+
|
608
|
+
versions_s.split(/\s*&&\s*/).each do |token|
|
609
|
+
next if token.strip.empty?
|
610
|
+
tokens = token.strip.split(/\s+/).collect {|t| t.strip}
|
611
|
+
operator = case tokens[0]
|
612
|
+
when "<" then :lt
|
613
|
+
when "<=" then :lte
|
614
|
+
when "=" then :eq
|
615
|
+
when "==" then :eq
|
616
|
+
when ">=" then :gte
|
617
|
+
when ">" then :gt
|
618
|
+
else tokens[0].downcase.to_sym
|
619
|
+
end
|
620
|
+
|
621
|
+
versions[operator] = tokens[1]
|
622
|
+
end
|
623
|
+
else
|
624
|
+
versions = {} if !versions.is_a?(::Hash)
|
625
|
+
end
|
626
|
+
|
627
|
+
versions.each do |operator, value|
|
628
|
+
value = value.ensure_string
|
629
|
+
rv = rv && Brauser::Browser.compare_versions(@version, operator, value)
|
630
|
+
break if !rv
|
631
|
+
end
|
632
|
+
|
633
|
+
::Brauser::Query.new(self, rv)
|
634
|
+
end
|
635
|
+
|
636
|
+
# Checks if the brower is a specific version.
|
637
|
+
#
|
638
|
+
# @param versions [String|Hash] A string in the form `operator version && ...` (example: `>= 7 && < 4`) or an hash with specific version to match against, in form `{:operator => version}`, where operator is one of `:lt, :lte, :eq, :gt, :gte`.
|
639
|
+
# @return [Boolean] `true` if current browser matches, `false` otherwise.
|
640
|
+
def v?(versions = {})
|
641
|
+
self.v(versions).result
|
642
|
+
end
|
643
|
+
|
644
|
+
# Check if the browser is on a specific platform.
|
645
|
+
#
|
646
|
+
# @param platforms [Symbol|Array] A list of specific platform to match.
|
647
|
+
# @return [Query] A query which can evaluated for concatenation or result.
|
648
|
+
def on(platforms = [])
|
649
|
+
self.parse_agent(@agent) if !@platform
|
650
|
+
::Brauser::Query.new(self, platforms.blank? || platforms.ensure_array.collect {|p| p.ensure_string.to_sym }.include?(@platform))
|
651
|
+
end
|
652
|
+
|
653
|
+
# Check if the browser is on a specific platform.
|
654
|
+
#
|
655
|
+
# @param platforms [Symbol|Array] A list of specific platform to match.
|
656
|
+
# @return [Boolean] `true` if current browser matches, `false` otherwise.
|
657
|
+
def on?(platforms = [])
|
658
|
+
self.on(platforms).result
|
659
|
+
end
|
660
|
+
|
661
|
+
# Check if the browser accepts the specified languages.
|
662
|
+
#
|
663
|
+
# @param langs [String|Array] A list of languages to match against.
|
664
|
+
# @return [Query] A query which can evaluated for concatenation or result.
|
665
|
+
def accepts(langs = [])
|
666
|
+
self.parse_accept_language(@accept_language) if !@languages
|
667
|
+
::Brauser::Query.new(self, (@languages & langs.ensure_array).present?)
|
668
|
+
end
|
669
|
+
|
670
|
+
# Check if the browser accepts the specified languages.
|
671
|
+
#
|
672
|
+
# @param langs [String|Array] A list of languages to match against.
|
673
|
+
# @return [Boolean] `true` if current browser matches, `false` otherwise.
|
674
|
+
def accepts?(langs = [])
|
675
|
+
self.accepts(langs).result
|
676
|
+
end
|
677
|
+
|
678
|
+
# This method enables the use of dynamic queries in just one method.
|
679
|
+
#
|
680
|
+
# For example:
|
681
|
+
#
|
682
|
+
# ```ruby
|
683
|
+
# browser.is_msie_gt_4_1__on_windows?
|
684
|
+
# #=> true
|
685
|
+
# ```
|
686
|
+
#
|
687
|
+
# If you don't provide a trailing `?`, you will get a Brauser::Query.
|
688
|
+
#
|
689
|
+
# If the syntax is invalid, a `NoMethodError` exception will be raised.
|
690
|
+
#
|
691
|
+
# @param query [String] The query to issue. Use `__` to separate query and `_` in place of `.` in the version.
|
692
|
+
# @param arguments [Array] The arguments to pass the method. Unused from the query.
|
693
|
+
# @param block [Proc] A block to pass to the method. Unused from the query.
|
694
|
+
# @return [Boolean|Query|nil] A query or a boolean value (if `method` ends with `?`). If the query is not valid, `NoMethodError` will be raised.
|
695
|
+
def method_missing(query, *arguments, &block)
|
696
|
+
rv = nil
|
697
|
+
oq = query
|
698
|
+
oa = arguments
|
699
|
+
|
700
|
+
query = query.ensure_string
|
701
|
+
|
702
|
+
# See if return a boolean in case of valid query
|
703
|
+
as_result = query =~ /\?$/
|
704
|
+
query = query.gsub(/\?$/, "")
|
705
|
+
|
706
|
+
# Parse the query
|
707
|
+
query.ensure_string.split("__").each do |parts|
|
708
|
+
tokens = parts.split("_")
|
709
|
+
method = tokens[0]
|
710
|
+
arguments = tokens[1, tokens.length].join("_")
|
711
|
+
|
712
|
+
if method == "v" then
|
713
|
+
arguments.gsub("_", ".")
|
714
|
+
end
|
715
|
+
|
716
|
+
if ["is", "v", "on"].include?(method) then
|
717
|
+
if method == "v" then
|
718
|
+
arguments = arguments.gsub(/_?eq_?/, " == ") # Parse ==
|
719
|
+
arguments = arguments.gsub(/_?lte_?/, " <= ").gsub(/_?gte_?/, " >= ") # Parse <= and >=
|
720
|
+
arguments = arguments.gsub(/_?lt_?/, " < ").gsub(/_?gt_?/, " > ") # Parse < and >
|
721
|
+
arguments = arguments.gsub(/_?and_?/, " && ") # Parse &&
|
722
|
+
arguments = arguments.gsub("_", ".").gsub(/\s+/, " ").strip # Replace _ and spaces, then strip
|
723
|
+
end
|
724
|
+
|
725
|
+
rv = Brauser::Query.new(self, true) if !rv
|
726
|
+
rv = rv.send(method, *arguments)
|
727
|
+
|
728
|
+
break if rv.result == false
|
729
|
+
else # Invalid finder
|
730
|
+
rv = nil
|
731
|
+
break
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
rv = rv.result if !rv.nil? && as_result
|
736
|
+
!rv.nil? ? rv : super(oq, *oa, &block)
|
737
|
+
end
|
738
|
+
|
739
|
+
# Returns an array of information about the browser. Information are strings which are suitable to use as CSS classes.
|
740
|
+
#
|
741
|
+
# For version, it will be included a class for every token of the version. For example, version `7.0.1.2` will return this:
|
742
|
+
#
|
743
|
+
# ```ruby
|
744
|
+
# ["version-7", "version-7_0", "version-7_0_1", "version-7_0_1_2"]
|
745
|
+
# ```
|
746
|
+
#
|
747
|
+
# If you provide a block (with accepts name, version and platform as arguments), it will be used for translating the name.
|
748
|
+
#
|
749
|
+
# @param join [String|NilClass] If non falsy, the separator to use to join information. If falsy, informations will be returned as array.
|
750
|
+
# @param name [Boolean] If non falsy, the string to prepend to the name. If falsy, the name information will not be included.
|
751
|
+
# @param version [String|NilClass] If non falsy, the string to prepend to the version. If falsy, the version information will not be included.
|
752
|
+
# @param platform [String|NilClass] If non falsy, the string to prepend to the platform. If falsy, the platform information will not be included.
|
753
|
+
# @return [String|Array] CSS ready information of the current browser.
|
754
|
+
def classes(join = " ", name = "", version = "version-", platform = "platform-")
|
755
|
+
self.parse_agent(@agent) if !@name
|
756
|
+
rv = []
|
757
|
+
name = "" if name == true
|
758
|
+
version = "version-" if version == true
|
759
|
+
platform = "platform-" if platform == true
|
760
|
+
|
761
|
+
# Manage name
|
762
|
+
if name then
|
763
|
+
final_name = block_given? ? yield(@name, @version, @platform) : @name
|
764
|
+
rv << name + final_name.ensure_string
|
765
|
+
end
|
766
|
+
|
767
|
+
# Manage platform
|
768
|
+
if version then
|
769
|
+
vbuffer = []
|
770
|
+
vtokens = @version.split(".")
|
771
|
+
|
772
|
+
vtokens.each do |v|
|
773
|
+
vbuffer << v
|
774
|
+
rv << (version + vbuffer.join("_"))
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
rv << (platform + @platform.to_s) if platform
|
779
|
+
|
780
|
+
# Return
|
781
|
+
join ? rv.join(join) : rv
|
782
|
+
end
|
783
|
+
alias :meta :classes
|
784
|
+
|
785
|
+
# Returns the current browser as a string.
|
786
|
+
#
|
787
|
+
# @see #classes
|
788
|
+
#
|
789
|
+
# @return [String] A string representation of the current browser.
|
790
|
+
def to_s
|
791
|
+
self.classes
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|