device_detector 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/Rakefile +12 -6
  4. data/device_detector.gemspec +1 -1
  5. data/lib/device_detector.rb +65 -4
  6. data/lib/device_detector/client.rb +6 -6
  7. data/lib/device_detector/device.rb +62 -31
  8. data/lib/device_detector/memory_cache.rb +1 -1
  9. data/lib/device_detector/metadata_extractor.rb +4 -13
  10. data/lib/device_detector/model_extractor.rb +10 -1
  11. data/lib/device_detector/name_extractor.rb +1 -1
  12. data/lib/device_detector/os.rb +141 -5
  13. data/lib/device_detector/parser.rb +23 -12
  14. data/lib/device_detector/version.rb +1 -1
  15. data/lib/device_detector/version_extractor.rb +1 -1
  16. data/regexes/bots.yml +36 -1
  17. data/regexes/{browser_engines.yml → client/browser_engine.yml} +4 -1
  18. data/regexes/{browsers.yml → client/browsers.yml} +31 -27
  19. data/regexes/{feed_readers.yml → client/feed_readers.yml} +0 -1
  20. data/regexes/{libraries.yml → client/libraries.yml} +1 -1
  21. data/regexes/{mediaplayers.yml → client/mediaplayers.yml} +1 -1
  22. data/regexes/{mobile_apps.yml → client/mobile_apps.yml} +0 -1
  23. data/regexes/{pim.yml → client/pim.yml} +1 -1
  24. data/regexes/{devices → device}/cameras.yml +1 -1
  25. data/regexes/{devices → device}/car_browsers.yml +0 -1
  26. data/regexes/{devices → device}/consoles.yml +0 -1
  27. data/regexes/{devices → device}/mobiles.yml +193 -49
  28. data/regexes/{devices/portable_media_players.yml → device/portable_media_player.yml} +0 -1
  29. data/regexes/{devices → device}/televisions.yml +0 -3
  30. data/regexes/oss.yml +105 -102
  31. data/regexes/vendorfragments.yml +70 -0
  32. data/spec/device_detector/bot_fixtures_spec.rb +30 -0
  33. data/spec/device_detector/client_fixtures_spec.rb +31 -0
  34. data/spec/device_detector/concrete_user_agent_spec.rb +41 -1
  35. data/spec/device_detector/detector_fixtures_spec.rb +56 -0
  36. data/spec/device_detector/device_fixtures_spec.rb +36 -0
  37. data/spec/device_detector/device_spec.rb +3 -3
  38. data/spec/device_detector/memory_cache_spec.rb +1 -1
  39. data/spec/device_detector/model_extractor_spec.rb +7 -7
  40. data/spec/device_detector/os_fixtures_spec.rb +26 -0
  41. data/spec/device_detector/version_extractor_spec.rb +10 -10
  42. data/spec/device_detector_spec.rb +1 -1
  43. data/spec/fixtures/client/browser.yml +986 -0
  44. data/spec/fixtures/client/feed_reader.yml +180 -0
  45. data/spec/fixtures/client/library.yml +78 -0
  46. data/spec/fixtures/client/mediaplayer.yml +144 -0
  47. data/spec/fixtures/client/mobile_app.yml +24 -0
  48. data/spec/fixtures/client/pim.yml +90 -0
  49. data/spec/fixtures/detector/bots.yml +716 -0
  50. data/spec/fixtures/detector/camera.yml +91 -0
  51. data/spec/fixtures/detector/car_browser.yml +19 -0
  52. data/spec/fixtures/detector/console.yml +253 -0
  53. data/spec/fixtures/detector/desktop.yml +4568 -0
  54. data/spec/fixtures/detector/feature_phone.yml +719 -0
  55. data/spec/fixtures/detector/feed_reader.yml +462 -0
  56. data/spec/fixtures/detector/mediaplayer.yml +141 -0
  57. data/spec/fixtures/detector/mobile_apps.yml +32 -0
  58. data/spec/fixtures/detector/phablet.yml +1095 -0
  59. data/spec/fixtures/detector/portable_media_player.yml +145 -0
  60. data/spec/fixtures/detector/smart_display.yml +19 -0
  61. data/spec/fixtures/detector/smartphone.yml +28673 -0
  62. data/spec/fixtures/detector/tablet.yml +13201 -0
  63. data/spec/fixtures/detector/tv.yml +1380 -0
  64. data/spec/fixtures/detector/unknown.yml +3536 -0
  65. data/spec/fixtures/device/camera.yml +18 -0
  66. data/spec/fixtures/device/car_browser.yml +6 -0
  67. data/spec/fixtures/device/console.yml +72 -0
  68. data/spec/fixtures/parser/bots.yml +2055 -0
  69. data/spec/fixtures/parser/oss.yml +607 -0
  70. data/spec/fixtures/parser/vendorfragments.yml +156 -0
  71. data/spec/spec_helper.rb +6 -2
  72. metadata +84 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89802f3d1a2276198bb17c6889848941b692e962
4
- data.tar.gz: e4ee7d45dd2e0307a5e547f1f77e0e5f4f99e725
3
+ metadata.gz: 98cdaab8265efd95799dcb71e4588cef6c300528
4
+ data.tar.gz: f43a7ef253ca782d672d8856d1b1214c6b300cfb
5
5
  SHA512:
6
- metadata.gz: 5c52585792a08a2bd4e9ee96439af347898113872f888119e60f0096239fec438d058c6782c89c4ed52e42cc1414d0776219b2b350730888b71ccf19e05cab65
7
- data.tar.gz: 988d986ea34e7a9fa96efdf3414ea97eba1931d4b74e7db18b794dca732b2787907c8323f91f54d6102b7cb345367961881d812e6137690fd979ae208b3f3f52
6
+ metadata.gz: 579c755cd334bb9dbb4f23402de2f7982d7b62f1d6a852524834ddc28f09a099e849d8a7c3111e89dfbed39ece1eb2467a07cb333dc8b7f1b458cc209eda1bc1
7
+ data.tar.gz: 84c58c57130167b1eab5162aaf4aa000e62ec1f38fd3e550f802602f483e85c1a4ff969fe31d7bc30fff2cfc14e3c8243a90dfde32495257cb4bb0070bc509b5
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in device_detector.gemspec
4
4
  gemspec
5
+
6
+ if RUBY_VERSION >= "2.0" && defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
7
+ gem 'byebug'
8
+ end
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
 
4
+ $:.unshift 'lib'
5
+ require 'device_detector'
6
+
4
7
  Rake::TestTask.new do |t|
5
8
  t.pattern = 'spec/**/*_spec.rb'
6
9
  t.libs.push 'spec'
@@ -9,14 +12,12 @@ end
9
12
  task default: :test
10
13
 
11
14
  task :detectable_names do
12
- require 'device_detector'
13
- bot_names = DeviceDetector::Bot.new.send(:regexes).map { |r| r['name'] }.uniq
15
+ bot_names = DeviceDetector::Bot.new.send(:regexes).map { |r| r['name'] }.uniq.sort_by { |n| n.downcase }
14
16
  bot_names.delete('$1')
15
- client_names = DeviceDetector::Client.new.send(:regexes).map { |r| r['name'] }.uniq
17
+ client_names = DeviceDetector::Client.new.send(:regexes).map { |r| r['name'] }.uniq.sort_by { |n| n.downcase }
16
18
  client_names.delete('$1')
17
- device_filepaths = DeviceDetector::Device.new.send(:filepaths)
18
- device_regexes = DeviceDetector::Device.load_regexes(device_filepaths)
19
- device_names = device_regexes.flat_map { |dn| dn.keys }.sort.uniq
19
+ device_regexes = DeviceDetector::Device.new.send(:load_regexes)
20
+ device_names = device_regexes.flat_map { |dn| dn.keys }.uniq.sort_by { |n| n.downcase }
20
21
 
21
22
  today = Date.today.strftime
22
23
 
@@ -38,3 +39,8 @@ task :detectable_names do
38
39
  puts
39
40
  end
40
41
 
42
+ desc 'update regex database from piwik project'
43
+ task :update_regexes do
44
+ top = File.expand_path('..', __FILE__)
45
+ system "curl -s -L https://api.github.com/repos/piwik/device-detector/tarball/master | tar xzvf - --strip-components 1 --include */regexes/*.yml -C #{top}"
46
+ end
@@ -22,5 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency 'minitest'
24
24
  spec.add_development_dependency 'rake'
25
- spec.add_development_dependency 'pry', '~> 0.10'
25
+ spec.add_development_dependency 'pry', '>= 0.10'
26
26
  end
@@ -1,8 +1,5 @@
1
1
  require 'yaml'
2
2
 
3
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- $LOAD_PATH.unshift(File.dirname(__FILE__))
5
-
6
3
  require 'device_detector/version'
7
4
  require 'device_detector/metadata_extractor'
8
5
  require 'device_detector/version_extractor'
@@ -44,7 +41,55 @@ class DeviceDetector
44
41
  end
45
42
 
46
43
  def device_type
47
- device.type
44
+ t = device.type
45
+
46
+ if t.nil? && android_tablet_fragment?
47
+ t = 'tablet'
48
+ end
49
+
50
+ if t.nil? && android_mobile_fragment?
51
+ t = 'smartphone'
52
+ end
53
+
54
+ # Android up to 3.0 was designed for smartphones only. But as 3.0,
55
+ # which was tablet only, was published too late, there were a
56
+ # bunch of tablets running with 2.x With 4.0 the two trees were
57
+ # merged and it is for smartphones and tablets
58
+ #
59
+ # So were are expecting that all devices running Android < 2 are
60
+ # smartphones Devices running Android 3.X are tablets. Device type
61
+ # of Android 2.X and 4.X+ are unknown
62
+ if t.nil? && os.short_name == 'AND' && os.full_version && !os.full_version.empty?
63
+ if os.full_version < '2'
64
+ t = 'smartphone'
65
+ elsif os.full_version >= '3' && os.full_version < '4'
66
+ t = 'tablet'
67
+ end
68
+ end
69
+
70
+ # All detected feature phones running android are more likely a smartphone
71
+ if t == 'feature phone' && os.family == 'Android'
72
+ t = 'smartphone'
73
+ end
74
+
75
+ # According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
76
+ # Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the
77
+ # UA string, the computer has touch capability, and is running Windows 8 (or later).
78
+ # This UA string will be transmitted on a touch-enabled system running Windows 8 (RT)
79
+ #
80
+ # As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that
81
+ # all Windows 8 touch devices are tablets.
82
+ if t.nil? && touch_enabled? &&
83
+ (os.short_name == 'WRT' || (os.short_name == 'WIN' && os.full_version && os.full_version >= '8'))
84
+ t = 'tablet'
85
+ end
86
+
87
+ # set device type to desktop for all devices running a desktop os that were not detected as an other device type
88
+ if t.nil? && os.desktop?
89
+ t = 'desktop'
90
+ end
91
+
92
+ t
48
93
  end
49
94
 
50
95
  def known?
@@ -104,4 +149,20 @@ class DeviceDetector
104
149
  @os ||= OS.new(user_agent)
105
150
  end
106
151
 
152
+ def android_tablet_fragment?
153
+ user_agent =~ build_regex('Android; Tablet;')
154
+ end
155
+
156
+ def android_mobile_fragment?
157
+ user_agent =~ build_regex('Android; Mobile;')
158
+ end
159
+
160
+ def touch_enabled?
161
+ user_agent =~ build_regex('Touch')
162
+ end
163
+
164
+ def build_regex(src)
165
+ Regexp.new('(?:^|[^A-Z0-9\_\-])(?:' + src + ')', Regexp::IGNORECASE)
166
+ end
167
+
107
168
  end
@@ -9,12 +9,12 @@ class DeviceDetector
9
9
 
10
10
  def filenames
11
11
  [
12
- 'browsers.yml',
13
- 'feed_readers.yml',
14
- 'libraries.yml',
15
- 'mediaplayers.yml',
16
- 'mobile_apps.yml',
17
- 'pim.yml'
12
+ 'client/feed_readers.yml',
13
+ 'client/mobile_apps.yml',
14
+ 'client/mediaplayers.yml',
15
+ 'client/pim.yml',
16
+ 'client/browsers.yml',
17
+ 'client/libraries.yml',
18
18
  ]
19
19
  end
20
20
  end
@@ -1,6 +1,21 @@
1
1
  class DeviceDetector
2
2
  class Device < Parser
3
3
 
4
+ # order is relevant for testing with fixtures
5
+ DEVICE_NAMES = [
6
+ 'desktop',
7
+ 'smartphone',
8
+ 'tablet',
9
+ 'feature phone',
10
+ 'console',
11
+ 'tv',
12
+ 'car browser',
13
+ 'smart display',
14
+ 'camera',
15
+ 'portable media player',
16
+ 'phablet'
17
+ ]
18
+
4
19
  def known?
5
20
  regex_meta.any?
6
21
  end
@@ -10,50 +25,66 @@ class DeviceDetector
10
25
  end
11
26
 
12
27
  def type
13
- regex_meta['device']
28
+ hbbtv? ? 'tv' : regex_meta[:device]
14
29
  end
15
30
 
16
31
  private
17
32
 
18
- # The order of files is relevant.
19
- # portable_media_players has to come before mobiles because of devices like the iPod
20
- # which would otherwise be detected as iPhones
21
- # televisions.yml works best at the end
33
+ # The order of files needs to be the same as the order of device
34
+ # parser classes used in the piwik project.
22
35
  def filenames
23
36
  [
24
- 'devices/cameras.yml',
25
- 'devices/car_browsers.yml',
26
- 'devices/consoles.yml',
27
- 'devices/portable_media_players.yml',
28
- 'devices/mobiles.yml',
29
- 'devices/televisions.yml',
37
+ 'device/televisions.yml',
38
+ 'device/consoles.yml',
39
+ 'device/car_browsers.yml',
40
+ 'device/cameras.yml',
41
+ 'device/portable_media_player.yml',
42
+ 'device/mobiles.yml',
30
43
  ]
31
44
  end
32
45
 
33
- def parse_regexes(raw_regexes, device = nil)
34
- raw_regexes.map { |base, nest|
46
+ def matching_regex
47
+ from_cache([self.class.name, user_agent]) do
48
+ regex_list = hbbtv? ? regexes_for_hbbtv : regexes_other
49
+ regex = regex_list.find { |r| user_agent =~ r[:regex] }
50
+ if regex && regex[:models]
51
+ model_regex = regex[:models].find { |m| user_agent =~ m[:regex]}
52
+ if model_regex
53
+ regex = regex.merge(:regex_model => model_regex[:regex], :model => model_regex[:model])
54
+ regex[:device] = model_regex[:device] if model_regex.key?(:device)
55
+ regex.delete(:models)
56
+ end
57
+ end
58
+ regex
59
+ end
60
+ end
61
+
62
+ def hbbtv?
63
+ @regex_hbbtv ||= build_regex('HbbTV/([1-9]{1}(\.[0-9]{1}){1,2})')
64
+ user_agent =~ @regex_hbbtv
65
+ end
35
66
 
36
- if !nest.nil? && nest.key?('models')
37
- default_device = nest['device']
38
- parse_regexes(nest['models'], default_device)
39
- else
40
- regex =
41
- case base
42
- when Hash
43
- base
44
- when String
45
- nest
46
- else
47
- fail "#{filenames.join(', ')} regexes are either malformed or format has changes."
48
- end
67
+ def regexes_for_hbbtv
68
+ regexes.select { |r| r[:path] == :'device/televisions.yml' }
69
+ end
49
70
 
50
- regex['device'] ||= device
51
- regex['regex'] = Regexp.new(regex['regex'])
71
+ def regexes_other
72
+ regexes.select { |r| r[:path] != :'device/televisions.yml' }
73
+ end
52
74
 
53
- regex
75
+ def parse_regexes(path, raw_regexes)
76
+ raw_regexes.map do |brand, meta|
77
+ fail "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String
78
+ meta[:regex] = build_regex(meta[:regex])
79
+ if meta.key?(:models)
80
+ meta[:models].each do |model|
81
+ fail "invalid model spec: #{model.inspect}" unless model[:regex].is_a? String
82
+ model[:regex] = build_regex(model[:regex])
83
+ end
54
84
  end
55
-
56
- }.flatten
85
+ meta[:path] = path
86
+ meta
87
+ end
57
88
  end
58
89
 
59
90
  end
@@ -42,7 +42,7 @@ class DeviceDetector
42
42
  attr_reader :lock
43
43
 
44
44
  def purge_cache
45
- key_size = data.keys.size
45
+ key_size = data.size
46
46
 
47
47
  if key_size >= max_keys
48
48
  # always remove about 1/3 of keys to reduce garbage collecting
@@ -14,23 +14,14 @@ class DeviceDetector
14
14
 
15
15
  def extract_metadata
16
16
  user_agent.match(regex) do |match_data|
17
- replace_metadata_string_with(match_data)
17
+ metadata_string.gsub(/\$(\d)/) {
18
+ match_data[$1.to_i].to_s
19
+ }.strip
18
20
  end
19
21
  end
20
22
 
21
- def replace_metadata_string_with(match_data)
22
- string = metadata_string
23
-
24
- 1.upto(9) do |index|
25
- matched_data = String(match_data[index])
26
- string = string.gsub(/\$#{index}/, matched_data)
27
- end
28
-
29
- string.strip
30
- end
31
-
32
23
  def regex
33
- @regex ||= regex_meta['regex']
24
+ @regex ||= regex_meta[:regex]
34
25
  end
35
26
 
36
27
  end
@@ -1,10 +1,19 @@
1
1
  class DeviceDetector
2
2
  class ModelExtractor < MetadataExtractor
3
3
 
4
+ def call
5
+ s = super.to_s.gsub('_',' ').strip
6
+ s.empty? ? nil : s
7
+ end
8
+
4
9
  private
5
10
 
6
11
  def metadata_string
7
- String(regex_meta['model'])
12
+ String(regex_meta[:model])
13
+ end
14
+
15
+ def regex
16
+ @regex ||= regex_meta[:regex_model] || regex_meta[:regex]
8
17
  end
9
18
 
10
19
  end
@@ -12,7 +12,7 @@ class DeviceDetector
12
12
  private
13
13
 
14
14
  def metadata_string
15
- regex_meta['name']
15
+ regex_meta[:name]
16
16
  end
17
17
 
18
18
  end
@@ -1,16 +1,152 @@
1
+ require 'set'
2
+
1
3
  class DeviceDetector
2
4
  class OS < Parser
3
5
 
6
+ def name
7
+ os_info[:name]
8
+ end
9
+
10
+ def short_name
11
+ os_info[:short]
12
+ end
13
+
14
+ def family
15
+ os_info[:family]
16
+ end
17
+
18
+ def desktop?
19
+ DESKTOP_OSS.include?(family)
20
+ end
21
+
4
22
  def full_version
5
- raw_version = super
23
+ raw_version = super.to_s.split('_').join('.')
24
+ raw_version == '' ? nil : raw_version
25
+ end
26
+
27
+ private
6
28
 
7
- # This solution won't scale, but for now it will do
8
- if raw_version && name == 'Mac'
9
- raw_version.split('_').join('.')
29
+ def os_info
30
+ from_cache(['os_info', self.class.name, user_agent]) do
31
+ os_name = NameExtractor.new(user_agent, regex_meta).call
32
+ if os_name && short = DOWNCASED_OPERATING_SYSTEMS[os_name.downcase]
33
+ os_name = OPERATING_SYSTEMS[short]
34
+ else
35
+ short = 'UNK'
36
+ end
37
+ { name: os_name, short: short, family: FAMILY_TO_OS[short] }
10
38
  end
11
39
  end
12
40
 
13
- private
41
+ DESKTOP_OSS = Set.new(['AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS'])
42
+
43
+ # OS short codes mapped to long names
44
+ 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
+ 'MDR' => 'Mandriva',
77
+ 'SMG' => 'MeeGo',
78
+ 'MCD' => 'MocorDroid',
79
+ 'MIN' => 'Mint',
80
+ 'MLD' => 'MildWild',
81
+ 'MOR' => 'MorphOS',
82
+ 'NBS' => 'NetBSD',
83
+ 'WII' => 'Nintendo',
84
+ 'NDS' => 'Nintendo Mobile',
85
+ 'OS2' => 'OS/2',
86
+ 'T64' => 'OSF1',
87
+ 'OBS' => 'OpenBSD',
88
+ 'PSP' => 'PlayStation Portable',
89
+ 'PS3' => 'PlayStation',
90
+ 'RHT' => 'Red Hat',
91
+ 'ROS' => 'RISC OS',
92
+ 'RZD' => 'RazoDroiD',
93
+ 'SAB' => 'Sabayon',
94
+ 'SSE' => 'SUSE',
95
+ 'SAF' => 'Sailfish OS',
96
+ 'SLW' => 'Slackware',
97
+ 'SOS' => 'Solaris',
98
+ 'SYL' => 'Syllable',
99
+ 'SYM' => 'Symbian',
100
+ 'SYS' => 'Symbian OS',
101
+ 'S40' => 'Symbian OS Series 40',
102
+ 'S60' => 'Symbian OS Series 60',
103
+ 'SY3' => 'Symbian^3',
104
+ 'TIZ' => 'Tizen',
105
+ 'UBT' => 'Ubuntu',
106
+ 'WTV' => 'WebTV',
107
+ 'WIN' => 'Windows',
108
+ 'WCE' => 'Windows CE',
109
+ 'WMO' => 'Windows Mobile',
110
+ 'WPH' => 'Windows Phone',
111
+ 'WRT' => 'Windows RT',
112
+ 'XBX' => 'Xbox',
113
+ 'XBT' => 'Xubuntu',
114
+ 'YNS' => 'YunOs',
115
+ 'IOS' => 'iOS',
116
+ 'POS' => 'palmOS',
117
+ 'WOS' => 'webOS'
118
+ }
119
+
120
+ DOWNCASED_OPERATING_SYSTEMS = OPERATING_SYSTEMS.each_with_object({}){|(short,long),h| h[long.downcase] = short}
121
+
122
+ OS_FAMILIES = {
123
+ 'Android' => ['AND', 'CYN', 'RZD', 'MLD', 'MCD'],
124
+ 'AmigaOS' => ['AMG', 'MOR'],
125
+ 'Apple TV' => ['ATV'],
126
+ 'BlackBerry' => ['BLB', 'QNX'],
127
+ 'Brew' => ['BMP'],
128
+ 'BeOS' => ['BEO', 'HAI'],
129
+ 'Chrome OS' => ['COS'],
130
+ 'Firefox OS' => ['FOS'],
131
+ 'Gaming Console' => ['WII', 'PS3'],
132
+ 'Google TV' => ['GTV'],
133
+ 'IBM' => ['OS2'],
134
+ 'iOS' => ['IOS'],
135
+ 'RISC OS' => ['ROS'],
136
+ 'GNU/Linux' => ['LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'YNS', 'SAF'],
137
+ 'Mac' => ['MAC'],
138
+ 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'],
139
+ 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG'],
140
+ 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'],
141
+ 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'],
142
+ 'WebTV' => ['WTV'],
143
+ 'Windows' => ['WIN'],
144
+ 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT']
145
+ }
146
+
147
+ FAMILY_TO_OS = OS_FAMILIES.each_with_object({}) do |(family,oss),h|
148
+ oss.each{|os| h[os] = family}
149
+ end
14
150
 
15
151
  def filenames
16
152
  ['oss.yml']