user_agent_parser 1.0.2 → 2.1.3
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.
- checksums.yaml +7 -0
- data/Readme.md +52 -28
- data/bin/user_agent_parser +56 -0
- data/lib/user_agent_parser.rb +4 -15
- data/lib/user_agent_parser/cli.rb +54 -0
- 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 +106 -49
- data/lib/user_agent_parser/user_agent.rb +20 -16
- data/lib/user_agent_parser/version.rb +30 -20
- data/vendor/ua-parser/regexes.yaml +385 -135
- metadata +11 -10
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a721f503d9e59cd7813d9c18db733ef6ecb24c5e
|
4
|
+
data.tar.gz: 7acb12c8b075c1f2cae73b8e3ce29023e5bae740
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d929dfb2af11c55a29a51cb81ae1a3210327275aed9c45c4055374d95d7d1957d6a446ec6cb3c78b9db90911ca7a041d66ca03ecae9e72a0fa423267005d1bc6
|
7
|
+
data.tar.gz: 482194e192ffaac970357316d7ee7976b1add1c18d922de35210d82018d4b3dd003723072abb6f37556c24011a72f20b2cb1820c5af06cad7ec71696485eee01
|
data/Readme.md
CHANGED
@@ -4,9 +4,7 @@ UserAgentParser is a simple, comprehensive Ruby gem for parsing user agent strin
|
|
4
4
|
|
5
5
|
## Requirements
|
6
6
|
|
7
|
-
* Ruby >= 1.
|
8
|
-
|
9
|
-
Note: 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,48 +17,62 @@ $ 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
|
-
|
22
|
+
user_agent.to_s
|
25
23
|
=> "IE 9.0"
|
26
|
-
|
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
|
35
33
|
=> #<UserAgentParser::OperatingSystem Windows Vista>
|
36
|
-
|
34
|
+
operating_system.to_s
|
37
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)>
|
38
44
|
```
|
39
45
|
|
40
|
-
|
46
|
+
In a larger application, you could store a parser in a global to avoid repeat pattern loading:
|
41
47
|
|
42
|
-
|
48
|
+
```ruby
|
49
|
+
module MyApplication
|
43
50
|
|
44
|
-
|
51
|
+
# Instantiate the parser on load as it's quite expensive
|
52
|
+
USER_AGENT_PARSER = UserAgentParser::Parser.new
|
45
53
|
|
46
|
-
|
47
|
-
|
54
|
+
def self.user_agent_parser
|
55
|
+
USER_AGENT_PARSER
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
48
59
|
```
|
49
60
|
|
50
|
-
##
|
61
|
+
## The pattern database
|
51
62
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
Finished tests in 144.220280s, 89.0027 tests/s, 234.9739 assertions/s.
|
63
|
+
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!).
|
64
|
+
|
65
|
+
You can also specify the path to your own, updated and/or customised `regexes.yaml` file as a second argument to `UserAgentParser.parse`:
|
57
66
|
|
58
|
-
|
67
|
+
```ruby
|
68
|
+
UserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')
|
59
69
|
```
|
60
70
|
|
61
|
-
|
71
|
+
or when instantiating a `UserAgentParser::Parser`:
|
62
72
|
|
63
|
-
|
73
|
+
```ruby
|
74
|
+
UserAgentParser::Parser.new(patterns_path: '/some/path/to/regexes.yaml').parse(ua_string)
|
75
|
+
```
|
64
76
|
|
65
77
|
## Contributing
|
66
78
|
|
@@ -71,6 +83,18 @@ There's no support for providing overrides from Javascript user agent detection
|
|
71
83
|
|
72
84
|
All accepted pull requests will earn you commit and release rights.
|
73
85
|
|
86
|
+
## Releasing a new version
|
87
|
+
|
88
|
+
1. Update the version in `user_agent_parser.gemspec`
|
89
|
+
2. `git commit user_agent_parser.gemspec` with the following message format:
|
90
|
+
|
91
|
+
Version x.x.x
|
92
|
+
|
93
|
+
Changelog:
|
94
|
+
* Some new feature
|
95
|
+
* Some new bug fix
|
96
|
+
3. `rake release`
|
97
|
+
|
74
98
|
## License
|
75
99
|
|
76
|
-
MIT
|
100
|
+
MIT
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path('../../lib', File.readlink(__FILE__))
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
require 'user_agent_parser'
|
8
|
+
require 'user_agent_parser/cli'
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
optparse = OptionParser.new do|opts|
|
13
|
+
opts.on('--name', 'Print name only') do
|
14
|
+
options[:name] = true
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on('--version', 'Print version only') do
|
18
|
+
options[:version] = true
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('--major', 'Print major version only') do
|
22
|
+
options[:major] = true
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('--minor', 'Print minor version only') do
|
26
|
+
options[:minor] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('--os', 'Print operating system only') do
|
30
|
+
options[:os] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('--format format',
|
34
|
+
'Print output in specified format. The available formatters are:',
|
35
|
+
' - %n: name',
|
36
|
+
' - %v: version',
|
37
|
+
' - %M: major version',
|
38
|
+
' - %m: minor version',
|
39
|
+
' - %o: operating system'
|
40
|
+
) do |format|
|
41
|
+
options[:format] = format
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-h', '--help', 'Display this screen') do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
optparse.parse!
|
51
|
+
|
52
|
+
parser = UserAgentParser::Parser.new
|
53
|
+
|
54
|
+
ARGF.each do |line|
|
55
|
+
puts UserAgentParser::Cli.new(parser.parse(line), options).run!
|
56
|
+
end
|
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,54 @@
|
|
1
|
+
module UserAgentParser
|
2
|
+
class Cli
|
3
|
+
def initialize(user_agent, options = {})
|
4
|
+
@user_agent = user_agent
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def run!
|
9
|
+
if @options[:name]
|
10
|
+
@user_agent.name
|
11
|
+
elsif @options[:version]
|
12
|
+
with_version do |version|
|
13
|
+
version.to_s
|
14
|
+
end
|
15
|
+
elsif @options[:major]
|
16
|
+
major
|
17
|
+
elsif @options[:minor]
|
18
|
+
minor
|
19
|
+
elsif @options[:os]
|
20
|
+
@user_agent.os.to_s
|
21
|
+
elsif format = @options[:format]
|
22
|
+
format.gsub('%n', @user_agent.name).
|
23
|
+
gsub('%v', version.to_s).
|
24
|
+
gsub('%M', major.to_s).
|
25
|
+
gsub('%m', minor.to_s).
|
26
|
+
gsub('%o', @user_agent.os.to_s)
|
27
|
+
else
|
28
|
+
@user_agent.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def major
|
35
|
+
with_version do |version|
|
36
|
+
version.major
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def minor
|
41
|
+
with_version do |version|
|
42
|
+
version.minor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def version
|
47
|
+
@version ||= @user_agent.version
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_version(&block)
|
51
|
+
block.call(version) if version
|
52
|
+
end
|
53
|
+
end
|
54
|
+
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,94 @@ 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, v4 = match[1], match[2], match[3], match[4], match[5]
|
75
|
+
|
57
76
|
if pattern["family_replacement"]
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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 || '')
|
90
|
+
end
|
91
|
+
|
92
|
+
if pattern["v4_replacement"]
|
93
|
+
v4 = pattern["v4_replacement"].sub('$1', v4 || '')
|
94
|
+
end
|
95
|
+
|
96
|
+
version = version_from_segments(v1, v2, v3, v4)
|
97
|
+
|
98
|
+
UserAgent.new(name, version, os, device)
|
66
99
|
end
|
67
|
-
|
68
|
-
def os_from_pattern_match
|
100
|
+
|
101
|
+
def os_from_pattern_match(pattern, match)
|
69
102
|
os, v1, v2, v3, v4 = match[1], match[2], match[3], match[4], match[5]
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
103
|
+
|
104
|
+
if pattern["os_replacement"]
|
105
|
+
os = pattern["os_replacement"].sub('$1', os || '')
|
106
|
+
end
|
107
|
+
|
108
|
+
if pattern["os_v1_replacement"]
|
109
|
+
v1 = pattern["os_v1_replacement"].sub('$1', v1 || '')
|
110
|
+
end
|
111
|
+
|
112
|
+
if pattern["os_v2_replacement"]
|
113
|
+
v2 = pattern["os_v2_replacement"].sub('$1', v2 || '')
|
114
|
+
end
|
115
|
+
|
116
|
+
if pattern["os_v3_replacement"]
|
117
|
+
v3 = pattern["os_v3_replacement"].sub('$1', v3 || '')
|
118
|
+
end
|
119
|
+
|
120
|
+
if pattern["os_v4_replacement"]
|
121
|
+
v4 = pattern["os_v4_replacement"].sub('$1', v4 || '')
|
122
|
+
end
|
123
|
+
|
124
|
+
version = version_from_segments(v1, v2, v3, v4)
|
125
|
+
|
126
|
+
OperatingSystem.new(os, version)
|
78
127
|
end
|
79
|
-
|
128
|
+
|
129
|
+
def device_from_pattern_match(pattern, match)
|
130
|
+
device = match[1]
|
131
|
+
|
132
|
+
if pattern["device_replacement"]
|
133
|
+
device = pattern["device_replacement"].sub('$1', device || '')
|
134
|
+
end
|
135
|
+
|
136
|
+
Device.new(device)
|
137
|
+
end
|
138
|
+
|
80
139
|
def version_from_segments(*segments)
|
81
140
|
version_string = segments.compact.join(".")
|
82
141
|
version_string.empty? ? nil : Version.new(version_string)
|
83
142
|
end
|
84
|
-
|
85
143
|
end
|
86
|
-
|
87
144
|
end
|