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 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 utilises [BrowserScope](http://www.browserscope.org/)'s UA parser via the most excellent [ua-parser project](https://github.com/tobie/ua-parser) by [Tobie Langel](https://github.com/tobie/).
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.9.2
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
- ua = UserAgentParser.parse 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0;)'
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
- ua.to_s
25
- => "IE 9.0 (Windows Vista)"
26
- ua.family
22
+ user_agent.to_s
23
+ => "IE 9.0"
24
+ user_agent.name
27
25
  => "IE"
28
- ua.version.to_s
26
+ user_agent.version.to_s
29
27
  => "9.0"
30
- ua.version[0]
31
- => 9
32
- ua.version[1]
33
- => 0
34
- ua.os.name
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 = '/some/path/to/regexes.yaml'
53
+ UserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')
46
54
  ```
47
55
 
48
- ## Comprehensive you say?
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
- 12836 tests, 33888 assertions, 0 failures, 0 errors, 0 skips
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
@@ -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 user_agent_string
22
- Parser.new.parse user_agent_string
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
- attr_accessor :name, :version
6
-
7
- def initialize(name="Other", version=nil)
8
- self.name = name
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
- s = name
14
- s += " #{version}" if version
15
- s
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 ==(other)
23
- name == other.name && version == other.version
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
- end
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 parse user_agent
8
- ua = parse_ua(user_agent)
9
- ua.os = parse_os(user_agent)
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 all_patterns
16
- @all_patterns ||= YAML.load(File.read(UserAgentParser.patterns_path))
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
- def patterns type
20
- @patterns ||= {}
21
- @patterns[type] ||= begin
22
- all_patterns[type].each do |p|
23
- p["regex"] = Regexp.new(p["regex"])
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 user_agent
29
- pattern, match = first_pattern_match(patterns("user_agent_parsers"), user_agent)
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 user_agent
38
- pattern, match = first_pattern_match(patterns("os_parsers"), user_agent)
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 first_pattern_match patterns, value
47
- for p in patterns
48
- if m = p["regex"].match(value)
49
- return [p, m]
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 pattern, match
56
- family, v1, v2, v3 = match[1], match[2], match[3], match[4]
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
- family = pattern["family_replacement"].sub('$1', family || '')
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
- v1 = pattern["v1_replacement"].sub('$1', v1 || '') if pattern["v1_replacement"]
61
- v2 = pattern["v2_replacement"].sub('$1', v2 || '') if pattern["v2_replacement"]
62
- v3 = pattern["v3_replacement"].sub('$1', v3 || '') if pattern["v3_replacement"]
63
- ua = UserAgent.new(family)
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 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
- os = pattern["os_replacement"].sub('$1', os || '') if pattern["os_replacement"]
71
- v1 = pattern["v1_replacement"].sub('$1', v1 || '') if pattern["v1_replacement"]
72
- v2 = pattern["v2_replacement"].sub('$1', v2 || '') if pattern["v2_replacement"]
73
- v3 = pattern["v3_replacement"].sub('$1', v3 || '') if pattern["v3_replacement"]
74
- v4 = pattern["v3_replacement"].sub('$1', v3 || '') if pattern["v4_replacement"]
75
- os = OperatingSystem.new(os)
76
- os.version = version_from_segments(v1, v2, v3, v4)
77
- os
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
- attr_accessor :family, :version, :os
5
+ # For backwards compatibility with older versions of this gem.
6
+ alias_method :family, :name
6
7
 
7
- def initialize(family="Other", version=nil, os=nil)
8
- self.family = family
9
- self.version = version
10
- self.os = os
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
- s = family
15
- s += " #{version}" if version
16
- s += " (#{os})" if os
17
- s
16
+ string = name
17
+ string += " #{version}" if version
18
+ string
18
19
  end
19
20
 
20
21
  def inspect
21
- "#<#{self.class} #{to_s}>"
22
+ string = to_s
23
+ string += " (#{os})" if os
24
+ string += " (#{device})" if device
25
+ "#<#{self.class} #{string}>"
22
26
  end
23
-
24
- def ==(other)
25
- family == other.family &&
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
- attr_accessor :version
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
- self.version = version.to_s.strip
12
+ @version = version.to_s.strip
10
13
  end
11
14
 
12
- def segments
13
- version.scan(/\d+\-\d+|\d+[a-zA-Z]+$|\d+|[A-Za-z][0-9A-Za-z-]*$/).map do |s|
14
- /^\d+$/ =~ s ? s.to_i : s
15
- end
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 [](segment)
19
- segments[segment]
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 ==(other)
27
- version == other.version
34
+
35
+ def eql?(other)
36
+ self.class.eql?(other.class) &&
37
+ version == other.version
28
38
  end
29
39
 
30
- end
40
+ alias_method :==, :eql?
31
41
 
32
- end
42
+ # Private
43
+ def segments
44
+ @segments ||= version.scan(SEGMENTS_REGEX)
45
+ end
46
+ end
47
+ end