device_detector 0.7.0 → 0.8.0

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.
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']