carrierwave 0.11.2 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +255 -125
  3. data/lib/carrierwave/compatibility/paperclip.rb +0 -2
  4. data/lib/carrierwave/error.rb +1 -0
  5. data/lib/carrierwave/locale/en.yml +7 -4
  6. data/lib/carrierwave/mount.rb +219 -176
  7. data/lib/carrierwave/mounter.rb +165 -0
  8. data/lib/carrierwave/orm/activerecord.rb +50 -21
  9. data/lib/carrierwave/processing/mini_magick.rb +86 -15
  10. data/lib/carrierwave/processing/rmagick.rb +63 -8
  11. data/lib/carrierwave/processing.rb +0 -1
  12. data/lib/carrierwave/sanitized_file.rb +51 -46
  13. data/lib/carrierwave/storage/abstract.rb +15 -2
  14. data/lib/carrierwave/storage/file.rb +69 -2
  15. data/lib/carrierwave/storage/fog.rb +152 -33
  16. data/lib/carrierwave/storage.rb +0 -9
  17. data/lib/carrierwave/test/matchers.rb +77 -12
  18. data/lib/carrierwave/uploader/cache.rb +41 -27
  19. data/lib/carrierwave/uploader/callbacks.rb +0 -2
  20. data/lib/carrierwave/uploader/configuration.rb +54 -9
  21. data/lib/carrierwave/uploader/content_type_whitelist.rb +1 -1
  22. data/lib/carrierwave/uploader/default_url.rb +3 -5
  23. data/lib/carrierwave/uploader/download.rb +66 -15
  24. data/lib/carrierwave/uploader/extension_blacklist.rb +14 -10
  25. data/lib/carrierwave/uploader/extension_whitelist.rb +13 -10
  26. data/lib/carrierwave/uploader/file_size.rb +43 -0
  27. data/lib/carrierwave/uploader/mountable.rb +7 -8
  28. data/lib/carrierwave/uploader/processing.rb +10 -10
  29. data/lib/carrierwave/uploader/proxy.rb +5 -7
  30. data/lib/carrierwave/uploader/remove.rb +0 -2
  31. data/lib/carrierwave/uploader/serialization.rb +1 -3
  32. data/lib/carrierwave/uploader/store.rb +14 -23
  33. data/lib/carrierwave/uploader/url.rb +3 -5
  34. data/lib/carrierwave/uploader/versions.rb +82 -82
  35. data/lib/carrierwave/uploader.rb +11 -2
  36. data/lib/carrierwave/utilities/uri.rb +5 -6
  37. data/lib/carrierwave/utilities.rb +0 -3
  38. data/lib/carrierwave/validations/active_model.rb +3 -5
  39. data/lib/carrierwave/version.rb +1 -1
  40. data/lib/carrierwave.rb +32 -10
  41. data/lib/generators/templates/uploader.rb +4 -8
  42. metadata +64 -79
  43. data/lib/carrierwave/locale/cs.yml +0 -11
  44. data/lib/carrierwave/locale/de.yml +0 -11
  45. data/lib/carrierwave/locale/el.yml +0 -11
  46. data/lib/carrierwave/locale/es.yml +0 -11
  47. data/lib/carrierwave/locale/fr.yml +0 -11
  48. data/lib/carrierwave/locale/ja.yml +0 -11
  49. data/lib/carrierwave/locale/nb.yml +0 -11
  50. data/lib/carrierwave/locale/nl.yml +0 -11
  51. data/lib/carrierwave/locale/pl.yml +0 -11
  52. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  53. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  54. data/lib/carrierwave/locale/ru.yml +0 -11
  55. data/lib/carrierwave/locale/sk.yml +0 -11
  56. data/lib/carrierwave/locale/tr.yml +0 -11
  57. data/lib/carrierwave/processing/mime_types.rb +0 -74
  58. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -9,6 +7,10 @@ module CarrierWave
9
7
  # pretty much it.
10
8
  #
11
9
  class File < Abstract
10
+ def initialize(*)
11
+ super
12
+ @cache_called = nil
13
+ end
12
14
 
13
15
  ##
14
16
  # Move the file to the uploader's store path.
@@ -51,6 +53,71 @@ module CarrierWave
51
53
  CarrierWave::SanitizedFile.new(path)
52
54
  end
53
55
 
56
+ ##
57
+ # Stores given file to cache directory.
58
+ #
59
+ # === Parameters
60
+ #
61
+ # [new_file (File, IOString, Tempfile)] any kind of file object
62
+ #
63
+ # === Returns
64
+ #
65
+ # [CarrierWave::SanitizedFile] a sanitized file
66
+ #
67
+ def cache!(new_file)
68
+ new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true)
69
+ rescue Errno::EMLINK, Errno::ENOSPC => e
70
+ raise(e) if @cache_called
71
+ @cache_called = true
72
+
73
+ # NOTE: Remove cached files older than 10 minutes
74
+ clean_cache!(600)
75
+
76
+ cache!(new_file)
77
+ end
78
+
79
+ ##
80
+ # Retrieves the file with the given cache_name from the cache.
81
+ #
82
+ # === Parameters
83
+ #
84
+ # [cache_name (String)] uniquely identifies a cache file
85
+ #
86
+ # === Raises
87
+ #
88
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
89
+ #
90
+ def retrieve_from_cache!(identifier)
91
+ CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root))
92
+ end
93
+
94
+ ##
95
+ # Deletes a cache dir
96
+ #
97
+ def delete_dir!(path)
98
+ if path
99
+ begin
100
+ Dir.rmdir(::File.expand_path(path, uploader.root))
101
+ rescue Errno::ENOENT
102
+ # Ignore: path does not exist
103
+ rescue Errno::ENOTDIR
104
+ # Ignore: path is not a dir
105
+ rescue Errno::ENOTEMPTY, Errno::EEXIST
106
+ # Ignore: dir is not empty
107
+ end
108
+ end
109
+ end
110
+
111
+ def clean_cache!(seconds)
112
+ Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), CarrierWave.root)).each do |dir|
113
+ # generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
114
+ time = dir.scan(/(\d+)-\d+-\d+-\d+/).first.map(&:to_i)
115
+ time = Time.at(*time)
116
+ if time < (Time.now.utc - seconds)
117
+ FileUtils.rm_rf(dir)
118
+ end
119
+ end
120
+ end
54
121
  end # File
55
122
  end # Storage
56
123
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -18,6 +16,8 @@ module CarrierWave
18
16
  # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
19
17
  # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
20
18
  # [:fog_use_ssl_for_aws] (optional) #public_url will use https for the AWS generated URL]
19
+ # [:fog_aws_accelerate] (optional) #public_url will use s3-accelerate subdomain
20
+ # instead of s3, defaults to false
21
21
  #
22
22
  #
23
23
  # AWS credentials contain the following keys:
@@ -25,7 +25,7 @@ module CarrierWave
25
25
  # [:aws_access_key_id]
26
26
  # [:aws_secret_access_key]
27
27
  # [:region] (optional) defaults to 'us-east-1'
28
- # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1']
28
+ # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1', 'eu-central-1']
29
29
  #
30
30
  #
31
31
  # Google credentials contain the following keys:
@@ -94,6 +94,57 @@ module CarrierWave
94
94
  CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
95
95
  end
96
96
 
97
+ ##
98
+ # Stores given file to cache directory.
99
+ #
100
+ # === Parameters
101
+ #
102
+ # [new_file (File, IOString, Tempfile)] any kind of file object
103
+ #
104
+ # === Returns
105
+ #
106
+ # [CarrierWave::SanitizedFile] a sanitized file
107
+ #
108
+ def cache!(new_file)
109
+ f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path)
110
+ f.store(new_file)
111
+ f
112
+ end
113
+
114
+ ##
115
+ # Retrieves the file with the given cache_name from the cache.
116
+ #
117
+ # === Parameters
118
+ #
119
+ # [cache_name (String)] uniquely identifies a cache file
120
+ #
121
+ # === Raises
122
+ #
123
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
124
+ #
125
+ def retrieve_from_cache!(identifier)
126
+ CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path(identifier))
127
+ end
128
+
129
+ ##
130
+ # Deletes a cache dir
131
+ #
132
+ def delete_dir!(path)
133
+ # do nothing, because there's no such things as 'empty directory'
134
+ end
135
+
136
+ def clean_cache!(seconds)
137
+ connection.directories.new(
138
+ :key => uploader.fog_directory,
139
+ :public => uploader.fog_public
140
+ ).files.all(:prefix => uploader.cache_dir).each do |file|
141
+ # generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
142
+ time = file.key.scan(/(\d+)-\d+-\d+-\d+/).first.map { |t| t.to_i }
143
+ time = Time.at(*time)
144
+ file.destroy if time < (Time.now.utc - seconds)
145
+ end
146
+ end
147
+
97
148
  def connection
98
149
  @connection ||= begin
99
150
  options = credentials = uploader.fog_credentials
@@ -126,7 +177,7 @@ module CarrierWave
126
177
 
127
178
  ##
128
179
  # Return a temporary authenticated url to a private file, if available
129
- # Only supported for AWS, Rackspace and Google providers
180
+ # Only supported for AWS, Rackspace, Google and AzureRM providers
130
181
  #
131
182
  # === Returns
132
183
  #
@@ -135,19 +186,25 @@ module CarrierWave
135
186
  # [NilClass] no authenticated url available
136
187
  #
137
188
  def authenticated_url(options = {})
138
- if ['AWS', 'Google', 'Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
189
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM'].include?(@uploader.fog_credentials[:provider])
139
190
  # avoid a get by using local references
140
191
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
141
192
  local_file = local_directory.files.new(:key => path)
142
- if @uploader.fog_credentials[:provider] == "AWS"
143
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options)
144
- elsif ['Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
145
- connection.get_object_https_url(@uploader.fog_directory, path, ::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
146
- else
147
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
193
+ expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
194
+ case @uploader.fog_credentials[:provider]
195
+ when 'AWS', 'Google'
196
+ # Older versions of fog-google do not support options as a parameter
197
+ if url_options_supported?(local_file)
198
+ local_file.url(expire_at, options)
199
+ else
200
+ warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
201
+ local_file.url(expire_at)
202
+ end
203
+ when 'Rackspace', 'OpenStack'
204
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
205
+ else
206
+ local_file.url(expire_at)
148
207
  end
149
- else
150
- nil
151
208
  end
152
209
  end
153
210
 
@@ -159,7 +216,7 @@ module CarrierWave
159
216
  # [String] value of content-type
160
217
  #
161
218
  def content_type
162
- @content_type || file.content_type
219
+ @content_type || !file.nil? && file.content_type
163
220
  end
164
221
 
165
222
  ##
@@ -213,7 +270,7 @@ module CarrierWave
213
270
  end
214
271
 
215
272
  def initialize(uploader, base, path)
216
- @uploader, @base, @path = uploader, base, path
273
+ @uploader, @base, @path, @content_type = uploader, base, path, nil
217
274
  end
218
275
 
219
276
  ##
@@ -223,6 +280,16 @@ module CarrierWave
223
280
  #
224
281
  # [String] contents of file
225
282
  def read
283
+ file_body = file.body
284
+
285
+ return if file_body.nil?
286
+ return file_body unless file_body.is_a?(::File)
287
+
288
+ # Fog::Storage::XXX::File#body could return the source file which was upoloaded to the remote server.
289
+ read_source_file(file_body) if ::File.exist?(file_body.path)
290
+
291
+ # If the source file doesn't exist, the remote content is read
292
+ @file = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
226
293
  file.body
227
294
  end
228
295
 
@@ -234,7 +301,7 @@ module CarrierWave
234
301
  # [Integer] size of file body
235
302
  #
236
303
  def size
237
- file.content_length
304
+ file.nil? ? 0 : file.content_length
238
305
  end
239
306
 
240
307
  ##
@@ -244,7 +311,7 @@ module CarrierWave
244
311
  #
245
312
  # [Boolean] true if file exists or false
246
313
  def exists?
247
- !!directory.files.head(path)
314
+ !!file
248
315
  end
249
316
 
250
317
  ##
@@ -254,15 +321,19 @@ module CarrierWave
254
321
  #
255
322
  # [Boolean] true on success or raises error
256
323
  def store(new_file)
257
- fog_file = new_file.to_file
258
- @content_type ||= new_file.content_type
259
- @file = directory.files.create({
260
- :body => fog_file ? fog_file : new_file.read,
261
- :content_type => @content_type,
262
- :key => path,
263
- :public => @uploader.fog_public
264
- }.merge(@uploader.fog_attributes))
265
- fog_file.close if fog_file && !fog_file.closed?
324
+ if new_file.is_a?(self.class)
325
+ new_file.copy_to(path)
326
+ else
327
+ fog_file = new_file.to_file
328
+ @content_type ||= new_file.content_type
329
+ @file = directory.files.create({
330
+ :body => fog_file ? fog_file : new_file.read,
331
+ :content_type => @content_type,
332
+ :key => path,
333
+ :public => @uploader.fog_public
334
+ }.merge(@uploader.fog_attributes))
335
+ fog_file.close if fog_file && !fog_file.closed?
336
+ end
266
337
  true
267
338
  end
268
339
 
@@ -285,23 +356,29 @@ module CarrierWave
285
356
  end
286
357
  else
287
358
  # AWS/Google optimized for speed over correctness
288
- case @uploader.fog_credentials[:provider]
359
+ case fog_provider
289
360
  when 'AWS'
290
361
  # check if some endpoint is set in fog_credentials
291
362
  if @uploader.fog_credentials.has_key?(:endpoint)
292
363
  "#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}"
293
364
  else
294
365
  protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
366
+
367
+ subdomain_regex = /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
368
+ valid_subdomain = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
369
+
295
370
  # if directory is a valid subdomain, use that style for access
296
- if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
297
- "#{protocol}://#{@uploader.fog_directory}.s3.amazonaws.com/#{encoded_path}"
371
+ if valid_subdomain
372
+ s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
373
+ "#{protocol}://#{@uploader.fog_directory}.#{s3_subdomain}.amazonaws.com/#{encoded_path}"
298
374
  else
299
375
  # directory is not a valid subdomain, so use path style for access
300
376
  "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}"
301
377
  end
302
378
  end
303
379
  when 'Google'
304
- "https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
380
+ # https://cloud.google.com/storage/docs/access-public-data
381
+ "https://storage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
305
382
  else
306
383
  # avoid a get by just using local reference
307
384
  directory.files.new(:key => path).public_url
@@ -336,9 +413,24 @@ module CarrierWave
336
413
  # [NilClass] no file name available
337
414
  #
338
415
  def filename(options = {})
339
- if file_url = url(options)
340
- URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1').split('?').first
341
- end
416
+ return unless file_url = url(options)
417
+ CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
418
+ end
419
+
420
+ ##
421
+ # Creates a copy of this file and returns it.
422
+ #
423
+ # === Parameters
424
+ #
425
+ # [new_path (String)] The path where the file should be copied to.
426
+ #
427
+ # === Returns
428
+ #
429
+ # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
430
+ #
431
+ def copy_to(new_path)
432
+ connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
433
+ CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
342
434
  end
343
435
 
344
436
  private
@@ -381,6 +473,33 @@ module CarrierWave
381
473
  @file ||= directory.files.head(path)
382
474
  end
383
475
 
476
+ def acl_header
477
+ if fog_provider == 'AWS'
478
+ { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
479
+ else
480
+ {}
481
+ end
482
+ end
483
+
484
+ def fog_provider
485
+ @uploader.fog_credentials[:provider].to_s
486
+ end
487
+
488
+ def read_source_file(file_body)
489
+ return unless ::File.exist?(file_body.path)
490
+
491
+ begin
492
+ file_body = ::File.open(file_body.path) if file_body.closed? # Reopen if it's already closed
493
+ file_body.read
494
+ ensure
495
+ file_body.close
496
+ end
497
+ end
498
+
499
+ def url_options_supported?(local_file)
500
+ parameters = local_file.method(:url).parameters
501
+ parameters.count == 2 && parameters[1].include?(:options)
502
+ end
384
503
  end
385
504
 
386
505
  end # Fog
@@ -1,11 +1,2 @@
1
1
  require "carrierwave/storage/abstract"
2
2
  require "carrierwave/storage/file"
3
-
4
- %w(aws google openstack rackspace).each do |fog_dependency|
5
- begin
6
- require "fog/#{fog_dependency}"
7
- rescue LoadError
8
- end
9
- end
10
-
11
- require "carrierwave/storage/fog" if defined?(Fog)
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Test
5
3
 
@@ -23,13 +21,16 @@ module CarrierWave
23
21
  "expected #{@actual.inspect} to be identical to #{@expected.inspect}"
24
22
  end
25
23
 
26
- def negative_failure_message
24
+ def failure_message_when_negated
27
25
  "expected #{@actual.inspect} to not be identical to #{@expected.inspect}"
28
26
  end
29
27
 
30
28
  def description
31
29
  "be identical to #{@expected.inspect}"
32
30
  end
31
+
32
+ # RSpec 2 compatibility:
33
+ alias_method :negative_failure_message, :failure_message_when_negated
33
34
  end
34
35
 
35
36
  def be_identical_to(expected)
@@ -51,13 +52,16 @@ module CarrierWave
51
52
  "expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
52
53
  end
53
54
 
54
- def negative_failure_message
55
+ def failure_message_when_negated
55
56
  "expected #{@actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
56
57
  end
57
58
 
58
59
  def description
59
60
  "have permissions #{@expected.to_s(8)}"
60
61
  end
62
+
63
+ # RSpec 2 compatibility:
64
+ alias_method :negative_failure_message, :failure_message_when_negated
61
65
  end
62
66
 
63
67
  def have_permissions(expected)
@@ -79,13 +83,16 @@ module CarrierWave
79
83
  "expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
80
84
  end
81
85
 
82
- def negative_failure_message
86
+ def failure_message_when_negated
83
87
  "expected #{File.dirname @actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
84
88
  end
85
89
 
86
90
  def description
87
91
  "have permissions #{@expected.to_s(8)}"
88
92
  end
93
+
94
+ # RSpec 2 compatibility:
95
+ alias_method :negative_failure_message, :failure_message_when_negated
89
96
  end
90
97
 
91
98
  def have_directory_permissions(expected)
@@ -110,13 +117,16 @@ module CarrierWave
110
117
  "expected #{@actual.current_path.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
111
118
  end
112
119
 
113
- def negative_failure_message
120
+ def failure_message_when_negated
114
121
  "expected #{@actual.current_path.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
115
122
  end
116
123
 
117
124
  def description
118
125
  "be no larger than #{@width} by #{@height}"
119
126
  end
127
+
128
+ # RSpec 2 compatibility:
129
+ alias_method :negative_failure_message, :failure_message_when_negated
120
130
  end
121
131
 
122
132
  def be_no_larger_than(width, height)
@@ -141,13 +151,16 @@ module CarrierWave
141
151
  "expected #{@actual.current_path.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
142
152
  end
143
153
 
144
- def negative_failure_message
154
+ def failure_message_when_negated
145
155
  "expected #{@actual.current_path.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
146
156
  end
147
157
 
148
158
  def description
149
159
  "have an exact size of #{@width} by #{@height}"
150
160
  end
161
+
162
+ # RSpec 2 compatibility:
163
+ alias_method :negative_failure_message, :failure_message_when_negated
151
164
  end
152
165
 
153
166
  def have_dimensions(width, height)
@@ -171,13 +184,16 @@ module CarrierWave
171
184
  "expected #{@actual.current_path.inspect} to have an exact size of #{@height}, but it was #{@actual_height}."
172
185
  end
173
186
 
174
- def negative_failure_message
187
+ def failure_message_when_negated
175
188
  "expected #{@actual.current_path.inspect} not to have an exact size of #{@height}, but it did."
176
189
  end
177
190
 
178
191
  def description
179
192
  "have an exact height of #{@height}"
180
193
  end
194
+
195
+ # RSpec 2 compatibility:
196
+ alias_method :negative_failure_message, :failure_message_when_negated
181
197
  end
182
198
 
183
199
  def have_height(height)
@@ -201,13 +217,16 @@ module CarrierWave
201
217
  "expected #{@actual.current_path.inspect} to have an exact size of #{@width}, but it was #{@actual_width}."
202
218
  end
203
219
 
204
- def negative_failure_message
220
+ def failure_message_when_negated
205
221
  "expected #{@actual.current_path.inspect} not to have an exact size of #{@width}, but it did."
206
222
  end
207
223
 
208
224
  def description
209
225
  "have an exact width of #{@width}"
210
226
  end
227
+
228
+ # RSpec 2 compatibility:
229
+ alias_method :negative_failure_message, :failure_message_when_negated
211
230
  end
212
231
 
213
232
  def have_width(width)
@@ -231,13 +250,16 @@ module CarrierWave
231
250
  "expected #{@actual.current_path.inspect} to be no wider than #{@width}, but it was #{@actual_width}."
232
251
  end
233
252
 
234
- def negative_failure_message
253
+ def failure_message_when_negated
235
254
  "expected #{@actual.current_path.inspect} not to be wider than #{@width}, but it is."
236
255
  end
237
256
 
238
257
  def description
239
258
  "have a width less than or equal to #{@width}"
240
259
  end
260
+
261
+ # RSpec 2 compatibility:
262
+ alias_method :negative_failure_message, :failure_message_when_negated
241
263
  end
242
264
 
243
265
  def be_no_wider_than(width)
@@ -261,19 +283,55 @@ module CarrierWave
261
283
  "expected #{@actual.current_path.inspect} to be no taller than #{@height}, but it was #{@actual_height}."
262
284
  end
263
285
 
264
- def negative_failure_message
286
+ def failure_message_when_negated
265
287
  "expected #{@actual.current_path.inspect} not to be taller than #{@height}, but it is."
266
288
  end
267
289
 
268
290
  def description
269
291
  "have a height less than or equal to #{@height}"
270
292
  end
293
+
294
+ # RSpec 2 compatibility:
295
+ alias_method :negative_failure_message, :failure_message_when_negated
271
296
  end
272
297
 
273
298
  def be_no_taller_than(height)
274
299
  BeNoTallerThan.new(height)
275
300
  end
276
301
 
302
+ class BeFormat # :nodoc:
303
+ def initialize(expected)
304
+ @expected = expected
305
+ end
306
+
307
+ def matches?(actual)
308
+ @actual = actual
309
+ # Satisfy expectation here. Return false or raise an error if it's not met.
310
+ image = ImageLoader.load_image(@actual.current_path)
311
+ @actual_expected = image.format
312
+ !@expected.nil? && @actual_expected.casecmp(@expected).zero?
313
+ end
314
+
315
+ def failure_message
316
+ "expected #{@actual.current_path.inspect} to have #{@expected} format, but it was #{@actual_expected}."
317
+ end
318
+
319
+ def failure_message_when_negated
320
+ "expected #{@actual.current_path.inspect} not to have #{@expected} format, but it did."
321
+ end
322
+
323
+ def description
324
+ "have #{@expected} format"
325
+ end
326
+
327
+ # RSpec 2 compatibility:
328
+ alias_method :negative_failure_message, :failure_message_when_negated
329
+ end
330
+
331
+ def be_format(expected)
332
+ BeFormat.new(expected)
333
+ end
334
+
277
335
  class ImageLoader # :nodoc:
278
336
  def self.load_image(filename)
279
337
  if defined? ::MiniMagick
@@ -303,6 +361,10 @@ module CarrierWave
303
361
  image.rows
304
362
  end
305
363
 
364
+ def format
365
+ image.format
366
+ end
367
+
306
368
  def initialize(filename)
307
369
  @image = ::Magick::Image.read(filename).first
308
370
  end
@@ -318,6 +380,10 @@ module CarrierWave
318
380
  image[:height]
319
381
  end
320
382
 
383
+ def format
384
+ image[:format]
385
+ end
386
+
321
387
  def initialize(filename)
322
388
  @image = ::MiniMagick::Image.open(filename)
323
389
  end
@@ -326,4 +392,3 @@ module CarrierWave
326
392
  end # Matchers
327
393
  end # Test
328
394
  end # CarrierWave
329
-