Class: Marcel::Magic

Inherits:
Object
  • Object
show all
Defined in:
lib/marcel/magic.rb,
lib/marcel/magic/xml.rb,
lib/marcel/magic/zip.rb

Overview

Mime type detection

Defined Under Namespace

Modules: Xml, Zip

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type) ⇒ Magic

Mime type by type string



16
17
18
19
# File 'lib/marcel/magic.rb', line 16

def initialize(type)
  @type = type
  @mediatype, @subtype = type.split('/', 2)
end

Instance Attribute Details

#mediatypeObject (readonly)

Returns the value of attribute mediatype.



13
14
15
# File 'lib/marcel/magic.rb', line 13

def mediatype
  @mediatype
end

#subtypeObject (readonly)

Returns the value of attribute subtype.



13
14
15
# File 'lib/marcel/magic.rb', line 13

def subtype
  @subtype
end

#typeObject (readonly)

Returns the value of attribute type.



13
14
15
# File 'lib/marcel/magic.rb', line 13

def type
  @type
end

Class Method Details

.add(type, options) ⇒ Object

Add custom mime type. Arguments:

  • type: Mime type
  • options: Options hash

Option keys:

  • :extensions: String list or single string of file extensions
  • :parents: String list or single string of parent mime types
  • :aliases: String list or single string of aliased mime types
  • :magic: Mime magic specification
  • :comment: Comment string


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/marcel/magic.rb', line 31

def self.add(type, options)
  # Validate the complete registration before mutating any table, so a rejected
  # registration leaves every registry untouched.
  #
  # Alias keys are never registered types and alias values never alias keys, so
  # resolution is single-hop by construction: aliasing a registered type is rejected
  # here, and canonicalize (the sanctioned path) re-points existing aliases itself.
  aliases = [options[:aliases]].flatten.compact.map(&:downcase) - [type.downcase]
  aliases.each do |aliased|
    if TYPE_EXTS.key?(aliased) || TYPE_PARENTS.key?(aliased) || MAGIC.any? { |t, _| t == aliased }
      raise ArgumentError, "#{aliased} is a registered type; use canonicalize to alias it to #{type}"
    end
  end

  extensions = [options[:extensions]].flatten.compact
  TYPE_EXTS[type] = extensions
  extensions.each {|ext| EXTENSIONS[ext] = type }

  TYPE_ALIASES.delete(type)
  aliases.each {|aliased| TYPE_ALIASES[aliased] = type }

  parents = [options[:parents]].flatten.compact
  TYPE_PARENTS[type] = parents unless parents.empty?

  MAGIC.unshift [type, options[:magic]] if options[:magic]
end

.all_by_magic(io) ⇒ Object

Lookup all mime types by magic content analysis. This is a slower operation.



166
167
168
# File 'lib/marcel/magic.rb', line 166

def self.all_by_magic(io)
  magic_match(io, :select).map { |mime| new(mime[0]) }
end

.by_extension(ext) ⇒ Object

Lookup mime type by file extension



141
142
143
144
145
146
147
148
# File 'lib/marcel/magic.rb', line 141

def self.by_extension(ext)
  ext = ext.to_s
  return unless ext.valid_encoding?

  ext = ext.downcase
  mime = ext[0..0] == '.' ? EXTENSIONS[ext[1..-1]] : EXTENSIONS[ext]
  mime && new(mime)
end

.by_magic(io) ⇒ Object

Lookup mime type by magic content analysis. This is a slow operation.



159
160
161
162
# File 'lib/marcel/magic.rb', line 159

def self.by_magic(io)
  mime = magic_match(io, :find)
  mime && new(mime[0])
end

.by_path(path) ⇒ Object

Lookup mime type by filename



151
152
153
154
155
# File 'lib/marcel/magic.rb', line 151

def self.by_path(path)
  by_extension(File.extname(path))
rescue ArgumentError, EncodingError
  nil
end

.by_type(type) ⇒ Object

Lookup canonical mime type by mime type string, resolving aliases



136
137
138
# File 'lib/marcel/magic.rb', line 136

def self.by_type(type)
  new(canonical(type)) if type
end

.canonical(type) ⇒ Object

Resolve an aliased type string to its canonical type string



204
205
206
207
208
209
210
# File 'lib/marcel/magic.rb', line 204

def self.canonical(type)
  if type
    # Allocation-free for already-lowercase input: child? resolves every node it visits.
    type = type.downcase if /[A-Z]/.match?(type)
    TYPE_ALIASES[type] || type
  end
end

.canonicalize(type, instead_of:) ⇒ Object

Renames a canonical type: the instead_of type's extensions, magic matchers, parents, and aliases are re-registered under type, and the old name becomes an alias of the new. Useful when a historical or de facto type is preferable to the canonical type shipped in the generated tables, without giving up its matchers.

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/marcel/magic.rb', line 62

def self.canonicalize(type, instead_of:)
  raise ArgumentError, "#{instead_of} is an alias, not canonical" if TYPE_ALIASES[instead_of]

  # Displace whatever the new canonical type was registered as before.
  remove(type)

  # Re-register the old canonical type's dictionary under the new name.
  EXTENSIONS.select { |_, existing| existing == instead_of }.each_key do |ext|
    EXTENSIONS[ext] = type
  end

  if extensions = TYPE_EXTS.delete(instead_of)
    TYPE_EXTS[type] = extensions
  end

  TYPE_ALIASES.select { |_, canonical| canonical == instead_of }.each_key do |aliased|
    TYPE_ALIASES[aliased] = type
  end

  if parents = TYPE_PARENTS.delete(instead_of)
    TYPE_PARENTS[type] = parents
  end

  MAGIC.each { |pair| pair[0] = type if pair[0] == instead_of }

  # Alias the old canonical type to the new.
  TYPE_ALIASES[instead_of] = type
end

.child?(child, parent) ⇒ Boolean

Returns:

  • (Boolean)


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/marcel/magic.rb', line 186

def self.child?(child, parent)
  parent = canonical(parent)
  pending = [child]
  visited = {}

  until pending.empty?
    type = canonical(pending.pop)
    return true if type == parent
    next if visited[type]

    visited[type] = true
    pending.concat(TYPE_PARENTS[type] || [])
  end

  false
end

.remove(type) ⇒ Object

Removes a mime type from the dictionary. You might want to do this if you're seeing impossible conflicts (for instance, application/x-gmc-link).

  • type: The mime type to remove. All associated extensions, magic, and aliases are removed too.


95
96
97
98
99
100
101
# File 'lib/marcel/magic.rb', line 95

def self.remove(type)
  EXTENSIONS.delete_if {|ext, t| t == type }
  MAGIC.delete_if {|t, m| t == type }
  TYPE_EXTS.delete(type)
  TYPE_PARENTS.delete(type)
  TYPE_ALIASES.delete_if {|aliased, canonical| aliased == type || canonical == type }
end

Instance Method Details

#audio?Boolean

Returns:

  • (Boolean)


108
# File 'lib/marcel/magic.rb', line 108

def audio?; mediatype == 'audio'; end

#canonicalObject

Resolve an aliased type to its canonical type; canonical types return themselves



122
123
124
125
126
127
128
# File 'lib/marcel/magic.rb', line 122

def canonical
  if canonical_type = TYPE_ALIASES[type]
    self.class.new(canonical_type)
  else
    self
  end
end

#child_of?(parent) ⇒ Boolean

Returns true if type is child of parent type

Returns:

  • (Boolean)


112
113
114
# File 'lib/marcel/magic.rb', line 112

def child_of?(parent)
  self.class.child?(type, parent)
end

#commentObject

Get mime comment



131
132
133
# File 'lib/marcel/magic.rb', line 131

def comment
  nil # deprecated
end

#eql?(other) ⇒ Boolean Also known as: ==

Allow comparison with string

Returns:

  • (Boolean)


176
177
178
# File 'lib/marcel/magic.rb', line 176

def eql?(other)
  type == other.to_s
end

#extensionsObject

Get string list of file extensions



117
118
119
# File 'lib/marcel/magic.rb', line 117

def extensions
  TYPE_EXTS[type] || []
end

#hashObject



180
181
182
# File 'lib/marcel/magic.rb', line 180

def hash
  type.hash
end

#image?Boolean

Mediatype shortcuts

Returns:

  • (Boolean)


107
# File 'lib/marcel/magic.rb', line 107

def image?; mediatype == 'image'; end

#text?Boolean

Returns true if type is a text format

Returns:

  • (Boolean)


104
# File 'lib/marcel/magic.rb', line 104

def text?; mediatype == 'text' || child_of?('text/plain'); end

#to_sObject

Return type as string



171
172
173
# File 'lib/marcel/magic.rb', line 171

def to_s
  type
end

#video?Boolean

Returns:

  • (Boolean)


109
# File 'lib/marcel/magic.rb', line 109

def video?; mediatype == 'video'; end