Skip to content

Commit

Permalink
Allow passing multiple pattern databases (#70)
Browse files Browse the repository at this point in the history
- Support `patterns_path` argument but deprecate `patterns_path` attribute accessor
- Add new `patterns_paths` array argument and accessor to enable loading multiple patterns files

Fixes #11

Co-authored-by: Mikhail Doronin <mikhail.doronin@capitainetrain.com>
  • Loading branch information
misdoro and Mikhail Doronin authored Oct 21, 2022
1 parent 495e3e6 commit 4a29630
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 28 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ or when instantiating a `UserAgentParser::Parser`:
UserAgentParser::Parser.new(patterns_path: '/some/path/to/regexes.yaml').parse(ua_string)
```

Extending the standard database is possible by providing multiple files in `patterns_paths` (plural) array argument:
```ruby
UserAgentParser::Parser.new(patterns_paths: [UserAgentParser::DefaultPatternsPath, '/some/path/to/regexes.yaml'])
```

## Command line tool

The gem incldes a `user_agent_parser` bin command which will read from
Expand Down
4 changes: 2 additions & 2 deletions lib/user_agent_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module UserAgentParser
DefaultPatternsPath = File.join(File.dirname(__FILE__), '../vendor/uap-core/regexes.yaml')

# Parse the given +user_agent_string+, returning a +UserAgent+
def self.parse(user_agent_string, options = {})
Parser.new(options).parse(user_agent_string)
def self.parse(user_agent_string, **args)
Parser.new(**args).parse(user_agent_string)
end
end
49 changes: 27 additions & 22 deletions lib/user_agent_parser/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

module UserAgentParser
class Parser
extend Gem::Deprecate

FAMILY_REPLACEMENT_KEYS = %w[
family_replacement
v1_replacement
Expand All @@ -22,11 +24,13 @@ class Parser

private_constant :FAMILY_REPLACEMENT_KEYS, :OS_REPLACEMENT_KEYS

attr_reader :patterns_path
attr_reader :patterns_paths

def initialize(patterns_path: nil, patterns_paths: [])
@patterns_paths = [patterns_path, *patterns_paths].compact
@patterns_paths = [UserAgentParser::DefaultPatternsPath] if @patterns_paths.empty?

def initialize(options = {})
@patterns_path = options[:patterns_path] || UserAgentParser::DefaultPatternsPath
@ua_patterns, @os_patterns, @device_patterns = load_patterns(patterns_path)
@ua_patterns, @os_patterns, @device_patterns = load_patterns(@patterns_paths)
end

def parse(user_agent)
Expand All @@ -35,9 +39,23 @@ def parse(user_agent)
parse_ua(user_agent, os, device)
end

def patterns_path
patterns_paths.first
end
deprecate :patterns_path, :patterns_paths, 2022, 12

private

def load_patterns(path)
def load_patterns(patterns_paths)
patterns_paths.each_with_object([[], [], []]) do |path, patterns|
ua_patterns, os_patterns, device_patterns = load_patterns_file(path)
patterns[0] += ua_patterns
patterns[1] += os_patterns
patterns[2] += device_patterns
end
end

def load_patterns_file(path)
yml = begin
YAML.load_file(path, freeze: true)
rescue ArgumentError
Expand Down Expand Up @@ -88,24 +106,11 @@ def parse_device(user_agent)
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
def first_pattern_match(patterns, value)
patterns.each do |pattern|
return [pattern, pattern[:regex].match(value)] if pattern[:regex].match?(value)
end
nil
end

def user_agent_from_pattern_match(pattern, match, os = nil, device = nil)
Expand Down
6 changes: 3 additions & 3 deletions spec/custom_regexes.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
user_agent_parsers:
- regex: '.*'
- regex: 'Any.*'
family_replacement: 'Custom browser'
v1_replacement: '1'
v2_replacement: '2'
v3_replacement: '3'
v4_replacement: '4'

os_parsers:
- regex: '.*'
- regex: 'Any.*'
os_replacement: 'Custom OS'
os_v1_replacement: '1'
os_v2_replacement: '2'

device_parsers:
- regex: '.*'
- regex: 'Any.*'
device_replacement: 'Custom device'
17 changes: 17 additions & 0 deletions spec/other_regexes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
user_agent_parsers:
- regex: 'Other.*'
family_replacement: 'Other browser'
v1_replacement: '1'
v2_replacement: '2'
v3_replacement: '3'
v4_replacement: '4'

os_parsers:
- regex: 'Other.*'
os_replacement: 'Other OS'
os_v1_replacement: '1'
os_v2_replacement: '2'

device_parsers:
- regex: 'Other.*'
device_replacement: 'Other device'
23 changes: 22 additions & 1 deletion spec/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def custom_patterns_path
File.join(File.dirname(__FILE__), 'custom_regexes.yaml')
end

def other_patterns_path
File.join(File.dirname(__FILE__), 'other_regexes.yaml')
end

describe '::parse' do
it 'parses a UA' do
ua = UserAgentParser.parse('Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3')
Expand All @@ -69,10 +73,13 @@ def custom_patterns_path
end

describe '#initialize with a custom patterns path' do
it 'uses the custom patterns' do
it 'accepts a single patterns_path string' do
parser = UserAgentParser::Parser.new(patterns_path: custom_patterns_path)
ua = parser.parse('Any user agent string')

_(parser.patterns_paths).must_equal([custom_patterns_path])
_(parser.patterns_path).must_equal(custom_patterns_path)

_(ua.family).must_equal('Custom browser')
_(ua.version.major).must_equal('1')
_(ua.version.minor).must_equal('2')
Expand All @@ -85,6 +92,20 @@ def custom_patterns_path

_(ua.device.family).must_equal('Custom device')
end

it 'accepts patterns_paths array' do
patterns_paths = [custom_patterns_path, other_patterns_path]
parser = UserAgentParser::Parser.new(patterns_paths: patterns_paths)

_(parser.patterns_paths).must_equal(patterns_paths)
_(parser.patterns_path).must_equal(custom_patterns_path)

ua = parser.parse('Any user agent string')
oua = parser.parse('Other user agent string')

_(ua.family).must_equal('Custom browser')
_(oua.family).must_equal('Other browser')
end
end

describe '#parse' do
Expand Down

0 comments on commit 4a29630

Please sign in to comment.