File: parser.rb

package info (click to toggle)
ruby-device-detector 1.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,820 kB
  • sloc: ruby: 3,650; makefile: 5
file content (118 lines) | stat: -rw-r--r-- 2,990 bytes parent folder | download
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
# frozen_string_literal: true

class DeviceDetector
  class Parser
    ROOT = File.expand_path('../..', __dir__)

    REGEX_CACHE = ::DeviceDetector::MemoryCache.new({})
    private_constant :REGEX_CACHE

    def initialize(user_agent)
      @user_agent = user_agent
    end

    attr_reader :user_agent

    def name
      from_cache(['name', self.class.name, user_agent]) do
        NameExtractor.new(user_agent, regex_meta).call
      end
    end

    def full_version
      from_cache(['full_version', self.class.name, user_agent]) do
        VersionExtractor.new(user_agent, regex_meta).call
      end
    end

    private

    def regex_meta
      @regex_meta ||= matching_regex || {}
    end

    def matching_regex
      from_cache([self.class.name, user_agent]) do
        regexes.find { |r| user_agent =~ r[:regex] }
      end
    end

    def regexes
      @regexes ||= regexes_for(filepaths)
    end

    def filenames
      raise NotImplementedError
    end

    def filepaths
      filenames.map do |filename|
        [filename.to_sym, File.join(ROOT, 'regexes', filename)]
      end
    end

    def regexes_for(file_paths)
      REGEX_CACHE.get_or_set(file_paths) do
        load_regexes(file_paths).flat_map { |path, regex| parse_regexes(path, regex) }
      end
    end

    def load_regexes(file_paths)
      file_paths.map do |path, full_path|
        object = YAML.load_file(full_path)
        object = rewrite_device_object!(object) if device_yml_file?(full_path)
        object = rewrite_vendor_object!(object) if vendor_yml_file?(full_path)

        [path, symbolize_keys!(object)]
      end
    end

    def device_yml_file?(file_path)
      file_path.include?('/regexes/device/')
    end

    def vendor_yml_file?(file_path)
      file_path.include?('/regexes/vendorfragments')
    end

    def rewrite_vendor_object!(object)
      object.map { |key, values| values.map { |v| { 'regex_name' => key, 'regex' => v } } }.flatten
    end

    def rewrite_device_object!(object)
      object.map { |key, value| [key, { 'regex_name' => key }.merge!(value)] }.to_h
    end

    def symbolize_keys!(object)
      case object
      when Array
        object.map! { |v| symbolize_keys!(v) }
      when Hash
        keys = object.keys
        keys.each do |k|
          object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String)
        end
      end
      object
    end

    def parse_regexes(path, raw_regexes)
      raw_regexes.map do |meta|
        raise "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String

        meta[:regex] = build_regex(meta[:regex])
        meta[:versions].each { |v| v[:regex] = build_regex(v[:regex]) } if meta.key?(:versions)
        meta[:path] = path
        meta
      end
    end

    def build_regex(src)
      Regexp.new("(?:^|[^A-Z0-9_-]|[^A-Z0-9-]_|sprd-|MZ-)(?:#{src})", Regexp::IGNORECASE)
    end

    def from_cache(key, &block)
      DeviceDetector.cache.get_or_set(key, &block)
    end
  end
end