browserino 3.0.1 → 4.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c6f36b794e0bff81d17dbf38d47cbed631945a6
4
- data.tar.gz: 98d2b7537b25b4a93de0a7f277ca0d92eae46d5d
3
+ metadata.gz: 1bdd2af440a3b1476900a3cdd36d2905660053c6
4
+ data.tar.gz: 0c7a2151e6ec79341ba5dda81eceddef2e8128ed
5
5
  SHA512:
6
- metadata.gz: c4b0dac61fd41e1dbf75d57592cc6f12aeadfb80212be8c84012fb12bff41e44a9f0cfa97407fad98fe72ca9c41ceefc134cb379986e13a0dbf3eec9daf70b30
7
- data.tar.gz: 6c785f94a902086de7f5479ee59f4f468fd60dc2fa79cb6ef9bfd1c366441f38688d1fe62628982b87728d92c25735f48f6fcee74d4a54ae9adbd9b3289a2650
6
+ metadata.gz: fb4d367fb7cfd3bd9cf637998de4a41549e18f20aaa0f14e007cb28883aff70b66e4beb75f757d99fe299b61a77db0c4322b90da74691e30677c524428a1c3eb
7
+ data.tar.gz: c13adf9ce004e08c5504b07367c5f362446ca06dbc6abe372872f703cca9694e8b3dd6f8fd7900970dea265a917e5d37ca9e951aef94176f241afaa4eba3f82c
data/.rubocop.yml CHANGED
@@ -7,3 +7,13 @@ AllCops:
7
7
 
8
8
  Documentation:
9
9
  Enabled: false
10
+
11
+ Lint/AmbiguousRegexpLiteral:
12
+ Enabled: false
13
+
14
+ Style/RegexpLiteral:
15
+ Enabled: false
16
+
17
+ Metrics/MethodLength:
18
+ CountComments: false
19
+ Max: 15
data/.travis.yml CHANGED
@@ -1,10 +1,9 @@
1
1
  language: ruby
2
- cache: bundler
3
2
  rvm:
4
- - 2.3.1
5
- - 2.3.0
6
- - 2.2.1
7
- - 2.1.0
8
3
  - 2.0.0
9
- before_install: gem install bundler -v 1.10.5
10
- script: 'bundle exec rspec spec'
4
+ - 2.1.0
5
+ - 2.2.0
6
+ - 2.3.0
7
+ - 2.4.0
8
+ before_install: gem install bundler
9
+ script: bundle exec rspec spec
data/bin/browserino CHANGED
@@ -1,60 +1,28 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'bundler/setup'
3
+ require 'pry'
4
4
  require_relative '../lib/browserino'
5
5
 
6
- aliasses = [:fb, :ddg, :ff, :internet_explorer, :win, :osx, :bb]
6
+ props = Browserino.parse(ARGV[0].dup).properties
7
7
 
8
- case ARGV[0]
9
- when 'parse', '-p'
10
- if ARGV[1]
11
- puts Browserino.parse(ARGV[1]).to_console_s
12
- else
13
- puts ' Usage: browserino parse [ua]'
14
- puts ' * ua: the user agent string you want to parse'
15
- end
16
- when 'list', '-l'
17
- ARGV[1] ||= 'info'
18
- type = (ARGV[1].to_s.gsub(/[\s\-]/, '_')).to_sym
19
- if type == :info
20
- puts ' Usage: browserino list [subject]'
21
- puts ' * subject: the type you want to display'
22
- puts ' * all - list everything that is supported'
23
- puts ' * bots - list supported bots'
24
- puts ' * browsers - list supported browsers'
25
- puts ' * social-media - list supported social media'
26
- puts ' * search-engines - list supported search engines'
27
- puts ' * consoles - list supported consoles'
28
- puts ' * libraries - list supported libraries'
29
- puts ' * operating-systems - list supported operating systems'
30
- else
31
- res = {}
32
- res[:bots] = Browserino::Core::SUPPORTED[:bots] - aliasses if type == :all || type == :bots
33
- res[:browsers] = Browserino::Core::SUPPORTED[:browsers] - aliasses if type == :all || type == :browsers
34
- res[:consoles] = Browserino::Core::SUPPORTED[:consoles] - aliasses if type == :all || type == :consoles
35
- res[:libraries] = Browserino::Core::SUPPORTED[:libraries] - aliasses if type == :all || type == :libraries
36
- res[:social_media] = Browserino::Core::SUPPORTED[:social_media] - aliasses if type == :all || type == :social_media
37
- res[:search_engines] = Browserino::Core::SUPPORTED[:search_engines] - aliasses if type == :all || type == :search_engines
38
- res[:operating_systems] = Browserino::Core::SUPPORTED[:operating_systems] - aliasses if type == :all || type == :operating_systems
8
+ parsed = props.each_with_object({}) do |(prop, val), store|
9
+ store[prop] = val.to_s
10
+ end
39
11
 
40
- out = res.each_with_object([]) do |(type, supported_names), arr|
41
- arr << " \e[1;32;49m#{type.to_s.tr('_', ' ')}:\e[0m"
42
- supported_names.each { |name| arr << " \e[1;95;49m- #{name}\e[0m" }
43
- end
44
- puts out
45
- end
46
- when 'console', '-c'
47
- require 'pry'
48
- puts "Starting Browserino #{Browserino::VERSION} on Ruby #{RUBY_VERSION}"
49
- Pry.start
12
+ max_key_len = parsed.keys.map(&:size).max
13
+ max_val_len = parsed.values.map(&:size).max
14
+ spacing = 2
15
+ spacer = ' ' * spacing
50
16
 
51
- else
52
- puts ' Usage: browserino [command] [args]'
53
- puts "\n"
54
- puts ' Calling browserino with anything other than these options will show this help'
55
- puts ' Entering a [command] without [args] will show help for that command if it requires [args]'
56
- puts "\n"
57
- puts ' * parse or -p - parse a user agent'
58
- puts ' * list or -l - list supported entities'
59
- puts ' * console or -c - enter a console'
17
+ out = parsed.map do |pair|
18
+ key = [spacer, pair[0], spacer].join.ljust(max_key_len + (spacing * 2))
19
+ val = [spacer, pair[1], spacer].join.ljust(max_val_len + (spacing * 2))
20
+ tmp = [key, val].join '|'
21
+ "|#{tmp}|\n"
60
22
  end
23
+
24
+ row = '+' * (out.map(&:size).max - 1) + "\n"
25
+
26
+ out[0] = "#{row}#{out[0]}"
27
+ out[-1] = "#{out[-1]}#{row}"
28
+ puts out.join("#{row}")
data/bin/console CHANGED
@@ -1,15 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'browserino'
5
4
  require 'pry'
5
+ require 'browserino'
6
6
 
7
- ua = 'Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.2; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.40.1 Safari/534.6 TouchPad/1.0'
8
- @agent = Browserino.parse(ua)
9
-
10
- puts "> @agent variable available parsed with: '#{ua}'\n\n"
11
- puts "> You can create a fresh object by using\n"
12
- puts "> Browserino.parse([string] ua)\n\n"
13
- puts "> ua - The user agent you'd actually want to parse as a string"
7
+ @client = Browserino.parse ARGV[0].dup || 'Mozilla/5.0 (Macintosh; Intel Mac\
8
+ OS X 10_11_2) AppleWebKit/601.3.9\
9
+ (KHTML, l ike Gecko) Version/9.0.\
10
+ 2 Safari/601.3.9'
14
11
 
15
12
  Pry.start
data/browserino.gemspec CHANGED
@@ -8,7 +8,6 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Browserino::VERSION
9
9
  spec.authors = ["Sidney Liebrand"]
10
10
  spec.email = ["sidneyliebrand@gmail.com"]
11
-
12
11
  spec.summary = %q{A browser identification gem with command line and Rails (>= 3.2.0) integration}
13
12
  spec.homepage = "https://sidofc.github.io/projects/browserino/"
14
13
  spec.license = "MIT"
@@ -18,12 +17,9 @@ Gem::Specification.new do |spec|
18
17
  spec.executables = ['browserino']
19
18
  spec.require_paths = ["lib"]
20
19
 
21
- spec.add_development_dependency "json"
22
- spec.add_development_dependency "bundler", "~> 1.10"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "term-ansicolor"
20
+ spec.add_development_dependency "bundler", "~> 1.14"
21
+ spec.add_development_dependency "rake", "~> 12.0"
25
22
  spec.add_development_dependency "rspec"
26
- spec.add_development_dependency "tins"
27
23
  spec.add_development_dependency "coveralls"
28
24
  spec.add_development_dependency "pry"
29
25
  end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+ module Browserino
3
+ class Client
4
+ attr_reader :property_names
5
+
6
+ def initialize(props = {}, like = nil)
7
+ @property_names = props.is_a?(Hash) && props.keys || []
8
+ @like = like if like
9
+ # properties are added as methods that will each be defined in a specific
10
+ # order below. First, seperate static value methods from procs,
11
+ # procs will be able to call methods in this instances' context
12
+ # therefore we need to define static methods before procs
13
+
14
+ # for each static property:
15
+ # -- define a method that returns it's property value
16
+ # -- define a question method that returns it's property value or
17
+ # checks version against supplied value if truthy
18
+ define_simple_methods! props
19
+
20
+ # for each proc property:
21
+ # -- define a method that returns it's instance evalled block value
22
+ # -- define a question method that returns it's instance evalled block
23
+ # value or checks version against supplied value if truthy
24
+ define_proc_methods! props
25
+
26
+ # for each of #name, #engine and #platform, use their results as
27
+ # methods names, this will create a method #firefox? for the output
28
+ # of a #name # => :firefox for example.
29
+ define_name_result_methods!
30
+
31
+ # finally, add labels and their aliasses into the mix
32
+ # none of these methods should override an existing method
33
+ # therefore we check it's existence using defined?
34
+ define_label_methods!
35
+ end
36
+
37
+ def properties
38
+ @properties ||= @property_names.each_with_object({}) do |prop, result|
39
+ result[prop] = send prop
40
+ end
41
+ end
42
+
43
+ def like
44
+ @like ||= self
45
+ end
46
+
47
+ def like?(sym = nil, opts = {})
48
+ opts = sym if sym.is_a? Hash
49
+ must = []
50
+ must << like.is?(sym) if sym
51
+ must << like.version?(opts[:version]) if opts[:version]
52
+
53
+ must.any? && must.all?
54
+ end
55
+
56
+ def x64?
57
+ @x64_cache ||= architecture == :x64
58
+ end
59
+
60
+ def x32?
61
+ @x32_cache ||= architecture == :x32
62
+ end
63
+
64
+ # check the type of a Client
65
+ def type?(sym)
66
+ return type == sym if sym.is_a? Symbol
67
+ type == sym.to_sym
68
+ end
69
+
70
+ # the catch all method, anything it can ask as question will respond
71
+ # otherwise nil is returned, this is also true when supplying a version
72
+ def is?(sym, opts = {})
73
+ return send "#{sym}?", opts[:version] if opts[:version]
74
+ send "#{sym}?"
75
+ end
76
+
77
+ def ===(other)
78
+ return false unless name
79
+
80
+ case other
81
+ when Regexp then other =~ name
82
+ when String then other.to_sym == name
83
+ when Symbol, Browserino::Client then other == name
84
+ else false
85
+ end
86
+ end
87
+
88
+ def ==(other)
89
+ self === other
90
+ end
91
+
92
+ def =~(other)
93
+ self === other
94
+ end
95
+
96
+ def to_str
97
+ to_s
98
+ end
99
+
100
+ def to_json(*args)
101
+ @json_cache ||= properties.map do |prop, val|
102
+ final = val.is_a?(Version) && val.full || val
103
+ [prop, final]
104
+ end.to_h.to_json(*args)
105
+ end
106
+
107
+ def to_s
108
+ @str_cache ||= [:name, :engine, :platform].map do |prop|
109
+ name = send prop
110
+ ver = version if prop == :name
111
+ ver = send "#{prop}_version" if ver.nil?
112
+
113
+ [[name], [name, (ver.major if ver > '0.0.0')].compact.join.to_sym]
114
+ end.flatten.uniq.join(' ').gsub(/\s{2,}/, ' ').strip
115
+ end
116
+
117
+ def to_hash
118
+ properties
119
+ end
120
+
121
+ def to_h
122
+ properties
123
+ end
124
+
125
+ def to_a
126
+ properties.to_a
127
+ end
128
+
129
+ # scary, I know, but a falsy value is all we need to return if some
130
+ # property isn't known or true as any property can be defined on the Client
131
+ def method_missing(_, *__, &___)
132
+ nil
133
+ end
134
+
135
+ # always respond to missing, read method_missing comment
136
+ def respond_to_missing?(_, *__, &___)
137
+ true
138
+ end
139
+
140
+ private
141
+
142
+ def define_simple_methods!(props)
143
+ props.reject { |val| val.respond_to? :call }.each do |name, value|
144
+ define_singleton_method(name) { value }
145
+ define_singleton_method("#{name}?") do |val = nil, **opts|
146
+ values = [value, *Browserino.config.aliasses[value]]
147
+ return values.include? val if val && !opts[:version]
148
+ if val && opts[:version]
149
+ ver_res = send(name == :name ? :version : "#{name}_version")
150
+ return (ver_res == opts[:version]) && values.include?(val)
151
+ end
152
+ return value > 0 if value.is_a? Version
153
+ value && true
154
+ end
155
+ end
156
+ end
157
+
158
+ def define_proc_methods!(props)
159
+ props.select { |_, val| val.respond_to? :call }.each do |name, value|
160
+ result = instance_eval(&value)
161
+ define_singleton_method(name) { result }
162
+ end
163
+ end
164
+
165
+ def define_name_result_methods!
166
+ [:name, :engine, :platform].each do |prop|
167
+ result = send prop
168
+ ver_res = version if prop == :name
169
+ ver_res = send("#{prop}_version") if ver_res.nil?
170
+
171
+ # for each of the props:
172
+ # -- define a question method using the value of prop
173
+ # (ex: name # => firefox # => "firefox?")
174
+ # -- when supplied with a value, check it against {prop_res}_version
175
+ # -- when called without argument, return result
176
+ define_singleton_method("#{result}?") do |value = nil|
177
+ return ver_res == value if value
178
+ result && true
179
+ end
180
+
181
+ # for each of the aliasses found:
182
+ # -- define a question method using the current alias
183
+ # -- when supplied with a value, check it against {prop_res}_version
184
+ # -- when called without argument, return result
185
+ Browserino.config.aliasses[result].each do |alt|
186
+ define_singleton_method("#{alt}?") do |value = nil|
187
+ return ver_res == value if value
188
+ result && true
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ def define_label_methods!
195
+ property_names.select { |name| name =~ /label/i }.each do |prop|
196
+ next unless (result = send(prop))
197
+
198
+ ver_type = prop.to_s.gsub /_?label/, ''
199
+ ver_res = version if ver_type.empty?
200
+ ver_res = send("#{ver_type}_version") unless prop == :label
201
+
202
+ define_singleton_method("#{result}?") do |value = nil|
203
+ return ver_res == value if value
204
+ result && true
205
+ end
206
+
207
+ Browserino.config.aliasses[result].each do |alt|
208
+ define_singleton_method("#{alt}?") do |value = nil|
209
+ return ver_res == value if value
210
+ result && true
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ module Browserino
3
+ class Config < Options
4
+ def define(&block)
5
+ instance_eval(&block)
6
+ return unless identities.any? && identities.all?
7
+
8
+ identities.each do |identity|
9
+ properties << identity.properties.keys
10
+ types << identity.type
11
+ names << identity.name
12
+ end
13
+
14
+ properties.flatten!.uniq!
15
+ types.uniq!
16
+ names.uniq!
17
+ end
18
+
19
+ def label(name, **opts)
20
+ return false unless opts[:for]
21
+ opts[:name] ||= name
22
+ labels[opts[:for]] << opts
23
+ end
24
+
25
+ def filter(*props, &block)
26
+ props << :global unless props.any?
27
+ props.each { |prop| filters[prop] << block }
28
+ end
29
+
30
+ def smart_match(prop, **options)
31
+ smart_matchers[prop] = options if options[:with]
32
+ end
33
+
34
+ def match(rgxp = nil, **opts, &block)
35
+ rgxp, opts = [nil, rgxp] if rgxp.is_a? Hash
36
+ opts = @tmp_defaults.merge opts if @tmp_defaults.is_a? Hash
37
+
38
+ if rgxp && opts[:like]
39
+ identities.unshift with_alias(rgxp, opts, &block)
40
+ elsif rgxp
41
+ identities << Identity.new(rgxp, opts, &block)
42
+ else
43
+ global_identities.unshift Identity.new(&block)
44
+ end
45
+ end
46
+
47
+ def alias_for(name, *names)
48
+ aliasses[name] += names
49
+ end
50
+
51
+ def before_parse(&block)
52
+ @options[:before_parse] << block if block
53
+ @options[:before_parse]
54
+ end
55
+
56
+ def preset(props, &block)
57
+ @tmp_defaults = props
58
+ instance_eval(&block)
59
+ @tmp_defaults = nil
60
+ end
61
+
62
+ def like(tmp, &block)
63
+ preset like: tmp.to_sym, &block
64
+ end
65
+
66
+ def validators(&block)
67
+ preset type: :validator, &block
68
+ end
69
+
70
+ def browsers(&block)
71
+ preset type: :browser, &block
72
+ end
73
+
74
+ def bots(&block)
75
+ preset type: :bot, &block
76
+ end
77
+
78
+ def libraries(&block)
79
+ preset type: :library, &block
80
+ end
81
+
82
+ def with_alias(pattern, **opts, &block)
83
+ id = identities.select { |id| id == opts[:like] }.first
84
+
85
+ raise "No alias found for: #{opts[:like] || 'nil'}" unless id
86
+
87
+ Identity.new pattern, id.properties.merge(opts), &block
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ Browserino.config.define do
3
+ # aliasses will be defined after a Client has been initialized
4
+ # only the aliasses matching the Client will be defined
5
+ alias_for :firefox, :ff
6
+ alias_for :windows, :win
7
+ alias_for :macintosh, :mac, :osx, :macos
8
+ alias_for :blackberry, :bb
9
+ alias_for :ie, :internet_explorer
10
+ alias_for :facebook, :fb
11
+ alias_for :duckduckgo, :ddg
12
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ Browserino.config.define do
3
+ # executes before parsing the user agent, 's' in this case is the actual
4
+ # user agent string in full glory. Do manipulations as you wish, I'm
5
+ # using it to successfully strip lies from the user agent and to sometimes
6
+ # simplify a word e.g. 'AppleWebKit' => 'webkit'
7
+
8
+ # the returned result will be used as the user agent to parse so make
9
+ # sure to return the final ua at the end
10
+ before_parse do |ua|
11
+ ua = ua.gsub(%r{applewebkit}i, 'webkit').gsub %r{(Mozilla/[\d\.]+)}i, ''
12
+ ua = ua.gsub(%r{chrome|safari}i, '').gsub('OPR', 'opera') if ua =~ %r{OPR}
13
+ ua = ua.gsub %r{9\.80}, '' if ua =~ %r{opera}i
14
+ ua = ua.gsub %r{webkit/}i, '' if ua =~ %r{presto}i
15
+ ua = ua.gsub %r{(?:ms)?ie}i, '' if ua =~ %r{rv:}i
16
+ ua = ua.gsub %r{android|linux}i, '' if ua =~ %r{tizen}i
17
+ ua = ua.gsub %r{linux}i, '' if ua =~ %r{android|s(unos|olaris)|w(eb)?os}i
18
+ ua = ua.gsub %r{x11}i, '' if ua =~ %r{bsd|s(unos|olaris)}i
19
+ ua = ua.gsub %r{windows\snt}i, '' if ua =~ %r{windows\sphone}i
20
+ ua = ua.gsub %r{rv:}i, '' if ua =~ %r{servo}i
21
+ ua = ua.gsub %r{mac\sos\sx}i, '' if ua =~ %r{ip(?:[ao]d|hone)}i
22
+ ua = ua.gsub %r{msie}i, '' if ua =~ %r{huaweisymantecspider}i
23
+ ua
24
+ end
25
+
26
+ # after an identity is found, it's values are filtered in two stages
27
+ # first, filters will parse all statically defined values (e.g.) no regexp
28
+ # or block within matchers, after that, smart matcher patterns will be
29
+ # conditionally added and parsed with the previously collected values and
30
+ # then also filtered
31
+
32
+ # this is a global filter, multiple can be defined and they will all run
33
+ # before any named filters
34
+ filter do |value|
35
+ case value
36
+ when TrueClass, FalseClass, NilClass, Proc then value
37
+ when %r{\A[\d_\.]+\z}i then value.to_s.strip.tr('_', '.')
38
+ else value.to_s.downcase.strip.gsub(%r{[\s-]+}i, '_').to_sym
39
+ end
40
+ end
41
+
42
+ # this is a named filter, it defines the same filter for 3 properties
43
+ # multiple name filters for the same property can be created, they will be
44
+ # executed in order of addition
45
+ filter :version, :engine_version, :platform_version do |value|
46
+ Browserino::Version.new value
47
+ end
48
+
49
+ filter :platform do |value|
50
+ value = :ios if value =~ %r{ip(?:[ao]d|hone)}i
51
+ value = :webos if value =~ %r{w(?:eb)?os}i
52
+ value = :linux if value =~ %r{ubuntu|x11}i
53
+ value = :solaris if value =~ %r{s(?:unos|olaris)}i
54
+ value = :macintosh if value =~ %r{mac_os_x}i
55
+ value
56
+ end
57
+
58
+ filter :architecture do |value|
59
+ value = :x64 if value && value =~ %r{(?:x86_|amd|wow)?64|i686}i
60
+ value = :x32 if value && value != :x64
61
+ value
62
+ end
63
+
64
+ filter :mobile do |value|
65
+ !value.to_s.strip.empty?
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ Browserino.config.define do
3
+ # labels will be defined before a client has been initialized
4
+ # they will not be filtered before being injected into the Client
5
+
6
+ # labels will be matched against values of #name, #engine and #platform and
7
+ # will create #label, #engine_label and #platform_label properties
8
+ # respectively. These enjoy all the features that regular defined properties
9
+ # in for instance methods enjoy, such as automatic question methods like
10
+ # client.platform_label? :mavericks
11
+ label :android, for: :android, range: '1'..'1.4.9'
12
+ label :cupcake, for: :android, range: '1.5'..'1.9.9'
13
+ label :eclair, for: :android, range: '2'..'2.1'
14
+ label :froyo, for: :android, range: '2.2'..'2.2.3'
15
+ label :gingerbread, for: :android, range: '2.2.4'..'2.3.7'
16
+ label :honeycomb, for: :android, range: '3'..'3.2.6'
17
+ label :ice_cream_sandwich, for: :android, range: '4'..'4.0.4'
18
+ label :jelly_bean, for: :android, range: '4.1'..'4.3.1'
19
+ label :kitkat, for: :android, range: '4.4'..'4.4.4'
20
+ label :lollipop, for: :android, range: '5'..'5.1.1'
21
+ label :marshmallow, for: :android, range: '6'..'6.1.1'
22
+ label :nougat, for: :android, range: '7'..'7.1.1'
23
+
24
+ label :cheetah, for: :macintosh, range: '10.0'..'10.0.9'
25
+ label :puma, for: :macintosh, range: '10.1'..'10.1.9'
26
+ label :jaguar, for: :macintosh, range: '10.2'..'10.2.9'
27
+ label :panther, for: :macintosh, range: '10.3'..'10.3.9'
28
+ label :tiger, for: :macintosh, range: '10.4'..'10.4.9'
29
+ label :leopard, for: :macintosh, range: '10.5'..'10.5.9'
30
+ label :snow_leopard, for: :macintosh, range: '10.6'..'10.6.9'
31
+ label :lion, for: :macintosh, range: '10.7'..'10.7.9'
32
+ label :mountain_lion, for: :macintosh, range: '10.8'..'10.8.9'
33
+ label :mavericks, for: :macintosh, range: '10.9'..'10.9.9'
34
+ label :yosemite, for: :macintosh, range: '10.10'..'10.10.9'
35
+ label :el_capitan, for: :macintosh, range: '10.11'..'10.11.9'
36
+ label :sierra, for: :macintosh, range: '10.12'..'10.12.9'
37
+
38
+ label :windows_dos, for: :windows, range: '3.1'..'4.0'
39
+ label :windows_2000, for: :windows, range: '5.0'..'5.0.9'
40
+ label :windows_xp, for: :windows, range: '5.1'..'5.2.9'
41
+ label :windows_vista, for: :windows, range: '6.0'..'6.0.9'
42
+ label :windows_7, for: :windows, range: '6.1'..'6.1.9'
43
+ label :windows_8, for: :windows, range: '6.2'..'6.3'
44
+ label :windows_10, for: :windows, range: '10.0'..'10.0.9'
45
+ end