device_detector 0.9.1 → 1.0.4
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +49 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -9
- data/CHANGELOG.md +16 -3
- data/README.md +7 -9
- data/Rakefile +19 -13
- data/device_detector.gemspec +1 -0
- data/lib/device_detector.rb +32 -28
- data/lib/device_detector/bot.rb +2 -2
- data/lib/device_detector/client.rb +3 -2
- data/lib/device_detector/device.rb +44 -21
- data/lib/device_detector/memory_cache.rb +26 -19
- data/lib/device_detector/metadata_extractor.rb +7 -8
- data/lib/device_detector/model_extractor.rb +3 -3
- data/lib/device_detector/name_extractor.rb +2 -2
- data/lib/device_detector/os.rb +121 -111
- data/lib/device_detector/parser.rb +22 -9
- data/lib/device_detector/version.rb +3 -1
- data/lib/device_detector/version_extractor.rb +2 -3
- data/regexes/bots.yml +840 -20
- data/regexes/client/browser_engine.yml +11 -2
- data/regexes/client/browsers.yml +909 -108
- data/regexes/client/feed_readers.yml +38 -2
- data/regexes/client/libraries.yml +76 -2
- data/regexes/client/mediaplayers.yml +25 -5
- data/regexes/client/mobile_apps.yml +167 -2
- data/regexes/client/pim.yml +10 -1
- data/regexes/device/cameras.yml +1 -1
- data/regexes/device/car_browsers.yml +7 -3
- data/regexes/device/consoles.yml +3 -3
- data/regexes/device/mobiles.yml +10123 -465
- data/regexes/device/portable_media_player.yml +4 -6
- data/regexes/device/televisions.yml +18 -4
- data/regexes/oss.yml +115 -21
- data/regexes/vendorfragments.yml +6 -2
- data/spec/device_detector/concrete_user_agent_spec.rb +16 -17
- data/spec/device_detector/detector_fixtures_spec.rb +51 -11
- data/spec/device_detector/device_spec.rb +28 -48
- data/spec/device_detector/memory_cache_spec.rb +60 -28
- data/spec/device_detector/model_extractor_spec.rb +3 -3
- data/spec/device_detector/version_extractor_spec.rb +5 -6
- data/spec/device_detector_spec.rb +60 -69
- data/spec/fixtures/client/browser.yml +1785 -262
- data/spec/fixtures/client/feed_reader.yml +47 -35
- data/spec/fixtures/client/library.yml +112 -3
- data/spec/fixtures/client/mediaplayer.yml +32 -37
- data/spec/fixtures/client/mobile_app.yml +193 -6
- data/spec/fixtures/client/pim.yml +37 -18
- data/spec/fixtures/detector/bots.yml +1426 -118
- data/spec/fixtures/detector/camera.yml +36 -10
- data/spec/fixtures/detector/car_browser.yml +64 -3
- data/spec/fixtures/detector/console.yml +80 -26
- data/spec/fixtures/detector/desktop.yml +2222 -1589
- data/spec/fixtures/detector/feature_phone.yml +151 -42
- data/spec/fixtures/detector/feed_reader.yml +186 -121
- data/spec/fixtures/detector/mediaplayer.yml +113 -39
- data/spec/fixtures/detector/mobile_apps.yml +366 -21
- data/spec/fixtures/detector/phablet.yml +2597 -570
- data/spec/fixtures/detector/portable_media_player.yml +41 -16
- data/spec/fixtures/detector/smart_display.yml +8 -5
- data/spec/fixtures/detector/smart_speaker.yml +55 -0
- data/spec/fixtures/detector/smartphone-1.yml +5468 -5010
- data/spec/fixtures/detector/smartphone-10.yml +9977 -0
- data/spec/fixtures/detector/smartphone-11.yml +9891 -0
- data/spec/fixtures/detector/smartphone-12.yml +9906 -0
- data/spec/fixtures/detector/smartphone-13.yml +9920 -0
- data/spec/fixtures/detector/smartphone-14.yml +2662 -0
- data/spec/fixtures/detector/smartphone-2.yml +5213 -4635
- data/spec/fixtures/detector/smartphone-3.yml +5082 -4533
- data/spec/fixtures/detector/smartphone-4.yml +6806 -2625
- data/spec/fixtures/detector/smartphone-5.yml +9914 -0
- data/spec/fixtures/detector/smartphone-6.yml +9962 -0
- data/spec/fixtures/detector/smartphone-7.yml +9899 -0
- data/spec/fixtures/detector/smartphone-8.yml +9931 -0
- data/spec/fixtures/detector/smartphone-9.yml +9899 -0
- data/spec/fixtures/detector/smartphone.yml +5225 -4652
- data/spec/fixtures/detector/tablet-1.yml +4691 -4191
- data/spec/fixtures/detector/tablet-2.yml +9800 -71
- data/spec/fixtures/detector/tablet-3.yml +9959 -0
- data/spec/fixtures/detector/tablet-4.yml +4528 -0
- data/spec/fixtures/detector/tablet.yml +4664 -4177
- data/spec/fixtures/detector/tv.yml +3399 -1048
- data/spec/fixtures/detector/unknown.yml +1017 -977
- data/spec/fixtures/detector/wearable.yml +61 -0
- data/spec/fixtures/device/camera.yml +4 -3
- data/spec/fixtures/device/car_browser.yml +9 -2
- data/spec/fixtures/device/console.yml +15 -14
- data/spec/fixtures/parser/oss.yml +284 -2
- data/spec/fixtures/parser/vendorfragments.yml +8 -2
- metadata +50 -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
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
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
|
-
|
51
|
-
|
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 = "#{
|
12
|
-
|
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[
|
19
|
-
|
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
|
data/lib/device_detector/os.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
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
|
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
|
-
|
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
|
-
|
45
|
+
raise NotImplementedError
|
36
46
|
end
|
37
47
|
|
38
48
|
def filepaths
|
39
49
|
filenames.map do |filename|
|
40
|
-
[
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|