marcel 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35fe8aac124b049b86f375622d6255bb549fa4c972db38f27328472c96a74497
4
- data.tar.gz: 9b4800e39fa6c1506acb964730567f4457a2b5e89be6c0fc271144f2dc5b34f8
3
+ metadata.gz: d72cd32835e2c94cb305350ca65c70471ba0003730705c42f880571344b9a79c
4
+ data.tar.gz: bf87bf4e6739db822a9e474677d4785aad74a37add0ad88fba3c2daac24bb142
5
5
  SHA512:
6
- metadata.gz: cbf93f7d5b30bcb3927c49fe3cbcc238f040689178a810892be64095908112dbe27381a438bb7d6cf89980af14e82b52d46263f9df8577d2836c5dbd9858771b
7
- data.tar.gz: 787c8be7892b97d02b96edf1340656d43c98829a187f7d90d7b720b188d793e26c8c34f55f1e7529ed646f0e7cb6d8ed8fd87c7b4299510ffed89f7d03cc23b5
6
+ metadata.gz: 002d7bf37923e0ce1da283ee7d41f36f604c454aa0924066013590805bb5bcaa878fab1cda9a884544afbcaf589384e0960a80774723f202a7c6dfa637bea05a
7
+ data.tar.gz: c3c361fc72ceec92d38e938613f8e1bbb7821cbe7221a59255d5d6eb8012aafd775b38f431c34df3fb5e9b443fedfdcda500fb76a2cb52e03cbe6028a042c26e
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Marcel
2
2
 
3
- Marcel attempts to choose the most appropriate content type for a given file by looking at the binary data, the filename, and any declared type (perhaps passed as a request header):
4
-
5
- It's used like this:
3
+ Marcel attempts to choose the most appropriate content type for a given file by looking at the binary data, the filename, and any declared type (perhaps passed as a request header). This is done via the `Marcel::MimeType.for` method, and is used like this:
6
4
 
7
5
  ```ruby
8
6
  Marcel::MimeType.for Pathname.new("example.gif")
@@ -43,6 +41,14 @@ Marcel::MimeType.for Pathname.new("example.png"), name: "example.ai"
43
41
  # As "application/illustrator" is not a more specific type of "image/png", the filename is ignored
44
42
  ```
45
43
 
44
+ Custom file types not supported by Marcel can be added using `Marcel::MimeType.extend`.
45
+
46
+ ```ruby
47
+ Marcel::MimeType.extend "text/custom", extensions: %w( customtxt )
48
+ Marcel::MimeType.for name: "file.customtxt"
49
+ # => "text/custom"
50
+ ```
51
+
46
52
  ## Motivation
47
53
 
48
54
  Marcel was extracted from Basecamp 3, in order to make our file detection logic both easily reusable but more importantly, easily testable. Test fixtures have been added for all of the most common file types uploaded to Basecamp, and other common file types too. We hope to expand this test coverage with other file types as and when problems are identified.
data/lib/marcel/magic.rb CHANGED
@@ -29,9 +29,9 @@ module Marcel
29
29
  # * <i>:comment</i>: Comment string
30
30
  def self.add(type, options)
31
31
  extensions = [options[:extensions]].flatten.compact
32
- TYPES[type] = [extensions,
33
- [options[:parents]].flatten.compact,
34
- options[:comment]]
32
+ TYPE_EXTS[type] = extensions
33
+ parents = [options[:parents]].flatten.compact
34
+ TYPE_PARENTS[type] = parents unless parents.empty?
35
35
  extensions.each {|ext| EXTENSIONS[ext] = type }
36
36
  MAGIC.unshift [type, options[:magic]] if options[:magic]
37
37
  end
@@ -42,7 +42,8 @@ module Marcel
42
42
  def self.remove(type)
43
43
  EXTENSIONS.delete_if {|ext, t| t == type }
44
44
  MAGIC.delete_if {|t, m| t == type }
45
- TYPES.delete(type)
45
+ TYPE_EXTS.delete(type)
46
+ TYPE_PARENTS.delete(type)
46
47
  end
47
48
 
48
49
  # Returns true if type is a text format
@@ -60,12 +61,12 @@ module Marcel
60
61
 
61
62
  # Get string list of file extensions
62
63
  def extensions
63
- TYPES.key?(type) ? TYPES[type][0] : []
64
+ TYPE_EXTS[type] || []
64
65
  end
65
66
 
66
67
  # Get mime comment
67
68
  def comment
68
- (TYPES.key?(type) ? TYPES[type][2] : nil).to_s
69
+ nil # deprecated
69
70
  end
70
71
 
71
72
  # Lookup mime type by file extension
@@ -110,7 +111,7 @@ module Marcel
110
111
  alias == eql?
111
112
 
112
113
  def self.child?(child, parent)
113
- child == parent || TYPES.key?(child) && TYPES[child][1].any? {|p| child?(p, parent) }
114
+ child == parent || TYPE_PARENTS[child]&.any? {|p| child?(p, parent) }
114
115
  end
115
116
 
116
117
  def self.magic_match(io, method)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Marcel::MimeType.extend "text/plain", extensions: %w( txt asc )
2
4
 
3
5
  Marcel::MimeType.extend "application/illustrator", parents: "application/pdf"
@@ -31,7 +33,7 @@ Marcel::MimeType.extend "application/vnd.apple.numbers", extensions: %w( numbers
31
33
  Marcel::MimeType.extend "application/vnd.apple.keynote", extensions: %w( key ), parents: "application/zip"
32
34
 
33
35
  Marcel::MimeType.extend "audio/aac", extensions: %w( aac ), parents: "audio/x-aac"
34
-
36
+ Marcel::MimeType.extend("audio/ogg", extensions: %w( ogg oga ), magic: [[0, 'OggS', [[29, 'vorbis']]]])
35
37
 
36
38
  Marcel::MimeType.extend "image/vnd.dwg", magic: [[0, "AC10"]]
37
39
 
@@ -57,8 +59,8 @@ Marcel::MimeType.extend(
57
59
  "application/vnd.ms-access",
58
60
  extensions: %w( mdb mde accdb accde ),
59
61
  magic: [
60
- [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x4a\x65\x74\x20\x44\x42"], # \x00\x01\x00\xStandard Jet DB
61
- [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x41\x43\x45\x20\x44\x42"], # \x00\x01\x00\xStandard ACE DB
62
+ [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x4a\x65\x74\x20\x44\x42"], # "\x00\x01\x00Standard Jet DB"
63
+ [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x41\x43\x45\x20\x44\x42"], # "\x00\x01\x00Standard ACE DB"
62
64
  ],
63
65
  parents: "application/x-msaccess"
64
66
  )
@@ -1,30 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Marcel
2
4
  class MimeType
3
5
  BINARY = "application/octet-stream"
4
6
 
5
7
  class << self
6
8
  def extend(type, extensions: [], parents: [], magic: nil)
7
- existing = Marcel::TYPES[type] || [[], [], ""]
8
-
9
- extensions = (Array(extensions) + existing[0]).uniq
10
- parents = (Array(parents) + existing[1]).uniq
11
- comment = existing[2]
12
-
13
- Magic.add(type, extensions: extensions, magic: magic, parents: parents, comment: comment)
9
+ extensions = (Array(extensions) + Array(Marcel::TYPE_EXTS[type])).uniq
10
+ parents = (Array(parents) + Array(Marcel::TYPE_PARENTS[type])).uniq
11
+ Magic.add(type, extensions: extensions, magic: magic, parents: parents)
14
12
  end
15
13
 
14
+ # Returns the most appropriate content type for the given file.
15
+ #
16
+ # The first argument should be a +Pathname+ or an +IO+. If it is a +Pathname+, the specified
17
+ # file will be opened first.
18
+ #
19
+ # Optional parameters:
20
+ # * +name+: file name, if known
21
+ # * +extension+: file extension, if known
22
+ # * +declared_type+: MIME type, if known
23
+ #
24
+ # The most appropriate type is determined by the following:
25
+ # * type declared by binary magic number data
26
+ # * type declared by the first of file name, file extension, or declared MIME type
27
+ #
28
+ # If no type can be determined, then +application/octet-stream+ is returned.
16
29
  def for(pathname_or_io = nil, name: nil, extension: nil, declared_type: nil)
17
- type_from_data = for_data(pathname_or_io)
18
- fallback_type = for_declared_type(declared_type) || for_name(name) || for_extension(extension) || BINARY
19
-
20
- if type_from_data
21
- most_specific_type type_from_data, fallback_type
22
- else
23
- fallback_type
24
- end
30
+ filename_type = for_name(name) || for_extension(extension)
31
+ most_specific_type for_data(pathname_or_io), for_declared_type(declared_type), filename_type, BINARY
25
32
  end
26
33
 
27
34
  private
35
+
28
36
  def for_data(pathname_or_io)
29
37
  if pathname_or_io
30
38
  with_io(pathname_or_io) do |io|
@@ -52,11 +60,7 @@ module Marcel
52
60
  end
53
61
 
54
62
  def for_declared_type(declared_type)
55
- type = parse_media_type(declared_type)
56
-
57
- if type != BINARY && !type.nil?
58
- type.downcase
59
- end
63
+ parse_media_type(declared_type)
60
64
  end
61
65
 
62
66
  def with_io(pathname_or_io, &block)
@@ -77,19 +81,9 @@ module Marcel
77
81
  # For some document types (notably Microsoft Office) we recognise the main content
78
82
  # type with magic, but not the specific subclass. In this situation, if we can get a more
79
83
  # specific class using either the name or declared_type, we should use that in preference
80
- def most_specific_type(from_magic_type, fallback_type)
81
- if (root_types(from_magic_type) & root_types(fallback_type)).any?
82
- fallback_type
83
- else
84
- from_magic_type
85
- end
86
- end
87
-
88
- def root_types(type)
89
- if TYPES[type].nil? || TYPES[type][1].empty?
90
- [ type ]
91
- else
92
- TYPES[type][1].map {|t| root_types t }.flatten
84
+ def most_specific_type(*candidates)
85
+ candidates.compact.uniq.reduce do |type, candidate|
86
+ Marcel::Magic.child?(candidate, type) ? candidate : type
93
87
  end
94
88
  end
95
89
  end