From 5d51d0a516380055a0dc93d653a8ae8af81514fe Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Thu, 16 Dec 2021 21:36:00 -0500 Subject: [PATCH 01/11] Reformat the .hoerc --- .hoerc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.hoerc b/.hoerc index 55c87d2..0d0aae2 100644 --- a/.hoerc +++ b/.hoerc @@ -1,8 +1,8 @@ --- exclude: !ruby/regexp '/ \.(?: - tmp | - swp + tmp + | swp )$ | \.(?: @@ -18,8 +18,7 @@ exclude: !ruby/regexp '/ [gG]emfile(?:\.lock)? | (?: - support | - research + support )\/ | \b(?i:TAGS)$ From dbf38aafb85bdd9cf6498c390db3d3ab8ddf559f Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Thu, 16 Dec 2021 21:37:01 -0500 Subject: [PATCH 02/11] Deprecate MIME::Types.new(Array) --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 9576a40..4820473 100644 --- a/History.md +++ b/History.md @@ -123,7 +123,7 @@ there are some validation changes and updated code with formatting. ## 3.3 / 2019-09-04 -- 1 minor enhancement +- 1 minor enhancement: - Jean Boussier reduced memory usage for Ruby versions 2.3 or higher by interning various string values in each type. This is done with a From 5f65140b98b3f94e5a6e6d81fd50d7a60ab1cb74 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Thu, 16 Dec 2021 22:03:37 -0500 Subject: [PATCH 03/11] Deprecate MIME::Types.new(String) --- mime-types.gemspec | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/mime-types.gemspec b/mime-types.gemspec index 8dc0a21..0cb7516 100644 --- a/mime-types.gemspec +++ b/mime-types.gemspec @@ -1,4 +1,3 @@ -# -*- encoding: utf-8 -*- # stub: mime-types 3.6.0 ruby lib Gem::Specification.new do |s| @@ -6,10 +5,9 @@ Gem::Specification.new do |s| s.version = "3.6.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "bug_tracker_uri" => "https://github.com/mime-types/ruby-mime-types/issues", "changelog_uri" => "https://github.com/mime-types/ruby-mime-types/blob/master/History.md", "homepage_uri" => "https://github.com/mime-types/ruby-mime-types/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/mime-types/ruby-mime-types/" } if s.respond_to? :metadata= + s.metadata = {"bug_tracker_uri" => "https://github.com/mime-types/ruby-mime-types/issues", "changelog_uri" => "https://github.com/mime-types/ruby-mime-types/blob/master/History.md", "homepage_uri" => "https://github.com/mime-types/ruby-mime-types/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/mime-types/ruby-mime-types/"} if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Austin Ziegler".freeze] - s.date = "2024-10-02" s.description = "The mime-types library provides a library and registry for information about\nMIME content type definitions. It can be used to determine defined filename\nextensions for MIME types, or to use filename extensions to look up the likely\nMIME type definitions.\n\nVersion 3.0 is a major release that requires Ruby 2.0 compatibility and removes\ndeprecated functions. The columnar registry format introduced in 2.6 has been\nmade the primary format; the registry data has been extracted from this library\nand put into {mime-types-data}[https://github.com/mime-types/mime-types-data].\nAdditionally, mime-types is now licensed exclusively under the MIT licence and\nthere is a code of conduct in effect. There are a number of other smaller\nchanges described in the History file.".freeze s.email = ["halostatue@gmail.com".freeze] s.extra_rdoc_files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze] @@ -18,24 +16,21 @@ Gem::Specification.new do |s| s.licenses = ["MIT".freeze] s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze) - s.rubygems_version = "3.5.16".freeze s.summary = "The mime-types library provides a library and registry for information about MIME content type definitions".freeze - s.specification_version = 4 - - s.add_runtime_dependency(%q.freeze, ["~> 3.2015".freeze]) - s.add_runtime_dependency(%q.freeze, [">= 0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 5.25".freeze]) - s.add_development_dependency(%q.freeze, [">= 3.0".freeze, "< 5".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.1".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.7".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.4".freeze]) - s.add_development_dependency(%q.freeze, [">= 10.0".freeze, "< 14.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, [">= 4.0".freeze, "< 7".freeze]) - s.add_development_dependency(%q.freeze, ["~> 0.21".freeze]) + s.add_runtime_dependency("mime-types-data".freeze, ["~> 3.2015".freeze]) + s.add_runtime_dependency("logger".freeze, [">= 0".freeze]) + s.add_development_dependency("minitest".freeze, ["~> 5.25".freeze]) + s.add_development_dependency("hoe".freeze, [">= 3.0".freeze, "< 5".freeze]) + s.add_development_dependency("hoe-doofus".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("hoe-gemspec2".freeze, ["~> 1.1".freeze]) + s.add_development_dependency("hoe-git2".freeze, ["~> 1.7".freeze]) + s.add_development_dependency("hoe-rubygems".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-autotest".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-focus".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-hooks".freeze, ["~> 1.4".freeze]) + s.add_development_dependency("rake".freeze, [">= 10.0".freeze, "< 14.0".freeze]) + s.add_development_dependency("standard".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("rdoc".freeze, [">= 4.0".freeze, "< 7".freeze]) + s.add_development_dependency("simplecov".freeze, ["~> 0.21".freeze]) end From 91a1ec5f3f05c776cd45c213a402658a136ad4ec Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Fri, 17 Dec 2021 01:12:03 -0500 Subject: [PATCH 04/11] Improve comparison performance --- History.md | 16 ++++++ lib/mime/type.rb | 141 ++++++++++++++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 50 deletions(-) diff --git a/History.md b/History.md index 4820473..aaf453c 100644 --- a/History.md +++ b/History.md @@ -18,6 +18,22 @@ release after 3.6, we do not need to make the type initialization deprecations frequent with this release. +- 1 enhancement: + + - Improved the performance of sorting by eliminating the complex comparison + flow from `MIME::Type#priority_compare`. The old version shows under 600 + i/s, and the new version shows over 900 i/s. In sorting the full set of MIME + data, there are three differences between the old and new versions; after + comparison, these differences are considered acceptable. + +- 1 bug fix: + + - Simplified the default compare implementation (`MIME::Type#<=>`) to use the + new `MIME::Type#priority_compare` operation and simplify the fallback to + `String` comparison. This _may_ result in exceptions where there had been + none, as explicit support for several special values (which should have + caused errors in any case) have been removed. + ## 3.5.2 / 2024-01-02 There are no primary code changes, but we are releasing this as an update as diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 983b64c..c5223a3 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -137,6 +137,8 @@ def initialize(content_type) # :yields: self @friendly = {} @obsolete = @registered = @provisional = false @preferred_extension = @docs = @use_instead = nil + @priority = 0 + self.extensions = [] case content_type @@ -183,62 +185,40 @@ def like?(other) # Compares the +other+ MIME::Type against the exact content type or the # simplified type (the simplified type will be used if comparing against - # something that can be treated as a String with #to_s). In comparisons, this - # is done against the lowercase version of the MIME::Type. + # something that can be treated as a String with #to_s). In comparisons, + # this is done against the lowercase version of the MIME::Type. + # + # Note that this implementation of #<=> is deprecated and will be changed + # in the next major version to be the same as #priority_compare. def <=>(other) - if other.nil? - -1 - elsif other.respond_to?(:simplified) - simplified <=> other.simplified - else - filtered = "silent" if other == :silent - filtered ||= "true" if other == true - filtered ||= other.to_s - - simplified <=> MIME::Type.simplified(filtered) - end + return priority_compare(other) if other.is_a?(MIME::Type) + simplified <=> other end - # Compares the +other+ MIME::Type based on how reliable it is before doing a - # normal <=> comparison. Used by MIME::Types#[] to sort types. The + # Compares the +other+ MIME::Type using the simplified representation, then + # a pre-computed priority value. Used by MIME::Types#[] to sort types. The # comparisons involved are: # # 1. self.simplified <=> other.simplified (ensures that we # do not try to compare different types) - # 2. IANA-registered definitions < other definitions. - # 3. Complete definitions < incomplete definitions. - # 4. Current definitions < obsolete definitions. - # 5. Obselete with use-instead names < obsolete without. - # 6. Obsolete use-instead definitions are compared. - # - # While this method is public, its use is strongly discouraged by consumers - # of mime-types. In mime-types 3, this method is likely to see substantial - # revision and simplification to ensure current registered content types sort - # before unregistered or obsolete content types. + # 2. active definitions < obsolete definitions + # 3. IANA-registered definitions < unregistered definitions + # 4. Normal registrations < Provisional registrations + # 5. Complete definitions < incomplete definitions. + # 6. self.extension count <=> other.extension count (capped to 16) + # + # After the first comparison, the comparison is simplified by using a + # precomputed 8-bit flag value. + # + # While this method is public, its direct use is strongly discouraged by + # consumers of mime-types. For the next major version of MIME::Types, this + # method will become #<=> and #priority_compare will be removed. def priority_compare(other) - pc = simplified <=> other.simplified - if pc.zero? || !(extensions & other.extensions).empty? - pc = - if (reg = registered?) != other.registered? - reg ? -1 : 1 # registered < unregistered - elsif (comp = complete?) != other.complete? - comp ? -1 : 1 # complete < incomplete - elsif (obs = obsolete?) != other.obsolete? - obs ? 1 : -1 # current < obsolete - elsif obs && ((ui = use_instead) != (oui = other.use_instead)) - if ui.nil? - 1 - elsif oui.nil? - -1 - else - ui <=> oui - end - else - 0 - end + if (cmp = simplified <=> other.simplified).zero? + __priority <=> other.__priority + else + cmp end - - pc end # Returns +true+ if the +other+ object is a MIME::Type and the content types @@ -273,6 +253,10 @@ def hash simplified.hash end + # The computed priority value. This is _not_ intended to be used + # by most callers. + attr_reader :__priority + # Returns the whole MIME content-type string. # # The content type is a presentation value from the MIME type registry and @@ -328,6 +312,8 @@ def extensions ## def extensions=(value) # :nodoc: @extensions = Set[*Array(value).flatten.compact].freeze + update_priority + MIME::Types.send(:reindex_extensions, self) end @@ -408,9 +394,17 @@ def use_instead attr_writer :use_instead # Returns +true+ if the media type is obsolete. - attr_accessor :obsolete + # + # :attr_accessor: obsolete + attr_reader :obsolete alias_method :obsolete?, :obsolete + ## + def obsolete=(value) + @obsolete = !!value + update_priority + end + # The documentation for this MIME::Type. attr_accessor :docs @@ -468,11 +462,27 @@ def xref_urls end # Indicates whether the MIME type has been registered with IANA. - attr_accessor :registered + # + # :attr_accessor: registered + attr_reader :registered alias_method :registered?, :registered + ## + def registered=(value) + @registered = !!value + update_priority + end + # Indicates whether the MIME type's registration with IANA is provisional. - attr_accessor :provisional + # + # :attr_accessor: provisional + attr_reader :provisional + + ## + def provisional=(value) + @provisional = !!value + update_priority + end # Indicates whether the MIME type's registration with IANA is provisional. def provisional? @@ -631,6 +641,37 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/") private + # :stopdoc: + PRIORITY_MASK = 0b11111111 + # :startdoc: + private_constant :PRIORITY_MASK + + # Update the __priority value. Note that @provisional and @obsolete are + # _inverted_ values and are lower if false. + # + # The __priority value is masked as follows: + # + # | bit | meaning | + # | --- | -------------------- | + # | 7 | not obsolete | + # | 6 | registered | + # | 5 | not provisional | + # | 4 | complete | + # | 3 | number of extensions | + # | 2 | number of extensions | + # | 1 | number of extensions | + # | 0 | number of extensions | + def update_priority + extension_count = @extensions.length + obsolete = @obsolete ? 0 : 1 << 7 + registered = @registered ? 1 << 6 : 0 + provisional = @provisional ? 0 : 1 << 5 + complete = extension_count.nonzero? ? 1 << 6 : 0 + extension_count = [0, [extension_count, 16].max].min + + @__priority = obsolete | registered | provisional | complete | extension_count + end + def content_type=(type_string) match = MEDIA_TYPE_RE.match(type_string) fail InvalidContentType, type_string if match.nil? From 915459e0f768d2d794157eeb0985325f9419bee7 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Fri, 17 Dec 2021 11:36:12 -0500 Subject: [PATCH 05/11] Do not document `MIME::Type#__priority` --- History.md | 39 ++++++++++++++++++++++++--------------- lib/mime/type.rb | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/History.md b/History.md index aaf453c..f3d57cd 100644 --- a/History.md +++ b/History.md @@ -1,22 +1,13 @@ # Changelog -## 3.6.0 / 2024-10-02 +## NEXT / YYYY-MM-DD -- 2 deprecations: +- 1 deprecation: - - Array-based MIME::Type initialization - - String-based MIME::Type initialization - - Use of these these will result in deprecation warnings. - -- Added `logger` to the gemspec to suppress a bundled gem warning with Ruby - 3.3.5. This warning should not be showing up until Ruby 3.4.0 is released and - will be suppressed in Ruby 3.3.6. - -- Reworked the deprecation message code to be somewhat more flexible and allow - for outputting certain warnings once. Because there will be at least one other - release after 3.6, we do not need to make the type initialization deprecations - frequent with this release. + - Deprecated `MIME::Type#priority_compare`. In a future release, this will be + will be renamed to `MIME::Type#<=>`. This method is used in tight loops, so + there is no warning message for either `MIME::Type#priority_compare` or + `MIME::Type#<=>`. - 1 enhancement: @@ -34,6 +25,24 @@ none, as explicit support for several special values (which should have caused errors in any case) have been removed. +## 3.6.0 / 2024-10-02 + +- 2 deprecations: + + - Array-based MIME::Type initialization + - String-based MIME::Type initialization + + Use of these these will result in deprecation warnings. + +- Added `logger` to the gemspec to suppress a bundled gem warning with Ruby + 3.3.5. This warning should not be showing up until Ruby 3.4.0 is released and + will be suppressed in Ruby 3.3.6. + +- Reworked the deprecation message code to be somewhat more flexible and allow + for outputting certain warnings once. Because there will be at least one other + release after 3.6, we do not need to make the type initialization deprecations + frequent with this release. + ## 3.5.2 / 2024-01-02 There are no primary code changes, but we are releasing this as an update as diff --git a/lib/mime/type.rb b/lib/mime/type.rb index c5223a3..2e55481 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -255,7 +255,7 @@ def hash # The computed priority value. This is _not_ intended to be used # by most callers. - attr_reader :__priority + attr_reader :__priority #:nodoc: # Returns the whole MIME content-type string. # From 2fed119a889087986b532a2cac87453dd7198505 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Fri, 17 Dec 2021 12:19:55 -0500 Subject: [PATCH 06/11] Reworked bitmap sort priority --- lib/mime/type.rb | 94 ++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 2e55481..966ebee 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -136,8 +136,7 @@ def to_s def initialize(content_type) # :yields: self @friendly = {} @obsolete = @registered = @provisional = false - @preferred_extension = @docs = @use_instead = nil - @priority = 0 + @preferred_extension = @docs = @use_instead = @__sort_priority = nil self.extensions = [] @@ -169,6 +168,8 @@ def initialize(content_type) # :yields: self self.xrefs ||= {} yield self if block_given? + + update_sort_priority end # Indicates that a MIME type is like another type. This differs from @@ -196,26 +197,14 @@ def <=>(other) end # Compares the +other+ MIME::Type using the simplified representation, then - # a pre-computed priority value. Used by MIME::Types#[] to sort types. The - # comparisons involved are: - # - # 1. self.simplified <=> other.simplified (ensures that we - # do not try to compare different types) - # 2. active definitions < obsolete definitions - # 3. IANA-registered definitions < unregistered definitions - # 4. Normal registrations < Provisional registrations - # 5. Complete definitions < incomplete definitions. - # 6. self.extension count <=> other.extension count (capped to 16) - # - # After the first comparison, the comparison is simplified by using a - # precomputed 8-bit flag value. + # a pre-computed sort priority value. Used by MIME::Types#[] to sort types. # # While this method is public, its direct use is strongly discouraged by # consumers of mime-types. For the next major version of MIME::Types, this # method will become #<=> and #priority_compare will be removed. def priority_compare(other) if (cmp = simplified <=> other.simplified).zero? - __priority <=> other.__priority + __sort_priority <=> other.__sort_priority else cmp end @@ -253,9 +242,11 @@ def hash simplified.hash end - # The computed priority value. This is _not_ intended to be used - # by most callers. - attr_reader :__priority #:nodoc: + # The computed sort priority value. This is _not_ intended to be used by most + # callers. + def __sort_priority + @__sort_priority || update_sort_priority + end # Returns the whole MIME content-type string. # @@ -311,9 +302,8 @@ def extensions ## def extensions=(value) # :nodoc: + clear_sort_priority @extensions = Set[*Array(value).flatten.compact].freeze - update_priority - MIME::Types.send(:reindex_extensions, self) end @@ -401,8 +391,8 @@ def use_instead ## def obsolete=(value) + clear_sort_priority @obsolete = !!value - update_priority end # The documentation for this MIME::Type. @@ -469,8 +459,8 @@ def xref_urls ## def registered=(value) + clear_sort_priority @registered = !!value - update_priority end # Indicates whether the MIME type's registration with IANA is provisional. @@ -480,8 +470,8 @@ def registered=(value) ## def provisional=(value) + clear_sort_priority @provisional = !!value - update_priority end # Indicates whether the MIME type's registration with IANA is provisional. @@ -565,6 +555,7 @@ def encode_with(coder) coder["registered"] = registered? coder["provisional"] = provisional? if provisional? coder["signature"] = signature? if signature? + coder["__sort_priority"] = __sort_priority coder end @@ -584,6 +575,7 @@ def init_with(coder) self.signature = coder["signature"] self.xrefs = coder["xrefs"] || {} self.use_instead = coder["use-instead"] + @__sort_priority = coder["__sort_priority"] friendly(coder["friendly"] || {}) end @@ -641,35 +633,35 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/") private - # :stopdoc: - PRIORITY_MASK = 0b11111111 - # :startdoc: - private_constant :PRIORITY_MASK - - # Update the __priority value. Note that @provisional and @obsolete are - # _inverted_ values and are lower if false. - # - # The __priority value is masked as follows: - # - # | bit | meaning | - # | --- | -------------------- | - # | 7 | not obsolete | - # | 6 | registered | - # | 5 | not provisional | - # | 4 | complete | - # | 3 | number of extensions | - # | 2 | number of extensions | - # | 1 | number of extensions | - # | 0 | number of extensions | - def update_priority + def clear_sort_priority + @__sort_priority = nil + end + + # Update the __sort_priority value. Lower numbers sort better, so the + # bitmapping may seem a little odd. The _best_ sort priority is 0. + # + # | bit | meaning | details | + # | --- | --------------- | --------- | + # | 7 | obsolete | 1 if true | + # | 6 | provisional | 1 if true | + # | 5 | registered | 0 if true | + # | 4 | complete | 0 if true | + # | 3 | # of extensions | see below | + # | 2 | # of extensions | see below | + # | 1 | # of extensions | see below | + # | 0 | # of extensions | see below | + # + # The # of extensions is marked as the number of extensions subtracted from + # 16, to a minimum of 0. + def update_sort_priority extension_count = @extensions.length - obsolete = @obsolete ? 0 : 1 << 7 - registered = @registered ? 1 << 6 : 0 - provisional = @provisional ? 0 : 1 << 5 - complete = extension_count.nonzero? ? 1 << 6 : 0 - extension_count = [0, [extension_count, 16].max].min + obsolete = @obsolete ? 1 << 7 : 0 + provisional = @provisional ? 1 << 6 : 0 + registered = @registered ? 0 : 1 << 5 + complete = extension_count.nonzero? ? 0 : 1 << 4 + extension_count = [0, 16 - extension_count].max - @__priority = obsolete | registered | provisional | complete | extension_count + @__sort_priority = obsolete | registered | provisional | complete | extension_count end def content_type=(type_string) From b3555379227868afbceef6f01231b2f520a98055 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Wed, 5 Jan 2022 11:45:44 -0500 Subject: [PATCH 07/11] Support extension priorities --- lib/mime/type.rb | 44 +++++++++++++++++++++++++++++++++++-- lib/mime/type/columnar.rb | 2 ++ lib/mime/types.rb | 14 +++++++++--- lib/mime/types/_columnar.rb | 35 ++++++++++++++++++++++++++--- 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 966ebee..6a9fa55 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -137,6 +137,7 @@ def initialize(content_type) # :yields: self @friendly = {} @obsolete = @registered = @provisional = false @preferred_extension = @docs = @use_instead = @__sort_priority = nil + __extension_priorities self.extensions = [] @@ -331,10 +332,29 @@ def preferred_extension def preferred_extension=(value) # :nodoc: if value add_extensions(value) + set_preferred_extension_priority(value) + else + clear_extension_priority(@preferred_extension) end @preferred_extension = value end + ## + # Optional extension priorities for this MIME type. This is a relative value + # similar to nice(1). An explicitly set `preferred_extension` is automatically + # given a relative priority of `-10`. + # + # :attr_reader: extension_priorities + attr_accessor :extension_priorities + + ## + # Returns the priority for the provided extension or extensions. If a priority + # is not set, the default priority is 0. The range for priorities is -20..20, + # inclusive. + def extension_priority(*exts) + exts.map { |ext| get_extension_priority(ext) }.min + end + ## # The encoding (+7bit+, +8bit+, quoted-printable, or +base64+) # required to transport the data of this content type safely across a @@ -555,7 +575,8 @@ def encode_with(coder) coder["registered"] = registered? coder["provisional"] = provisional? if provisional? coder["signature"] = signature? if signature? - coder["__sort_priority"] = __sort_priority + coder["sort-priority"] = __sort_priority + coder["extension-priorities"] = __extension_priorities unless __extension_priorities.empty? coder end @@ -564,6 +585,7 @@ def encode_with(coder) # # This method should be considered a private implementation detail. def init_with(coder) + @__sort_priority = 0 self.content_type = coder["content-type"] self.docs = coder["docs"] || "" self.encoding = coder["encoding"] @@ -575,9 +597,11 @@ def init_with(coder) self.signature = coder["signature"] self.xrefs = coder["xrefs"] || {} self.use_instead = coder["use-instead"] - @__sort_priority = coder["__sort_priority"] + self.extension_priorities = coder["extension-priorities"] friendly(coder["friendly"] || {}) + + update_sort_priority end def inspect # :nodoc: @@ -633,6 +657,22 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/") private + def __extension_priorities + @extension_priorities ||= {} + end + + def clear_extension_priority(ext) + __extension_priorities.delete(ext) if ext + end + + def get_extension_priority(ext) + [[-20, __extension_priorities[ext] || 0].max, 20].min + end + + def set_preferred_extension_priority(ext) + __extension_priorities[ext] = -10 unless __extension_priorities.has_key?(ext) + end + def clear_sort_priority @__sort_priority = nil end diff --git a/lib/mime/type/columnar.rb b/lib/mime/type/columnar.rb index 1b7c3ca..ec4dec2 100644 --- a/lib/mime/type/columnar.rb +++ b/lib/mime/type/columnar.rb @@ -39,6 +39,7 @@ def self.column(*methods, file: nil) # :nodoc: :signature?, :provisional, :provisional=, :provisional?, file: "flags" column :xrefs, :xrefs=, :xref_urls column :use_instead, :use_instead= + column :extension_priorities, :extension_priorities= def encode_with(coder) # :nodoc: @container.send(:load_friendly) @@ -48,6 +49,7 @@ def encode_with(coder) # :nodoc: @container.send(:load_use_instead) @container.send(:load_xrefs) @container.send(:load_preferred_extension) + @container.send(:load_extension_priorities) super end diff --git a/lib/mime/types.rb b/lib/mime/types.rb index 026be34..26fad76 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -151,10 +151,18 @@ def [](type_id, complete: false, registered: false) # puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif)) # => [application/xml, image/gif, text/xml] def type_for(filename) - Array(filename).flat_map { |fn| - @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/m, 1]] + extensions = Array(filename).map { |fn| fn.chomp.downcase[/\.?([^.]*?)$/, 1] } + + extensions.flat_map { |ext| + @extension_index[ext] }.compact.inject(Set.new, :+).sort { |a, b| - a.priority_compare(b) + by_ext = a.extension_priority(*extensions) <=> b.extension_priority(*extensions) + + if by_ext.zero? + a.priority_compare(b) + else + by_ext + end } end alias_method :of, :type_for diff --git a/lib/mime/types/_columnar.rb b/lib/mime/types/_columnar.rb index 9f8c132..253920c 100644 --- a/lib/mime/types/_columnar.rb +++ b/lib/mime/types/_columnar.rb @@ -91,7 +91,7 @@ def load_flags def load_xrefs each_file_line("xrefs") { |type, line| - type.instance_variable_set(:@xrefs, dict(line, array: true)) + type.instance_variable_set(:@xrefs, dict(line, transform: :array)) } end @@ -107,18 +107,47 @@ def load_use_instead end end - def dict(line, array: false) + def load_extension_priorities + each_file_line("extpri") do |type, line| + type.instance_variable_set(:@extension_priorities, dict(line, transform: :extension_priority)) + end + rescue + # This path preserves backwards compatibility. + end + + def dict(line, transform: nil) if line == "-" {} else line.split("|").each_with_object({}) { |l, h| k, v = l.split("^") v = nil if v.empty? - h[k] = array ? Array(v) : v + + if transform + send(:"dict_#{transform}", h, k, v) + else + h[k] = v + end } end end + def dict_extension_priority(h, k, v) + return if v.nil? + + v = v.to_i if v.kind_of?(String) + v = v.trunc if v.kind_of?(Float) + v = [[-20, v].max, 20].min + + return if v.zero? + + h[k] = v + end + + def dict_array(h, k, v) + h[k] = Array(v) + end + def arr(line) if line == "-" [] From 4fbe291dd957dfaf387eb0ef5749482f91ec8fe2 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Thu, 6 Jan 2022 23:06:12 -0500 Subject: [PATCH 08/11] Ensure priority sort over alpha sort - Added extension priority map. This is an imperfect solution, and is not used by default with default configuration (column-based data). - We may want to consider a revised columnar format for a future version that has a bit more information than is present in the base file. - Adding the sort priority and extension priority helped, but because the alphanumeric sort was first in `MIME::Type#priority_compare`, the results weren't as good as they should have been. We now sort by the sort priority values _first_ and the alphanumeric values _second_. - Stored sort priority was not respected because it depends on flags not kept in the base file. Added support for a binary file with this to ensure it is loaded. --- lib/mime/type.rb | 31 ++++++++++++++++++++++--------- lib/mime/type/columnar.rb | 11 +++++++++++ lib/mime/types.rb | 4 ++++ lib/mime/types/_columnar.rb | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 6a9fa55..792d38f 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -204,8 +204,8 @@ def <=>(other) # consumers of mime-types. For the next major version of MIME::Types, this # method will become #<=> and #priority_compare will be removed. def priority_compare(other) - if (cmp = simplified <=> other.simplified).zero? - __sort_priority <=> other.__sort_priority + if (cmp = __sort_priority <=> other.__sort_priority).zero? + simplified <=> other.simplified else cmp end @@ -245,7 +245,7 @@ def hash # The computed sort priority value. This is _not_ intended to be used by most # callers. - def __sort_priority + def __sort_priority # :nodoc: @__sort_priority || update_sort_priority end @@ -340,17 +340,24 @@ def preferred_extension=(value) # :nodoc: end ## - # Optional extension priorities for this MIME type. This is a relative value - # similar to nice(1). An explicitly set `preferred_extension` is automatically - # given a relative priority of `-10`. + # Optional extension priorities for this MIME type. This is a map of + # extensions to relative priority values (+-20..20+) similar to +nice(1)+. + # Unless otherwise specified in the data, an explicitly set + # +preferred_extension+ is automatically given a relative priority of +-10+. # # :attr_reader: extension_priorities attr_accessor :extension_priorities ## # Returns the priority for the provided extension or extensions. If a priority - # is not set, the default priority is 0. The range for priorities is -20..20, - # inclusive. + # is not set, the default priority is +0+. The range for priorities is + # +-20..20+, inclusive. + # + # Obsolete MIME types have a +3 penalty applied to their + # extension priority and unregistered MIME types have a +2 + # penalty to their extension priority, meaning that the highest priority an + # obsolete, unregistered MIME type can have is +-15+. The lowest priority is + # always +20. def extension_priority(*exts) exts.map { |ext| get_extension_priority(ext) }.min end @@ -666,7 +673,7 @@ def clear_extension_priority(ext) end def get_extension_priority(ext) - [[-20, __extension_priorities[ext] || 0].max, 20].min + [[-20, (__extension_priorities[ext] || 0) + __priority_penalty].max, 20].min end def set_preferred_extension_priority(ext) @@ -702,6 +709,12 @@ def update_sort_priority extension_count = [0, 16 - extension_count].max @__sort_priority = obsolete | registered | provisional | complete | extension_count + @__priority_penalty = (@obsolete ? 3 : 0) + (@registered ? 0 : 2) + end + + def __priority_penalty + update_sort_priority if @__priority_penalty.nil? + @__priority_penalty end def content_type=(type_string) diff --git a/lib/mime/type/columnar.rb b/lib/mime/type/columnar.rb index ec4dec2..a51f9d9 100644 --- a/lib/mime/type/columnar.rb +++ b/lib/mime/type/columnar.rb @@ -53,6 +53,17 @@ def encode_with(coder) # :nodoc: super end + def update_sort_priority + if @container.__fully_loaded? + super + else + obsolete = (@__sort_priority & (1 << 7)) != 0 + registered = (@__sort_priority & (1 << 5)) == 0 + + @__priority_penalty = (@obsolete ? 3 : 0) + (@registered ? 0 : 2) + end + end + class << self undef column end diff --git a/lib/mime/types.rb b/lib/mime/types.rb index 26fad76..b8e1377 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -204,6 +204,10 @@ def add_type(type, quiet = false) index_extensions!(type) end + def __fully_loaded? # :nodoc: + true + end + private def add_type_variant!(mime_type) diff --git a/lib/mime/types/_columnar.rb b/lib/mime/types/_columnar.rb index 253920c..c191e39 100644 --- a/lib/mime/types/_columnar.rb +++ b/lib/mime/types/_columnar.rb @@ -18,6 +18,10 @@ def self.extended(obj) # :nodoc: obj.instance_variable_set(:@__files__, Set.new) end + def __fully_loaded? # :nodoc: + @__files__.size == 10 + end + # Load the first column data file (type and extensions). def load_base_data(path) # :nodoc: @__root__ = path @@ -33,6 +37,10 @@ def load_base_data(path) # :nodoc: add(type) end + each_file_byte("spri") do |type, byte| + type.instance_variable_set(:@__sort_priority, byte) + end + self end @@ -60,6 +68,25 @@ def each_file_line(name, lookup = true) end end + def each_file_byte(name) + LOAD_MUTEX.synchronize do + next if @__files__.include?(name) + + i = -1 + + filename = File.join(@__root__, "mime.#{name}.column") + + next unless File.exist?(filename) + + IO.binread(filename).unpack("C*").each do |byte| + (type = @__mime_data__[i += 1]) || next + yield type, byte + end + + @__files__ << name + end + end + def load_encoding each_file_line("encoding") do |type, line| pool ||= {} From 0d6d589a45c4c8a1c1109e848df2c1f3a1014773 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Fri, 7 Jan 2022 11:48:24 -0500 Subject: [PATCH 09/11] WIP --- lib/mime/type.rb | 24 +++++++++------- lib/mime/type/columnar.rb | 6 ++-- lib/mime/types/_columnar.rb | 7 ++--- test/test_mime_type.rb | 53 ++++++++++++++++++++--------------- test/test_mime_types_class.rb | 5 ++-- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 792d38f..ce82459 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -192,6 +192,8 @@ def like?(other) # # Note that this implementation of #<=> is deprecated and will be changed # in the next major version to be the same as #priority_compare. + # + # Note that MIME::Types no longer compare against nil. def <=>(other) return priority_compare(other) if other.is_a?(MIME::Type) simplified <=> other @@ -246,7 +248,8 @@ def hash # The computed sort priority value. This is _not_ intended to be used by most # callers. def __sort_priority # :nodoc: - @__sort_priority || update_sort_priority + update_sort_priority if !instance_variable_defined?(:@__sort_priority) || @__sort_priority.nil? + @__sort_priority end # Returns the whole MIME content-type string. @@ -333,7 +336,7 @@ def preferred_extension=(value) # :nodoc: if value add_extensions(value) set_preferred_extension_priority(value) - else + elsif instance_variable_defined?(:@preferred_extension) clear_extension_priority(@preferred_extension) end @preferred_extension = value @@ -582,7 +585,7 @@ def encode_with(coder) coder["registered"] = registered? coder["provisional"] = provisional? if provisional? coder["signature"] = signature? if signature? - coder["sort-priority"] = __sort_priority + coder["sort-priority"] = __sort_priority || 0b11111111 coder["extension-priorities"] = __extension_priorities unless __extension_priorities.empty? coder end @@ -662,12 +665,12 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/") end end - private - - def __extension_priorities + def __extension_priorities # :nodoc: @extension_priorities ||= {} end + private + def clear_extension_priority(ext) __extension_priorities.delete(ext) if ext end @@ -702,14 +705,15 @@ def clear_sort_priority # 16, to a minimum of 0. def update_sort_priority extension_count = @extensions.length - obsolete = @obsolete ? 1 << 7 : 0 - provisional = @provisional ? 1 << 6 : 0 - registered = @registered ? 0 : 1 << 5 + obsolete = instance_variable_defined?(:@obsolete) && @obsolete ? 1 << 7 : 0 + provisional = instance_variable_defined?(:@provisional) && @provisional ? 1 << 6 : 0 + registered = instance_variable_defined?(:@registered) && @registered ? 0 : 1 << 5 complete = extension_count.nonzero? ? 0 : 1 << 4 extension_count = [0, 16 - extension_count].max @__sort_priority = obsolete | registered | provisional | complete | extension_count - @__priority_penalty = (@obsolete ? 3 : 0) + (@registered ? 0 : 2) + @__priority_penalty = (instance_variable_defined?(:@obsolete) && @obsolete ? 3 : 0) + + (instance_variable_defined?(:@registered) && @registered ? 0 : 2) end def __priority_penalty diff --git a/lib/mime/type/columnar.rb b/lib/mime/type/columnar.rb index a51f9d9..11719d8 100644 --- a/lib/mime/type/columnar.rb +++ b/lib/mime/type/columnar.rb @@ -15,8 +15,10 @@ class MIME::Type::Columnar < MIME::Type def initialize(container, content_type, extensions) # :nodoc: @container = container + @__priority_penalty = nil self.content_type = content_type - self.extensions = extensions + @extensions = Set[*Array(extensions).flatten.compact].freeze + clear_sort_priority end def self.column(*methods, file: nil) # :nodoc: @@ -60,7 +62,7 @@ def update_sort_priority obsolete = (@__sort_priority & (1 << 7)) != 0 registered = (@__sort_priority & (1 << 5)) == 0 - @__priority_penalty = (@obsolete ? 3 : 0) + (@registered ? 0 : 2) + @__priority_penalty = (obsolete ? 3 : 0) + (registered ? 0 : 2) end end diff --git a/lib/mime/types/_columnar.rb b/lib/mime/types/_columnar.rb index c191e39..ab39df1 100644 --- a/lib/mime/types/_columnar.rb +++ b/lib/mime/types/_columnar.rb @@ -30,7 +30,6 @@ def load_base_data(path) # :nodoc: line = line.split content_type = line.shift extensions = line - # content_type, *extensions = line.split type = MIME::Type::Columnar.new(self, content_type, extensions) @__mime_data__ << type @@ -162,9 +161,9 @@ def dict(line, transform: nil) def dict_extension_priority(h, k, v) return if v.nil? - v = v.to_i if v.kind_of?(String) - v = v.trunc if v.kind_of?(Float) - v = [[-20, v].max, 20].min + v = v.to_i if v.is_a?(String) + v = v.trunc if v.is_a?(Float) + v = [[-20, v].max, 20].min return if v.zero? diff --git a/test/test_mime_type.rb b/test/test_mime_type.rb index a2349ca..4735b5e 100644 --- a/test/test_mime_type.rb +++ b/test/test_mime_type.rb @@ -175,11 +175,6 @@ def mime_type(content_type) refute_equal text_plain, "text/html" assert_operator text_plain, :>, "text/html" end - - it "correctly compares against nil" do - refute_equal text_html, nil - assert_operator text_plain, :<, nil - end end describe "#ascii?" do @@ -326,16 +321,20 @@ def mime_type(content_type) end describe "#priority_compare" do + def priority(type) + "#{type} (#{("%08b" % type.__sort_priority).split(//).join(" ")})" + end + def assert_priority_less(left, right) - assert_equal(-1, left.priority_compare(right)) + assert_equal -1, left.priority_compare(right), "#{priority(left)} is not less than #{priority(right)}" end def assert_priority_same(left, right) - assert_equal 0, left.priority_compare(right) + assert_equal 0, left.priority_compare(right), "#{priority(left)} is not equal to #{priority(right)}" end def assert_priority_more(left, right) - assert_equal 1, left.priority_compare(right) + assert_equal 1, left.priority_compare(right), "#{priority(left)} is not more than #{priority(right)}" end def assert_priority(left, middle, right) @@ -348,21 +347,27 @@ def assert_priority(left, middle, right) let(:text_1p) { mime_type("content-type" => "text/1") } let(:text_2) { mime_type("content-type" => "text/2") } - it "sorts (1) based on the simplified type" do + it "sorts based on the simplified type when the sort priorities are the same" do assert_priority text_1, text_1p, text_2 end - it "sorts (2) based on extensions" do - text_1.extensions = ["foo", "bar"] - text_2.extensions = ["foo"] + it "sorts obsolete types higher than non-obsolete types" do + text_1.obsolete = text_1p.obsolete = false + text_1b = mime_type(text_1) { |t| t.obsolete = true } + + assert_priority_less text_1, text_1b - assert_priority_same text_1, text_2 + assert_priority text_1, text_1p, text_1b + end - text_2.registered = true + it "sorts provisional types higher than non-provisional types" do + text_1.provisional = text_1p.provisional = true + text_1b = mime_type(text_1) { |t| t.provisional = false } - assert_priority_more text_1, text_2 + assert_priority text_1, text_1p, text_1b end + it "sorts (3) based on the registration state" do text_1.registered = text_1p.registered = true text_1b = mime_type(text_1) { |t| t.registered = false } @@ -377,12 +382,6 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end - it "sorts (5) based on obsolete status" do - text_1.obsolete = text_1p.obsolete = false - text_1b = mime_type(text_1) { |t| t.obsolete = true } - - assert_priority text_1, text_1p, text_1b - end it "sorts (5) based on the use-instead value" do text_1.obsolete = text_1p.obsolete = true @@ -395,6 +394,14 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end + + it "sorts based on extensions (more extensions sort lower)" do + text_1.extensions = ["foo", "bar"] + text_2.extensions = ["foo"] + + assert_priority_less text_1, text_2 + end + end describe "#raw_media_type" do @@ -502,10 +509,10 @@ def assert_has_keys(wanted_keys, actual, msg = nil) describe "#to_json" do let(:expected_1) { - '{"content-type":"a/b","encoding":"base64","registered":false}' + '{"content-type":"a/b","encoding":"base64","registered":false,"sort-priority":48}' } let(:expected_2) { - '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true}' + '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true,"sort-priority":80}' } it "converts to JSON when requested" do diff --git a/test/test_mime_types_class.rb b/test/test_mime_types_class.rb index c4b8a33..b42d643 100644 --- a/test/test_mime_types_class.rb +++ b/test/test_mime_types_class.rb @@ -47,7 +47,7 @@ def setup } # This is this way because of a new type ending with gzip that only # appears in some data files. - assert_equal %w[application/gzip application/x-gzip multipart/x-gzip], types + assert_equal %w[application/gzip multipart/x-gzip application/x-gzip], types assert_equal 3, types.size end @@ -87,8 +87,7 @@ def setup end it "finds multiple extensions" do - assert_equal %w[image/jpeg text/plain], - MIME::Types.type_for(%w[foo.txt foo.jpeg]) + assert_equal %w[text/plain image/jpeg], MIME::Types.type_for(%w[foo.txt foo.jpeg]) end it "does not find unknown extensions" do From 194dc974c5dcf9a8db30501cdc5a796d8a111a3b Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Tue, 2 Jan 2024 00:49:40 -0500 Subject: [PATCH 10/11] More WIP, still does not work --- lib/mime/type.rb | 12 ++++++------ lib/mime/types.rb | 6 ++---- test/test_mime_type.rb | 7 ++----- test/test_mime_types_class.rb | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/mime/type.rb b/lib/mime/type.rb index ce82459..23036d5 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -206,7 +206,7 @@ def <=>(other) # consumers of mime-types. For the next major version of MIME::Types, this # method will become #<=> and #priority_compare will be removed. def priority_compare(other) - if (cmp = __sort_priority <=> other.__sort_priority).zero? + if (cmp = __sort_priority <=> other.__sort_priority) == 0 simplified <=> other.simplified else cmp @@ -705,15 +705,15 @@ def clear_sort_priority # 16, to a minimum of 0. def update_sort_priority extension_count = @extensions.length - obsolete = instance_variable_defined?(:@obsolete) && @obsolete ? 1 << 7 : 0 - provisional = instance_variable_defined?(:@provisional) && @provisional ? 1 << 6 : 0 - registered = instance_variable_defined?(:@registered) && @registered ? 0 : 1 << 5 + obsolete = (instance_variable_defined?(:@obsolete) && @obsolete) ? 1 << 7 : 0 + provisional = (instance_variable_defined?(:@provisional) && @provisional) ? 1 << 6 : 0 + registered = (instance_variable_defined?(:@registered) && @registered) ? 0 : 1 << 5 complete = extension_count.nonzero? ? 0 : 1 << 4 extension_count = [0, 16 - extension_count].max @__sort_priority = obsolete | registered | provisional | complete | extension_count - @__priority_penalty = (instance_variable_defined?(:@obsolete) && @obsolete ? 3 : 0) + - (instance_variable_defined?(:@registered) && @registered ? 0 : 2) + @__priority_penalty = ((instance_variable_defined?(:@obsolete) && @obsolete) ? 3 : 0) + + ((instance_variable_defined?(:@registered) && @registered) ? 0 : 2) end def __priority_penalty diff --git a/lib/mime/types.rb b/lib/mime/types.rb index b8e1377..a632a0b 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -151,10 +151,8 @@ def [](type_id, complete: false, registered: false) # puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif)) # => [application/xml, image/gif, text/xml] def type_for(filename) - extensions = Array(filename).map { |fn| fn.chomp.downcase[/\.?([^.]*?)$/, 1] } - - extensions.flat_map { |ext| - @extension_index[ext] + Array(filename).flat_map { |fn| + @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/, 1]] }.compact.inject(Set.new, :+).sort { |a, b| by_ext = a.extension_priority(*extensions) <=> b.extension_priority(*extensions) diff --git a/test/test_mime_type.rb b/test/test_mime_type.rb index 4735b5e..0a0666a 100644 --- a/test/test_mime_type.rb +++ b/test/test_mime_type.rb @@ -322,11 +322,11 @@ def mime_type(content_type) describe "#priority_compare" do def priority(type) - "#{type} (#{("%08b" % type.__sort_priority).split(//).join(" ")})" + "#{type} (#{("%08b" % type.__sort_priority).chars.join(" ")})" end def assert_priority_less(left, right) - assert_equal -1, left.priority_compare(right), "#{priority(left)} is not less than #{priority(right)}" + assert_equal(-1, left.priority_compare(right), "#{priority(left)} is not less than #{priority(right)}") end def assert_priority_same(left, right) @@ -367,7 +367,6 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end - it "sorts (3) based on the registration state" do text_1.registered = text_1p.registered = true text_1b = mime_type(text_1) { |t| t.registered = false } @@ -382,7 +381,6 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end - it "sorts (5) based on the use-instead value" do text_1.obsolete = text_1p.obsolete = true text_1.use_instead = text_1p.use_instead = "abc/xyz" @@ -401,7 +399,6 @@ def assert_priority(left, middle, right) assert_priority_less text_1, text_2 end - end describe "#raw_media_type" do diff --git a/test/test_mime_types_class.rb b/test/test_mime_types_class.rb index b42d643..66122f3 100644 --- a/test/test_mime_types_class.rb +++ b/test/test_mime_types_class.rb @@ -86,7 +86,7 @@ def setup assert_equal %w[image/jpeg], MIME::Types.of(["foo.jpeg", "bar.jpeg"]) end - it "finds multiple extensions" do + it "finds multiple extensions ordered by the filename list" do assert_equal %w[text/plain image/jpeg], MIME::Types.type_for(%w[foo.txt foo.jpeg]) end From 428afcb719ebb96b518c0c02dbe9668b4ed181b4 Mon Sep 17 00:00:00 2001 From: Austin Ziegler Date: Wed, 2 Oct 2024 00:45:41 -0400 Subject: [PATCH 11/11] WIP --- .hoerc | 7 ++++--- lib/mime/type.rb | 4 ++-- lib/mime/types.rb | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.hoerc b/.hoerc index 0d0aae2..55c87d2 100644 --- a/.hoerc +++ b/.hoerc @@ -1,8 +1,8 @@ --- exclude: !ruby/regexp '/ \.(?: - tmp - | swp + tmp | + swp )$ | \.(?: @@ -18,7 +18,8 @@ exclude: !ruby/regexp '/ [gG]emfile(?:\.lock)? | (?: - support + support | + research )\/ | \b(?i:TAGS)$ diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 23036d5..259af18 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -187,8 +187,8 @@ def like?(other) # Compares the +other+ MIME::Type against the exact content type or the # simplified type (the simplified type will be used if comparing against - # something that can be treated as a String with #to_s). In comparisons, - # this is done against the lowercase version of the MIME::Type. + # something that can be treated as a String with #to_s). In comparisons, this + # is done against the lowercase version of the MIME::Type. # # Note that this implementation of #<=> is deprecated and will be changed # in the next major version to be the same as #priority_compare. diff --git a/lib/mime/types.rb b/lib/mime/types.rb index a632a0b..ab891f0 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -152,9 +152,9 @@ def [](type_id, complete: false, registered: false) # => [application/xml, image/gif, text/xml] def type_for(filename) Array(filename).flat_map { |fn| - @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/, 1]] + @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/m, 1]] }.compact.inject(Set.new, :+).sort { |a, b| - by_ext = a.extension_priority(*extensions) <=> b.extension_priority(*extensions) + by_ext = a.extension_priority(*a.extensions) <=> b.extension_priority(*b.extensions) if by_ext.zero? a.priority_compare(b)