dm-paperclip 2.1.4 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +13 -2
- data/Rakefile +6 -0
- data/lib/dm-paperclip.rb +187 -61
- data/lib/dm-paperclip/attachment.rb +240 -149
- data/lib/dm-paperclip/callback_compatability.rb +33 -0
- data/lib/dm-paperclip/geometry.rb +22 -14
- data/lib/dm-paperclip/interpolations.rb +123 -0
- data/lib/dm-paperclip/iostream.rb +17 -2
- data/lib/dm-paperclip/processor.rb +49 -0
- data/lib/dm-paperclip/storage.rb +72 -55
- data/lib/dm-paperclip/thumbnail.rb +18 -36
- data/lib/dm-paperclip/upfile.rb +12 -1
- data/lib/dm-paperclip/validations.rb +35 -0
- data/test/attachment_test.rb +24 -32
- data/test/geometry_test.rb +1 -1
- data/test/helper.rb +6 -0
- data/test/storage_test.rb +3 -3
- data/test/thumbnail_test.rb +17 -15
- metadata +19 -9
@@ -1,40 +1,35 @@
|
|
1
1
|
module Paperclip
|
2
2
|
# Handles thumbnailing images that are uploaded.
|
3
|
-
class Thumbnail
|
3
|
+
class Thumbnail < Processor
|
4
4
|
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options
|
6
6
|
|
7
7
|
# Creates a Thumbnail object set to work on the +file+ given. It
|
8
8
|
# will attempt to transform the image into one defined by +target_geometry+
|
9
9
|
# which is a "WxH"-style string. +format+ will be inferred from the +file+
|
10
10
|
# unless specified. Thumbnail creation will raise no errors unless
|
11
|
-
# +
|
12
|
-
# set, the options will be appended to the convert command upon image conversion
|
13
|
-
def initialize file,
|
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]
|
14
16
|
@file = file
|
15
|
-
@crop =
|
16
|
-
@target_geometry = Geometry.parse
|
17
|
-
@current_geometry = Geometry.from_file file
|
18
|
-
@convert_options = convert_options
|
19
|
-
@
|
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]
|
20
23
|
|
21
24
|
@current_format = File.extname(@file.path)
|
22
25
|
@basename = File.basename(@file.path, @current_format)
|
23
|
-
|
24
|
-
@format = format
|
25
|
-
end
|
26
|
-
|
27
|
-
# Creates a thumbnail, as specified in +initialize+, +make+s it, and returns the
|
28
|
-
# resulting Tempfile.
|
29
|
-
def self.make file, dimensions, format = nil, convert_options = nil, whiny_thumbnails = true
|
30
|
-
new(file, dimensions, format, convert_options, whiny_thumbnails).make
|
31
26
|
end
|
32
27
|
|
33
28
|
# Returns true if the +target_geometry+ is meant to crop.
|
34
29
|
def crop?
|
35
30
|
@crop
|
36
31
|
end
|
37
|
-
|
32
|
+
|
38
33
|
# Returns true if the image is meant to make use of additional convert options.
|
39
34
|
def convert_options?
|
40
35
|
not @convert_options.blank?
|
@@ -48,15 +43,15 @@ module Paperclip
|
|
48
43
|
dst.binmode
|
49
44
|
|
50
45
|
command = <<-end_command
|
51
|
-
#{ Paperclip.path_for_command('convert') }
|
52
46
|
"#{ File.expand_path(src.path) }[0]"
|
53
47
|
#{ transformation_command }
|
54
48
|
"#{ File.expand_path(dst.path) }"
|
55
49
|
end_command
|
56
|
-
success = system(command.gsub(/\s+/, " "))
|
57
50
|
|
58
|
-
|
59
|
-
|
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
|
60
55
|
end
|
61
56
|
|
62
57
|
dst
|
@@ -72,17 +67,4 @@ module Paperclip
|
|
72
67
|
trans
|
73
68
|
end
|
74
69
|
end
|
75
|
-
|
76
|
-
# Due to how ImageMagick handles its image format conversion and how Tempfile
|
77
|
-
# handles its naming scheme, it is necessary to override how Tempfile makes
|
78
|
-
# its names so as to allow for file extensions. Idea taken from the comments
|
79
|
-
# on this blog post:
|
80
|
-
# http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
|
81
|
-
class Tempfile < ::Tempfile
|
82
|
-
# Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
|
83
|
-
def make_tmpname(basename, n)
|
84
|
-
extension = File.extname(basename)
|
85
|
-
sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
|
86
|
-
end
|
87
|
-
end
|
88
70
|
end
|
data/lib/dm-paperclip/upfile.rb
CHANGED
@@ -28,9 +28,20 @@ module Paperclip
|
|
28
28
|
File.size(self)
|
29
29
|
end
|
30
30
|
end
|
31
|
+
end
|
31
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
|
32
43
|
end
|
33
44
|
|
34
45
|
class File #:nodoc:
|
35
46
|
include Paperclip::Upfile
|
36
|
-
end
|
47
|
+
end
|
@@ -1,6 +1,41 @@
|
|
1
1
|
module Paperclip
|
2
2
|
module Validate
|
3
3
|
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
# Places ActiveRecord-style validations on the size of the file assigned. The
|
7
|
+
# possible options are:
|
8
|
+
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
9
|
+
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
10
|
+
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
11
|
+
# * +message+: error message to display, use :min and :max as replacements
|
12
|
+
def validates_attachment_size(*fields)
|
13
|
+
opts = opts_from_validator_args(fields)
|
14
|
+
add_validator_to_context(opts, fields, Paperclip::Validate::SizeValidator)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
|
18
|
+
def validates_attachment_thumbnails name, options = {}
|
19
|
+
self.attachment_definitions[name][:whiny_thumbnails] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Places ActiveRecord-style validations on the presence of a file.
|
23
|
+
def validates_attachment_presence(*fields)
|
24
|
+
opts = opts_from_validator_args(fields)
|
25
|
+
add_validator_to_context(opts, fields, Paperclip::Validate::RequiredFieldValidator)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Places ActiveRecord-style validations on the content type of the file assigned. The
|
29
|
+
# possible options are:
|
30
|
+
# * +content_type+: Allowed content types. Can be a single content type or an array. Allows all by default.
|
31
|
+
# * +message+: The message to display when the uploaded file has an invalid content type.
|
32
|
+
def validates_attachment_content_type(*fields)
|
33
|
+
opts = opts_from_validator_args(fields)
|
34
|
+
add_validator_to_context(opts, fields, Paperclip::Validate::ContentTypeValidator)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
4
39
|
class SizeValidator < DataMapper::Validate::GenericValidator #:nodoc:
|
5
40
|
def initialize(field_name, options={})
|
6
41
|
super
|
data/test/attachment_test.rb
CHANGED
@@ -138,11 +138,9 @@ class AttachmentTest < Test::Unit::TestCase
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@dummy.avatar = @file
|
145
|
-
end
|
141
|
+
should "call post_process" do
|
142
|
+
@dummy.avatar.expects(:post_process).once
|
143
|
+
@dummy.avatar = @file
|
146
144
|
end
|
147
145
|
end
|
148
146
|
end
|
@@ -150,20 +148,20 @@ class AttachmentTest < Test::Unit::TestCase
|
|
150
148
|
context "Assigning an attachment" do
|
151
149
|
setup do
|
152
150
|
rebuild_model
|
153
|
-
|
151
|
+
|
154
152
|
@not_file = mock
|
155
153
|
@not_file.stubs(:nil?).returns(false)
|
156
154
|
@not_file.expects(:to_tempfile).returns(self)
|
157
155
|
@not_file.expects(:original_filename).returns("filename.png\r\n")
|
158
156
|
@not_file.expects(:content_type).returns("image/png\r\n")
|
159
157
|
@not_file.expects(:size).returns(10)
|
160
|
-
|
158
|
+
|
161
159
|
@dummy = Dummy.new
|
162
160
|
@attachment = @dummy.avatar
|
163
161
|
@attachment.expects(:valid_assignment?).with(@not_file).returns(true)
|
164
162
|
@attachment.expects(:queue_existing_for_delete)
|
165
163
|
@attachment.expects(:post_process)
|
166
|
-
@attachment.expects(:validate)
|
164
|
+
@attachment.expects(:validate).twice
|
167
165
|
@dummy.avatar = @not_file
|
168
166
|
end
|
169
167
|
|
@@ -174,33 +172,31 @@ class AttachmentTest < Test::Unit::TestCase
|
|
174
172
|
should "strip whitespace from content_type field" do
|
175
173
|
assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
|
176
174
|
end
|
177
|
-
|
178
175
|
end
|
179
176
|
|
180
177
|
context "Attachment with strange letters" do
|
181
178
|
setup do
|
182
179
|
rebuild_model
|
183
|
-
|
180
|
+
|
184
181
|
@not_file = mock
|
185
182
|
@not_file.stubs(:nil?).returns(false)
|
186
183
|
@not_file.expects(:to_tempfile).returns(self)
|
187
|
-
@not_file.expects(:original_filename).returns("
|
184
|
+
@not_file.expects(:original_filename).returns("sheep_say_b_.png\r\n")
|
188
185
|
@not_file.expects(:content_type).returns("image/png\r\n")
|
189
186
|
@not_file.expects(:size).returns(10)
|
190
|
-
|
187
|
+
|
191
188
|
@dummy = Dummy.new
|
192
189
|
@attachment = @dummy.avatar
|
193
190
|
@attachment.expects(:valid_assignment?).with(@not_file).returns(true)
|
194
191
|
@attachment.expects(:queue_existing_for_delete)
|
195
192
|
@attachment.expects(:post_process)
|
196
|
-
@attachment.expects(:validate)
|
193
|
+
@attachment.expects(:validate).twice
|
197
194
|
@dummy.avatar = @not_file
|
198
195
|
end
|
199
|
-
|
196
|
+
|
200
197
|
should "remove strange letters and replace with underscore (_)" do
|
201
198
|
assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
|
202
199
|
end
|
203
|
-
|
204
200
|
end
|
205
201
|
|
206
202
|
context "An attachment" do
|
@@ -230,25 +226,24 @@ class AttachmentTest < Test::Unit::TestCase
|
|
230
226
|
assert_equal "/avatars/original/missing.png", @attachment.url
|
231
227
|
assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
|
232
228
|
end
|
233
|
-
|
229
|
+
|
234
230
|
context "with a file assigned in the database" do
|
235
231
|
setup do
|
236
|
-
@instance.stubs(:
|
237
|
-
@instance.stubs(:
|
238
|
-
@instance.stubs(:
|
239
|
-
now = Time.now
|
240
|
-
Time.stubs(:now).returns(now)
|
241
|
-
@instance.stubs(:
|
232
|
+
@instance.stubs(:avatar_file_name).returns('5k.png')
|
233
|
+
@instance.stubs(:avatar_content_type).returns("image/png")
|
234
|
+
@instance.stubs(:avatar_file_size).returns(12345)
|
235
|
+
@now = Time.now
|
236
|
+
Time.stubs(:now).returns(@now)
|
237
|
+
@instance.stubs(:avatar_updated_at).returns(Time.now)
|
242
238
|
end
|
243
239
|
|
244
240
|
should "return a correct url even if the file does not exist" do
|
245
241
|
assert_nil @attachment.to_file
|
246
|
-
assert_match %r{^/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
|
242
|
+
assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
|
247
243
|
end
|
248
244
|
|
249
245
|
should "make sure the updated_at mtime is in the url if it is defined" do
|
250
|
-
|
251
|
-
assert_match %r{#{time.year}#{time.month}#{time.day}#{time.hour}#{time.min}#{time.sec}$}, @attachment.url(:blah)
|
246
|
+
assert_match %r{#{@now.to_i.to_s}$}, @attachment.url(:blah)
|
252
247
|
end
|
253
248
|
|
254
249
|
context "with the updated_at field removed" do
|
@@ -266,7 +261,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
266
261
|
end
|
267
262
|
|
268
263
|
should "return the proper path when filename has multiple .'s" do
|
269
|
-
@instance.stubs(:
|
264
|
+
@instance.stubs(:avatar_file_name).returns("5k.old.png")
|
270
265
|
assert_equal "./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png", @attachment.path
|
271
266
|
end
|
272
267
|
|
@@ -298,8 +293,8 @@ class AttachmentTest < Test::Unit::TestCase
|
|
298
293
|
|
299
294
|
should "return the real url" do
|
300
295
|
assert @attachment.to_file
|
301
|
-
assert_match %r{^/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
|
302
|
-
assert_match %r{^/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
|
296
|
+
assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
|
297
|
+
assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
|
303
298
|
end
|
304
299
|
|
305
300
|
should "commit the files to disk" do
|
@@ -333,15 +328,12 @@ class AttachmentTest < Test::Unit::TestCase
|
|
333
328
|
@existing_names = @attachment.styles.keys.collect do |style|
|
334
329
|
@attachment.path(style)
|
335
330
|
end
|
336
|
-
@instance.expects(:attributes=).with({ :avatar_file_name => nil,
|
337
|
-
:avatar_content_type => nil,
|
338
|
-
:avatar_file_size => nil })
|
339
331
|
@attachment.assign nil
|
340
332
|
@attachment.save
|
341
333
|
end
|
342
334
|
|
343
335
|
should "delete the files" do
|
344
|
-
@existing_names.each{|f| assert !
|
336
|
+
@existing_names.each { |f| assert !File.exists?(f) }
|
345
337
|
end
|
346
338
|
end
|
347
339
|
end
|
data/test/geometry_test.rb
CHANGED
@@ -62,7 +62,7 @@ class GeometryTest < Test::Unit::TestCase
|
|
62
62
|
should "make sure the modifier gets passed during transformation_to" do
|
63
63
|
assert @src = Paperclip::Geometry.parse("123x456")
|
64
64
|
assert @dst = Paperclip::Geometry.parse("123x456>")
|
65
|
-
assert_equal "123x456>", @src.transformation_to(@dst).
|
65
|
+
assert_equal "123x456>", @src.transformation_to(@dst).join
|
66
66
|
end
|
67
67
|
|
68
68
|
should "be generated from a file" do
|
data/test/helper.rb
CHANGED
@@ -42,6 +42,12 @@ unless defined?(Mash)
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
Paperclip.configure do |config|
|
46
|
+
config.root = Merb.root # the application root to anchor relative urls (defaults to Dir.pwd)
|
47
|
+
config.env = Merb.env # server env support, defaults to ENV['RACK_ENV'] or 'development'
|
48
|
+
config.use_dm_validations = true # validate attachment sizes and such, defaults to false
|
49
|
+
end
|
50
|
+
|
45
51
|
def rebuild_model options = {}
|
46
52
|
Object.send(:remove_const, "Dummy") rescue nil
|
47
53
|
Object.const_set("Dummy", Class.new())
|
data/test/storage_test.rb
CHANGED
@@ -24,21 +24,21 @@ class StorageTest < Test::Unit::TestCase
|
|
24
24
|
|
25
25
|
should "get the correct credentials when RAILS_ENV is production" do
|
26
26
|
ENV['RAILS_ENV'] = 'production'
|
27
|
-
assert_equal({
|
27
|
+
assert_equal({'key' => "12345"},
|
28
28
|
@avatar.parse_credentials('production' => {:key => '12345'},
|
29
29
|
:development => {:key => "54321"}))
|
30
30
|
end
|
31
31
|
|
32
32
|
should "get the correct credentials when RAILS_ENV is development" do
|
33
33
|
ENV['RAILS_ENV'] = 'development'
|
34
|
-
assert_equal({
|
34
|
+
assert_equal({'key' => "54321"},
|
35
35
|
@avatar.parse_credentials('production' => {:key => '12345'},
|
36
36
|
:development => {:key => "54321"}))
|
37
37
|
end
|
38
38
|
|
39
39
|
should "return the argument if the key does not exist" do
|
40
40
|
ENV['RAILS_ENV'] = "not really an env"
|
41
|
-
assert_equal({
|
41
|
+
assert_equal({'test' => "12345"}, @avatar.parse_credentials(:test => "12345"))
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
data/test/thumbnail_test.rb
CHANGED
@@ -48,11 +48,11 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
48
48
|
].each do |args|
|
49
49
|
context "being thumbnailed with a geometry of #{args[0]}" do
|
50
50
|
setup do
|
51
|
-
@thumb = Paperclip::Thumbnail.new(@file, args[0])
|
51
|
+
@thumb = Paperclip::Thumbnail.new(@file, :geometry => args[0])
|
52
52
|
end
|
53
53
|
|
54
54
|
should "start with dimensions of 434x66" do
|
55
|
-
cmd = %Q[identify -format "%wx%h" #{@file.path}]
|
55
|
+
cmd = %Q[identify -format "%wx%h" #{@file.path}]
|
56
56
|
assert_equal "434x66", `#{cmd}`.chomp
|
57
57
|
end
|
58
58
|
|
@@ -66,7 +66,7 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
66
66
|
end
|
67
67
|
|
68
68
|
should "be the size we expect it to be" do
|
69
|
-
cmd = %Q[identify -format "%wx%h" #{@thumb_result.path}]
|
69
|
+
cmd = %Q[identify -format "%wx%h" #{@thumb_result.path}]
|
70
70
|
assert_equal args[1], `#{cmd}`.chomp
|
71
71
|
end
|
72
72
|
end
|
@@ -75,7 +75,7 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
75
75
|
|
76
76
|
context "being thumbnailed at 100x50 with cropping" do
|
77
77
|
setup do
|
78
|
-
@thumb = Paperclip::Thumbnail.new(@file, "100x50#")
|
78
|
+
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#")
|
79
79
|
end
|
80
80
|
|
81
81
|
should "report its correct current and target geometries" do
|
@@ -88,16 +88,17 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
88
88
|
end
|
89
89
|
|
90
90
|
should "have whiny_thumbnails turned on by default" do
|
91
|
-
assert @thumb.
|
91
|
+
assert @thumb.whiny
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
should "have convert_options set to nil by default" do
|
95
95
|
assert_equal nil, @thumb.convert_options
|
96
96
|
end
|
97
97
|
|
98
98
|
should "send the right command to convert when sent #make" do
|
99
|
-
|
100
|
-
|
99
|
+
Paperclip.expects(:run).with do |cmd, arg|
|
100
|
+
cmd.match 'convert'
|
101
|
+
arg.match %r{"#{File.expand_path(@thumb.file.path)}\[0\]"\s+-resize\s+\"x50\"\s+-crop\s+\"100x50\+114\+0\"\s+\+repage\s+".*?"}
|
101
102
|
end
|
102
103
|
@thumb.make
|
103
104
|
end
|
@@ -107,10 +108,10 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
107
108
|
assert_match /100x50/, `identify #{dst.path}`
|
108
109
|
end
|
109
110
|
end
|
110
|
-
|
111
|
+
|
111
112
|
context "being thumbnailed with convert options set" do
|
112
113
|
setup do
|
113
|
-
@thumb = Paperclip::Thumbnail.new(@file, "100x50#", format=nil, convert_options="-strip -depth 8", whiny_thumbnails=true)
|
114
|
+
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#", :format => (format = nil), :convert_options => (convert_options="-strip -depth 8"), :whiny => (whiny_thumbnails=true))
|
114
115
|
end
|
115
116
|
|
116
117
|
should "have convert_options value set" do
|
@@ -118,8 +119,9 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
118
119
|
end
|
119
120
|
|
120
121
|
should "send the right command to convert when sent #make" do
|
121
|
-
|
122
|
-
|
122
|
+
Paperclip.expects(:run).with do |cmd, arg|
|
123
|
+
cmd.match 'convert'
|
124
|
+
arg.match %r{"#{File.expand_path(@thumb.file.path)}\[0\]"\s+-resize\s+"x50"\s+-crop\s+"100x50\+114\+0"\s+\+repage\s+-strip\s+-depth\s+8\s+".*?"}
|
123
125
|
end
|
124
126
|
@thumb.make
|
125
127
|
end
|
@@ -128,10 +130,10 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
128
130
|
dst = @thumb.make
|
129
131
|
assert_match /100x50/, `identify #{dst.path}`
|
130
132
|
end
|
131
|
-
|
133
|
+
|
132
134
|
context "redefined to have bad convert_options setting" do
|
133
135
|
setup do
|
134
|
-
@thumb = Paperclip::Thumbnail.new(@file, "100x50#", format
|
136
|
+
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#", :format => nil, :convert_options => "-this-aint-no-option", :whiny => true)
|
135
137
|
end
|
136
138
|
|
137
139
|
should "error when trying to create the thumbnail" do
|
@@ -139,7 +141,7 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
139
141
|
@thumb.make
|
140
142
|
end
|
141
143
|
end
|
142
|
-
end
|
144
|
+
end
|
143
145
|
end
|
144
146
|
end
|
145
147
|
end
|