paperclip 2.2.2 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- data/README.rdoc +2 -2
- data/Rakefile +0 -13
- data/lib/paperclip.rb +18 -9
- data/lib/paperclip/attachment.rb +29 -10
- data/lib/paperclip/geometry.rb +1 -1
- data/lib/paperclip/storage.rb +8 -5
- data/shoulda_macros/matchers.rb +4 -0
- data/shoulda_macros/matchers/have_attached_file_matcher.rb +49 -0
- data/shoulda_macros/matchers/validate_attachment_content_type_matcher.rb +66 -0
- data/shoulda_macros/matchers/validate_attachment_presence_matcher.rb +48 -0
- data/shoulda_macros/matchers/validate_attachment_size_matcher.rb +83 -0
- data/shoulda_macros/paperclip.rb +20 -105
- data/tasks/paperclip_tasks.rake +1 -1
- data/test/attachment_test.rb +41 -8
- data/test/fixtures/twopage.pdf +0 -0
- data/test/helper.rb +21 -1
- data/test/integration_test.rb +4 -0
- data/test/matchers/have_attached_file_matcher_test.rb +21 -0
- data/test/matchers/validate_attachment_content_type_matcher_test.rb +30 -0
- data/test/matchers/validate_attachment_presence_matcher_test.rb +21 -0
- data/test/matchers/validate_attachment_size_matcher_test.rb +50 -0
- data/test/thumbnail_test.rb +33 -0
- metadata +12 -2
data/README.rdoc
CHANGED
@@ -109,7 +109,7 @@ a set of styles for an attachment, by default it is expected that those
|
|
109
109
|
"styles" are actually "thumbnails". However, you can do more than just
|
110
110
|
thumbnail images. By defining a subclass of Paperclip::Processor, you can
|
111
111
|
perform any processing you want on the files that are attached. Any file in
|
112
|
-
your Rails app's lib/
|
112
|
+
your Rails app's lib/paperclip_processors directory is automatically loaded by
|
113
113
|
paperclip, allowing you to easily define custom processors. You can specify a
|
114
114
|
processor with the :processors option to has_attached_file:
|
115
115
|
|
@@ -152,7 +152,7 @@ are called before and after the processing of each attachment), and the
|
|
152
152
|
attachment-specific "before_<attachment>_post_process" and
|
153
153
|
"after_<attachment>_post_process". The callbacks are intended to be as close to
|
154
154
|
normal ActiveRecord callbacks as possible, so if you return false (specifically
|
155
|
-
|
155
|
+
- returning nil is not the same) in a before_ filter, the post processing step
|
156
156
|
will halt. Returning false in an after_ filter will not halt anything, but you
|
157
157
|
can access the model and the attachment if necessary.
|
158
158
|
|
data/Rakefile
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'rake/rdoctask'
|
4
|
-
require 'rake/gempackagetask'
|
5
4
|
|
6
5
|
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
7
6
|
require 'paperclip'
|
@@ -70,18 +69,6 @@ spec = Gem::Specification.new do |s|
|
|
70
69
|
s.add_development_dependency 'mocha'
|
71
70
|
end
|
72
71
|
|
73
|
-
desc "Release new version"
|
74
|
-
task :release => [:test, :sync_docs, :gem] do
|
75
|
-
require 'rubygems'
|
76
|
-
require 'rubyforge'
|
77
|
-
r = RubyForge.new
|
78
|
-
r.login
|
79
|
-
r.add_release spec.rubyforge_project,
|
80
|
-
spec.name,
|
81
|
-
spec.version,
|
82
|
-
File.join("pkg", "#{spec.name}-#{spec.version}.gem")
|
83
|
-
end
|
84
|
-
|
85
72
|
desc "Generate a gemspec file for GitHub"
|
86
73
|
task :gemspec do
|
87
74
|
File.open("#{spec.name}.gemspec", 'w') do |f|
|
data/lib/paperclip.rb
CHANGED
@@ -43,7 +43,7 @@ end
|
|
43
43
|
# documentation for Paperclip::ClassMethods for more useful information.
|
44
44
|
module Paperclip
|
45
45
|
|
46
|
-
VERSION = "2.2.
|
46
|
+
VERSION = "2.2.3"
|
47
47
|
|
48
48
|
class << self
|
49
49
|
# Provides configurability to Paperclip. There are a number of options available, such as:
|
@@ -60,20 +60,23 @@ module Paperclip
|
|
60
60
|
:whiny_thumbnails => true,
|
61
61
|
:image_magick_path => nil,
|
62
62
|
:command_path => nil,
|
63
|
-
:log => true
|
63
|
+
:log => true,
|
64
|
+
:swallow_stderr => true
|
64
65
|
}
|
65
66
|
end
|
66
67
|
|
67
68
|
def path_for_command command #:nodoc:
|
68
69
|
if options[:image_magick_path]
|
69
|
-
|
70
|
-
"will be removed. Use :command_path "+
|
71
|
-
"instead")
|
70
|
+
warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
|
72
71
|
end
|
73
|
-
path = [options[:
|
72
|
+
path = [options[:command_path] || options[:image_magick_path], command].compact
|
74
73
|
File.join(*path)
|
75
74
|
end
|
76
75
|
|
76
|
+
def interpolates key, &block
|
77
|
+
Paperclip::Attachment.interpolations[key] = block
|
78
|
+
end
|
79
|
+
|
77
80
|
# The run method takes a command to execute and a string of parameters
|
78
81
|
# that get passed to it. The command is prefixed with the :command_path
|
79
82
|
# option from Paperclip.options. If you have many commands to run and
|
@@ -84,7 +87,9 @@ module Paperclip
|
|
84
87
|
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
|
85
88
|
# a code of 0 is expected, but a list of codes may be passed if necessary.
|
86
89
|
def run cmd, params = "", expected_outcodes = 0
|
87
|
-
|
90
|
+
command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
|
91
|
+
command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
|
92
|
+
output = `#{command}`
|
88
93
|
unless [expected_outcodes].flatten.include?($?.exitstatus)
|
89
94
|
raise PaperclipCommandLineError, "Error while running #{cmd}"
|
90
95
|
end
|
@@ -204,7 +209,8 @@ module Paperclip
|
|
204
209
|
end
|
205
210
|
|
206
211
|
validates_each(name) do |record, attr, value|
|
207
|
-
|
212
|
+
attachment = record.attachment_for(name)
|
213
|
+
attachment.send(:flush_errors) unless attachment.valid?
|
208
214
|
end
|
209
215
|
end
|
210
216
|
|
@@ -250,6 +256,9 @@ module Paperclip
|
|
250
256
|
# match. Allows all by default.
|
251
257
|
# * +message+: The message to display when the uploaded file has an invalid
|
252
258
|
# content type.
|
259
|
+
# NOTE: If you do not specify an [attachment]_content_type field on your
|
260
|
+
# model, content_type validation will work _ONLY upon assignment_ and
|
261
|
+
# re-validation after the instance has been reloaded will always succeed.
|
253
262
|
def validates_attachment_content_type name, options = {}
|
254
263
|
attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
|
255
264
|
valid_types = [options[:content_type]].flatten
|
@@ -257,7 +266,7 @@ module Paperclip
|
|
257
266
|
unless attachment.original_filename.blank?
|
258
267
|
unless valid_types.blank?
|
259
268
|
content_type = attachment.instance_read(:content_type)
|
260
|
-
unless valid_types.any?{|t| t === content_type }
|
269
|
+
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
261
270
|
options[:message] || "is not one of the allowed file types."
|
262
271
|
end
|
263
272
|
end
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -89,6 +89,7 @@ module Paperclip
|
|
89
89
|
|
90
90
|
@dirty = true
|
91
91
|
|
92
|
+
solidify_style_definitions
|
92
93
|
post_process if valid?
|
93
94
|
|
94
95
|
# Reset the file size if the original file was reprocessed.
|
@@ -241,6 +242,7 @@ module Paperclip
|
|
241
242
|
def instance_write(attr, value)
|
242
243
|
setter = :"#{name}_#{attr}="
|
243
244
|
responds = instance.respond_to?(setter)
|
245
|
+
self.instance_variable_set("@_#{setter.to_s.chop}", value)
|
244
246
|
instance.send(setter, value) if responds || attr.to_s == "file_name"
|
245
247
|
end
|
246
248
|
|
@@ -249,20 +251,22 @@ module Paperclip
|
|
249
251
|
def instance_read(attr)
|
250
252
|
getter = :"#{name}_#{attr}"
|
251
253
|
responds = instance.respond_to?(getter)
|
254
|
+
cached = self.instance_variable_get("@_#{getter}")
|
255
|
+
return cached if cached
|
252
256
|
instance.send(getter) if responds || attr.to_s == "file_name"
|
253
257
|
end
|
254
258
|
|
255
259
|
private
|
256
260
|
|
257
|
-
def logger
|
261
|
+
def logger #:nodoc:
|
258
262
|
instance.logger
|
259
263
|
end
|
260
264
|
|
261
|
-
def log message
|
265
|
+
def log message #:nodoc:
|
262
266
|
logger.info("[paperclip] #{message}") if logging?
|
263
267
|
end
|
264
268
|
|
265
|
-
def logging?
|
269
|
+
def logging? #:nodoc:
|
266
270
|
Paperclip.options[:log]
|
267
271
|
end
|
268
272
|
|
@@ -283,7 +287,7 @@ module Paperclip
|
|
283
287
|
@validation_errors
|
284
288
|
end
|
285
289
|
|
286
|
-
def normalize_style_definition
|
290
|
+
def normalize_style_definition #:nodoc:
|
287
291
|
@styles.each do |name, args|
|
288
292
|
unless args.is_a? Hash
|
289
293
|
dimensions, format = [args, nil].flatten[0..1]
|
@@ -305,7 +309,15 @@ module Paperclip
|
|
305
309
|
end
|
306
310
|
end
|
307
311
|
|
308
|
-
def
|
312
|
+
def solidify_style_definitions #:nodoc:
|
313
|
+
@styles.each do |name, args|
|
314
|
+
if @styles[name][:geometry].respond_to?(:call)
|
315
|
+
@styles[name][:geometry] = @styles[name][:geometry].call(instance)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def initialize_storage #:nodoc:
|
309
321
|
@storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
|
310
322
|
self.extend(@storage_module)
|
311
323
|
end
|
@@ -321,8 +333,17 @@ module Paperclip
|
|
321
333
|
|
322
334
|
def post_process #:nodoc:
|
323
335
|
return if @queued_for_write[:original].nil?
|
324
|
-
return if
|
325
|
-
|
336
|
+
return if fire_events(:before)
|
337
|
+
post_process_styles
|
338
|
+
return if fire_events(:after)
|
339
|
+
end
|
340
|
+
|
341
|
+
def fire_events(which)
|
342
|
+
return true if callback(:"#{which}_post_process") == false
|
343
|
+
return true if callback(:"#{which}_#{name}_post_process") == false
|
344
|
+
end
|
345
|
+
|
346
|
+
def post_process_styles
|
326
347
|
log("Post-processing #{name}")
|
327
348
|
@styles.each do |name, args|
|
328
349
|
begin
|
@@ -336,11 +357,9 @@ module Paperclip
|
|
336
357
|
(@errors[:processing] ||= []) << e.message if @whiny
|
337
358
|
end
|
338
359
|
end
|
339
|
-
callback(:"after_#{name}_post_process")
|
340
|
-
callback(:after_post_process)
|
341
360
|
end
|
342
361
|
|
343
|
-
def callback which
|
362
|
+
def callback which #:nodoc:
|
344
363
|
instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
|
345
364
|
end
|
346
365
|
|
data/lib/paperclip/geometry.rb
CHANGED
@@ -16,7 +16,7 @@ module Paperclip
|
|
16
16
|
def self.from_file file
|
17
17
|
file = file.path if file.respond_to? "path"
|
18
18
|
geometry = begin
|
19
|
-
Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"])
|
19
|
+
Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"[0]])
|
20
20
|
rescue PaperclipCommandLineError
|
21
21
|
""
|
22
22
|
end
|
data/lib/paperclip/storage.rb
CHANGED
@@ -61,8 +61,11 @@ module Paperclip
|
|
61
61
|
path = File.dirname(path)
|
62
62
|
FileUtils.rmdir(path)
|
63
63
|
end
|
64
|
-
rescue Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL
|
64
|
+
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
|
65
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
|
66
69
|
end
|
67
70
|
end
|
68
71
|
@queued_for_delete = []
|
@@ -118,11 +121,11 @@ module Paperclip
|
|
118
121
|
require 'right_aws'
|
119
122
|
base.instance_eval do
|
120
123
|
@s3_credentials = parse_credentials(@options[:s3_credentials])
|
121
|
-
@bucket = @options[:bucket]
|
122
|
-
@s3_options = @options[:s3_options]
|
124
|
+
@bucket = @options[:bucket] || @s3_credentials[:bucket]
|
125
|
+
@s3_options = @options[:s3_options] || {}
|
123
126
|
@s3_permissions = @options[:s3_permissions] || 'public-read'
|
124
|
-
@s3_protocol = @options[:s3_protocol]
|
125
|
-
@s3_headers = @options[:s3_headers]
|
127
|
+
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
|
128
|
+
@s3_headers = @options[:s3_headers] || {}
|
126
129
|
@url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
|
127
130
|
end
|
128
131
|
base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
|
@@ -0,0 +1,4 @@
|
|
1
|
+
require 'shoulda_macros/matchers/have_attached_file_matcher'
|
2
|
+
require 'shoulda_macros/matchers/validate_attachment_presence_matcher'
|
3
|
+
require 'shoulda_macros/matchers/validate_attachment_content_type_matcher'
|
4
|
+
require 'shoulda_macros/matchers/validate_attachment_size_matcher'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Shoulda
|
3
|
+
module Matchers
|
4
|
+
def have_attached_file name
|
5
|
+
HaveAttachedFileMatcher.new(name)
|
6
|
+
end
|
7
|
+
|
8
|
+
class HaveAttachedFileMatcher
|
9
|
+
def initialize attachment_name
|
10
|
+
@attachment_name = attachment_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches? subject
|
14
|
+
@subject = subject
|
15
|
+
responds? && has_column? && included?
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_message
|
19
|
+
"Should have an attachment named #{@attachment_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def negative_failure_message
|
23
|
+
"Should not have an attachment named #{@attachment_name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
"have an attachment named #{@attachment_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def responds?
|
33
|
+
methods = @subject.instance_methods
|
34
|
+
methods.include?("#{@attachment_name}") &&
|
35
|
+
methods.include?("#{@attachment_name}=") &&
|
36
|
+
methods.include?("#{@attachment_name}?")
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_column?
|
40
|
+
@subject.column_names.include?("#{@attachment_name}_file_name")
|
41
|
+
end
|
42
|
+
|
43
|
+
def included?
|
44
|
+
@subject.ancestors.include?(Paperclip::InstanceMethods)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -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
|
+
|
data/shoulda_macros/paperclip.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'shoulda_macros/matchers'
|
2
|
+
|
1
3
|
module Paperclip
|
2
4
|
# =Paperclip Shoulda Macros
|
3
5
|
#
|
@@ -10,39 +12,20 @@ module Paperclip
|
|
10
12
|
# This will test whether you have defined your attachment correctly by
|
11
13
|
# checking for all the required fields exist after the definition of the
|
12
14
|
# attachment.
|
13
|
-
def should_have_attached_file name
|
14
|
-
klass
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
assert klass.instance_methods.include?(meth), "#{klass.name} does not respond to #{name}."
|
19
|
-
end
|
20
|
-
end
|
15
|
+
def should_have_attached_file name
|
16
|
+
klass = self.name.gsub(/Test$/, '').constantize
|
17
|
+
matcher = have_attached_file name
|
18
|
+
should matcher.description do
|
19
|
+
assert_accepts(matcher, klass)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
23
|
# Tests for validations on the presence of the attachment.
|
25
24
|
def should_validate_attachment_presence name
|
26
25
|
klass = self.name.gsub(/Test$/, '').constantize
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@attachment = klass.new.send(name)
|
31
|
-
@attachment.assign(nil)
|
32
|
-
end
|
33
|
-
should "have a :presence validation error" do
|
34
|
-
assert @assignment.errors[:presence]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
context "when the assignment is valid" do
|
38
|
-
setup do
|
39
|
-
@attachment = klass.new.send(name)
|
40
|
-
@attachment.assign(nil)
|
41
|
-
end
|
42
|
-
should "have a :presence validation error" do
|
43
|
-
assert ! @assignment.errors[:presence]
|
44
|
-
end
|
45
|
-
end
|
26
|
+
matcher = validate_attachment_presence name
|
27
|
+
should matcher.description do
|
28
|
+
assert_accepts(matcher, klass)
|
46
29
|
end
|
47
30
|
end
|
48
31
|
|
@@ -54,35 +37,9 @@ module Paperclip
|
|
54
37
|
klass = self.name.gsub(/Test$/, '').constantize
|
55
38
|
valid = [options[:valid]].flatten
|
56
39
|
invalid = [options[:invalid]].flatten
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
setup do
|
61
|
-
@file = StringIO.new(".")
|
62
|
-
class << @file; attr_accessor :content_type; end
|
63
|
-
@file.content_type = type
|
64
|
-
@attachment = klass.new.send(name)
|
65
|
-
@attachment.assign(@file)
|
66
|
-
end
|
67
|
-
should "not have a :content_type validation error" do
|
68
|
-
assert ! @assignment.errors[:content_type]
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
invalid.each do |type|
|
73
|
-
context "being assigned a file with a content_type of #{type}" do
|
74
|
-
setup do
|
75
|
-
@file = StringIO.new(".")
|
76
|
-
class << @file; attr_accessor :content_type; end
|
77
|
-
@file.content_type = type
|
78
|
-
@attachment = klass.new.send(name)
|
79
|
-
@attachment.assign(@file)
|
80
|
-
end
|
81
|
-
should "have a :content_type validation error" do
|
82
|
-
assert @assignment.errors[:content_type]
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
40
|
+
matcher = validate_attachment_presence(name).allows(valid).rejects(invalid)
|
41
|
+
should matcher.description do
|
42
|
+
assert_accepts(matcher, klass)
|
86
43
|
end
|
87
44
|
end
|
88
45
|
|
@@ -97,57 +54,15 @@ module Paperclip
|
|
97
54
|
min = options[:greater_than] || (options[:in] && options[:in].first) || 0
|
98
55
|
max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
|
99
56
|
range = (min..max)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
@file = StringIO.new("." * (max+1))
|
104
|
-
@attachment = klass.new.send(name)
|
105
|
-
@attachment.assign(@file)
|
106
|
-
end
|
107
|
-
|
108
|
-
should "have a :size validation error" do
|
109
|
-
assert @attachment.errors[:size]
|
110
|
-
end
|
111
|
-
end
|
112
|
-
context "with an attachment that us #{max-1} bytes" do
|
113
|
-
setup do
|
114
|
-
@file = StringIO.new("." * (max-1))
|
115
|
-
@attachment = klass.new.send(name)
|
116
|
-
@attachment.assign(@file)
|
117
|
-
end
|
118
|
-
|
119
|
-
should "not have a :size validation error" do
|
120
|
-
assert ! @attachment.errors[:size]
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
if min > 0
|
125
|
-
context "with an attachment that is #{min-1} bytes" do
|
126
|
-
setup do
|
127
|
-
@file = StringIO.new("." * (min-1))
|
128
|
-
@attachment = klass.new.send(name)
|
129
|
-
@attachment.assign(@file)
|
130
|
-
end
|
131
|
-
|
132
|
-
should "have a :size validation error" do
|
133
|
-
assert @attachment.errors[:size]
|
134
|
-
end
|
135
|
-
end
|
136
|
-
context "with an attachment that us #{min+1} bytes" do
|
137
|
-
setup do
|
138
|
-
@file = StringIO.new("." * (min+1))
|
139
|
-
@attachment = klass.new.send(name)
|
140
|
-
@attachment.assign(@file)
|
141
|
-
end
|
142
|
-
|
143
|
-
should "not have a :size validation error" do
|
144
|
-
assert ! @attachment.errors[:size]
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
57
|
+
matcher = validate_attachment_size(name).in(range)
|
58
|
+
should matcher.description do
|
59
|
+
assert_accepts(matcher, klass)
|
148
60
|
end
|
149
61
|
end
|
150
62
|
end
|
151
63
|
end
|
152
64
|
|
153
|
-
Test::Unit::TestCase
|
65
|
+
class Test::Unit::TestCase #:nodoc:
|
66
|
+
extend Paperclip::Shoulda
|
67
|
+
include Paperclip::Shoulda::Matchers
|
68
|
+
end
|
data/tasks/paperclip_tasks.rake
CHANGED
@@ -17,7 +17,7 @@ end
|
|
17
17
|
def for_all_attachments
|
18
18
|
klass = obtain_class
|
19
19
|
names = obtain_attachments
|
20
|
-
ids = klass.connection.select_values(
|
20
|
+
ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
|
21
21
|
|
22
22
|
ids.each do |id|
|
23
23
|
instance = klass.find(id)
|
data/test/attachment_test.rb
CHANGED
@@ -95,14 +95,10 @@ class AttachmentTest < Test::Unit::TestCase
|
|
95
95
|
rebuild_model :path => ":rails_env/:id.png"
|
96
96
|
@dummy = Dummy.new
|
97
97
|
@dummy.stubs(:id).returns(@id)
|
98
|
-
@file =
|
99
|
-
"fixtures",
|
100
|
-
"5k.png"), 'rb')
|
98
|
+
@file = StringIO.new(".")
|
101
99
|
@dummy.avatar = @file
|
102
100
|
end
|
103
101
|
|
104
|
-
teardown { @file.close }
|
105
|
-
|
106
102
|
should "return the proper path" do
|
107
103
|
temporary_rails_env(@rails_env) {
|
108
104
|
assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
|
@@ -170,6 +166,35 @@ class AttachmentTest < Test::Unit::TestCase
|
|
170
166
|
end
|
171
167
|
end
|
172
168
|
|
169
|
+
geometry_specs = [
|
170
|
+
[ lambda{|z| "50x50#" }, :png ],
|
171
|
+
lambda{|z| "50x50#" },
|
172
|
+
{ :geometry => lambda{|z| "50x50#" } }
|
173
|
+
]
|
174
|
+
geometry_specs.each do |geometry_spec|
|
175
|
+
context "An attachment geometry like #{geometry_spec}" do
|
176
|
+
setup do
|
177
|
+
rebuild_model :styles => { :normal => geometry_spec }
|
178
|
+
@attachment = Dummy.new.avatar
|
179
|
+
end
|
180
|
+
|
181
|
+
should "not run the procs immediately" do
|
182
|
+
assert_kind_of Proc, @attachment.styles[:normal][:geometry]
|
183
|
+
end
|
184
|
+
|
185
|
+
context "when assigned" do
|
186
|
+
setup do
|
187
|
+
@file = StringIO.new(".")
|
188
|
+
@attachment.assign(@file)
|
189
|
+
end
|
190
|
+
|
191
|
+
should "have the correct geometry" do
|
192
|
+
assert_equal "50x50#", @attachment.styles[:normal][:geometry]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
173
198
|
context "An attachment with both 'normal' and hash-style styles" do
|
174
199
|
setup do
|
175
200
|
rebuild_model :styles => {
|
@@ -234,7 +259,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
234
259
|
def do_before_all; end
|
235
260
|
def do_after_all; end
|
236
261
|
end
|
237
|
-
@file =
|
262
|
+
@file = StringIO.new(".")
|
238
263
|
@file.stubs(:to_tempfile).returns(@file)
|
239
264
|
@dummy = Dummy.new
|
240
265
|
Paperclip::Thumbnail.stubs(:make).returns(@file)
|
@@ -281,7 +306,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
281
306
|
context "Assigning an attachment" do
|
282
307
|
setup do
|
283
308
|
rebuild_model :styles => { :something => "100x100#" }
|
284
|
-
@file =
|
309
|
+
@file = StringIO.new(".")
|
285
310
|
@file.expects(:original_filename).returns("5k.png\n\n")
|
286
311
|
@file.expects(:content_type).returns("image/png\n\n")
|
287
312
|
@file.stubs(:to_tempfile).returns(@file)
|
@@ -521,8 +546,16 @@ class AttachmentTest < Test::Unit::TestCase
|
|
521
546
|
assert_nothing_raised { @dummy.avatar = @file }
|
522
547
|
end
|
523
548
|
|
524
|
-
should "return
|
549
|
+
should "return the time when sent #avatar_updated_at" do
|
550
|
+
now = Time.now
|
551
|
+
Time.stubs(:now).returns(now)
|
525
552
|
@dummy.avatar = @file
|
553
|
+
assert now, @dummy.avatar.updated_at
|
554
|
+
end
|
555
|
+
|
556
|
+
should "return nil when reloaded and sent #avatar_updated_at" do
|
557
|
+
@dummy.save
|
558
|
+
@dummy.reload
|
526
559
|
assert_nil @dummy.avatar.updated_at
|
527
560
|
end
|
528
561
|
|
Binary file
|
data/test/helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
|
+
gem 'thoughtbot-shoulda', ">= 2.9.0"
|
3
4
|
require 'shoulda'
|
4
5
|
require 'mocha'
|
5
6
|
require 'tempfile'
|
@@ -22,12 +23,31 @@ $LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
|
|
22
23
|
|
23
24
|
require File.join(ROOT, 'lib', 'paperclip.rb')
|
24
25
|
|
26
|
+
require 'shoulda_macros/paperclip'
|
27
|
+
|
25
28
|
ENV['RAILS_ENV'] ||= 'test'
|
26
29
|
|
27
30
|
FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
|
28
31
|
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
29
32
|
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
30
|
-
ActiveRecord::Base.establish_connection(config[
|
33
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
34
|
+
|
35
|
+
def reset_class class_name
|
36
|
+
ActiveRecord::Base.send(:include, Paperclip)
|
37
|
+
Object.send(:remove_const, class_name) rescue nil
|
38
|
+
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
|
39
|
+
klass.class_eval{ include Paperclip }
|
40
|
+
klass
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset_table table_name, &block
|
44
|
+
block ||= lambda{ true }
|
45
|
+
ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
|
46
|
+
end
|
47
|
+
|
48
|
+
def modify_table table_name, &block
|
49
|
+
ActiveRecord::Base.connection.change_table :dummies, &block
|
50
|
+
end
|
31
51
|
|
32
52
|
def rebuild_model options = {}
|
33
53
|
ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
|
data/test/integration_test.rb
CHANGED
@@ -106,6 +106,10 @@ class IntegrationTest < Test::Unit::TestCase
|
|
106
106
|
assert ! File.exists?(File.dirname(@saved_path))
|
107
107
|
assert ! File.exists?(File.dirname(File.dirname(@saved_path)))
|
108
108
|
end
|
109
|
+
|
110
|
+
before_should "not die if an unexpected SystemCallError happens" do
|
111
|
+
FileUtils.stubs(:rmdir).raises(Errno::EPIPE)
|
112
|
+
end
|
109
113
|
end
|
110
114
|
end
|
111
115
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class HaveAttachedFileMatcherTest < Test::Unit::TestCase
|
4
|
+
context "have_attached_file" do
|
5
|
+
setup do
|
6
|
+
@dummy_class = reset_class "Dummy"
|
7
|
+
reset_table "dummies"
|
8
|
+
@matcher = have_attached_file(:avatar)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "reject a class with no attachment" do
|
12
|
+
assert_rejects @matcher, @dummy_class
|
13
|
+
end
|
14
|
+
|
15
|
+
should "accept a class with an attachment" do
|
16
|
+
modify_table("dummies"){|d| d.string :avatar_file_name }
|
17
|
+
@dummy_class.has_attached_file :avatar
|
18
|
+
assert_accepts @matcher, @dummy_class
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase
|
4
|
+
context "validate_attachment_content_type" do
|
5
|
+
setup do
|
6
|
+
reset_table("dummies") do |d|
|
7
|
+
d.string :avatar_file_name
|
8
|
+
end
|
9
|
+
@dummy_class = reset_class "Dummy"
|
10
|
+
@dummy_class.has_attached_file :avatar
|
11
|
+
@matcher = validate_attachment_content_type(:avatar).
|
12
|
+
allowing(%w(image/png image/jpeg)).
|
13
|
+
rejecting(%w(audio/mp3 application/octet-stream))
|
14
|
+
end
|
15
|
+
|
16
|
+
should "reject a class with no validation" do
|
17
|
+
assert_rejects @matcher, @dummy_class
|
18
|
+
end
|
19
|
+
|
20
|
+
should "reject a class with a validation that doesn't match" do
|
21
|
+
@dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*}
|
22
|
+
assert_rejects @matcher, @dummy_class
|
23
|
+
end
|
24
|
+
|
25
|
+
should "accept a class with a validation" do
|
26
|
+
@dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*}
|
27
|
+
assert_accepts @matcher, @dummy_class
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase
|
4
|
+
context "validate_attachment_presence" do
|
5
|
+
setup do
|
6
|
+
reset_table("dummies"){|d| d.string :avatar_file_name }
|
7
|
+
@dummy_class = reset_class "Dummy"
|
8
|
+
@dummy_class.has_attached_file :avatar
|
9
|
+
@matcher = validate_attachment_presence(:avatar)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "reject a class with no validation" do
|
13
|
+
assert_rejects @matcher, @dummy_class
|
14
|
+
end
|
15
|
+
|
16
|
+
should "accept a class with a validation" do
|
17
|
+
@dummy_class.validates_attachment_presence :avatar
|
18
|
+
assert_accepts @matcher, @dummy_class
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase
|
4
|
+
context "validate_attachment_size" do
|
5
|
+
setup do
|
6
|
+
reset_table("dummies") do |d|
|
7
|
+
d.string :avatar_file_name
|
8
|
+
end
|
9
|
+
@dummy_class = reset_class "Dummy"
|
10
|
+
@dummy_class.has_attached_file :avatar
|
11
|
+
end
|
12
|
+
|
13
|
+
context "of limited size" do
|
14
|
+
setup{ @matcher = validate_attachment_size(:avatar).in(256..1024) }
|
15
|
+
|
16
|
+
should "reject a class with no validation" do
|
17
|
+
assert_rejects @matcher, @dummy_class
|
18
|
+
end
|
19
|
+
|
20
|
+
should "reject a class with a validation that's too high" do
|
21
|
+
@dummy_class.validates_attachment_size :avatar, :in => 256..2048
|
22
|
+
assert_rejects @matcher, @dummy_class
|
23
|
+
end
|
24
|
+
|
25
|
+
should "reject a class with a validation that's too low" do
|
26
|
+
@dummy_class.validates_attachment_size :avatar, :in => 0..1024
|
27
|
+
assert_rejects @matcher, @dummy_class
|
28
|
+
end
|
29
|
+
|
30
|
+
should "accept a class with a validation that matches" do
|
31
|
+
@dummy_class.validates_attachment_size :avatar, :in => 256..1024
|
32
|
+
assert_accepts @matcher, @dummy_class
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "validates_attachment_size with infinite range" do
|
37
|
+
setup{ @matcher = validate_attachment_size(:avatar) }
|
38
|
+
|
39
|
+
should "accept a class with an upper limit" do
|
40
|
+
@dummy_class.validates_attachment_size :avatar, :less_than => 1
|
41
|
+
assert_accepts @matcher, @dummy_class
|
42
|
+
end
|
43
|
+
|
44
|
+
should "accept a class with no upper limit" do
|
45
|
+
@dummy_class.validates_attachment_size :avatar, :greater_than => 1
|
46
|
+
assert_accepts @matcher, @dummy_class
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/test/thumbnail_test.rb
CHANGED
@@ -141,4 +141,37 @@ class ThumbnailTest < Test::Unit::TestCase
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
end
|
144
|
+
|
145
|
+
context "A multipage PDF" do
|
146
|
+
setup do
|
147
|
+
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "twopage.pdf"), 'rb')
|
148
|
+
end
|
149
|
+
|
150
|
+
teardown { @file.close }
|
151
|
+
|
152
|
+
should "start with two pages with dimensions 612x792" do
|
153
|
+
cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
|
154
|
+
assert_equal "612x792"*2, `#{cmd}`.chomp
|
155
|
+
end
|
156
|
+
|
157
|
+
context "being thumbnailed at 100x100 with cropping" do
|
158
|
+
setup do
|
159
|
+
@thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x100#", :format => :png)
|
160
|
+
end
|
161
|
+
|
162
|
+
should "report its correct current and target geometries" do
|
163
|
+
assert_equal "100x100#", @thumb.target_geometry.to_s
|
164
|
+
assert_equal "612x792", @thumb.current_geometry.to_s
|
165
|
+
end
|
166
|
+
|
167
|
+
should "report its correct format" do
|
168
|
+
assert_equal :png, @thumb.format
|
169
|
+
end
|
170
|
+
|
171
|
+
should "create the thumbnail when sent #make" do
|
172
|
+
dst = @thumb.make
|
173
|
+
assert_match /100x100/, `identify "#{dst.path}"`
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
144
177
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paperclip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Yurek
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-02-07 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -76,15 +76,25 @@ files:
|
|
76
76
|
- test/fixtures/5k.png
|
77
77
|
- test/fixtures/bad.png
|
78
78
|
- test/fixtures/text.txt
|
79
|
+
- test/fixtures/twopage.pdf
|
79
80
|
- test/geometry_test.rb
|
80
81
|
- test/helper.rb
|
81
82
|
- test/integration_test.rb
|
82
83
|
- test/iostream_test.rb
|
84
|
+
- test/matchers/have_attached_file_matcher_test.rb
|
85
|
+
- test/matchers/validate_attachment_content_type_matcher_test.rb
|
86
|
+
- test/matchers/validate_attachment_presence_matcher_test.rb
|
87
|
+
- test/matchers/validate_attachment_size_matcher_test.rb
|
83
88
|
- test/paperclip_test.rb
|
84
89
|
- test/processor_test.rb
|
85
90
|
- test/s3.yml
|
86
91
|
- test/storage_test.rb
|
87
92
|
- test/thumbnail_test.rb
|
93
|
+
- shoulda_macros/matchers/have_attached_file_matcher.rb
|
94
|
+
- shoulda_macros/matchers/validate_attachment_content_type_matcher.rb
|
95
|
+
- shoulda_macros/matchers/validate_attachment_presence_matcher.rb
|
96
|
+
- shoulda_macros/matchers/validate_attachment_size_matcher.rb
|
97
|
+
- shoulda_macros/matchers.rb
|
88
98
|
- shoulda_macros/paperclip.rb
|
89
99
|
has_rdoc: true
|
90
100
|
homepage: http://www.thoughtbot.com/projects/paperclip
|