mime-types 3.5.2 → 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
@@ -4,20 +4,22 @@
4
4
  module MIME
5
5
  end
6
6
 
7
+ require "mime/types/deprecations"
8
+
7
9
  # The definition of one MIME content-type.
8
10
  #
9
11
  # == Usage
10
- # require 'mime/types'
12
+ # require "mime/types"
11
13
  #
12
- # plaintext = MIME::Types['text/plain'] # => [ text/plain ]
14
+ # plaintext = MIME::Types["text/plain"] # => [ text/plain ]
13
15
  # text = plaintext.first
14
- # puts text.media_type # => 'text'
15
- # puts text.sub_type # => 'plain'
16
+ # puts text.media_type # => "text"
17
+ # puts text.sub_type # => "plain"
16
18
  #
17
- # puts text.extensions.join(' ') # => 'txt asc c cc h hh cpp hpp dat hlp'
18
- # puts text.preferred_extension # => 'txt'
19
- # puts text.friendly # => 'Text Document'
20
- # puts text.i18n_key # => 'text.plain'
19
+ # puts text.extensions.join(" ") # => "txt asc c cc h hh cpp hpp dat hlp"
20
+ # puts text.preferred_extension # => "txt"
21
+ # puts text.friendly # => "Text Document"
22
+ # puts text.i18n_key # => "text.plain"
21
23
  #
22
24
  # puts text.encoding # => quoted-printable
23
25
  # puts text.default_encoding # => quoted-printable
@@ -28,45 +30,45 @@ end
28
30
  # puts text.provisional? # => false
29
31
  # puts text.complete? # => true
30
32
  #
31
- # puts text # => 'text/plain'
33
+ # puts text # => "text/plain"
32
34
  #
33
- # puts text == 'text/plain' # => true
34
- # puts 'text/plain' == text # => true
35
- # puts text == 'text/x-plain' # => false
36
- # puts 'text/x-plain' == text # => false
35
+ # puts text == "text/plain" # => true
36
+ # puts "text/plain" == text # => true
37
+ # puts text == "text/x-plain" # => false
38
+ # puts "text/x-plain" == text # => false
37
39
  #
38
- # puts MIME::Type.simplified('x-appl/x-zip') # => 'x-appl/x-zip'
39
- # puts MIME::Type.i18n_key('x-appl/x-zip') # => 'x-appl.x-zip'
40
+ # puts MIME::Type.simplified("x-appl/x-zip") # => "x-appl/x-zip"
41
+ # puts MIME::Type.i18n_key("x-appl/x-zip") # => "x-appl.x-zip"
40
42
  #
41
- # puts text.like?('text/x-plain') # => true
42
- # puts text.like?(MIME::Type.new('x-text/x-plain')) # => true
43
+ # puts text.like?("text/x-plain") # => true
44
+ # puts text.like?(MIME::Type.new("content-type" => "x-text/x-plain")) # => true
43
45
  #
44
46
  # puts text.xrefs.inspect # => { "rfc" => [ "rfc2046", "rfc3676", "rfc5147" ] }
45
47
  # puts text.xref_urls # => [ "http://www.iana.org/go/rfc2046",
46
48
  # # "http://www.iana.org/go/rfc3676",
47
49
  # # "http://www.iana.org/go/rfc5147" ]
48
50
  #
49
- # xtext = MIME::Type.new('x-text/x-plain')
50
- # puts xtext.media_type # => 'text'
51
- # puts xtext.raw_media_type # => 'x-text'
52
- # puts xtext.sub_type # => 'plain'
53
- # puts xtext.raw_sub_type # => 'x-plain'
51
+ # xtext = MIME::Type.new("x-text/x-plain")
52
+ # puts xtext.media_type # => "text"
53
+ # puts xtext.raw_media_type # => "x-text"
54
+ # puts xtext.sub_type # => "plain"
55
+ # puts xtext.raw_sub_type # => "x-plain"
54
56
  # puts xtext.complete? # => false
55
57
  #
56
- # puts MIME::Types.any? { |type| type.content_type == 'text/plain' } # => true
58
+ # puts MIME::Types.any? { |type| type.content_type == "text/plain" } # => true
57
59
  # puts MIME::Types.all?(&:registered?) # => false
58
60
  #
59
61
  # # Various string representations of MIME types
60
- # qcelp = MIME::Types['audio/QCELP'].first # => audio/QCELP
61
- # puts qcelp.content_type # => 'audio/QCELP'
62
- # puts qcelp.simplified # => 'audio/qcelp'
62
+ # qcelp = MIME::Types["audio/QCELP"].first # => audio/QCELP
63
+ # puts qcelp.content_type # => "audio/QCELP"
64
+ # puts qcelp.simplified # => "audio/qcelp"
63
65
  #
64
- # xwingz = MIME::Types['application/x-Wingz'].first # => application/x-Wingz
65
- # puts xwingz.content_type # => 'application/x-Wingz'
66
- # puts xwingz.simplified # => 'application/x-wingz'
66
+ # xwingz = MIME::Types["application/x-Wingz"].first # => application/x-Wingz
67
+ # puts xwingz.content_type # => "application/x-Wingz"
68
+ # puts xwingz.simplified # => "application/x-wingz"
67
69
  class MIME::Type
68
70
  # Reflects a MIME content-type specification that is not correctly
69
- # formatted (it isn't +type+/+subtype+).
71
+ # formatted (it is not +type+/+subtype+).
70
72
  class InvalidContentType < ArgumentError
71
73
  # :stopdoc:
72
74
  def initialize(type_string)
@@ -92,15 +94,18 @@ class MIME::Type
92
94
  # :startdoc:
93
95
  end
94
96
 
95
- # The released version of the mime-types library.
96
- VERSION = "3.5.2"
97
-
98
97
  include Comparable
99
98
 
100
99
  # :stopdoc:
101
- # TODO verify mime-type character restrictions; I am pretty sure that this is
102
- # too wide open.
103
- MEDIA_TYPE_RE = %r{([-\w.+]+)/([-\w.+]*)}.freeze
100
+ # Full conformance with RFC 6838 §4.2 (the recommendation for < 64 characters is not
101
+ # enforced or reported because MIME::Types mostly deals with registered data). RFC 4288
102
+ # §4.2 does not restrict the first character to alphanumeric, but the total length of
103
+ # each part is limited to 127 characters. RFCC 2045 §5.1 does not restrict the character
104
+ # composition except for whitespace, but MIME::Type was always more strict than this.
105
+ restricted_name_first = "[0-9a-zA-Z]"
106
+ restricted_name_chars = "[-!#{$&}^_.+0-9a-zA-Z]{0,126}"
107
+ restricted_name = "#{restricted_name_first}#{restricted_name_chars}"
108
+ MEDIA_TYPE_RE = %r{(#{restricted_name})/(#{restricted_name})}.freeze
104
109
  I18N_RE = /[^[:alnum:]]/.freeze
105
110
  BINARY_ENCODINGS = %w[base64 8bit].freeze
106
111
  ASCII_ENCODINGS = %w[7bit quoted-printable].freeze
@@ -110,12 +115,15 @@ class MIME::Type
110
115
  :ASCII_ENCODINGS
111
116
 
112
117
  # Builds a MIME::Type object from the +content_type+, a MIME Content Type
113
- # value (e.g., 'text/plain' or 'application/x-eruby'). The constructed object
118
+ # value (e.g., "text/plain" or "application/x-eruby"). The constructed object
114
119
  # is yielded to an optional block for additional configuration, such as
115
120
  # associating extensions and encoding information.
116
121
  #
117
122
  # * When provided a Hash or a MIME::Type, the MIME::Type will be
118
123
  # constructed with #init_with.
124
+ #
125
+ # There are two deprecated initialization forms:
126
+ #
119
127
  # * When provided an Array, the MIME::Type will be constructed using
120
128
  # the first element as the content type and the remaining flattened
121
129
  # elements as extensions.
@@ -125,18 +133,31 @@ class MIME::Type
125
133
  def initialize(content_type) # :yields: self
126
134
  @friendly = {}
127
135
  @obsolete = @registered = @provisional = false
128
- @preferred_extension = @docs = @use_instead = nil
136
+ @preferred_extension = @docs = @use_instead = @__sort_priority = nil
137
+
129
138
  self.extensions = []
130
139
 
131
140
  case content_type
132
141
  when Hash
133
142
  init_with(content_type)
134
143
  when Array
144
+ MIME::Types.deprecated(
145
+ class: MIME::Type,
146
+ method: :new,
147
+ pre: "when called with an Array",
148
+ once: true
149
+ )
135
150
  self.content_type = content_type.shift
136
151
  self.extensions = content_type.flatten
137
152
  when MIME::Type
138
153
  init_with(content_type.to_h)
139
154
  else
155
+ MIME::Types.deprecated(
156
+ class: MIME::Type,
157
+ method: :new,
158
+ pre: "when called with a String",
159
+ once: true
160
+ )
140
161
  self.content_type = content_type
141
162
  end
142
163
 
@@ -144,6 +165,8 @@ class MIME::Type
144
165
  self.xrefs ||= {}
145
166
 
146
167
  yield self if block_given?
168
+
169
+ update_sort_priority
147
170
  end
148
171
 
149
172
  # Indicates that a MIME type is like another type. This differs from
@@ -162,60 +185,54 @@ class MIME::Type
162
185
  # simplified type (the simplified type will be used if comparing against
163
186
  # something that can be treated as a String with #to_s). In comparisons, this
164
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.
165
193
  def <=>(other)
166
- if other.nil?
167
- -1
168
- 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
169
205
  simplified <=> other.simplified
170
206
  else
171
- filtered = "silent" if other == :silent
172
- filtered ||= "true" if other == true
173
- filtered ||= other.to_s
174
-
175
- simplified <=> MIME::Type.simplified(filtered)
207
+ cmp
176
208
  end
177
209
  end
178
210
 
179
- # Compares the +other+ MIME::Type based on how reliable it is before doing a
180
- # normal <=> comparison. Used by MIME::Types#[] to sort types. The
181
- # comparisons involved are:
182
- #
183
- # 1. self.simplified <=> other.simplified (ensures that we
184
- # don't try to compare different types)
185
- # 2. IANA-registered definitions < other definitions.
186
- # 3. Complete definitions < incomplete definitions.
187
- # 4. Current definitions < obsolete definitions.
188
- # 5. Obselete with use-instead names < obsolete without.
189
- # 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.
190
213
  #
191
- # While this method is public, its use is strongly discouraged by consumers
192
- # of mime-types. In mime-types 3, this method is likely to see substantial
193
- # revision and simplification to ensure current registered content types sort
194
- # before unregistered or obsolete content types.
195
- def priority_compare(other)
196
- pc = simplified <=> other.simplified
197
- if pc.zero? || !(extensions & other.extensions).empty?
198
- pc =
199
- if (reg = registered?) != other.registered?
200
- reg ? -1 : 1 # registered < unregistered
201
- elsif (comp = complete?) != other.complete?
202
- comp ? -1 : 1 # complete < incomplete
203
- elsif (obs = obsolete?) != other.obsolete?
204
- obs ? 1 : -1 # current < obsolete
205
- elsif obs && ((ui = use_instead) != (oui = other.use_instead))
206
- if ui.nil?
207
- 1
208
- elsif oui.nil?
209
- -1
210
- else
211
- ui <=> oui
212
- end
213
- else
214
- 0
215
- 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
216
223
  end
217
224
 
218
- pc
225
+ osp = other.__sort_priority
226
+
227
+ if exts.include?(other.preferred_extension) && osp & 0b1000 != 0
228
+ osp = osp & 0b11110111 | 0b0111
229
+ end
230
+
231
+ if (cmp = tsp <=> osp) == 0
232
+ simplified <=> other.simplified
233
+ else
234
+ cmp
235
+ end
219
236
  end
220
237
 
221
238
  # Returns +true+ if the +other+ object is a MIME::Type and the content types
@@ -243,13 +260,20 @@ class MIME::Type
243
260
  # +a.simplified+.
244
261
  #
245
262
  # Presumably, if <code>a.simplified <=> b.simplified</code> is +0+, then
246
- # +a.simplified+ has the same hash as +b.simplified+. So we assume it's
263
+ # +a.simplified+ has the same hash as +b.simplified+. So we assume it is
247
264
  # suitable for #hash to delegate to #simplified in service of the #eql?
248
265
  # invariant.
249
266
  def hash
250
267
  simplified.hash
251
268
  end
252
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
+
253
277
  # Returns the whole MIME content-type string.
254
278
  #
255
279
  # The content type is a presentation value from the MIME type registry and
@@ -304,6 +328,7 @@ class MIME::Type
304
328
 
305
329
  ##
306
330
  def extensions=(value) # :nodoc:
331
+ clear_sort_priority
307
332
  @extensions = Set[*Array(value).flatten.compact].freeze
308
333
  MIME::Types.send(:reindex_extensions, self)
309
334
  end
@@ -319,7 +344,7 @@ class MIME::Type
319
344
  # exceptions defined, the first extension will be used.
320
345
  #
321
346
  # When setting #preferred_extensions, if #extensions does not contain this
322
- # extension, this will be added to #xtensions.
347
+ # extension, this will be added to #extensions.
323
348
  #
324
349
  # :attr_accessor: preferred_extension
325
350
 
@@ -343,7 +368,7 @@ class MIME::Type
343
368
  # provided is invalid.
344
369
  #
345
370
  # If the encoding is not provided on construction, this will be either
346
- # 'quoted-printable' (for text/* media types) and 'base64' for eveything
371
+ # "quoted-printable" (for text/* media types) and "base64" for eveything
347
372
  # else.
348
373
  #
349
374
  # :attr_accessor: encoding
@@ -383,9 +408,17 @@ class MIME::Type
383
408
  attr_writer :use_instead
384
409
 
385
410
  # Returns +true+ if the media type is obsolete.
386
- attr_accessor :obsolete
411
+ #
412
+ # :attr_accessor: obsolete
413
+ attr_reader :obsolete
387
414
  alias_method :obsolete?, :obsolete
388
415
 
416
+ ##
417
+ def obsolete=(value)
418
+ clear_sort_priority
419
+ @obsolete = !!value
420
+ end
421
+
389
422
  # The documentation for this MIME::Type.
390
423
  attr_accessor :docs
391
424
 
@@ -393,7 +426,7 @@ class MIME::Type
393
426
  #
394
427
  # call-seq:
395
428
  # text_plain.friendly # => "Text File"
396
- # text_plain.friendly('en') # => "Text File"
429
+ # text_plain.friendly("en") # => "Text File"
397
430
  def friendly(lang = "en")
398
431
  @friendly ||= {}
399
432
 
@@ -443,11 +476,27 @@ class MIME::Type
443
476
  end
444
477
 
445
478
  # Indicates whether the MIME type has been registered with IANA.
446
- attr_accessor :registered
479
+ #
480
+ # :attr_accessor: registered
481
+ attr_reader :registered
447
482
  alias_method :registered?, :registered
448
483
 
484
+ ##
485
+ def registered=(value)
486
+ clear_sort_priority
487
+ @registered = !!value
488
+ end
489
+
449
490
  # Indicates whether the MIME type's registration with IANA is provisional.
450
- 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
451
500
 
452
501
  # Indicates whether the MIME type's registration with IANA is provisional.
453
502
  def provisional?
@@ -486,7 +535,7 @@ class MIME::Type
486
535
  # Returns the MIME::Type as a string for implicit conversions. This allows
487
536
  # MIME::Type objects to appear on either side of a comparison.
488
537
  #
489
- # 'text/plain' == MIME::Type.new('text/plain')
538
+ # "text/plain" == MIME::Type.new("content-type" => "text/plain")
490
539
  def to_str
491
540
  content_type
492
541
  end
@@ -530,6 +579,7 @@ class MIME::Type
530
579
  coder["registered"] = registered?
531
580
  coder["provisional"] = provisional? if provisional?
532
581
  coder["signature"] = signature? if signature?
582
+ coder["sort-priority"] = __sort_priority || 0b11111111
533
583
  coder
534
584
  end
535
585
 
@@ -538,6 +588,7 @@ class MIME::Type
538
588
  #
539
589
  # This method should be considered a private implementation detail.
540
590
  def init_with(coder)
591
+ @__sort_priority = 0
541
592
  self.content_type = coder["content-type"]
542
593
  self.docs = coder["docs"] || ""
543
594
  self.encoding = coder["encoding"]
@@ -551,6 +602,8 @@ class MIME::Type
551
602
  self.use_instead = coder["use-instead"]
552
603
 
553
604
  friendly(coder["friendly"] || {})
605
+
606
+ update_sort_priority
554
607
  end
555
608
 
556
609
  def inspect # :nodoc:
@@ -606,6 +659,37 @@ class MIME::Type
606
659
 
607
660
  private
608
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
+
609
693
  def content_type=(type_string)
610
694
  match = MEDIA_TYPE_RE.match(type_string)
611
695
  fail InvalidContentType, type_string if match.nil?
@@ -627,7 +711,7 @@ class MIME::Type
627
711
  -string
628
712
  end
629
713
  else
630
- # MRI 2.2 and older don't have a method for string interning,
714
+ # MRI 2.2 and older do not have a method for string interning,
631
715
  # so we simply freeze them for keeping a similar interface
632
716
  def intern_string(string)
633
717
  string.freeze
@@ -658,3 +742,5 @@ class MIME::Type
658
742
  "http://www.iana.org/assignments/media-types/%s" % value
659
743
  end
660
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,17 +1,14 @@
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
8
- # the Marshal format correctly, it won't survive any other serialization
7
+ # the Marshal format correctly, it will not survive any other serialization
9
8
  # format (plus, a default of a mutable object resuls in a shared mess).
10
9
  # Hash#default_proc cannot be used without a wrapper because it prevents
11
- # Marshal serialization (and doesn't survive the round-trip).
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)