Skip to content

Commit 8748d86

Browse files
committed
Support extension priorities
1 parent e0bc5e9 commit 8748d86

File tree

4 files changed

+92
-11
lines changed

4 files changed

+92
-11
lines changed

lib/mime/type.rb

+47-5
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def to_s
102102
# :stopdoc:
103103
# TODO verify mime-type character restrictions; I am pretty sure that this is
104104
# too wide open.
105-
MEDIA_TYPE_RE = %r{([-\w.+]+)/([-\w.+]*)}.freeze
105+
MEDIA_TYPE_RE = %r{([a-zA-Z][-a-zA-Z0-9+_.]*)/([a-zA-Z0-9][-a-zA-Z0-9+_.]*)}.freeze
106106
I18N_RE = /[^[:alnum:]]/.freeze
107107
BINARY_ENCODINGS = %w[base64 8bit].freeze
108108
ASCII_ENCODINGS = %w[7bit quoted-printable].freeze
@@ -131,6 +131,7 @@ def initialize(content_type) # :yields: self
131131
@friendly = {}
132132
@obsolete = @registered = @provisional = false
133133
@preferred_extension = @docs = @use_instead = @__sort_priority = nil
134+
self.__extension_priorities
134135

135136
self.extensions = []
136137

@@ -302,7 +303,7 @@ def add_extensions(*extensions)
302303
# exceptions defined, the first extension will be used.
303304
#
304305
# When setting #preferred_extensions, if #extensions does not contain this
305-
# extension, this will be added to #xtensions.
306+
# extension, this will be added to #extensions.
306307
#
307308
# :attr_accessor: preferred_extension
308309

@@ -313,10 +314,31 @@ def preferred_extension
313314

314315
##
315316
def preferred_extension=(value) # :nodoc:
316-
add_extensions(value) if value
317+
if value
318+
add_extensions(value)
319+
set_preferred_extension_priority(value)
320+
else
321+
clear_extension_priority(@preferred_extension)
322+
end
317323
@preferred_extension = value
318324
end
319325

326+
##
327+
# Optional extension priorities for this MIME type. This is a relative value
328+
# similar to nice(1). An explicitly set `preferred_extension` is automatically
329+
# given a relative priority of `-10`.
330+
#
331+
# :attr_reader: extension_priorities
332+
attr_accessor :extension_priorities
333+
334+
##
335+
# Returns the priority for the provided extension or extensions. If a priority
336+
# is not set, the default priority is 0. The range for priorities is -20..20,
337+
# inclusive.
338+
def extension_priority(*exts)
339+
exts.map { |ext| get_extension_priority(ext) }.min
340+
end
341+
320342
##
321343
# The encoding (+7bit+, +8bit+, <tt>quoted-printable</tt>, or +base64+)
322344
# required to transport the data of this content type safely across a
@@ -537,7 +559,8 @@ def encode_with(coder)
537559
coder["registered"] = registered?
538560
coder["provisional"] = provisional? if provisional?
539561
coder["signature"] = signature? if signature?
540-
coder["__sort_priority"] = __sort_priority
562+
coder["sort-priority"] = __sort_priority
563+
coder["extension-priorities"] = __extension_priorities unless __extension_priorities.empty?
541564
coder
542565
end
543566

@@ -546,6 +569,7 @@ def encode_with(coder)
546569
#
547570
# This method should be considered a private implementation detail.
548571
def init_with(coder)
572+
@__sort_priority = 0
549573
self.content_type = coder["content-type"]
550574
self.docs = coder["docs"] || ""
551575
self.encoding = coder["encoding"]
@@ -557,9 +581,11 @@ def init_with(coder)
557581
self.signature = coder["signature"]
558582
self.xrefs = coder["xrefs"] || {}
559583
self.use_instead = coder["use-instead"]
560-
@__sort_priority = coder["__sort_priority"]
584+
self.extension_priorities = coder["extension-priorities"]
561585

562586
friendly(coder["friendly"] || {})
587+
588+
update_sort_priority
563589
end
564590

565591
def inspect # :nodoc:
@@ -615,6 +641,22 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/")
615641

616642
private
617643

644+
def __extension_priorities
645+
@extension_priorities ||= {}
646+
end
647+
648+
def clear_extension_priority(ext)
649+
__extension_priorities.delete(ext) if ext
650+
end
651+
652+
def get_extension_priority(ext)
653+
[[-20, __extension_priorities[ext] || 0].max, 20].min
654+
end
655+
656+
def set_preferred_extension_priority(ext)
657+
__extension_priorities[ext] = -10 unless __extension_priorities.has_key?(ext)
658+
end
659+
618660
def clear_sort_priority
619661
@__sort_priority = nil
620662
end

lib/mime/type/columnar.rb

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def self.column(*methods, file: nil) # :nodoc:
3939
:signature?, :provisional, :provisional=, :provisional?, file: "flags"
4040
column :xrefs, :xrefs=, :xref_urls
4141
column :use_instead, :use_instead=
42+
column :extension_priorities, :extension_priorities=
4243

4344
def encode_with(coder) # :nodoc:
4445
@container.send(:load_friendly)
@@ -48,6 +49,7 @@ def encode_with(coder) # :nodoc:
4849
@container.send(:load_use_instead)
4950
@container.send(:load_xrefs)
5051
@container.send(:load_preferred_extension)
52+
@container.send(:load_extension_priorities)
5153
super
5254
end
5355

lib/mime/types.rb

+11-3
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,18 @@ def [](type_id, complete: false, registered: false)
151151
# puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif))
152152
# => [application/xml, image/gif, text/xml]
153153
def type_for(filename)
154-
Array(filename).flat_map { |fn|
155-
@extension_index[fn.chomp.downcase[/\.?([^.]*?)$/, 1]]
154+
extensions = Array(filename).map { |fn| fn.chomp.downcase[/\.?([^.]*?)$/, 1] }
155+
156+
extensions.flat_map { |ext|
157+
@extension_index[ext]
156158
}.compact.inject(Set.new, :+).sort { |a, b|
157-
a.priority_compare(b)
159+
by_ext = a.extension_priority(*extensions) <=> b.extension_priority(*extensions)
160+
161+
if by_ext.zero?
162+
a.priority_compare(b)
163+
else
164+
by_ext
165+
end
158166
}
159167
end
160168
alias_method :of, :type_for

lib/mime/types/_columnar.rb

+32-3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def load_flags
9191

9292
def load_xrefs
9393
each_file_line("xrefs") { |type, line|
94-
type.instance_variable_set(:@xrefs, dict(line, array: true))
94+
type.instance_variable_set(:@xrefs, dict(line, transform: :array))
9595
}
9696
end
9797

@@ -107,18 +107,47 @@ def load_use_instead
107107
end
108108
end
109109

110-
def dict(line, array: false)
110+
def load_extension_priorities
111+
each_file_line("extpri") do |type, line|
112+
type.instance_variable_set(:@extension_priorities, dict(line, transform: :extension_priority))
113+
end
114+
rescue
115+
# This path preserves backwards compatibility.
116+
end
117+
118+
def dict(line, transform: nil)
111119
if line == "-"
112120
{}
113121
else
114122
line.split("|").each_with_object({}) { |l, h|
115123
k, v = l.split("^")
116124
v = nil if v.empty?
117-
h[k] = array ? Array(v) : v
125+
126+
if transform
127+
send(:"dict_#{transform}", h, k, v)
128+
else
129+
h[k] = v
130+
end
118131
}
119132
end
120133
end
121134

135+
def dict_extension_priority(h, k, v)
136+
return if v.nil?
137+
138+
v = v.to_i if v.kind_of?(String)
139+
v = v.trunc if v.kind_of?(Float)
140+
v = [[-20, v].max, 20].min
141+
142+
return if v.zero?
143+
144+
h[k] = v
145+
end
146+
147+
def dict_array(h, k, v)
148+
h[k] = Array(v)
149+
end
150+
122151
def arr(line)
123152
if line == "-"
124153
[]

0 commit comments

Comments
 (0)