user_agent_parser 0.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of user_agent_parser might be problematic. Click here for more details.
- data/Readme.md +29 -32
- data/lib/user_agent_parser.rb +4 -15
- data/lib/user_agent_parser/device.rb +23 -0
- data/lib/user_agent_parser/operating_system.rb +20 -18
- data/lib/user_agent_parser/parser.rb +101 -48
- data/lib/user_agent_parser/user_agent.rb +20 -15
- data/lib/user_agent_parser/version.rb +30 -15
- data/vendor/ua-parser/regexes.yaml +373 -137
- metadata +4 -6
data/Readme.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
# UserAgentParser
|
1
|
+
# UserAgentParser [![Build Status](https://secure.travis-ci.org/toolmantim/user_agent_parser.png?branch=master)](http://travis-ci.org/toolmantim/user_agent_parser)
|
2
2
|
|
3
|
-
UserAgentParser is a simple, comprehensive Ruby gem for parsing user agent strings. It
|
3
|
+
UserAgentParser is a simple, comprehensive Ruby gem for parsing user agent strings. It uses [BrowserScope](http://www.browserscope.org/)'s [parsing patterns](https://github.com/tobie/ua-parser).
|
4
4
|
|
5
5
|
## Requirements
|
6
6
|
|
7
|
-
* Ruby >= 1.
|
8
|
-
|
9
|
-
Ruby 1.8.7 is not supported due to the requirement for the newer psych YAML parser. If you can get it working on 1.8.7 please send a pull request.
|
7
|
+
* Ruby >= 1.8.7
|
10
8
|
|
11
9
|
## Installation
|
12
10
|
|
@@ -19,49 +17,48 @@ $ gem install user_agent_parser
|
|
19
17
|
```ruby
|
20
18
|
require 'user_agent_parser'
|
21
19
|
=> true
|
22
|
-
|
20
|
+
user_agent = UserAgentParser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'
|
23
21
|
=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>
|
24
|
-
|
25
|
-
=> "IE 9.0
|
26
|
-
|
22
|
+
user_agent.to_s
|
23
|
+
=> "IE 9.0"
|
24
|
+
user_agent.name
|
27
25
|
=> "IE"
|
28
|
-
|
26
|
+
user_agent.version.to_s
|
29
27
|
=> "9.0"
|
30
|
-
|
31
|
-
=> 9
|
32
|
-
|
33
|
-
=> 0
|
34
|
-
|
28
|
+
user_agent.version.major
|
29
|
+
=> "9"
|
30
|
+
user_agent.version.minor
|
31
|
+
=> "0"
|
32
|
+
operating_system = user_agent.os
|
33
|
+
=> #<UserAgentParser::OperatingSystem Windows Vista>
|
34
|
+
operating_system.to_s
|
35
35
|
=> "Windows Vista"
|
36
|
+
|
37
|
+
# The parser database will be loaded and parsed on every call to
|
38
|
+
# UserAgentParser.parse. To avoid this, instantiate your own Parser instance.
|
39
|
+
parser = UserAgentParser::Parser.new
|
40
|
+
parser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'
|
41
|
+
=> #<UserAgentParser::UserAgent IE 9.0 (Windows Vista)>
|
42
|
+
parser.parse 'Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.24 Version/10.53'
|
43
|
+
=> #<UserAgentParser::UserAgent Opera 10.53 (Windows XP)>
|
36
44
|
```
|
37
45
|
|
38
46
|
## The pattern database
|
39
47
|
|
40
48
|
The [ua-parser database](https://github.com/tobie/ua-parser/blob/master/regexes.yaml) is included via a [git submodule](http://help.github.com/submodules/). To update the database the submodule needs to be updated and the gem re-released (pull requests for this are very welcome!).
|
41
49
|
|
42
|
-
You can also specify the path to your own, updated and/or customised `regexes.yaml` file
|
50
|
+
You can also specify the path to your own, updated and/or customised `regexes.yaml` file as a second argument to `UserAgentParser.parse`:
|
43
51
|
|
44
52
|
```ruby
|
45
|
-
UserAgentParser.patterns_path
|
53
|
+
UserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')
|
46
54
|
```
|
47
55
|
|
48
|
-
|
49
|
-
|
50
|
-
```bash
|
51
|
-
$ rake test
|
52
|
-
...
|
53
|
-
|
54
|
-
Finished tests in 144.220280s, 89.0027 tests/s, 234.9739 assertions/s.
|
56
|
+
or when instantiating a `UserAgentParser::Parser`:
|
55
57
|
|
56
|
-
|
58
|
+
```ruby
|
59
|
+
UserAgentParser::Parser.new(patterns_path: '/some/path/to/regexes.yaml').parse(ua_string)
|
57
60
|
```
|
58
61
|
|
59
|
-
## Limitations
|
60
|
-
|
61
|
-
Unlike the original Browserscope code there's no support for providing overrides from Javascript user agent detection. There's no current intention of adding support as the Javascript overrides were only necessary for IE 9 Platform Preview and older versions of [Chrome Frame](https://developers.google.com/chrome/chrome-frame/).
|
62
|
-
|
63
|
-
Chrome Frame detection is not yet included, but once [ua-parser issue #14](https://github.com/tobie/ua-parser/issues/14) is resolved this gem will be updated along with it.
|
64
|
-
|
65
62
|
## Contributing
|
66
63
|
|
67
64
|
1. Fork
|
@@ -73,4 +70,4 @@ All accepted pull requests will earn you commit and release rights.
|
|
73
70
|
|
74
71
|
## License
|
75
72
|
|
76
|
-
MIT
|
73
|
+
MIT
|
data/lib/user_agent_parser.rb
CHANGED
@@ -2,24 +2,13 @@ require 'user_agent_parser/parser'
|
|
2
2
|
require 'user_agent_parser/user_agent'
|
3
3
|
require 'user_agent_parser/version'
|
4
4
|
require 'user_agent_parser/operating_system'
|
5
|
+
require 'user_agent_parser/device'
|
5
6
|
|
6
7
|
module UserAgentParser
|
7
|
-
|
8
|
-
# Path to the ua-parser regexes pattern database
|
9
|
-
def self.patterns_path
|
10
|
-
@patterns_path
|
11
|
-
end
|
12
|
-
|
13
|
-
# Sets the path to the ua-parser regexes pattern database
|
14
|
-
def self.patterns_path=(path)
|
15
|
-
@patterns_path = path
|
16
|
-
end
|
17
|
-
|
18
|
-
self.patterns_path = File.join(File.dirname(__FILE__), "../vendor/ua-parser/regexes.yaml")
|
8
|
+
DefaultPatternsPath = File.join(File.dirname(__FILE__), "../vendor/ua-parser/regexes.yaml")
|
19
9
|
|
20
10
|
# Parse the given +user_agent_string+, returning a +UserAgent+
|
21
|
-
def self.parse
|
22
|
-
Parser.new.parse
|
11
|
+
def self.parse(user_agent_string, options={})
|
12
|
+
Parser.new(options).parse(user_agent_string)
|
23
13
|
end
|
24
|
-
|
25
14
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module UserAgentParser
|
2
|
+
class Device
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name = nil)
|
6
|
+
@name = name || 'Other'
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
name
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"#<#{self.class} #{to_s}>"
|
15
|
+
end
|
16
|
+
|
17
|
+
def eql?(other)
|
18
|
+
self.class.eql?(other.class) && name == other.name
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :==, :eql?
|
22
|
+
end
|
23
|
+
end
|
@@ -1,28 +1,30 @@
|
|
1
1
|
module UserAgentParser
|
2
|
-
|
3
2
|
class OperatingSystem
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
self.version = version
|
3
|
+
attr_reader :name, :version
|
4
|
+
|
5
|
+
def initialize(name = 'Other', version = nil)
|
6
|
+
@name = name
|
7
|
+
@version = version
|
10
8
|
end
|
11
|
-
|
9
|
+
|
12
10
|
def to_s
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
string = name
|
12
|
+
unless version.nil?
|
13
|
+
string += " #{version}"
|
14
|
+
end
|
15
|
+
string
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def inspect
|
19
19
|
"#<#{self.class} #{to_s}>"
|
20
20
|
end
|
21
|
-
|
22
|
-
def
|
23
|
-
|
21
|
+
|
22
|
+
def eql?(other)
|
23
|
+
self.class.eql?(other.class) &&
|
24
|
+
name == other.name &&
|
25
|
+
version == other.version
|
24
26
|
end
|
25
|
-
|
26
|
-
end
|
27
27
|
|
28
|
-
|
28
|
+
alias_method :==, :eql?
|
29
|
+
end
|
30
|
+
end
|
@@ -1,41 +1,49 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module UserAgentParser
|
4
|
-
|
4
|
+
|
5
5
|
class Parser
|
6
|
+
attr_reader :patterns_path
|
6
7
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
ua
|
8
|
+
def initialize(options={})
|
9
|
+
@patterns_path = options[:patterns_path] || UserAgentParser::DefaultPatternsPath
|
10
|
+
@ua_patterns, @os_patterns, @device_patterns = load_patterns(patterns_path)
|
11
11
|
end
|
12
|
-
|
13
|
-
private
|
14
12
|
|
15
|
-
def
|
16
|
-
|
13
|
+
def parse(user_agent)
|
14
|
+
os = parse_os(user_agent)
|
15
|
+
device = parse_device(user_agent)
|
16
|
+
parse_ua(user_agent, os, device)
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
def load_patterns(path)
|
22
|
+
yml = YAML.load_file(path)
|
23
|
+
|
24
|
+
# Parse all the regexs
|
25
|
+
yml.each_pair do |type, patterns|
|
26
|
+
patterns.each do |pattern|
|
27
|
+
pattern["regex"] = Regexp.new(pattern["regex"])
|
24
28
|
end
|
25
29
|
end
|
30
|
+
|
31
|
+
[ yml["user_agent_parsers"], yml["os_parsers"], yml["device_parsers"] ]
|
26
32
|
end
|
27
|
-
|
28
|
-
def parse_ua
|
29
|
-
pattern, match = first_pattern_match(
|
33
|
+
|
34
|
+
def parse_ua(user_agent, os = nil, device = nil)
|
35
|
+
pattern, match = first_pattern_match(@ua_patterns, user_agent)
|
36
|
+
|
30
37
|
if match
|
31
|
-
user_agent_from_pattern_match(pattern, match)
|
38
|
+
user_agent_from_pattern_match(pattern, match, os, device)
|
32
39
|
else
|
33
|
-
UserAgent.new
|
40
|
+
UserAgent.new(nil, nil, os, device)
|
34
41
|
end
|
35
42
|
end
|
36
|
-
|
37
|
-
def parse_os
|
38
|
-
pattern, match = first_pattern_match(
|
43
|
+
|
44
|
+
def parse_os(user_agent)
|
45
|
+
pattern, match = first_pattern_match(@os_patterns, user_agent)
|
46
|
+
|
39
47
|
if match
|
40
48
|
os_from_pattern_match(pattern, match)
|
41
49
|
else
|
@@ -43,45 +51,90 @@ module UserAgentParser
|
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
54
|
+
def parse_device(user_agent)
|
55
|
+
pattern, match = first_pattern_match(@device_patterns, user_agent)
|
56
|
+
|
57
|
+
if match
|
58
|
+
device_from_pattern_match(pattern, match)
|
59
|
+
else
|
60
|
+
Device.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def first_pattern_match(patterns, value)
|
65
|
+
patterns.each do |pattern|
|
66
|
+
if match = pattern["regex"].match(value)
|
67
|
+
return [pattern, match]
|
50
68
|
end
|
51
69
|
end
|
52
70
|
nil
|
53
71
|
end
|
54
72
|
|
55
|
-
def user_agent_from_pattern_match
|
56
|
-
|
73
|
+
def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)
|
74
|
+
name, v1, v2, v3 = match[1], match[2], match[3], match[4]
|
75
|
+
|
57
76
|
if pattern["family_replacement"]
|
58
|
-
|
77
|
+
name = pattern["family_replacement"].sub('$1', name || '')
|
78
|
+
end
|
79
|
+
|
80
|
+
if pattern["v1_replacement"]
|
81
|
+
v1 = pattern["v1_replacement"].sub('$1', v1 || '')
|
82
|
+
end
|
83
|
+
|
84
|
+
if pattern["v2_replacement"]
|
85
|
+
v2 = pattern["v2_replacement"].sub('$1', v2 || '')
|
86
|
+
end
|
87
|
+
|
88
|
+
if pattern["v3_replacement"]
|
89
|
+
v3 = pattern["v3_replacement"].sub('$1', v3 || '')
|
59
90
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
ua.version = version_from_segments(v1, v2, v3)
|
65
|
-
ua
|
91
|
+
|
92
|
+
version = version_from_segments(v1, v2, v3)
|
93
|
+
|
94
|
+
UserAgent.new(name, version, os, device)
|
66
95
|
end
|
67
|
-
|
68
|
-
def os_from_pattern_match
|
96
|
+
|
97
|
+
def os_from_pattern_match(pattern, match)
|
69
98
|
os, v1, v2, v3, v4 = match[1], match[2], match[3], match[4], match[5]
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
99
|
+
|
100
|
+
if pattern["os_replacement"]
|
101
|
+
os = pattern["os_replacement"].sub('$1', os || '')
|
102
|
+
end
|
103
|
+
|
104
|
+
if pattern["os_v1_replacement"]
|
105
|
+
v1 = pattern["os_v1_replacement"].sub('$1', v1 || '')
|
106
|
+
end
|
107
|
+
|
108
|
+
if pattern["os_v2_replacement"]
|
109
|
+
v2 = pattern["os_v2_replacement"].sub('$1', v2 || '')
|
110
|
+
end
|
111
|
+
|
112
|
+
if pattern["os_v3_replacement"]
|
113
|
+
v3 = pattern["os_v3_replacement"].sub('$1', v3 || '')
|
114
|
+
end
|
115
|
+
|
116
|
+
if pattern["os_v4_replacement"]
|
117
|
+
v4 = pattern["os_v4_replacement"].sub('$1', v4 || '')
|
118
|
+
end
|
119
|
+
|
120
|
+
version = version_from_segments(v1, v2, v3, v4)
|
121
|
+
|
122
|
+
OperatingSystem.new(os, version)
|
78
123
|
end
|
79
|
-
|
124
|
+
|
125
|
+
def device_from_pattern_match(pattern, match)
|
126
|
+
device = match[1]
|
127
|
+
|
128
|
+
if pattern["device_replacement"]
|
129
|
+
device = pattern["device_replacement"].sub('$1', device || '')
|
130
|
+
end
|
131
|
+
|
132
|
+
Device.new(device)
|
133
|
+
end
|
134
|
+
|
80
135
|
def version_from_segments(*segments)
|
81
136
|
version_string = segments.compact.join(".")
|
82
137
|
version_string.empty? ? nil : Version.new(version_string)
|
83
138
|
end
|
84
|
-
|
85
139
|
end
|
86
|
-
|
87
140
|
end
|
@@ -1,32 +1,37 @@
|
|
1
1
|
module UserAgentParser
|
2
|
-
|
3
2
|
class UserAgent
|
3
|
+
attr_reader :name, :version, :os, :device
|
4
4
|
|
5
|
-
|
5
|
+
# For backwards compatibility with older versions of this gem.
|
6
|
+
alias_method :family, :name
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def initialize(name = nil, version = nil, os = nil, device = nil)
|
9
|
+
@name = name || 'Other'
|
10
|
+
@version = version
|
11
|
+
@os = os
|
12
|
+
@device = device
|
11
13
|
end
|
12
14
|
|
13
15
|
def to_s
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
s
|
16
|
+
string = name
|
17
|
+
string += " #{version}" if version
|
18
|
+
string
|
18
19
|
end
|
19
20
|
|
20
21
|
def inspect
|
21
|
-
|
22
|
+
string = to_s
|
23
|
+
string += " (#{os})" if os
|
24
|
+
string += " (#{device})" if device
|
25
|
+
"#<#{self.class} #{string}>"
|
22
26
|
end
|
23
|
-
|
24
|
-
def
|
25
|
-
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self.class.eql?(other.class) &&
|
30
|
+
name == other.name &&
|
26
31
|
version == other.version &&
|
27
32
|
os == other.os
|
28
33
|
end
|
29
34
|
|
35
|
+
alias_method :==, :eql?
|
30
36
|
end
|
31
|
-
|
32
37
|
end
|
@@ -1,32 +1,47 @@
|
|
1
1
|
module UserAgentParser
|
2
|
-
|
3
2
|
class Version
|
4
3
|
|
5
|
-
|
4
|
+
# Private: Regex used to split version string into major, minor, patch,
|
5
|
+
# and patch_minor.
|
6
|
+
SEGMENTS_REGEX = /\d+\-\d+|\d+[a-zA-Z]+$|\d+|[A-Za-z][0-9A-Za-z-]*$/
|
7
|
+
|
8
|
+
attr_reader :version
|
6
9
|
alias :to_s :version
|
7
10
|
|
8
11
|
def initialize(version)
|
9
|
-
|
12
|
+
@version = version.to_s.strip
|
10
13
|
end
|
11
14
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def major
|
16
|
+
segments[0]
|
17
|
+
end
|
18
|
+
|
19
|
+
def minor
|
20
|
+
segments[1]
|
21
|
+
end
|
22
|
+
|
23
|
+
def patch
|
24
|
+
segments[2]
|
16
25
|
end
|
17
26
|
|
18
|
-
def
|
19
|
-
segments[
|
27
|
+
def patch_minor
|
28
|
+
segments[3]
|
20
29
|
end
|
21
|
-
|
30
|
+
|
22
31
|
def inspect
|
23
32
|
"#<#{self.class} #{to_s}>"
|
24
33
|
end
|
25
|
-
|
26
|
-
def
|
27
|
-
|
34
|
+
|
35
|
+
def eql?(other)
|
36
|
+
self.class.eql?(other.class) &&
|
37
|
+
version == other.version
|
28
38
|
end
|
29
39
|
|
30
|
-
|
40
|
+
alias_method :==, :eql?
|
31
41
|
|
32
|
-
|
42
|
+
# Private
|
43
|
+
def segments
|
44
|
+
@segments ||= version.scan(SEGMENTS_REGEX)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|