lostboy-paperclip 2.2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +172 -0
  3. data/Rakefile +77 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/paperclip/attachment.rb +398 -0
  9. data/lib/paperclip/callback_compatability.rb +33 -0
  10. data/lib/paperclip/geometry.rb +115 -0
  11. data/lib/paperclip/iostream.rb +58 -0
  12. data/lib/paperclip/matchers/have_attached_file_matcher.rb +49 -0
  13. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +66 -0
  14. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +48 -0
  15. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +83 -0
  16. data/lib/paperclip/matchers.rb +4 -0
  17. data/lib/paperclip/processor.rb +48 -0
  18. data/lib/paperclip/storage.rb +241 -0
  19. data/lib/paperclip/thumbnail.rb +70 -0
  20. data/lib/paperclip/upfile.rb +48 -0
  21. data/lib/paperclip.rb +318 -0
  22. data/shoulda_macros/paperclip.rb +68 -0
  23. data/tasks/paperclip_tasks.rake +79 -0
  24. data/test/attachment_test.rb +723 -0
  25. data/test/database.yml +4 -0
  26. data/test/fixtures/12k.png +0 -0
  27. data/test/fixtures/50x50.png +0 -0
  28. data/test/fixtures/5k.png +0 -0
  29. data/test/fixtures/bad.png +1 -0
  30. data/test/fixtures/text.txt +0 -0
  31. data/test/fixtures/twopage.pdf +0 -0
  32. data/test/geometry_test.rb +168 -0
  33. data/test/helper.rb +82 -0
  34. data/test/integration_test.rb +495 -0
  35. data/test/iostream_test.rb +71 -0
  36. data/test/matchers/have_attached_file_matcher_test.rb +21 -0
  37. data/test/matchers/validate_attachment_content_type_matcher_test.rb +30 -0
  38. data/test/matchers/validate_attachment_presence_matcher_test.rb +21 -0
  39. data/test/matchers/validate_attachment_size_matcher_test.rb +50 -0
  40. data/test/paperclip_test.rb +237 -0
  41. data/test/processor_test.rb +10 -0
  42. data/test/storage_test.rb +277 -0
  43. data/test/thumbnail_test.rb +177 -0
  44. metadata +133 -0
@@ -0,0 +1,66 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_content_type name
5
+ ValidateAttachmentContentTypeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentContentTypeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def allowing *types
14
+ @allowed_types = types.flatten
15
+ self
16
+ end
17
+
18
+ def rejecting *types
19
+ @rejected_types = types.flatten
20
+ self
21
+ end
22
+
23
+ def matches? subject
24
+ @subject = subject
25
+ @allowed_types && @rejected_types &&
26
+ allowed_types_allowed? && rejected_types_rejected?
27
+ end
28
+
29
+ def failure_message
30
+ "Content types #{@allowed_types.join(", ")} should be accepted" +
31
+ " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
32
+ end
33
+
34
+ def negative_failure_message
35
+ "Content types #{@allowed_types.join(", ")} should be rejected" +
36
+ " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
37
+ end
38
+
39
+ def description
40
+ "validate the content types allowed on attachment #{@attachment_name}"
41
+ end
42
+
43
+ protected
44
+
45
+ def allow_types?(types)
46
+ types.all? do |type|
47
+ file = StringIO.new(".")
48
+ file.content_type = type
49
+ attachment = @subject.new.attachment_for(@attachment_name)
50
+ attachment.assign(file)
51
+ attachment.errors[:content_type].nil?
52
+ end
53
+ end
54
+
55
+ def allowed_types_allowed?
56
+ allow_types?(@allowed_types)
57
+ end
58
+
59
+ def rejected_types_rejected?
60
+ not allow_types?(@rejected_types)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_presence name
5
+ ValidateAttachmentPresenceMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentPresenceMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def matches? subject
14
+ @subject = subject
15
+ error_when_not_valid? && no_error_when_valid?
16
+ end
17
+
18
+ def failure_message
19
+ "Attachment #{@attachment_name} should be required"
20
+ end
21
+
22
+ def negative_failure_message
23
+ "Attachment #{@attachment_name} should not be required"
24
+ end
25
+
26
+ def description
27
+ "require presence of attachment #{@attachment_name}"
28
+ end
29
+
30
+ protected
31
+
32
+ def error_when_not_valid?
33
+ @attachment = @subject.new.send(@attachment_name)
34
+ @attachment.assign(nil)
35
+ not @attachment.errors[:presence].nil?
36
+ end
37
+
38
+ def no_error_when_valid?
39
+ @file = StringIO.new(".")
40
+ @attachment = @subject.new.send(@attachment_name)
41
+ @attachment.assign(@file)
42
+ @attachment.errors[:presence].nil?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,83 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_size name
5
+ ValidateAttachmentSizeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentSizeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ @low, @high = 0, (1.0/0)
12
+ end
13
+
14
+ def less_than size
15
+ @high = size
16
+ self
17
+ end
18
+
19
+ def greater_than size
20
+ @low = size
21
+ self
22
+ end
23
+
24
+ def in range
25
+ @low, @high = range.first, range.last
26
+ self
27
+ end
28
+
29
+ def matches? subject
30
+ @subject = subject
31
+ lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
32
+ end
33
+
34
+ def failure_message
35
+ "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
36
+ end
37
+
38
+ def negative_failure_message
39
+ "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
40
+ end
41
+
42
+ def description
43
+ "validate the size of attachment #{@attachment_name}"
44
+ end
45
+
46
+ protected
47
+
48
+ def override_method object, method, &replacement
49
+ (class << object; self; end).class_eval do
50
+ define_method(method, &replacement)
51
+ end
52
+ end
53
+
54
+ def passes_validation_with_size(new_size)
55
+ file = StringIO.new(".")
56
+ override_method(file, :size){ new_size }
57
+ attachment = @subject.new.attachment_for(@attachment_name)
58
+ attachment.assign(file)
59
+ attachment.errors[:size].nil?
60
+ end
61
+
62
+ def lower_than_low?
63
+ not passes_validation_with_size(@low - 1)
64
+ end
65
+
66
+ def higher_than_low?
67
+ passes_validation_with_size(@low + 1)
68
+ end
69
+
70
+ def lower_than_high?
71
+ return true if @high == (1.0/0)
72
+ passes_validation_with_size(@high - 1)
73
+ end
74
+
75
+ def higher_than_high?
76
+ return true if @high == (1.0/0)
77
+ not passes_validation_with_size(@high + 1)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,4 @@
1
+ require 'paperclip/matchers/have_attached_file_matcher'
2
+ require 'paperclip/matchers/validate_attachment_presence_matcher'
3
+ require 'paperclip/matchers/validate_attachment_content_type_matcher'
4
+ require 'paperclip/matchers/validate_attachment_size_matcher'
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ # Paperclip processors allow you to modify attached files when they are
3
+ # attached in any way you are able. Paperclip itself uses command-line
4
+ # programs for its included Thumbnail processor, but custom processors
5
+ # are not required to follow suit.
6
+ #
7
+ # Processors are required to be defined inside the Paperclip module and
8
+ # are also required to be a subclass of Paperclip::Processor. There are
9
+ # only two methods you must implement to properly be a subclass:
10
+ # #initialize and #make. Initialize's arguments are the file that will
11
+ # be operated on (which is an instance of File), and a hash of options
12
+ # that were defined in has_attached_file's style hash.
13
+ #
14
+ # All #make needs to do is return an instance of File (Tempfile is
15
+ # acceptable) which contains the results of the processing.
16
+ #
17
+ # See Paperclip.run for more information about using command-line
18
+ # utilities from within Processors.
19
+ class Processor
20
+ attr_accessor :file, :options, :attachment
21
+
22
+ def initialize file, options = {}, attachment = nil
23
+ @file = file
24
+ @options = options
25
+ @attachment = attachment
26
+ end
27
+
28
+ def make
29
+ end
30
+
31
+ def self.make file, options = {}, attachment = nil
32
+ new(file, options, attachment).make
33
+ end
34
+ end
35
+
36
+ # Due to how ImageMagick handles its image format conversion and how Tempfile
37
+ # handles its naming scheme, it is necessary to override how Tempfile makes
38
+ # its names so as to allow for file extensions. Idea taken from the comments
39
+ # on this blog post:
40
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
41
+ class Tempfile < ::Tempfile
42
+ # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
43
+ def make_tmpname(basename, n)
44
+ extension = File.extname(basename)
45
+ sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,241 @@
1
+ module Paperclip
2
+ module Storage
3
+
4
+ # The default place to store attachments is in the filesystem. Files on the local
5
+ # filesystem can be very easily served by Apache without requiring a hit to your app.
6
+ # They also can be processed more easily after they've been saved, as they're just
7
+ # normal files. There is one Filesystem-specific option for has_attached_file.
8
+ # * +path+: The location of the repository of attachments on disk. This can (and, in
9
+ # almost all cases, should) be coordinated with the value of the +url+ option to
10
+ # allow files to be saved into a place where Apache can serve them without
11
+ # hitting your app. Defaults to
12
+ # ":rails_root/public/:attachment/:id/:style/:basename.:extension"
13
+ # By default this places the files in the app's public directory which can be served
14
+ # directly. If you are using capistrano for deployment, a good idea would be to
15
+ # make a symlink to the capistrano-created system directory from inside your app's
16
+ # public directory.
17
+ # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
18
+ # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
19
+ module Filesystem
20
+ def self.extended base
21
+ end
22
+
23
+ def exists?(style = default_style)
24
+ if original_filename
25
+ File.exist?(path(style))
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ # Returns representation of the data of the file assigned to the given
32
+ # style, in the format most representative of the current storage.
33
+ def to_file style = default_style
34
+ @queued_for_write[style] || (File.new(path(style), 'rb') if exists?(style))
35
+ end
36
+ alias_method :to_io, :to_file
37
+
38
+ def flush_writes #:nodoc:
39
+ logger.info("[paperclip] Writing files for #{name}")
40
+ @queued_for_write.each do |style, file|
41
+ file.close
42
+ FileUtils.mkdir_p(File.dirname(path(style)))
43
+ logger.info("[paperclip] -> #{path(style)}")
44
+ FileUtils.mv(file.path, path(style))
45
+ FileUtils.chmod(0644, path(style))
46
+ end
47
+ @queued_for_write = {}
48
+ end
49
+
50
+ def flush_deletes #:nodoc:
51
+ logger.info("[paperclip] Deleting files for #{name}")
52
+ @queued_for_delete.each do |path|
53
+ begin
54
+ logger.info("[paperclip] -> #{path}")
55
+ FileUtils.rm(path) if File.exist?(path)
56
+ rescue Errno::ENOENT => e
57
+ # ignore file-not-found, let everything else pass
58
+ end
59
+ begin
60
+ while(true)
61
+ path = File.dirname(path)
62
+ FileUtils.rmdir(path)
63
+ end
64
+ rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
65
+ # Stop trying to remove parent directories
66
+ rescue SystemCallError => e
67
+ logger.info("[paperclip] There was an unexpected error while deleting directories: #{e.class}")
68
+ # Ignore it
69
+ end
70
+ end
71
+ @queued_for_delete = []
72
+ end
73
+ end
74
+
75
+ # Amazon's S3 file hosting service is a scalable, easy place to store files for
76
+ # distribution. You can find out more about it at http://aws.amazon.com/s3
77
+ # There are a few S3-specific options for has_attached_file:
78
+ # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
79
+ # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
80
+ # gives you. You can 'environment-space' this just like you do to your
81
+ # database.yml file, so different environments can use different accounts:
82
+ # development:
83
+ # access_key_id: 123...
84
+ # secret_access_key: 123...
85
+ # test:
86
+ # access_key_id: abc...
87
+ # secret_access_key: abc...
88
+ # production:
89
+ # access_key_id: 456...
90
+ # secret_access_key: 456...
91
+ # This is not required, however, and the file may simply look like this:
92
+ # access_key_id: 456...
93
+ # secret_access_key: 456...
94
+ # In which case, those access keys will be used in all environments. You can also
95
+ # put your bucket name in this file, instead of adding it to the code directly.
96
+ # This is useful when you want the same account but a different bucket for
97
+ # development versus production.
98
+ # * +s3_permissions+: This is a String that should be one of the "canned" access
99
+ # policies that S3 provides (more information can be found here:
100
+ # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
101
+ # The default for Paperclip is "public-read".
102
+ # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
103
+ # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are 'public-read' (the
104
+ # default), and 'https' when your :s3_permissions are anything else.
105
+ # * +s3_headers+: A hash of headers such as {'Expires' => 1.year.from_now.httpdate}
106
+ # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
107
+ # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
108
+ # Paperclip will attempt to create it. The bucket name will not be interpolated.
109
+ # You can define the bucket as a Proc if you want to determine it's name at runtime.
110
+ # Paperclip will call that Proc with attachment as the only argument.
111
+ # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
112
+ # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
113
+ # link in the +url+ entry for more information about S3 domains and buckets.
114
+ # * +url+: There are three options for the S3 url. You can choose to have the bucket's name
115
+ # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
116
+ # Lastly, you can specify a CNAME (which requires the CNAME to be specified as
117
+ # :s3_alias_url. You can read more about CNAMEs and S3 at
118
+ # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
119
+ # Normally, this won't matter in the slightest and you can leave the default (which is
120
+ # path-style, or :s3_path_url). But in some cases paths don't work and you need to use
121
+ # the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
122
+ # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
123
+ # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
124
+ # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
125
+ # by S3.
126
+ # * +path+: This is the key under the bucket in which the file will be stored. The
127
+ # URL will be constructed from the bucket and the path. This is what you will want
128
+ # to interpolate. Keys should be unique, like filenames, and despite the fact that
129
+ # S3 (strictly speaking) does not support directories, you can still use a / to
130
+ # separate parts of your file name.
131
+ module S3
132
+ def self.extended base
133
+ require 'right_aws'
134
+ base.instance_eval do
135
+ @s3_credentials = parse_credentials(@options[:s3_credentials])
136
+ @bucket = @options[:bucket] || @s3_credentials[:bucket]
137
+ @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
138
+ @s3_options = @options[:s3_options] || {}
139
+ @s3_permissions = @options[:s3_permissions] || 'public-read'
140
+ @s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
141
+ @s3_headers = @options[:s3_headers] || {}
142
+ @s3_host_alias = @options[:s3_host_alias]
143
+ @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
144
+ end
145
+ base.class.interpolations[:s3_alias_url] = lambda do |attachment, style|
146
+ "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
147
+ end
148
+ base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
149
+ "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
150
+ end
151
+ base.class.interpolations[:s3_domain_url] = lambda do |attachment, style|
152
+ "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
153
+ end
154
+ ActiveRecord::Base.logger.info("[paperclip] S3 Storage Initalized.")
155
+ end
156
+
157
+ def s3
158
+ @s3 ||= RightAws::S3.new(@s3_credentials[:access_key_id],
159
+ @s3_credentials[:secret_access_key],
160
+ @s3_options)
161
+ end
162
+
163
+ def s3_bucket
164
+ @s3_bucket ||= s3.bucket(@bucket, true, @s3_permissions)
165
+ end
166
+
167
+ def bucket_name
168
+ @bucket
169
+ end
170
+
171
+ def s3_host_alias
172
+ @s3_host_alias
173
+ end
174
+
175
+ def parse_credentials creds
176
+ creds = find_credentials(creds).stringify_keys
177
+ (creds[ENV['RAILS_ENV']] || creds).symbolize_keys
178
+ end
179
+
180
+ def exists?(style = default_style)
181
+ s3_bucket.key(path(style)) ? true : false
182
+ end
183
+
184
+ def s3_protocol
185
+ @s3_protocol
186
+ end
187
+
188
+ # Returns representation of the data of the file assigned to the given
189
+ # style, in the format most representative of the current storage.
190
+ def to_file style = default_style
191
+ @queued_for_write[style] || s3_bucket.key(path(style))
192
+ end
193
+ alias_method :to_io, :to_file
194
+
195
+ def flush_writes #:nodoc:
196
+ logger.info("[paperclip] Writing files for #{name}")
197
+ @queued_for_write.each do |style, file|
198
+ begin
199
+ logger.info("[paperclip] -> #{path(style)}")
200
+ key = s3_bucket.key(path(style))
201
+ key.data = file
202
+ key.put(nil, @s3_permissions, {'Content-type' => instance_read(:content_type)}.merge(@s3_headers))
203
+ rescue RightAws::AwsError => e
204
+ raise
205
+ end
206
+ end
207
+ @queued_for_write = {}
208
+ end
209
+
210
+ def flush_deletes #:nodoc:
211
+ logger.info("[paperclip] Writing files for #{name}")
212
+ @queued_for_delete.each do |path|
213
+ begin
214
+ logger.info("[paperclip] -> #{path}")
215
+ if file = s3_bucket.key(path)
216
+ file.delete
217
+ end
218
+ rescue RightAws::AwsError
219
+ # Ignore this.
220
+ end
221
+ end
222
+ @queued_for_delete = []
223
+ end
224
+
225
+ def find_credentials creds
226
+ case creds
227
+ when File
228
+ YAML.load_file(creds.path)
229
+ when String
230
+ YAML.load_file(creds)
231
+ when Hash
232
+ creds
233
+ else
234
+ raise ArgumentError, "Credentials are not a path, file, or hash."
235
+ end
236
+ end
237
+ private :find_credentials
238
+
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,70 @@
1
+ module Paperclip
2
+ # Handles thumbnailing images that are uploaded.
3
+ class Thumbnail < Processor
4
+
5
+ attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options
6
+
7
+ # Creates a Thumbnail object set to work on the +file+ given. It
8
+ # will attempt to transform the image into one defined by +target_geometry+
9
+ # which is a "WxH"-style string. +format+ will be inferred from the +file+
10
+ # unless specified. Thumbnail creation will raise no errors unless
11
+ # +whiny+ is true (which it is, by default. If +convert_options+ is
12
+ # set, the options will be appended to the convert command upon image conversion
13
+ def initialize file, options = {}, attachment = nil
14
+ super
15
+ geometry = options[:geometry]
16
+ @file = file
17
+ @crop = geometry[-1,1] == '#'
18
+ @target_geometry = Geometry.parse geometry
19
+ @current_geometry = Geometry.from_file @file
20
+ @convert_options = options[:convert_options]
21
+ @whiny = options[:whiny].nil? ? true : options[:whiny]
22
+ @format = options[:format]
23
+
24
+ @current_format = File.extname(@file.path)
25
+ @basename = File.basename(@file.path, @current_format)
26
+ end
27
+
28
+ # Returns true if the +target_geometry+ is meant to crop.
29
+ def crop?
30
+ @crop
31
+ end
32
+
33
+ # Returns true if the image is meant to make use of additional convert options.
34
+ def convert_options?
35
+ not @convert_options.blank?
36
+ end
37
+
38
+ # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
39
+ # that contains the new image.
40
+ def make
41
+ src = @file
42
+ dst = Tempfile.new([@basename, @format].compact.join("."))
43
+ dst.binmode
44
+
45
+ command = <<-end_command
46
+ "#{ File.expand_path(src.path) }[0]"
47
+ #{ transformation_command }
48
+ "#{ File.expand_path(dst.path) }"
49
+ end_command
50
+
51
+ begin
52
+ success = Paperclip.run("convert", command.gsub(/\s+/, " "))
53
+ rescue PaperclipCommandLineError
54
+ raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
55
+ end
56
+
57
+ dst
58
+ end
59
+
60
+ # Returns the command ImageMagick's +convert+ needs to transform the image
61
+ # into the thumbnail.
62
+ def transformation_command
63
+ scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
64
+ trans = "-resize \"#{scale}\""
65
+ trans << " -crop \"#{crop}\" +repage" if crop
66
+ trans << " #{convert_options}" if convert_options?
67
+ trans
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ # The Upfile module is a convenience module for adding uploaded-file-type methods
3
+ # to the +File+ class. Useful for testing.
4
+ # user.avatar = File.new("test/test_avatar.jpg")
5
+ module Upfile
6
+
7
+ # Infer the MIME-type of the file from the extension.
8
+ def content_type
9
+ type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
10
+ case type
11
+ when %r"jpe?g" then "image/jpeg"
12
+ when %r"tiff?" then "image/tiff"
13
+ when %r"png", "gif", "bmp" then "image/#{type}"
14
+ when "txt" then "text/plain"
15
+ when %r"html?" then "text/html"
16
+ when "csv", "xml", "css", "js" then "text/#{type}"
17
+ else "application/x-#{type}"
18
+ end
19
+ end
20
+
21
+ # Returns the file's normal name.
22
+ def original_filename
23
+ File.basename(self.path)
24
+ end
25
+
26
+ # Returns the size of the file.
27
+ def size
28
+ File.size(self)
29
+ end
30
+ end
31
+ end
32
+
33
+ if defined? StringIO
34
+ class StringIO
35
+ attr_accessor :original_filename, :content_type
36
+ def original_filename
37
+ @original_filename ||= "stringio.txt"
38
+ end
39
+ def content_type
40
+ @content_type ||= "text/plain"
41
+ end
42
+ end
43
+ end
44
+
45
+ class File #:nodoc:
46
+ include Paperclip::Upfile
47
+ end
48
+