mime-types 3.6.0 → 3.7.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.
data/lib/mime/type.rb CHANGED
@@ -94,9 +94,6 @@ class MIME::Type
94
94
  # :startdoc:
95
95
  end
96
96
 
97
- # The released version of the mime-types library.
98
- VERSION = "3.6.0"
99
-
100
97
  include Comparable
101
98
 
102
99
  # :stopdoc:
@@ -136,7 +133,8 @@ class MIME::Type
136
133
  def initialize(content_type) # :yields: self
137
134
  @friendly = {}
138
135
  @obsolete = @registered = @provisional = false
139
- @preferred_extension = @docs = @use_instead = nil
136
+ @preferred_extension = @docs = @use_instead = @__sort_priority = nil
137
+
140
138
  self.extensions = []
141
139
 
142
140
  case content_type
@@ -167,6 +165,8 @@ class MIME::Type
167
165
  self.xrefs ||= {}
168
166
 
169
167
  yield self if block_given?
168
+
169
+ update_sort_priority
170
170
  end
171
171
 
172
172
  # Indicates that a MIME type is like another type. This differs from
@@ -185,60 +185,54 @@ class MIME::Type
185
185
  # simplified type (the simplified type will be used if comparing against
186
186
  # something that can be treated as a String with #to_s). In comparisons, this
187
187
  # is done against the lowercase version of the MIME::Type.
188
+ #
189
+ # Note that this implementation of #<=> is deprecated and will be changed
190
+ # in the next major version to be the same as #priority_compare.
191
+ #
192
+ # Note that MIME::Types no longer compare against nil.
188
193
  def <=>(other)
189
- if other.nil?
190
- -1
191
- elsif other.respond_to?(:simplified)
194
+ return priority_compare(other) if other.is_a?(MIME::Type)
195
+ simplified <=> other
196
+ end
197
+
198
+ # Compares the +other+ MIME::Type using a pre-computed sort priority value,
199
+ # then the simplified representation for an alphabetical sort.
200
+ #
201
+ # For the next major version of MIME::Types, this method will become #<=> and
202
+ # #priority_compare will be removed.
203
+ def priority_compare(other)
204
+ if (cmp = __sort_priority <=> other.__sort_priority) == 0
192
205
  simplified <=> other.simplified
193
206
  else
194
- filtered = "silent" if other == :silent
195
- filtered ||= "true" if other == true
196
- filtered ||= other.to_s
197
-
198
- simplified <=> MIME::Type.simplified(filtered)
207
+ cmp
199
208
  end
200
209
  end
201
210
 
202
- # Compares the +other+ MIME::Type based on how reliable it is before doing a
203
- # normal <=> comparison. Used by MIME::Types#[] to sort types. The
204
- # comparisons involved are:
205
- #
206
- # 1. self.simplified <=> other.simplified (ensures that we
207
- # do not try to compare different types)
208
- # 2. IANA-registered definitions < other definitions.
209
- # 3. Complete definitions < incomplete definitions.
210
- # 4. Current definitions < obsolete definitions.
211
- # 5. Obselete with use-instead names < obsolete without.
212
- # 6. Obsolete use-instead definitions are compared.
211
+ # Uses a modified pre-computed sort priority value based on whether one of the provided
212
+ # extensions is the preferred extension for a type.
213
213
  #
214
- # While this method is public, its use is strongly discouraged by consumers
215
- # of mime-types. In mime-types 3, this method is likely to see substantial
216
- # revision and simplification to ensure current registered content types sort
217
- # before unregistered or obsolete content types.
218
- def priority_compare(other)
219
- pc = simplified <=> other.simplified
220
- if pc.zero? || !(extensions & other.extensions).empty?
221
- pc =
222
- if (reg = registered?) != other.registered?
223
- reg ? -1 : 1 # registered < unregistered
224
- elsif (comp = complete?) != other.complete?
225
- comp ? -1 : 1 # complete < incomplete
226
- elsif (obs = obsolete?) != other.obsolete?
227
- obs ? 1 : -1 # current < obsolete
228
- elsif obs && ((ui = use_instead) != (oui = other.use_instead))
229
- if ui.nil?
230
- 1
231
- elsif oui.nil?
232
- -1
233
- else
234
- ui <=> oui
235
- end
236
- else
237
- 0
238
- end
214
+ # This is an internal function. If an extension provided is a preferred extension either
215
+ # for this instance or the compared instance, the corresponding extension has its top
216
+ # _extension_ bit cleared from its sort priority. That means that a type with between
217
+ # 0 and 8 extensions will be treated as if it had 9 extensions.
218
+ def __extension_priority_compare(other, exts) # :nodoc:
219
+ tsp = __sort_priority
220
+
221
+ if exts.include?(preferred_extension) && tsp & 0b1000 != 0
222
+ tsp = tsp & 0b11110111 | 0b0111
223
+ end
224
+
225
+ osp = other.__sort_priority
226
+
227
+ if exts.include?(other.preferred_extension) && osp & 0b1000 != 0
228
+ osp = osp & 0b11110111 | 0b0111
239
229
  end
240
230
 
241
- pc
231
+ if (cmp = tsp <=> osp) == 0
232
+ simplified <=> other.simplified
233
+ else
234
+ cmp
235
+ end
242
236
  end
243
237
 
244
238
  # Returns +true+ if the +other+ object is a MIME::Type and the content types
@@ -273,6 +267,13 @@ class MIME::Type
273
267
  simplified.hash
274
268
  end
275
269
 
270
+ # The computed sort priority value. This is _not_ intended to be used by most
271
+ # callers.
272
+ def __sort_priority # :nodoc:
273
+ update_sort_priority if !instance_variable_defined?(:@__sort_priority) || @__sort_priority.nil?
274
+ @__sort_priority
275
+ end
276
+
276
277
  # Returns the whole MIME content-type string.
277
278
  #
278
279
  # The content type is a presentation value from the MIME type registry and
@@ -327,6 +328,7 @@ class MIME::Type
327
328
 
328
329
  ##
329
330
  def extensions=(value) # :nodoc:
331
+ clear_sort_priority
330
332
  @extensions = Set[*Array(value).flatten.compact].freeze
331
333
  MIME::Types.send(:reindex_extensions, self)
332
334
  end
@@ -353,9 +355,7 @@ class MIME::Type
353
355
 
354
356
  ##
355
357
  def preferred_extension=(value) # :nodoc:
356
- if value
357
- add_extensions(value)
358
- end
358
+ add_extensions(value) if value
359
359
  @preferred_extension = value
360
360
  end
361
361
 
@@ -408,9 +408,17 @@ class MIME::Type
408
408
  attr_writer :use_instead
409
409
 
410
410
  # Returns +true+ if the media type is obsolete.
411
- attr_accessor :obsolete
411
+ #
412
+ # :attr_accessor: obsolete
413
+ attr_reader :obsolete
412
414
  alias_method :obsolete?, :obsolete
413
415
 
416
+ ##
417
+ def obsolete=(value)
418
+ clear_sort_priority
419
+ @obsolete = !!value
420
+ end
421
+
414
422
  # The documentation for this MIME::Type.
415
423
  attr_accessor :docs
416
424
 
@@ -468,11 +476,27 @@ class MIME::Type
468
476
  end
469
477
 
470
478
  # Indicates whether the MIME type has been registered with IANA.
471
- attr_accessor :registered
479
+ #
480
+ # :attr_accessor: registered
481
+ attr_reader :registered
472
482
  alias_method :registered?, :registered
473
483
 
484
+ ##
485
+ def registered=(value)
486
+ clear_sort_priority
487
+ @registered = !!value
488
+ end
489
+
474
490
  # Indicates whether the MIME type's registration with IANA is provisional.
475
- attr_accessor :provisional
491
+ #
492
+ # :attr_accessor: provisional
493
+ attr_reader :provisional
494
+
495
+ ##
496
+ def provisional=(value)
497
+ clear_sort_priority
498
+ @provisional = !!value
499
+ end
476
500
 
477
501
  # Indicates whether the MIME type's registration with IANA is provisional.
478
502
  def provisional?
@@ -555,6 +579,7 @@ class MIME::Type
555
579
  coder["registered"] = registered?
556
580
  coder["provisional"] = provisional? if provisional?
557
581
  coder["signature"] = signature? if signature?
582
+ coder["sort-priority"] = __sort_priority || 0b11111111
558
583
  coder
559
584
  end
560
585
 
@@ -563,6 +588,7 @@ class MIME::Type
563
588
  #
564
589
  # This method should be considered a private implementation detail.
565
590
  def init_with(coder)
591
+ @__sort_priority = 0
566
592
  self.content_type = coder["content-type"]
567
593
  self.docs = coder["docs"] || ""
568
594
  self.encoding = coder["encoding"]
@@ -576,6 +602,8 @@ class MIME::Type
576
602
  self.use_instead = coder["use-instead"]
577
603
 
578
604
  friendly(coder["friendly"] || {})
605
+
606
+ update_sort_priority
579
607
  end
580
608
 
581
609
  def inspect # :nodoc:
@@ -631,6 +659,37 @@ class MIME::Type
631
659
 
632
660
  private
633
661
 
662
+ def clear_sort_priority
663
+ @__sort_priority = nil
664
+ end
665
+
666
+ # Update the __sort_priority value. Lower numbers sort better, so the
667
+ # bitmapping may seem a little odd. The _best_ sort priority is 0.
668
+ #
669
+ # | bit | meaning | details |
670
+ # | --- | --------------- | --------- |
671
+ # | 7 | obsolete | 1 if true |
672
+ # | 6 | provisional | 1 if true |
673
+ # | 5 | registered | 0 if true |
674
+ # | 4 | complete | 0 if true |
675
+ # | 3 | # of extensions | see below |
676
+ # | 2 | # of extensions | see below |
677
+ # | 1 | # of extensions | see below |
678
+ # | 0 | # of extensions | see below |
679
+ #
680
+ # The # of extensions is marked as the number of extensions subtracted from
681
+ # 16, to a minimum of 0.
682
+ def update_sort_priority
683
+ extension_count = @extensions.length
684
+ obsolete = (instance_variable_defined?(:@obsolete) && @obsolete) ? 1 << 7 : 0
685
+ provisional = (instance_variable_defined?(:@provisional) && @provisional) ? 1 << 6 : 0
686
+ registered = (instance_variable_defined?(:@registered) && @registered) ? 0 : 1 << 5
687
+ complete = extension_count.nonzero? ? 0 : 1 << 4
688
+ extension_count = [0, 16 - extension_count].max
689
+
690
+ @__sort_priority = obsolete | registered | provisional | complete | extension_count
691
+ end
692
+
634
693
  def content_type=(type_string)
635
694
  match = MEDIA_TYPE_RE.match(type_string)
636
695
  fail InvalidContentType, type_string if match.nil?
@@ -683,3 +742,5 @@ class MIME::Type
683
742
  "http://www.iana.org/assignments/media-types/%s" % value
684
743
  end
685
744
  end
745
+
746
+ require "mime/types/version"
@@ -18,6 +18,10 @@ module MIME::Types::Columnar
18
18
  obj.instance_variable_set(:@__files__, Set.new)
19
19
  end
20
20
 
21
+ def __fully_loaded? # :nodoc:
22
+ @__files__.size == 10
23
+ end
24
+
21
25
  # Load the first column data file (type and extensions).
22
26
  def load_base_data(path) # :nodoc:
23
27
  @__root__ = path
@@ -26,13 +30,16 @@ module MIME::Types::Columnar
26
30
  line = line.split
27
31
  content_type = line.shift
28
32
  extensions = line
29
- # content_type, *extensions = line.split
30
33
 
31
34
  type = MIME::Type::Columnar.new(self, content_type, extensions)
32
35
  @__mime_data__ << type
33
36
  add(type)
34
37
  end
35
38
 
39
+ each_file_byte("spri") do |type, byte|
40
+ type.instance_variable_set(:@__sort_priority, byte)
41
+ end
42
+
36
43
  self
37
44
  end
38
45
 
@@ -60,10 +67,29 @@ module MIME::Types::Columnar
60
67
  end
61
68
  end
62
69
 
70
+ def each_file_byte(name)
71
+ LOAD_MUTEX.synchronize do
72
+ next if @__files__.include?(name)
73
+
74
+ i = -1
75
+
76
+ filename = File.join(@__root__, "mime.#{name}.column")
77
+
78
+ next unless File.exist?(filename)
79
+
80
+ IO.binread(filename).unpack("C*").each do |byte|
81
+ (type = @__mime_data__[i += 1]) || next
82
+ yield type, byte
83
+ end
84
+
85
+ @__files__ << name
86
+ end
87
+ end
88
+
63
89
  def load_encoding
64
90
  each_file_line("encoding") do |type, line|
65
91
  pool ||= {}
66
- type.instance_variable_set(:@encoding, (pool[line] ||= line))
92
+ type.instance_variable_set(:@encoding, pool[line] ||= line)
67
93
  end
68
94
  end
69
95
 
@@ -91,7 +117,7 @@ module MIME::Types::Columnar
91
117
 
92
118
  def load_xrefs
93
119
  each_file_line("xrefs") { |type, line|
94
- type.instance_variable_set(:@xrefs, dict(line, array: true))
120
+ type.instance_variable_set(:@xrefs, dict(line, transform: :array))
95
121
  }
96
122
  end
97
123
 
@@ -107,18 +133,39 @@ module MIME::Types::Columnar
107
133
  end
108
134
  end
109
135
 
110
- def dict(line, array: false)
136
+ def dict(line, transform: nil)
111
137
  if line == "-"
112
138
  {}
113
139
  else
114
140
  line.split("|").each_with_object({}) { |l, h|
115
141
  k, v = l.split("^")
116
142
  v = nil if v.empty?
117
- h[k] = array ? Array(v) : v
143
+
144
+ if transform
145
+ send(:"dict_#{transform}", h, k, v)
146
+ else
147
+ h[k] = v
148
+ end
118
149
  }
119
150
  end
120
151
  end
121
152
 
153
+ def dict_extension_priority(h, k, v)
154
+ return if v.nil?
155
+
156
+ v = v.to_i if v.is_a?(String)
157
+ v = v.trunc if v.is_a?(Float)
158
+ v = [[-20, v].max, 20].min
159
+
160
+ return if v.zero?
161
+
162
+ h[k] = v
163
+ end
164
+
165
+ def dict_array(h, k, v)
166
+ h[k] = Array(v)
167
+ end
168
+
122
169
  def arr(line)
123
170
  if line == "-"
124
171
  []
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
- require "forwardable"
5
4
 
6
5
  # MIME::Types requires a serializable keyed container that returns an empty Set
7
6
  # on a key miss. Hash#default_value cannot be used because, while it traverses
@@ -10,8 +9,6 @@ require "forwardable"
10
9
  # Hash#default_proc cannot be used without a wrapper because it prevents
11
10
  # Marshal serialization (and does not survive the round-trip).
12
11
  class MIME::Types::Container # :nodoc:
13
- extend Forwardable
14
-
15
12
  def initialize(hash = {})
16
13
  @container = {}
17
14
  merge!(hash)
@@ -47,16 +44,49 @@ class MIME::Types::Container # :nodoc:
47
44
  container
48
45
  end
49
46
 
50
- def_delegators :@container,
51
- :==,
52
- :count,
53
- :each,
54
- :each_value,
55
- :empty?,
56
- :flat_map,
57
- :keys,
58
- :select,
59
- :values
47
+ def ==(other)
48
+ container == other
49
+ end
50
+
51
+ def count(*args, &block)
52
+ if args.size == 0
53
+ container.count
54
+ elsif block
55
+ container.count(&block)
56
+ else
57
+ container.count(args.first)
58
+ end
59
+ end
60
+
61
+ def each_pair(&block)
62
+ container.each_pair(&block)
63
+ end
64
+
65
+ alias_method :each, :each_pair
66
+
67
+ def each_value(&block)
68
+ container.each_value(&block)
69
+ end
70
+
71
+ def empty?
72
+ container.empty?
73
+ end
74
+
75
+ def flat_map(&block)
76
+ container.flat_map(&block)
77
+ end
78
+
79
+ def keys
80
+ container.keys
81
+ end
82
+
83
+ def values
84
+ container.values
85
+ end
86
+
87
+ def select(&block)
88
+ container.select(&block)
89
+ end
60
90
 
61
91
  def add(key, value)
62
92
  (container[key] ||= Set.new).add(value)
@@ -79,7 +79,7 @@ class MIME::Types::Loader
79
79
  #
80
80
  # This will load from columnar files (#load_columnar) if <tt>columnar:
81
81
  # true</tt> is provided in +options+ and there are columnar files in +path+.
82
- def load(options = {columnar: false})
82
+ def load(options = {columnar: true})
83
83
  if options[:columnar] && !Dir[columnar_path].empty?
84
84
  load_columnar
85
85
  else
@@ -9,7 +9,18 @@ module MIME
9
9
  class << self
10
10
  # Configure the MIME::Types logger. This defaults to an instance of a
11
11
  # logger that passes messages (unformatted) through to Kernel#warn.
12
- attr_accessor :logger
12
+ # :attr_accessor: logger
13
+ attr_reader :logger
14
+
15
+ ##
16
+ def logger=(logger) # :nodoc
17
+ @logger =
18
+ if logger.nil?
19
+ NullLogger.new
20
+ else
21
+ logger
22
+ end
23
+ end
13
24
  end
14
25
 
15
26
  class WarnLogger < ::Logger # :nodoc:
@@ -25,13 +36,33 @@ module MIME
25
36
  end
26
37
  end
27
38
 
28
- def initialize(_one, _two = nil, _three = nil)
39
+ def initialize(*)
29
40
  super(nil)
30
41
  @logdev = WarnLogDevice.new
31
42
  @formatter = ->(_s, _d, _p, m) { m }
32
43
  end
33
44
  end
34
45
 
35
- self.logger = WarnLogger.new(nil)
46
+ class NullLogger < ::Logger
47
+ def initialize(*)
48
+ super(nil)
49
+ @logdev = nil
50
+ end
51
+
52
+ def reopen(_)
53
+ self
54
+ end
55
+
56
+ def <<(_)
57
+ end
58
+
59
+ def close
60
+ end
61
+
62
+ def add(_severity, _message = nil, _progname = nil)
63
+ end
64
+ end
65
+
66
+ self.logger = WarnLogger.new
36
67
  end
37
68
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ module MIME
5
+ class Types
6
+ # The released version of the mime-types library.
7
+ VERSION = "3.7.0"
8
+ end
9
+
10
+ class Type
11
+ # The released version of the mime-types library.
12
+ VERSION = MIME::Types::VERSION
13
+ end
14
+ end
data/lib/mime/types.rb CHANGED
@@ -66,9 +66,6 @@ require "mime/type"
66
66
  # puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip'
67
67
  #
68
68
  class MIME::Types
69
- # The release version of Ruby MIME::Types
70
- VERSION = MIME::Type::VERSION
71
-
72
69
  include Enumerable
73
70
 
74
71
  # Creates a new MIME::Types registry.
@@ -133,9 +130,7 @@ class MIME::Types
133
130
  @type_variants[MIME::Type.simplified(type_id)]
134
131
  end
135
132
 
136
- prune_matches(matches, complete, registered).sort { |a, b|
137
- a.priority_compare(b)
138
- }
133
+ prune_matches(matches, complete, registered).sort
139
134
  end
140
135
 
141
136
  # Return the list of MIME::Types which belongs to the file based on its
@@ -151,10 +146,14 @@ class MIME::Types
151
146
  # puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif))
152
147
  # => [application/xml, image/gif, text/xml]
153
148
  def type_for(filename)
154
- Array(filename).flat_map { |fn|
155
- @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/m, 1]]
156
- }.compact.inject(Set.new, :+).sort { |a, b|
157
- a.priority_compare(b)
149
+ wanted = Array(filename).map { |fn| fn.chomp.downcase[/\.?([^.]*?)\z/m, 1] }
150
+
151
+ wanted
152
+ .flat_map { |ext| @extension_index[ext] }
153
+ .compact
154
+ .reduce(Set.new, :+)
155
+ .sort { |a, b|
156
+ a.__extension_priority_compare(b, wanted)
158
157
  }
159
158
  end
160
159
  alias_method :of, :type_for
@@ -196,6 +195,10 @@ class MIME::Types
196
195
  index_extensions!(type)
197
196
  end
198
197
 
198
+ def __fully_loaded? # :nodoc:
199
+ true
200
+ end
201
+
199
202
  private
200
203
 
201
204
  def add_type_variant!(mime_type)
@@ -223,6 +226,12 @@ class MIME::Types
223
226
  k =~ pattern
224
227
  }.values.inject(Set.new, :+)
225
228
  end
229
+
230
+ # def stable_sort(list)
231
+ # list.lazy.each_with_index.sort { |(a, ai), (b, bi)|
232
+ # a.priority_compare(b).nonzero? || ai <=> bi
233
+ # }.map(&:first)
234
+ # end
226
235
  end
227
236
 
228
237
  require "mime/types/cache"
@@ -231,3 +240,4 @@ require "mime/types/loader"
231
240
  require "mime/types/logger"
232
241
  require "mime/types/_columnar"
233
242
  require "mime/types/registry"
243
+ require "mime/types/version"
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mime/type"
4
- require "fileutils"
5
-
6
3
  gem "minitest"
7
4
  require "minitest/focus"
8
5
  require "minitest/hooks"
9
6
 
7
+ require "fileutils"
8
+
9
+ require "mime/type"
10
10
  ENV["RUBY_MIME_TYPES_LAZY_LOAD"] = "yes"