user_agent_parser 1.0.2 → 2.1.3

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.
@@ -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.9.2
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
- 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
22
+ user_agent.to_s
25
23
  => "IE 9.0"
26
- ua.family
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.major
31
- => 9
32
- ua.version.minor
33
- => 0
34
- os = ua.os
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
- os.to_s
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
- ## The pattern database
46
+ In a larger application, you could store a parser in a global to avoid repeat pattern loading:
41
47
 
42
- 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!).
48
+ ```ruby
49
+ module MyApplication
43
50
 
44
- You can also specify the path to your own, updated and/or customised `regexes.yaml` file:
51
+ # Instantiate the parser on load as it's quite expensive
52
+ USER_AGENT_PARSER = UserAgentParser::Parser.new
45
53
 
46
- ```ruby
47
- UserAgentParser.patterns_path = '/some/path/to/regexes.yaml'
54
+ def self.user_agent_parser
55
+ USER_AGENT_PARSER
56
+ end
57
+
58
+ end
48
59
  ```
49
60
 
50
- ## Comprehensive you say?
61
+ ## The pattern database
51
62
 
52
- ```bash
53
- $ rake test
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
- 12836 tests, 33888 assertions, 0 failures, 0 errors, 0 skips
67
+ ```ruby
68
+ UserAgentParser.parse(ua_string, patterns_path: '/some/path/to/regexes.yaml')
59
69
  ```
60
70
 
61
- ## Limitations
71
+ or when instantiating a `UserAgentParser::Parser`:
62
72
 
63
- There's no support for providing overrides from Javascript user agent detection like there is with original BrowserScope library. The Javascript overrides were only necessary for detecting IE 9 Platform Preview and older versions of [Chrome Frame](https://developers.google.com/chrome/chrome-frame/).
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
@@ -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,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
- 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(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,94 @@ 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, v4 = match[1], match[2], match[3], match[4], match[5]
75
+
57
76
  if pattern["family_replacement"]
58
- family = pattern["family_replacement"].sub('$1', family || '')
59
- 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
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 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
- 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
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