device_detector 1.0.1 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +49 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +5 -10
  5. data/CHANGELOG.md +14 -1
  6. data/README.md +6 -6
  7. data/Rakefile +20 -13
  8. data/device_detector.gemspec +1 -0
  9. data/lib/device_detector.rb +30 -26
  10. data/lib/device_detector/bot.rb +2 -2
  11. data/lib/device_detector/client.rb +3 -2
  12. data/lib/device_detector/device.rb +46 -20
  13. data/lib/device_detector/memory_cache.rb +26 -19
  14. data/lib/device_detector/metadata_extractor.rb +7 -8
  15. data/lib/device_detector/model_extractor.rb +3 -3
  16. data/lib/device_detector/name_extractor.rb +2 -2
  17. data/lib/device_detector/os.rb +121 -111
  18. data/lib/device_detector/parser.rb +22 -9
  19. data/lib/device_detector/version.rb +3 -1
  20. data/lib/device_detector/version_extractor.rb +2 -3
  21. data/regexes/bots.yml +442 -19
  22. data/regexes/client/browser_engine.yml +7 -1
  23. data/regexes/client/browsers.yml +773 -103
  24. data/regexes/client/feed_readers.yml +14 -8
  25. data/regexes/client/libraries.yml +43 -2
  26. data/regexes/client/mediaplayers.yml +21 -5
  27. data/regexes/client/mobile_apps.yml +131 -1
  28. data/regexes/client/pim.yml +6 -1
  29. data/regexes/device/cameras.yml +1 -1
  30. data/regexes/device/car_browsers.yml +7 -3
  31. data/regexes/device/consoles.yml +3 -3
  32. data/regexes/device/mobiles.yml +11365 -791
  33. data/regexes/device/notebooks.yml +114 -0
  34. data/regexes/device/portable_media_player.yml +2 -2
  35. data/regexes/device/televisions.yml +17 -3
  36. data/regexes/oss.yml +115 -47
  37. data/regexes/vendorfragments.yml +6 -2
  38. data/spec/device_detector/concrete_user_agent_spec.rb +16 -17
  39. data/spec/device_detector/detector_fixtures_spec.rb +30 -35
  40. data/spec/device_detector/device_spec.rb +28 -48
  41. data/spec/device_detector/memory_cache_spec.rb +60 -28
  42. data/spec/device_detector/model_extractor_spec.rb +3 -3
  43. data/spec/device_detector/version_extractor_spec.rb +5 -6
  44. data/spec/device_detector_spec.rb +49 -78
  45. data/spec/fixtures/client/browser.yml +1521 -406
  46. data/spec/fixtures/client/feed_reader.yml +39 -51
  47. data/spec/fixtures/client/library.yml +72 -11
  48. data/spec/fixtures/client/mediaplayer.yml +29 -40
  49. data/spec/fixtures/client/mobile_app.yml +172 -32
  50. data/spec/fixtures/client/pim.yml +32 -19
  51. data/spec/fixtures/detector/bots.yml +854 -19
  52. data/spec/fixtures/detector/camera.yml +22 -2
  53. data/spec/fixtures/detector/car_browser.yml +60 -0
  54. data/spec/fixtures/detector/console.yml +43 -3
  55. data/spec/fixtures/detector/desktop.yml +2860 -1527
  56. data/spec/fixtures/detector/feature_phone.yml +69 -1
  57. data/spec/fixtures/detector/feed_reader.yml +158 -130
  58. data/spec/fixtures/detector/mediaplayer.yml +113 -39
  59. data/spec/fixtures/detector/mobile_apps.yml +262 -89
  60. data/spec/fixtures/detector/phablet.yml +3444 -663
  61. data/spec/fixtures/detector/portable_media_player.yml +57 -0
  62. data/spec/fixtures/detector/smart_speaker.yml +55 -0
  63. data/spec/fixtures/detector/smartphone-1.yml +4739 -4765
  64. data/spec/fixtures/detector/smartphone-10.yml +9973 -0
  65. data/spec/fixtures/detector/smartphone-11.yml +10015 -0
  66. data/spec/fixtures/detector/smartphone-12.yml +9897 -0
  67. data/spec/fixtures/detector/smartphone-13.yml +9912 -0
  68. data/spec/fixtures/detector/smartphone-14.yml +9935 -0
  69. data/spec/fixtures/detector/smartphone-15.yml +6595 -0
  70. data/spec/fixtures/detector/smartphone-16.yml +10021 -0
  71. data/spec/fixtures/detector/smartphone-17.yml +9408 -0
  72. data/spec/fixtures/detector/smartphone-2.yml +4265 -4238
  73. data/spec/fixtures/detector/smartphone-3.yml +4487 -4391
  74. data/spec/fixtures/detector/smartphone-4.yml +4210 -4179
  75. data/spec/fixtures/detector/smartphone-5.yml +5794 -2901
  76. data/spec/fixtures/detector/smartphone-6.yml +10114 -0
  77. data/spec/fixtures/detector/smartphone-7.yml +9975 -0
  78. data/spec/fixtures/detector/smartphone-8.yml +9897 -0
  79. data/spec/fixtures/detector/smartphone-9.yml +9880 -0
  80. data/spec/fixtures/detector/smartphone.yml +4152 -4048
  81. data/spec/fixtures/detector/tablet-1.yml +3997 -3991
  82. data/spec/fixtures/detector/tablet-2.yml +6820 -1935
  83. data/spec/fixtures/detector/tablet-3.yml +9968 -0
  84. data/spec/fixtures/detector/tablet-4.yml +7113 -0
  85. data/spec/fixtures/detector/tablet.yml +3789 -3804
  86. data/spec/fixtures/detector/tv.yml +3889 -1495
  87. data/spec/fixtures/detector/unknown.yml +45 -179
  88. data/spec/fixtures/detector/wearable.yml +61 -0
  89. data/spec/fixtures/device/camera.yml +4 -3
  90. data/spec/fixtures/device/car_browser.yml +9 -2
  91. data/spec/fixtures/device/console.yml +15 -14
  92. data/spec/fixtures/device/notebook.yml +7 -0
  93. data/spec/fixtures/parser/oss.yml +177 -0
  94. data/spec/fixtures/parser/vendorfragments.yml +6 -0
  95. metadata +57 -7
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
4
  class MemoryCache
3
-
4
5
  DEFAULT_MAX_KEYS = 5000
6
+ STORES_NIL_VALUE = :__is_nil__
5
7
 
6
8
  attr_reader :data, :max_keys, :lock
7
9
  private :lock
@@ -15,41 +17,46 @@ class DeviceDetector
15
17
  def set(key, value)
16
18
  lock.synchronize do
17
19
  purge_cache
18
- data[String(key)] = value
20
+ # convert nil values into symbol so we know a value is present
21
+ cache_value = value.nil? ? STORES_NIL_VALUE : value
22
+ data[String(key)] = cache_value
23
+ value
19
24
  end
20
25
  end
21
26
 
22
27
  def get(key)
23
- data[String(key)]
24
- end
25
-
26
- def key?(string_key)
27
- data.key?(string_key)
28
+ value, _hit = get_hit(key)
29
+ value
28
30
  end
29
31
 
30
32
  def get_or_set(key, value = nil)
31
33
  string_key = String(key)
32
34
 
33
- if key?(string_key)
34
- get(string_key)
35
- else
36
- value = yield if block_given?
37
- set(string_key, value)
38
- end
35
+ result, hit = get_hit(string_key)
36
+ return result if hit
37
+
38
+ value = yield if block_given?
39
+ set(string_key, value)
39
40
  end
40
41
 
41
42
  private
42
43
 
44
+ def get_hit(key)
45
+ value = data[String(key)]
46
+ is_hit = !value.nil? || value == STORES_NIL_VALUE
47
+ value = nil if value == STORES_NIL_VALUE
48
+ [value, is_hit]
49
+ end
50
+
43
51
  def purge_cache
44
52
  key_size = data.size
45
53
 
46
- if key_size >= max_keys
47
- # always remove about 1/3 of keys to reduce garbage collecting
48
- amount_of_keys = key_size / 3
54
+ return if key_size < max_keys
49
55
 
50
- data.keys.first(amount_of_keys).each { |key| data.delete(key) }
51
- end
52
- end
56
+ # always remove about 1/3 of keys to reduce garbage collecting
57
+ amount_of_keys = key_size / 3
53
58
 
59
+ data.keys.first(amount_of_keys).each { |key| data.delete(key) }
60
+ end
54
61
  end
55
62
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
4
  class MetadataExtractor < Struct.new(:user_agent, :regex_meta)
3
-
4
5
  def call
5
6
  regex_meta.any? ? extract_metadata : nil
6
7
  end
@@ -8,22 +9,20 @@ class DeviceDetector
8
9
  private
9
10
 
10
11
  def metadata_string
11
- message = "#{self.name} (a child of MetadataExtractor) must implement the '#{__method__}' method."
12
- fail NotImplementedError, message
12
+ message = "#{name} (a child of MetadataExtractor) must implement the '#{__method__}' method."
13
+ raise NotImplementedError, message
13
14
  end
14
15
 
15
16
  def extract_metadata
16
17
  user_agent.match(regex) do |match_data|
17
- metadata_string.gsub(/\$(\d)/) {
18
- match_data[$1.to_i].to_s
19
- }.strip
18
+ metadata_string.gsub(/\$(\d)/) do
19
+ match_data[Regexp.last_match(1).to_i].to_s
20
+ end.strip
20
21
  end
21
22
  end
22
23
 
23
24
  def regex
24
25
  @regex ||= regex_meta[:regex]
25
26
  end
26
-
27
27
  end
28
28
  end
29
-
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
4
  class ModelExtractor < MetadataExtractor
3
-
4
5
  def call
5
- s = super.to_s.gsub('_',' ').strip
6
+ s = super.to_s.gsub('_', ' ').strip
6
7
  s = s.gsub(/ TD$/i, '')
7
8
 
8
9
  return nil if s == 'Build'
@@ -19,6 +20,5 @@ class DeviceDetector
19
20
  def regex
20
21
  @regex ||= regex_meta[:regex_model] || regex_meta[:regex]
21
22
  end
22
-
23
23
  end
24
24
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
4
  class NameExtractor < MetadataExtractor
3
-
4
5
  def call
5
6
  if /\$[0-9]/ =~ metadata_string
6
7
  extract_metadata
@@ -14,6 +15,5 @@ class DeviceDetector
14
15
  def metadata_string
15
16
  regex_meta[:name]
16
17
  end
17
-
18
18
  end
19
19
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  class DeviceDetector
4
6
  class OS < Parser
5
-
6
7
  def name
7
8
  os_info[:name]
8
9
  end
@@ -38,125 +39,134 @@ class DeviceDetector
38
39
  end
39
40
  end
40
41
 
41
- DESKTOP_OSS = Set.new(['AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS'])
42
+ DESKTOP_OSS = Set.new(
43
+ [
44
+ 'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS'
45
+ ]
46
+ )
42
47
 
43
48
  # OS short codes mapped to long names
44
49
  OPERATING_SYSTEMS = {
45
- 'AIX' => 'AIX',
46
- 'AND' => 'Android',
47
- 'AMG' => 'AmigaOS',
48
- 'ATV' => 'Apple TV',
49
- 'ARL' => 'Arch Linux',
50
- 'BTR' => 'BackTrack',
51
- 'SBA' => 'Bada',
52
- 'BEO' => 'BeOS',
53
- 'BLB' => 'BlackBerry OS',
54
- 'QNX' => 'BlackBerry Tablet OS',
55
- 'BMP' => 'Brew',
56
- 'CES' => 'CentOS',
57
- 'COS' => 'Chrome OS',
58
- 'CYN' => 'CyanogenMod',
59
- 'DEB' => 'Debian',
60
- 'DFB' => 'DragonFly',
61
- 'FED' => 'Fedora',
62
- 'FOS' => 'Firefox OS',
63
- 'BSD' => 'FreeBSD',
64
- 'GNT' => 'Gentoo',
65
- 'GTV' => 'Google TV',
66
- 'HPX' => 'HP-UX',
67
- 'HAI' => 'Haiku OS',
68
- 'IRI' => 'IRIX',
69
- 'INF' => 'Inferno',
70
- 'KNO' => 'Knoppix',
71
- 'KBT' => 'Kubuntu',
72
- 'LIN' => 'GNU/Linux',
73
- 'LBT' => 'Lubuntu',
74
- 'VLN' => 'VectorLinux',
75
- 'MAC' => 'Mac',
76
- 'MAE' => 'Maemo',
77
- 'MDR' => 'Mandriva',
78
- 'SMG' => 'MeeGo',
79
- 'MCD' => 'MocorDroid',
80
- 'MIN' => 'Mint',
81
- 'MLD' => 'MildWild',
82
- 'MOR' => 'MorphOS',
83
- 'NBS' => 'NetBSD',
84
- 'MTK' => 'MTK / Nucleus',
85
- 'WII' => 'Nintendo',
86
- 'NDS' => 'Nintendo Mobile',
87
- 'OS2' => 'OS/2',
88
- 'T64' => 'OSF1',
89
- 'OBS' => 'OpenBSD',
90
- 'PSP' => 'PlayStation Portable',
91
- 'PS3' => 'PlayStation',
92
- 'RHT' => 'Red Hat',
93
- 'ROS' => 'RISC OS',
94
- 'REM' => 'Remix OS',
95
- 'RZD' => 'RazoDroiD',
96
- 'SAB' => 'Sabayon',
97
- 'SSE' => 'SUSE',
98
- 'SAF' => 'Sailfish OS',
99
- 'SLW' => 'Slackware',
100
- 'SOS' => 'Solaris',
101
- 'SYL' => 'Syllable',
102
- 'SYM' => 'Symbian',
103
- 'SYS' => 'Symbian OS',
104
- 'S40' => 'Symbian OS Series 40',
105
- 'S60' => 'Symbian OS Series 60',
106
- 'SY3' => 'Symbian^3',
107
- 'TDX' => 'ThreadX',
108
- 'TIZ' => 'Tizen',
109
- 'UBT' => 'Ubuntu',
110
- 'WTV' => 'WebTV',
111
- 'WIN' => 'Windows',
112
- 'WCE' => 'Windows CE',
113
- 'WMO' => 'Windows Mobile',
114
- 'WPH' => 'Windows Phone',
115
- 'WRT' => 'Windows RT',
116
- 'XBX' => 'Xbox',
117
- 'XBT' => 'Xubuntu',
118
- 'YNS' => 'YunOs',
119
- 'IOS' => 'iOS',
120
- 'POS' => 'palmOS',
121
- 'WOS' => 'webOS'
122
- }
123
-
124
- DOWNCASED_OPERATING_SYSTEMS = OPERATING_SYSTEMS.each_with_object({}){|(short,long),h| h[long.downcase] = short}
50
+ 'AIX' => 'AIX',
51
+ 'AND' => 'Android',
52
+ 'AMG' => 'AmigaOS',
53
+ 'ATV' => 'Apple TV',
54
+ 'ARL' => 'Arch Linux',
55
+ 'BTR' => 'BackTrack',
56
+ 'SBA' => 'Bada',
57
+ 'BEO' => 'BeOS',
58
+ 'BLB' => 'BlackBerry OS',
59
+ 'QNX' => 'BlackBerry Tablet OS',
60
+ 'BMP' => 'Brew',
61
+ 'CES' => 'CentOS',
62
+ 'COS' => 'Chrome OS',
63
+ 'CYN' => 'CyanogenMod',
64
+ 'DEB' => 'Debian',
65
+ 'DFB' => 'DragonFly',
66
+ 'FED' => 'Fedora',
67
+ 'FOS' => 'Firefox OS',
68
+ 'FIR' => 'Fire OS',
69
+ 'BSD' => 'FreeBSD',
70
+ 'GNT' => 'Gentoo',
71
+ 'GTV' => 'Google TV',
72
+ 'HPX' => 'HP-UX',
73
+ 'HAI' => 'Haiku OS',
74
+ 'IRI' => 'IRIX',
75
+ 'INF' => 'Inferno',
76
+ 'KOS' => 'KaiOS',
77
+ 'KNO' => 'Knoppix',
78
+ 'KBT' => 'Kubuntu',
79
+ 'LIN' => 'GNU/Linux',
80
+ 'LBT' => 'Lubuntu',
81
+ 'VLN' => 'VectorLinux',
82
+ 'MAC' => 'Mac',
83
+ 'MAE' => 'Maemo',
84
+ 'MDR' => 'Mandriva',
85
+ 'SMG' => 'MeeGo',
86
+ 'MCD' => 'MocorDroid',
87
+ 'MIN' => 'Mint',
88
+ 'MLD' => 'MildWild',
89
+ 'MOR' => 'MorphOS',
90
+ 'NBS' => 'NetBSD',
91
+ 'MTK' => 'MTK / Nucleus',
92
+ 'WII' => 'Nintendo',
93
+ 'NDS' => 'Nintendo Mobile',
94
+ 'OS2' => 'OS/2',
95
+ 'T64' => 'OSF1',
96
+ 'OBS' => 'OpenBSD',
97
+ 'ORD' => 'Ordissimo',
98
+ 'PSP' => 'PlayStation Portable',
99
+ 'PS3' => 'PlayStation',
100
+ 'RHT' => 'Red Hat',
101
+ 'ROS' => 'RISC OS',
102
+ 'REM' => 'Remix OS',
103
+ 'RZD' => 'RazoDroiD',
104
+ 'SAB' => 'Sabayon',
105
+ 'SSE' => 'SUSE',
106
+ 'SAF' => 'Sailfish OS',
107
+ 'SLW' => 'Slackware',
108
+ 'SOS' => 'Solaris',
109
+ 'SYL' => 'Syllable',
110
+ 'SYM' => 'Symbian',
111
+ 'SYS' => 'Symbian OS',
112
+ 'S40' => 'Symbian OS Series 40',
113
+ 'S60' => 'Symbian OS Series 60',
114
+ 'SY3' => 'Symbian^3',
115
+ 'TDX' => 'ThreadX',
116
+ 'TIZ' => 'Tizen',
117
+ 'TOS' => 'TmaxOS',
118
+ 'UBT' => 'Ubuntu',
119
+ 'WTV' => 'WebTV',
120
+ 'WIN' => 'Windows',
121
+ 'WCE' => 'Windows CE',
122
+ 'WIO' => 'Windows IoT',
123
+ 'WMO' => 'Windows Mobile',
124
+ 'WPH' => 'Windows Phone',
125
+ 'WRT' => 'Windows RT',
126
+ 'XBX' => 'Xbox',
127
+ 'XBT' => 'Xubuntu',
128
+ 'YNS' => 'YunOs',
129
+ 'IOS' => 'iOS',
130
+ 'POS' => 'palmOS',
131
+ 'WOS' => 'webOS'
132
+ }.freeze
133
+
134
+ DOWNCASED_OPERATING_SYSTEMS = OPERATING_SYSTEMS.each_with_object({}) do |(short, long), h|
135
+ h[long.downcase] = short
136
+ end
125
137
 
126
138
  OS_FAMILIES = {
127
- 'Android' => ['AND', 'CYN', 'REM', 'RZD', 'MLD', 'MCD', 'YNS'],
128
- 'AmigaOS' => ['AMG', 'MOR'],
129
- 'Apple TV' => ['ATV'],
130
- 'BlackBerry' => ['BLB', 'QNX'],
131
- 'Brew' => ['BMP'],
132
- 'BeOS' => ['BEO', 'HAI'],
133
- 'Chrome OS' => ['COS'],
134
- 'Firefox OS' => ['FOS'],
135
- 'Gaming Console' => ['WII', 'PS3'],
136
- 'Google TV' => ['GTV'],
137
- 'IBM' => ['OS2'],
138
- 'iOS' => ['IOS'],
139
- 'RISC OS' => ['ROS'],
140
- 'GNU/Linux' => ['LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF'],
141
- 'Mac' => ['MAC'],
142
- 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'],
143
- 'Real-time OS' => ['MTK', 'TDX'],
144
- 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE'],
145
- 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'],
146
- 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'],
147
- 'WebTV' => ['WTV'],
148
- 'Windows' => ['WIN'],
149
- 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT']
150
- }
151
-
152
- FAMILY_TO_OS = OS_FAMILIES.each_with_object({}) do |(family,oss),h|
153
- oss.each{|os| h[os] = family}
139
+ 'Android' => %w[AND CYN FIR REM RZD MLD MCD YNS],
140
+ 'AmigaOS' => %w[AMG MOR],
141
+ 'Apple TV' => ['ATV'],
142
+ 'BlackBerry' => %w[BLB QNX],
143
+ 'Brew' => ['BMP'],
144
+ 'BeOS' => %w[BEO HAI],
145
+ 'Chrome OS' => ['COS'],
146
+ 'Firefox OS' => %w[FOS KOS],
147
+ 'Gaming Console' => %w[WII PS3],
148
+ 'Google TV' => ['GTV'],
149
+ 'IBM' => ['OS2'],
150
+ 'iOS' => ['IOS'],
151
+ 'RISC OS' => ['ROS'],
152
+ 'GNU/Linux' => %w[LIN ARL DEB KNO MIN UBT KBT XBT LBT FED RHT VLN MDR GNT SAB SLW SSE CES BTR SAF ORD TOS],
153
+ 'Mac' => ['MAC'],
154
+ 'Mobile Gaming Console' => %w[PSP NDS XBX],
155
+ 'Real-time OS' => %w[MTK TDX],
156
+ 'Other Mobile' => %w[WOS POS SBA TIZ SMG MAE],
157
+ 'Symbian' => %w[SYM SYS SY3 S60 S40],
158
+ 'Unix' => %w[SOS AIX HPX BSD NBS OBS DFB SYL IRI T64 INF],
159
+ 'WebTV' => ['WTV'],
160
+ 'Windows' => ['WIN'],
161
+ 'Windows Mobile' => %w[WPH WMO WCE WRT WIO]
162
+ }.freeze
163
+
164
+ FAMILY_TO_OS = OS_FAMILIES.each_with_object({}) do |(family, oss), h|
165
+ oss.each { |os| h[os] = family }
154
166
  end
155
167
 
156
168
  def filenames
157
169
  ['oss.yml']
158
170
  end
159
-
160
171
  end
161
-
162
172
  end
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
- class Parser < Struct.new(:user_agent)
4
+ class Parser
5
+ ROOT = File.expand_path('../..', __dir__)
6
+
7
+ REGEX_CACHE = ::DeviceDetector::MemoryCache.new({})
8
+ private_constant :REGEX_CACHE
9
+
10
+ def initialize(user_agent)
11
+ @user_agent = user_agent
12
+ end
3
13
 
4
- ROOT = File.expand_path('../../..', __FILE__)
14
+ attr_reader :user_agent
5
15
 
6
16
  def name
7
17
  from_cache(['name', self.class.name, user_agent]) do
@@ -32,17 +42,17 @@ class DeviceDetector
32
42
  end
33
43
 
34
44
  def filenames
35
- fail NotImplementedError
45
+ raise NotImplementedError
36
46
  end
37
47
 
38
48
  def filepaths
39
49
  filenames.map do |filename|
40
- [ filename.to_sym, File.join(ROOT, 'regexes', filename) ]
50
+ [filename.to_sym, File.join(ROOT, 'regexes', filename)]
41
51
  end
42
52
  end
43
53
 
44
54
  def regexes_for(file_paths)
45
- from_cache(['regexes', self.class]) do
55
+ REGEX_CACHE.get_or_set(file_paths) do
46
56
  load_regexes(file_paths).flat_map { |path, regex| parse_regexes(path, regex) }
47
57
  end
48
58
  end
@@ -54,16 +64,20 @@ class DeviceDetector
54
64
  def symbolize_keys!(object)
55
65
  case object
56
66
  when Array
57
- object.map!{ |v| symbolize_keys!(v) }
67
+ object.map! { |v| symbolize_keys!(v) }
58
68
  when Hash
59
- object.keys.each{ |k| object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String) }
69
+ keys = object.keys
70
+ keys.each do |k|
71
+ object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String)
72
+ end
60
73
  end
61
74
  object
62
75
  end
63
76
 
64
77
  def parse_regexes(path, raw_regexes)
65
78
  raw_regexes.map do |meta|
66
- fail "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String
79
+ raise "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String
80
+
67
81
  meta[:regex] = build_regex(meta[:regex])
68
82
  meta[:path] = path
69
83
  meta
@@ -77,6 +91,5 @@ class DeviceDetector
77
91
  def from_cache(key)
78
92
  DeviceDetector.cache.get_or_set(key) { yield }
79
93
  end
80
-
81
94
  end
82
95
  end