# -*- ruby encoding: utf-8 -*-
require 'mime/type'
require 'mime/types/cache'
require 'mime/types/loader'
# MIME::Types is a registry of MIME types. It is both a class (created with
# MIME::Types.new) and a default registry (loaded automatically or through
# interactions with MIME::Types.[] and MIME::Types.type_for).
#
# == The Default mime-types Registry
#
# The default mime-types registry is loaded automatically when the library
# is required (require 'mime/types'), but it may be lazily loaded
# (loaded on first use) with the use of the environment variable
# +RUBY_MIME_TYPES_LAZY_LOAD+ having any value other than +false+. The
# initial startup is about 14× faster (~10 ms vs ~140 ms), but the
# registry will be loaded at some point in the future.
#
# The default mime-types registry can also be loaded from a Marshal cache
# file specific to the version of MIME::Types being loaded. This will be
# handled automatically with the use of a file referred to in the
# environment variable +RUBY_MIME_TYPES_CACHE+. MIME::Types will attempt to
# load the registry from this cache file (MIME::Type::Cache.load); if it
# cannot be loaded (because the file does not exist, there is an error, or
# the data is for a different version of mime-types), the default registry
# will be loaded from the normal JSON version and then the cache file will
# be *written* to the location indicated by +RUBY_MIME_TYPES_CACHE+. Cache
# file loads just over 4½× faster (~30 ms vs ~140 ms).
# loads.
#
# Notes:
# * The loading of the default registry is *not* atomic; when using a
# multi-threaded environment, it is recommended that lazy loading is not
# used and mime-types is loaded as early as possible.
# * Cache files should be specified per application in a multiprocess
# environment and should be initialized during deployment or before
# forking to minimize the chance that the multiple processes will be
# trying to write to the same cache file at the same time, or that two
# applications that are on different versions of mime-types would be
# thrashing the cache.
# * Unless cache files are preinitialized, the application using the
# mime-types cache file must have read/write permission to the cache file.
#
# == Usage
# require 'mime/types'
#
# plaintext = MIME::Types['text/plain']
# print plaintext.media_type # => 'text'
# print plaintext.sub_type # => 'plain'
#
# puts plaintext.extensions.join(" ") # => 'asc txt c cc h hh cpp'
#
# puts plaintext.encoding # => 8bit
# puts plaintext.binary? # => false
# puts plaintext.ascii? # => true
# puts plaintext.obsolete? # => false
# puts plaintext.registered? # => true
# puts plaintext == 'text/plain' # => true
# puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip'
#
class MIME::Types
# The release version of Ruby MIME::Types
VERSION = MIME::Type::VERSION
include Enumerable
# The data version.
attr_reader :data_version
# Creates a new MIME::Types registry.
def initialize
@type_variants = Container.new
@extension_index = Container.new
@data_version = VERSION.dup.freeze
end
def add_type_variant(mime_type) # :nodoc:
MIME.deprecated(self, __method__, :private)
add_type_variant!(mime_type)
end
def index_extensions(mime_type) # :nodoc:
MIME.deprecated(self, __method__, :private)
index_extensions!(mime_type)
end
def defined_types # :nodoc:
MIME.deprecated(self, __method__)
@type_variants.values.flatten
end
# Returns the number of known type variants.
def count
@type_variants.values.reduce(0) { |m, o| m + o.size }
end
# Iterates through the type variants.
def each
@type_variants.values.each { |tv| tv.each { |t| yield t } }
end
@__types__ = nil
# Returns a list of MIME::Type objects, which may be empty. The optional
# flag parameters are :complete (finds only complete MIME::Type
# objects) and :registered (finds only MIME::Types that are
# registered). It is possible for multiple matches to be returned for
# either type (in the example below, 'text/plain' returns two values --
# one for the general case, and one for VMS systems).
#
# puts "\nMIME::Types['text/plain']"
# MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") }
#
# puts "\nMIME::Types[/^image/, complete: true]"
# MIME::Types[/^image/, :complete => true].each do |t|
# puts t.to_a.join(", ")
# end
#
# If multiple type definitions are returned, returns them sorted as
# follows:
# 1. Complete definitions sort before incomplete ones;
# 2. IANA-registered definitions sort before LTSW-recorded
# definitions.
# 3. Generic definitions sort before platform-specific ones;
# 4. Current definitions sort before obsolete ones;
# 5. Obsolete definitions with use-instead clauses sort before those
# without;
# 6. Obsolete definitions use-instead clauses are compared.
# 7. Sort on name.
#
# An additional flag of :platform (finds only MIME::Types for the current
# platform) is currently supported but deprecated.
def [](type_id, flags = {})
if flags[:platform]
MIME.deprecated(self, __method__, "using the :platform flag")
end
matches = case type_id
when MIME::Type
@type_variants[type_id.simplified]
when Regexp
match(type_id)
else
@type_variants[MIME::Type.simplified(type_id)]
end
prune_matches(matches, flags).sort { |a, b| a.priority_compare(b) }
end
# Return the list of MIME::Types which belongs to the file based on its
# filename extension. If there is no extension, the filename will be used
# as the matching criteria on its own.
#
# This will always return a merged, flatten, priority sorted, unique array.
#
# puts MIME::Types.type_for('citydesk.xml')
# => [application/xml, text/xml]
# puts MIME::Types.type_for('citydesk.gif')
# => [image/gif]
# puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif))
# => [application/xml, image/gif, text/xml]
#
# If +platform+ is +true+, then only file types that are specific to the
# current platform will be returned. This parameter has been deprecated.
def type_for(filename, platform = false)
types = [ filename ].flatten.map { |fn|
@extension_index[File.basename(fn.chomp.downcase).gsub(/.*\./o, '')]
}.flatten.sort { |a, b| a.priority_compare(b) }.uniq
if platform
MIME.deprecated(self, __method__,
"using the platform parameter")
types.select(&:platform?)
else
types
end
end
alias_method :of, :type_for
# Add one or more MIME::Type objects to the set of known types. If the
# type is already known, a warning will be displayed.
#
# The last parameter may be the value :silent or +true+ which
# will suppress duplicate MIME type warnings.
def add(*types)
quiet = ((types.last == :silent) or (types.last == true))
types.each do |mime_type|
case mime_type
when true, false, nil, Symbol
nil
when MIME::Types
variants = mime_type.instance_variable_get(:@type_variants)
add(*variants.values.flatten, quiet)
when Array
add(*mime_type, quiet)
else
add_type(mime_type, quiet)
end
end
end
# Add a single MIME::Type object to the set of known types. If the type is
# already known, a warning will be displayed. The +quiet+ parameter may be
# a truthy value to suppress that warning.
def add_type(mime_type, quiet = false)
if !quiet and @type_variants[mime_type.simplified].include?(mime_type)
warn("Type %s is already registered as a variant of %s." % [
mime_type, mime_type.simplified ])
end
add_type_variant!(mime_type)
index_extensions!(mime_type)
end
class << self
include Enumerable
# Load MIME::Types from a v1 file registry.
#
# This method has been deprecated.
def load_from_file(filename)
MIME.deprecated(self, __method__)
MIME::Types::Loader.load_from_v1(filename)
end
# MIME::Types#[] against the default MIME::Types registry.
def [](type_id, flags = {})
__types__[type_id, flags]
end
# MIME::Types#count against the default MIME::Types registry.
def count
__types__.count
end
# MIME::Types#each against the default MIME::Types registry.
def each
__types__.each {|t| yield t }
end
# MIME::Types#type_for against the default MIME::Types registry.
def type_for(filename, platform = false)
__types__.type_for(filename, platform)
end
alias_method :of, :type_for
# MIME::Types#add against the default MIME::Types registry.
def add(*types)
__types__.add(*types)
end
# Returns the currently defined cache file, if any.
def cache_file
MIME.deprecated(self, __method__)
ENV['RUBY_MIME_TYPES_CACHE']
end
def add_type_variant(mime_type) # :nodoc:
__types__.add_type_variant(mime_type)
end
def index_extensions(mime_type) # :nodoc:
__types__.index_extensions(mime_type)
end
private
def lazy_load?
(lazy = ENV['RUBY_MIME_TYPES_LAZY_LOAD']) && (lazy != 'false')
end
def __types__
(defined?(@__types__) and @__types__) or load_default_mime_types
end
def load_default_mime_types
@__types__ = MIME::Types::Cache.load
unless @__types__
@__types__ = MIME::Types::Loader.load
MIME::Types::Cache.save(@__types__)
end
@__types__
end
end
private
def add_type_variant!(mime_type)
@type_variants[mime_type.simplified] << mime_type
end
def index_extensions!(mime_type)
mime_type.extensions.each { |ext| @extension_index[ext] << mime_type }
end
def prune_matches(matches, flags)
matches.delete_if { |e| not e.complete? } if flags[:complete]
matches.delete_if { |e| not e.platform? } if flags[:platform]
matches.delete_if { |e| not e.registered? } if flags[:registered]
matches
end
def match(pattern)
@type_variants.select { |k, v| k =~ pattern }.values.flatten
end
load_default_mime_types unless lazy_load?
end
# vim: ft=ruby