-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathparser.rb
More file actions
168 lines (137 loc) · 4.45 KB
/
parser.rb
File metadata and controls
168 lines (137 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# frozen_string_literal: true
require 'yaml'
module UserAgentParser
class Parser
FAMILY_REPLACEMENT_KEYS = %w[
family_replacement
v1_replacement
v2_replacement
v3_replacement
v4_replacement
].freeze
OS_REPLACEMENT_KEYS = %w[
os_replacement
os_v1_replacement
os_v2_replacement
os_v3_replacement
os_v4_replacement
].freeze
private_constant :FAMILY_REPLACEMENT_KEYS, :OS_REPLACEMENT_KEYS
attr_reader :patterns_path
def initialize(options = {})
@patterns_path = options[:patterns_path] || UserAgentParser::DefaultPatternsPath
@ua_patterns, @os_patterns, @device_patterns = load_patterns(patterns_path)
end
def parse(user_agent)
user_agent = user_agent&.encode('UTF-8', invalid: :replace)
os = parse_os(user_agent)
device = parse_device(user_agent)
parse_ua(user_agent, os, device)
end
private
def load_patterns(path)
yml = YAML.load_file(path)
# Parse all the regexs
yml.each_pair do |_type, patterns|
patterns.each do |pattern|
pattern[:regex] = Regexp.new(pattern['regex'], pattern['regex_flag'] == 'i')
end
end
[yml['user_agent_parsers'], yml['os_parsers'], yml['device_parsers']]
end
def parse_ua(user_agent, os = nil, device = nil)
pattern, match = first_pattern_match(@ua_patterns, user_agent)
if match
user_agent_from_pattern_match(pattern, match, os, device)
else
UserAgent.new(nil, nil, os, device)
end
end
def parse_os(user_agent)
pattern, match = first_pattern_match(@os_patterns, user_agent)
if match
os_from_pattern_match(pattern, match)
else
OperatingSystem.new
end
end
def parse_device(user_agent)
pattern, match = first_pattern_match(@device_patterns, user_agent)
if match
device_from_pattern_match(pattern, match)
else
Device.new
end
end
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
def first_pattern_match(patterns, value)
patterns.each do |pattern|
if pattern[:regex].match?(value)
return [pattern, pattern[:regex].match(value)]
end
end
nil
end
else
def first_pattern_match(patterns, value)
patterns.each do |pattern|
if (match = pattern[:regex].match(value))
return [pattern, match]
end
end
nil
end
end
def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)
family, *versions = from_pattern_match(FAMILY_REPLACEMENT_KEYS, pattern, match)
UserAgent.new(family, version_from_segments(*versions), os, device)
end
def os_from_pattern_match(pattern, match)
os, *versions = from_pattern_match(OS_REPLACEMENT_KEYS, pattern, match)
OperatingSystem.new(os, version_from_segments(*versions))
end
def device_from_pattern_match(pattern, match)
match = match.to_a.map(&:to_s)
family = model = match[1]
brand = nil
if pattern['device_replacement']
family = pattern['device_replacement']
match.each_with_index { |m, i| family = family.sub("$#{i}", m) }
end
if pattern['model_replacement']
model = pattern['model_replacement']
match.each_with_index { |m, i| model = model.sub("$#{i}", m) }
end
if pattern['brand_replacement']
brand = pattern['brand_replacement']
match.each_with_index { |m, i| brand = brand.sub("$#{i}", m) }
brand.strip!
end
model&.strip!
Device.new(family.strip, model, brand)
end
# Maps replacement keys to their values
def from_pattern_match(keys, pattern, match)
keys.each_with_index.map do |key, idx|
# Check if there is any replacement specified
if pattern[key]
interpolate(pattern[key], match)
else
# No replacement defined, just return correct match group
match[idx + 1]
end
end
end
# Interpolates a string with data from matches if specified
def interpolate(replacement, match)
group_idx = replacement.index('$')
return replacement if group_idx.nil?
group_nbr = replacement[group_idx + 1]
replacement.sub("$#{group_nbr}", match[group_nbr.to_i])
end
def version_from_segments(*segments)
return if segments.all?(&:nil?)
Version.new(*segments)
end
end
end