mime-types 3.6.2 → 3.7.0.pre2

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
  SHA256:
3
- metadata.gz: 6d48539865c96a17991bbceab6d3ff2b2d6db5e281448d9bd76dd5fc12a48cd2
4
- data.tar.gz: 79bcc6970d5f1774fc6a45162ba28222e72eeb37110e4d61eef96a50faeea4ff
3
+ metadata.gz: e25ed719cc7aaa5bebf7b6a4c701a9269aee07a7826d57f51b0e304c3cef489e
4
+ data.tar.gz: c7c7df7e30a4a6e03b60a4c3d19009a18a8cc4a263a9bc0e9d7317c1fa22868c
5
5
  SHA512:
6
- metadata.gz: 72aaaf23f926bd1ec8b4d978b8878d0c4b2a8b9083600ebbd051328c3f6cc3b15bbdb73be20cd87b9b3e49c83f29a63b8c54fad6026e4dc9dbaac052554f84f3
7
- data.tar.gz: 503bef89f86cf2cfbbbc5d76f642289ad011ca0c98ebef86e855be599efc3b90e59e3d4b984d4a17f59298ccf2b30c267ced17ee0b11ba905c9e732af51dd6f2
6
+ metadata.gz: 5f45853a6237e1773122595b9055112b053743daf726d209bbeabadda70dfb3248120db2a45807246739e87961efaebc35bedfd1e0418820f7c511fddc906633
7
+ data.tar.gz: 0da2dad77c9d62eb54bbbd5a830593d6e1e392c1c1f4b141aa242b9cff512f3918504baf29b2cbb4f18f90a9103cf024b1a85b834557a0430fb84ddce46e88ba
data/CHANGELOG.md CHANGED
@@ -1,10 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.7.0.pre2 / YYYY-MM-DD
4
+
5
+ - Deprecated `MIME::Type#priority_compare`. In a future release, this will be
6
+ will be renamed to `MIME::Type#<=>`. This method is used in tight loops, so
7
+ there is no warning message for either `MIME::Type#priority_compare` or
8
+ `MIME::Type#<=>`.
9
+
10
+ - Improved the performance of sorting by eliminating the complex comparison flow
11
+ from `MIME::Type#priority_compare`. The old version shows under 600 i/s, and
12
+ the new version shows over 900 i/s. In sorting the full set of MIME data,
13
+ there are three differences between the old and new versions; after
14
+ comparison, these differences are considered acceptable.
15
+
16
+ - Simplified the default compare implementation (`MIME::Type#<=>`) to use the
17
+ new `MIME::Type#priority_compare` operation and simplify the fallback to
18
+ `String` comparison. This _may_ result in exceptions where there had been
19
+ none, as explicit support for several special values (which should have caused
20
+ errors in any case) have been removed.
21
+
22
+ - When sorting the result of `MIME::Types#type_for`, provided a priority boost
23
+ if one of the target extensions is the type's preferred extension. This means
24
+ that for the case in [#148][issue-148], when getting the type for `foo.webm`,
25
+ the type `video/webm` will be returned before the type `audio/webm`, because
26
+ `.webm` is the preferred extension for `video/webm` but not `audio/webm`
27
+ (which has a preferred extension of `.weba`). Added tests to ensure MIME types
28
+ are retrieved in a stable order (which is alphabetical).
29
+
3
30
  ## 3.6.2 / 2025-03-25
4
31
 
5
32
  - Updated the reference to the changelog in the README, fixing RubyGems metadata
6
33
  on the next release. Fixed in [#189][pull-189] by nna774.
7
34
 
35
+ - Daniel Watkins fixed an error in the repo tag for this release because the
36
+ modified gemspec was not included in the release. Fixed in [#196][pull-196].
37
+
8
38
  ## 3.6.1 / 2025-03-15
9
39
 
10
40
  - Restructure project structure to be more consistent with mime-types-data.
@@ -148,7 +178,7 @@ there are some validation changes and updated code with formatting.
148
178
 
149
179
  ## 3.3 / 2019-09-04
150
180
 
151
- - 1 minor enhancement
181
+ - 1 minor enhancement:
152
182
 
153
183
  - Jean Boussier reduced memory usage for Ruby versions 2.3 or higher by
154
184
  interning various string values in each type. This is done with a
@@ -347,6 +377,7 @@ there are some validation changes and updated code with formatting.
347
377
  [issue-127]: https://github.com/mime-types/ruby-mime-types/issues/127
348
378
  [issue-134]: https://github.com/mime-types/ruby-mime-types/issues/134
349
379
  [issue-136]: https://github.com/mime-types/ruby-mime-types/issues/136
380
+ [issue-148]: https://github.com/mime-types/ruby-mime-types/issues/148
350
381
  [issue-166]: https://github.com/mime-types/ruby-mime-types/issues/166
351
382
  [issue-177]: https://github.com/mime-types/ruby-mime-types/issues/177
352
383
  [mime-types-data]: https://github.com/mime-types/mime-types-data
@@ -373,6 +404,7 @@ there are some validation changes and updated code with formatting.
373
404
  [pull-179]: https://github.com/mime-types/ruby-mime-types/pull/179
374
405
  [pull-180]: https://github.com/mime-types/ruby-mime-types/pull/180
375
406
  [pull-189]: https://github.com/mime-types/ruby-mime-types/pull/189
407
+ [pull-196]: https://github.com/mime-types/ruby-mime-types/pull/196
376
408
  [pull-79]: https://github.com/mime-types/ruby-mime-types/pull/79
377
409
  [pull-84]: https://github.com/mime-types/ruby-mime-types/pull/84
378
410
  [pull-85]: https://github.com/mime-types/ruby-mime-types/pull/85
data/CONTRIBUTORS.md CHANGED
@@ -14,6 +14,7 @@ Thanks to everyone else who has contributed to mime-types over the years:
14
14
  - Brandon Galbraith
15
15
  - Burke Libbey
16
16
  - Chris Gat
17
+ - Daniel Watkins
17
18
  - David Genord
18
19
  - Dillon Welch
19
20
  - Edward Betts
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require "rubygems"
2
2
  require "hoe"
3
3
  require "rake/clean"
4
4
  require "minitest"
5
+ require "minitest/test_task"
5
6
 
6
7
  Hoe.plugin :halostatue
7
8
  Hoe.plugin :rubygems
@@ -10,6 +11,7 @@ Hoe.plugins.delete :debug
10
11
  Hoe.plugins.delete :newb
11
12
  Hoe.plugins.delete :publish
12
13
  Hoe.plugins.delete :signing
14
+ Hoe.plugins.delete :test
13
15
 
14
16
  spec = Hoe.spec "mime-types" do
15
17
  developer("Austin Ziegler", "halostatue@gmail.com")
@@ -24,7 +26,7 @@ spec = Hoe.spec "mime-types" do
24
26
  val.merge!({"rubygems_mfa_required" => "true"})
25
27
  }
26
28
 
27
- extra_deps << ["mime-types-data", "~> 3.2015"]
29
+ extra_deps << ["mime-types-data", "~> 3.2025", ">= 3.2025.0506.pre2"]
28
30
  extra_deps << ["logger", ">= 0"]
29
31
 
30
32
  extra_dev_deps << ["hoe", "~> 4.0"]
@@ -65,6 +67,8 @@ Minitest::TestTask.create :coverage do |t|
65
67
  RUBY
66
68
  end
67
69
 
70
+ task default: :test
71
+
68
72
  namespace :benchmark do
69
73
  task :support do
70
74
  %w[lib support].each { |path|
@@ -174,6 +178,11 @@ namespace :convert do
174
178
  task docs: "convert:docs:run"
175
179
  end
176
180
 
181
+ task :version do
182
+ require "mime/types/version"
183
+ puts MIME::Types::VERSION
184
+ end
185
+
177
186
  namespace :deps do
178
187
  task :top, [:number] => "benchmark:support" do |_, args|
179
188
  require "deps"
@@ -15,8 +15,10 @@ require "mime/type"
15
15
  class MIME::Type::Columnar < MIME::Type
16
16
  def initialize(container, content_type, extensions) # :nodoc:
17
17
  @container = container
18
+ @__priority_penalty = nil
18
19
  self.content_type = content_type
19
- self.extensions = extensions
20
+ @extensions = Set[*Array(extensions).flatten.compact].freeze
21
+ clear_sort_priority
20
22
  end
21
23
 
22
24
  def self.column(*methods, file: nil) # :nodoc:
@@ -39,6 +41,7 @@ class MIME::Type::Columnar < MIME::Type
39
41
  :signature?, :provisional, :provisional=, :provisional?, file: "flags"
40
42
  column :xrefs, :xrefs=, :xref_urls
41
43
  column :use_instead, :use_instead=
44
+ column :extension_priorities, :extension_priorities=
42
45
 
43
46
  def encode_with(coder) # :nodoc:
44
47
  @container.send(:load_friendly)
@@ -48,9 +51,21 @@ class MIME::Type::Columnar < MIME::Type
48
51
  @container.send(:load_use_instead)
49
52
  @container.send(:load_xrefs)
50
53
  @container.send(:load_preferred_extension)
54
+ @container.send(:load_extension_priorities)
51
55
  super
52
56
  end
53
57
 
58
+ def update_sort_priority
59
+ if @container.__fully_loaded?
60
+ super
61
+ else
62
+ obsolete = (@__sort_priority & (1 << 7)) != 0
63
+ registered = (@__sort_priority & (1 << 5)) == 0
64
+
65
+ @__priority_penalty = (obsolete ? 3 : 0) + (registered ? 0 : 2)
66
+ end
67
+ end
68
+
54
69
  class << self
55
70
  undef column
56
71
  end
data/lib/mime/type.rb CHANGED
@@ -133,7 +133,8 @@ class MIME::Type
133
133
  def initialize(content_type) # :yields: self
134
134
  @friendly = {}
135
135
  @obsolete = @registered = @provisional = false
136
- @preferred_extension = @docs = @use_instead = nil
136
+ @preferred_extension = @docs = @use_instead = @__sort_priority = nil
137
+
137
138
  self.extensions = []
138
139
 
139
140
  case content_type
@@ -164,6 +165,8 @@ class MIME::Type
164
165
  self.xrefs ||= {}
165
166
 
166
167
  yield self if block_given?
168
+
169
+ update_sort_priority
167
170
  end
168
171
 
169
172
  # Indicates that a MIME type is like another type. This differs from
@@ -182,60 +185,54 @@ class MIME::Type
182
185
  # simplified type (the simplified type will be used if comparing against
183
186
  # something that can be treated as a String with #to_s). In comparisons, this
184
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.
185
193
  def <=>(other)
186
- if other.nil?
187
- -1
188
- 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
189
205
  simplified <=> other.simplified
190
206
  else
191
- filtered = "silent" if other == :silent
192
- filtered ||= "true" if other == true
193
- filtered ||= other.to_s
194
-
195
- simplified <=> MIME::Type.simplified(filtered)
207
+ cmp
196
208
  end
197
209
  end
198
210
 
199
- # Compares the +other+ MIME::Type based on how reliable it is before doing a
200
- # normal <=> comparison. Used by MIME::Types#[] to sort types. The
201
- # comparisons involved are:
202
- #
203
- # 1. self.simplified <=> other.simplified (ensures that we
204
- # do not try to compare different types)
205
- # 2. IANA-registered definitions < other definitions.
206
- # 3. Complete definitions < incomplete definitions.
207
- # 4. Current definitions < obsolete definitions.
208
- # 5. Obselete with use-instead names < obsolete without.
209
- # 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.
210
213
  #
211
- # While this method is public, its use is strongly discouraged by consumers
212
- # of mime-types. In mime-types 3, this method is likely to see substantial
213
- # revision and simplification to ensure current registered content types sort
214
- # before unregistered or obsolete content types.
215
- def priority_compare(other)
216
- pc = simplified <=> other.simplified
217
- if pc.zero? || !(extensions & other.extensions).empty?
218
- pc =
219
- if (reg = registered?) != other.registered?
220
- reg ? -1 : 1 # registered < unregistered
221
- elsif (comp = complete?) != other.complete?
222
- comp ? -1 : 1 # complete < incomplete
223
- elsif (obs = obsolete?) != other.obsolete?
224
- obs ? 1 : -1 # current < obsolete
225
- elsif obs && ((ui = use_instead) != (oui = other.use_instead))
226
- if ui.nil?
227
- 1
228
- elsif oui.nil?
229
- -1
230
- else
231
- ui <=> oui
232
- end
233
- else
234
- 0
235
- 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
236
229
  end
237
230
 
238
- pc
231
+ if (cmp = tsp <=> osp) == 0
232
+ simplified <=> other.simplified
233
+ else
234
+ cmp
235
+ end
239
236
  end
240
237
 
241
238
  # Returns +true+ if the +other+ object is a MIME::Type and the content types
@@ -270,6 +267,13 @@ class MIME::Type
270
267
  simplified.hash
271
268
  end
272
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
+
273
277
  # Returns the whole MIME content-type string.
274
278
  #
275
279
  # The content type is a presentation value from the MIME type registry and
@@ -324,6 +328,7 @@ class MIME::Type
324
328
 
325
329
  ##
326
330
  def extensions=(value) # :nodoc:
331
+ clear_sort_priority
327
332
  @extensions = Set[*Array(value).flatten.compact].freeze
328
333
  MIME::Types.send(:reindex_extensions, self)
329
334
  end
@@ -350,9 +355,7 @@ class MIME::Type
350
355
 
351
356
  ##
352
357
  def preferred_extension=(value) # :nodoc:
353
- if value
354
- add_extensions(value)
355
- end
358
+ add_extensions(value) if value
356
359
  @preferred_extension = value
357
360
  end
358
361
 
@@ -405,9 +408,17 @@ class MIME::Type
405
408
  attr_writer :use_instead
406
409
 
407
410
  # Returns +true+ if the media type is obsolete.
408
- attr_accessor :obsolete
411
+ #
412
+ # :attr_accessor: obsolete
413
+ attr_reader :obsolete
409
414
  alias_method :obsolete?, :obsolete
410
415
 
416
+ ##
417
+ def obsolete=(value)
418
+ clear_sort_priority
419
+ @obsolete = !!value
420
+ end
421
+
411
422
  # The documentation for this MIME::Type.
412
423
  attr_accessor :docs
413
424
 
@@ -465,11 +476,27 @@ class MIME::Type
465
476
  end
466
477
 
467
478
  # Indicates whether the MIME type has been registered with IANA.
468
- attr_accessor :registered
479
+ #
480
+ # :attr_accessor: registered
481
+ attr_reader :registered
469
482
  alias_method :registered?, :registered
470
483
 
484
+ ##
485
+ def registered=(value)
486
+ clear_sort_priority
487
+ @registered = !!value
488
+ end
489
+
471
490
  # Indicates whether the MIME type's registration with IANA is provisional.
472
- 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
473
500
 
474
501
  # Indicates whether the MIME type's registration with IANA is provisional.
475
502
  def provisional?
@@ -552,6 +579,7 @@ class MIME::Type
552
579
  coder["registered"] = registered?
553
580
  coder["provisional"] = provisional? if provisional?
554
581
  coder["signature"] = signature? if signature?
582
+ coder["sort-priority"] = __sort_priority || 0b11111111
555
583
  coder
556
584
  end
557
585
 
@@ -560,6 +588,7 @@ class MIME::Type
560
588
  #
561
589
  # This method should be considered a private implementation detail.
562
590
  def init_with(coder)
591
+ @__sort_priority = 0
563
592
  self.content_type = coder["content-type"]
564
593
  self.docs = coder["docs"] || ""
565
594
  self.encoding = coder["encoding"]
@@ -573,6 +602,8 @@ class MIME::Type
573
602
  self.use_instead = coder["use-instead"]
574
603
 
575
604
  friendly(coder["friendly"] || {})
605
+
606
+ update_sort_priority
576
607
  end
577
608
 
578
609
  def inspect # :nodoc:
@@ -628,6 +659,37 @@ class MIME::Type
628
659
 
629
660
  private
630
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
+
631
693
  def content_type=(type_string)
632
694
  match = MEDIA_TYPE_RE.match(type_string)
633
695
  fail InvalidContentType, type_string if match.nil?
@@ -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,6 +67,25 @@ 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 ||= {}
@@ -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,47 @@ module MIME::Types::Columnar
107
133
  end
108
134
  end
109
135
 
110
- def dict(line, array: false)
136
+ def load_extension_priorities
137
+ each_file_line("extpri") do |type, line|
138
+ type.instance_variable_set(:@extension_priorities, dict(line, transform: :extension_priority))
139
+ end
140
+ rescue
141
+ # This path preserves backwards compatibility.
142
+ end
143
+
144
+ def dict(line, transform: nil)
111
145
  if line == "-"
112
146
  {}
113
147
  else
114
148
  line.split("|").each_with_object({}) { |l, h|
115
149
  k, v = l.split("^")
116
150
  v = nil if v.empty?
117
- h[k] = array ? Array(v) : v
151
+
152
+ if transform
153
+ send(:"dict_#{transform}", h, k, v)
154
+ else
155
+ h[k] = v
156
+ end
118
157
  }
119
158
  end
120
159
  end
121
160
 
161
+ def dict_extension_priority(h, k, v)
162
+ return if v.nil?
163
+
164
+ v = v.to_i if v.is_a?(String)
165
+ v = v.trunc if v.is_a?(Float)
166
+ v = [[-20, v].max, 20].min
167
+
168
+ return if v.zero?
169
+
170
+ h[k] = v
171
+ end
172
+
173
+ def dict_array(h, k, v)
174
+ h[k] = Array(v)
175
+ end
176
+
122
177
  def arr(line)
123
178
  if line == "-"
124
179
  []
@@ -4,7 +4,7 @@
4
4
  module MIME
5
5
  class Types
6
6
  # The released version of the mime-types library.
7
- VERSION = "3.6.2"
7
+ VERSION = "3.7.0.pre2"
8
8
  end
9
9
 
10
10
  class Type
data/lib/mime/types.rb CHANGED
@@ -130,9 +130,8 @@ class MIME::Types
130
130
  @type_variants[MIME::Type.simplified(type_id)]
131
131
  end
132
132
 
133
- prune_matches(matches, complete, registered).sort { |a, b|
134
- a.priority_compare(b)
135
- }
133
+ # prune_matches(matches, complete, registered).sort { |a, b| a.priority_compare(b) }
134
+ prune_matches(matches, complete, registered).sort
136
135
  end
137
136
 
138
137
  # Return the list of MIME::Types which belongs to the file based on its
@@ -148,10 +147,14 @@ class MIME::Types
148
147
  # puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif))
149
148
  # => [application/xml, image/gif, text/xml]
150
149
  def type_for(filename)
151
- Array(filename).flat_map { |fn|
152
- @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/m, 1]]
153
- }.compact.inject(Set.new, :+).sort { |a, b|
154
- a.priority_compare(b)
150
+ wanted = Array(filename).map { |fn| fn.chomp.downcase[/\.?([^.]*?)\z/m, 1] }
151
+
152
+ wanted
153
+ .flat_map { |ext| @extension_index[ext] }
154
+ .compact
155
+ .reduce(Set.new, :+)
156
+ .sort { |a, b|
157
+ a.__extension_priority_compare(b, wanted)
155
158
  }
156
159
  end
157
160
  alias_method :of, :type_for
@@ -193,6 +196,10 @@ class MIME::Types
193
196
  index_extensions!(type)
194
197
  end
195
198
 
199
+ def __fully_loaded? # :nodoc:
200
+ true
201
+ end
202
+
196
203
  private
197
204
 
198
205
  def add_type_variant!(mime_type)
@@ -220,6 +227,12 @@ class MIME::Types
220
227
  k =~ pattern
221
228
  }.values.inject(Set.new, :+)
222
229
  end
230
+
231
+ # def stable_sort(list)
232
+ # list.lazy.each_with_index.sort { |(a, ai), (b, bi)|
233
+ # a.priority_compare(b).nonzero? || ai <=> bi
234
+ # }.map(&:first)
235
+ # end
223
236
  end
224
237
 
225
238
  require "mime/types/cache"
@@ -175,11 +175,6 @@ describe MIME::Type do
175
175
  refute_equal text_plain, "text/html"
176
176
  assert_operator text_plain, :>, "text/html"
177
177
  end
178
-
179
- it "correctly compares against nil" do
180
- refute_equal text_html, nil
181
- assert_operator text_plain, :<, nil
182
- end
183
178
  end
184
179
 
185
180
  describe "#ascii?" do
@@ -326,41 +321,56 @@ describe MIME::Type do
326
321
  end
327
322
 
328
323
  describe "#priority_compare" do
324
+ def priority(type)
325
+ priority = "OpRceXtN"
326
+ .chars
327
+ .zip(("%08b" % type.__sort_priority).chars)
328
+ .map { |e| e.join(":") }
329
+ .join(" ")
330
+
331
+ "#{type} (#{priority} / #{type.__sort_priority})"
332
+ end
333
+
329
334
  def assert_priority_less(left, right)
330
- assert_equal(-1, left.priority_compare(right))
335
+ assert_equal(-1, left.priority_compare(right), "#{priority(left)} is not less than #{priority(right)}")
331
336
  end
332
337
 
333
338
  def assert_priority_same(left, right)
334
- assert_equal 0, left.priority_compare(right)
339
+ assert_equal 0, left.priority_compare(right), "#{priority(left)} is not equal to #{priority(right)}"
335
340
  end
336
341
 
337
342
  def assert_priority_more(left, right)
338
- assert_equal 1, left.priority_compare(right)
343
+ assert_equal 1, left.priority_compare(right), "#{priority(left)} is not more than #{priority(right)}"
339
344
  end
340
345
 
341
346
  def assert_priority(left, middle, right)
342
347
  assert_priority_less left, right
343
348
  assert_priority_same left, middle
344
- assert_priority_more right, left
349
+ assert_priority_more right, middle
345
350
  end
346
351
 
347
352
  let(:text_1) { mime_type("content-type" => "text/1") }
348
353
  let(:text_1p) { mime_type("content-type" => "text/1") }
349
354
  let(:text_2) { mime_type("content-type" => "text/2") }
350
355
 
351
- it "sorts (1) based on the simplified type" do
356
+ it "sorts based on the simplified type when the sort priorities are the same" do
352
357
  assert_priority text_1, text_1p, text_2
353
358
  end
354
359
 
355
- it "sorts (2) based on extensions" do
356
- text_1.extensions = ["foo", "bar"]
357
- text_2.extensions = ["foo"]
360
+ it "sorts obsolete types higher than non-obsolete types" do
361
+ text_1.obsolete = text_1p.obsolete = false
362
+ text_1b = mime_type(text_1) { |t| t.obsolete = true }
363
+
364
+ assert_priority_less text_1, text_1b
358
365
 
359
- assert_priority_same text_1, text_2
366
+ assert_priority text_1, text_1p, text_1b
367
+ end
360
368
 
361
- text_2.registered = true
369
+ it "sorts provisional types higher than non-provisional types" do
370
+ text_1.provisional = text_1p.provisional = false
371
+ text_1b = mime_type(text_1) { |t| t.provisional = true }
362
372
 
363
- assert_priority_more text_1, text_2
373
+ assert_priority text_1, text_1p, text_1b
364
374
  end
365
375
 
366
376
  it "sorts (3) based on the registration state" do
@@ -377,23 +387,11 @@ describe MIME::Type do
377
387
  assert_priority text_1, text_1p, text_1b
378
388
  end
379
389
 
380
- it "sorts (5) based on obsolete status" do
381
- text_1.obsolete = text_1p.obsolete = false
382
- text_1b = mime_type(text_1) { |t| t.obsolete = true }
383
-
384
- assert_priority text_1, text_1p, text_1b
385
- end
386
-
387
- it "sorts (5) based on the use-instead value" do
388
- text_1.obsolete = text_1p.obsolete = true
389
- text_1.use_instead = text_1p.use_instead = "abc/xyz"
390
- text_1b = mime_type(text_1) { |t| t.use_instead = nil }
391
-
392
- assert_priority text_1, text_1p, text_1b
393
-
394
- text_1b.use_instead = "abc/zzz"
390
+ it "sorts based on extensions (more extensions sort lower)" do
391
+ text_1.extensions = ["foo", "bar"]
392
+ text_2.extensions = ["foo"]
395
393
 
396
- assert_priority text_1, text_1p, text_1b
394
+ assert_priority_less text_1, text_2
397
395
  end
398
396
  end
399
397
 
@@ -502,10 +500,10 @@ describe MIME::Type do
502
500
 
503
501
  describe "#to_json" do
504
502
  let(:expected_1) {
505
- '{"content-type":"a/b","encoding":"base64","registered":false}'
503
+ '{"content-type":"a/b","encoding":"base64","registered":false,"sort-priority":48}'
506
504
  }
507
505
  let(:expected_2) {
508
- '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true}'
506
+ '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true,"sort-priority":80}'
509
507
  }
510
508
 
511
509
  it "converts to JSON when requested" do
@@ -12,7 +12,8 @@ describe MIME::Types do
12
12
  MIME::Type.new("content-type" => "application/x-wordperfect6.1"),
13
13
  MIME::Type.new("content-type" => "application/x-www-form-urlencoded", "registered" => true),
14
14
  MIME::Type.new("content-type" => "application/x-gzip", "extensions" => %w[gz]),
15
- MIME::Type.new("content-type" => "application/gzip", "extensions" => "gz", "registered" => true)
15
+ MIME::Type.new("content-type" => "application/gzip", "extensions" => "gz", "registered" => true),
16
+ *MIME::Types.type_for("foo.webm")
16
17
  )
17
18
  }
18
19
  end
@@ -33,8 +34,8 @@ describe MIME::Types do
33
34
  end
34
35
 
35
36
  it "is countable with an enumerator" do
36
- assert_equal 6, mime_types.each.count
37
- assert_equal 6, mime_types.lazy.count
37
+ assert_equal 8, mime_types.each.count
38
+ assert_equal 8, mime_types.lazy.count
38
39
  end
39
40
  end
40
41
 
@@ -139,7 +140,7 @@ describe MIME::Types do
139
140
  end
140
141
 
141
142
  it "finds multiple extensions" do
142
- assert_equal %w[image/jpeg text/plain],
143
+ assert_equal %w[text/plain image/jpeg],
143
144
  mime_types.type_for(%w[foo.txt foo.jpeg])
144
145
  end
145
146
 
@@ -158,11 +159,15 @@ describe MIME::Types do
158
159
  it "handles newline characters correctly" do
159
160
  assert_includes mime_types.type_for("test.pdf\n.txt"), "text/plain"
160
161
  end
162
+
163
+ it "returns a stable order for types with equal priority" do
164
+ assert_equal %w[text/x-vcalendar text/x-vcard], MIME::Types[/text\/x-vca/]
165
+ end
161
166
  end
162
167
 
163
168
  describe "#count" do
164
169
  it "can count the number of types inside" do
165
- assert_equal 6, mime_types.count
170
+ assert_equal 8, mime_types.count
166
171
  end
167
172
  end
168
173
  end
@@ -47,7 +47,7 @@ describe MIME::Types, "registry" do
47
47
  }
48
48
  # This is this way because of a new type ending with gzip that only
49
49
  # appears in some data files.
50
- assert_equal %w[application/gzip application/x-gzip multipart/x-gzip], types
50
+ assert_equal %w[application/gzip multipart/x-gzip application/x-gzip], types
51
51
  assert_equal 3, types.size
52
52
  end
53
53
 
@@ -86,9 +86,11 @@ describe MIME::Types, "registry" do
86
86
  assert_equal %w[image/jpeg], MIME::Types.of(["foo.jpeg", "bar.jpeg"])
87
87
  end
88
88
 
89
- it "finds multiple extensions" do
90
- assert_equal %w[image/jpeg text/plain],
91
- MIME::Types.type_for(%w[foo.txt foo.jpeg])
89
+ it "finds multiple extensions ordered by the filename list" do
90
+ result = MIME::Types.type_for(%w[foo.txt foo.jpeg])
91
+
92
+ # assert_equal %w[text/plain image/jpeg], MIME::Types.type_for(%w[foo.txt foo.jpeg])
93
+ assert_equal %w[text/plain image/jpeg], result
92
94
  end
93
95
 
94
96
  it "does not find unknown extensions" do
@@ -105,6 +107,10 @@ describe MIME::Types, "registry" do
105
107
  assert_includes MIME::Types.type_for("test.pdf\n.txt"), "text/plain"
106
108
  assert_includes MIME::Types.type_for("test.txt\n.pdf"), "application/pdf"
107
109
  end
110
+
111
+ it "returns a stable order for types with equal priority" do
112
+ assert_equal %w[text/x-vcalendar text/x-vcard], MIME::Types[/text\/x-vca/]
113
+ end
108
114
  end
109
115
 
110
116
  describe ".count" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mime-types
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.2
4
+ version: 3.7.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Ziegler
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-25 00:00:00.000000000 Z
10
+ date: 2025-04-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: mime-types-data
@@ -15,14 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '3.2015'
18
+ version: '3.2025'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2025.0506.pre2
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
- version: '3.2015'
28
+ version: '3.2025'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 3.2025.0506.pre2
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: logger
28
34
  requirement: !ruby/object:Gem::Requirement
@@ -269,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
275
  - !ruby/object:Gem::Version
270
276
  version: '0'
271
277
  requirements: []
272
- rubygems_version: 3.6.2
278
+ rubygems_version: 3.6.6
273
279
  specification_version: 4
274
280
  summary: The mime-types library provides a library and registry for information about
275
281
  MIME content type definitions