dm-paperclip 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +29 -0
- data/Gemfile.lock +100 -0
- data/README.md +145 -0
- data/Rakefile +37 -71
- data/VERSION +1 -0
- data/dm-paperclip.gemspec +103 -0
- data/lib/dm-paperclip.rb +88 -74
- data/lib/dm-paperclip/attachment.rb +139 -102
- data/lib/dm-paperclip/callbacks.rb +55 -0
- data/lib/dm-paperclip/command_line.rb +86 -0
- data/lib/dm-paperclip/ext/blank.rb +24 -0
- data/lib/dm-paperclip/ext/class.rb +50 -0
- data/lib/dm-paperclip/ext/compatibility.rb +11 -0
- data/lib/dm-paperclip/ext/try_dup.rb +12 -0
- data/lib/dm-paperclip/geometry.rb +3 -5
- data/lib/dm-paperclip/interpolations.rb +57 -32
- data/lib/dm-paperclip/iostream.rb +12 -26
- data/lib/dm-paperclip/processor.rb +14 -4
- data/lib/dm-paperclip/storage.rb +2 -257
- data/lib/dm-paperclip/storage/filesystem.rb +73 -0
- data/lib/dm-paperclip/storage/s3.rb +209 -0
- data/lib/dm-paperclip/storage/s3/aws_library.rb +41 -0
- data/lib/dm-paperclip/storage/s3/aws_s3_library.rb +60 -0
- data/lib/dm-paperclip/style.rb +90 -0
- data/lib/dm-paperclip/thumbnail.rb +33 -24
- data/lib/dm-paperclip/upfile.rb +13 -5
- data/lib/dm-paperclip/validations.rb +40 -37
- data/lib/dm-paperclip/version.rb +4 -0
- data/test/attachment_test.rb +510 -67
- data/test/command_line_test.rb +138 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/fixtures/uppercase.PNG +0 -0
- data/test/geometry_test.rb +54 -19
- data/test/helper.rb +91 -28
- data/test/integration_test.rb +252 -79
- data/test/interpolations_test.rb +150 -0
- data/test/iostream_test.rb +8 -15
- data/test/paperclip_test.rb +222 -69
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +102 -23
- data/test/style_test.rb +141 -0
- data/test/thumbnail_test.rb +106 -18
- data/test/upfile_test.rb +36 -0
- metadata +136 -121
- data/README.rdoc +0 -116
- data/init.rb +0 -1
- data/lib/dm-paperclip/callback_compatability.rb +0 -33
@@ -0,0 +1,55 @@
|
|
1
|
+
module Paperclip; module Callbacks
|
2
|
+
class << self
|
3
|
+
def define(klass, name)
|
4
|
+
["before_#{name}", "after_#{name}"].each do |method|
|
5
|
+
klass.define_singleton_method(method) do |callback|
|
6
|
+
callbacks = (@_C2DE8FA4_FDA9_45A9_8952_0AEFB571DCC1_callbacks ||= {})
|
7
|
+
callbacks[method] ||= []
|
8
|
+
callbacks[method] << callback
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(instance, name, &block)
|
15
|
+
return false if run_callbacks(instance, "before_#{name}") == false
|
16
|
+
result = yield
|
17
|
+
return false if result == false
|
18
|
+
return false if run_callbacks(instance, "after_#{name}", true) == false
|
19
|
+
block_given? ? result : true
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def collect_callbacks(instance)
|
24
|
+
instance.class.ancestors.inject({}) do |memo, ancestor|
|
25
|
+
callbacks = ancestor.instance_variable_get(:@_C2DE8FA4_FDA9_45A9_8952_0AEFB571DCC1_callbacks)
|
26
|
+
if callbacks
|
27
|
+
callbacks.each do |name, methods|
|
28
|
+
memo[name] = methods + (memo[name] || [])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
memo
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_callbacks(instance, name, reversed = false)
|
36
|
+
#return true unless callbacks = instance.class._C2DE8FA4_FDA9_45A9_8952_0AEFB571DCC1_callbacks
|
37
|
+
return true unless callbacks = collect_callbacks(instance)
|
38
|
+
return true unless callbacks = callbacks[name]
|
39
|
+
|
40
|
+
callbacks = callbacks.reverse if reversed
|
41
|
+
callbacks.each do |callback|
|
42
|
+
result =
|
43
|
+
case callback
|
44
|
+
when Symbol
|
45
|
+
instance.send(callback)
|
46
|
+
when Proc
|
47
|
+
callback.call(instance)
|
48
|
+
end
|
49
|
+
return false if result == false
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end; end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class CommandLine
|
3
|
+
class << self
|
4
|
+
attr_accessor :path
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(binary, params = "", options = {})
|
8
|
+
@binary = binary.dup
|
9
|
+
@params = params.dup
|
10
|
+
@options = options.dup
|
11
|
+
@swallow_stderr = @options.has_key?(:swallow_stderr) ? @options.delete(:swallow_stderr) : Paperclip.options[:swallow_stderr]
|
12
|
+
@expected_outcodes = @options.delete(:expected_outcodes)
|
13
|
+
@expected_outcodes ||= [0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def command
|
17
|
+
cmd = []
|
18
|
+
cmd << full_path(@binary)
|
19
|
+
cmd << interpolate(@params, @options)
|
20
|
+
cmd << bit_bucket if @swallow_stderr
|
21
|
+
cmd.join(" ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
Paperclip.log(command)
|
26
|
+
begin
|
27
|
+
output = self.class.send(:'`', command)
|
28
|
+
rescue Errno::ENOENT
|
29
|
+
raise Paperclip::CommandNotFoundError
|
30
|
+
end
|
31
|
+
if $?.exitstatus == 127
|
32
|
+
raise Paperclip::CommandNotFoundError
|
33
|
+
end
|
34
|
+
unless @expected_outcodes.include?($?.exitstatus)
|
35
|
+
raise Paperclip::PaperclipCommandLineError, "Command '#{command}' returned #{$?.exitstatus}. Expected #{@expected_outcodes.join(", ")}"
|
36
|
+
end
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def full_path(binary)
|
43
|
+
[self.class.path, binary].compact.join("/")
|
44
|
+
end
|
45
|
+
|
46
|
+
def interpolate(pattern, vars)
|
47
|
+
# interpolates :variables and :{variables}
|
48
|
+
pattern.gsub(%r#:(?:\w+|\{\w+\})#) do |match|
|
49
|
+
key = match[1..-1]
|
50
|
+
key = key[1..-2] if key[0,1] == '{'
|
51
|
+
if invalid_variables.include?(key)
|
52
|
+
raise PaperclipCommandLineError,
|
53
|
+
"Interpolation of #{key} isn't allowed."
|
54
|
+
end
|
55
|
+
interpolation(vars, key) || match
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def invalid_variables
|
60
|
+
%w(expected_outcodes swallow_stderr)
|
61
|
+
end
|
62
|
+
|
63
|
+
def interpolation(vars, key)
|
64
|
+
if vars.key?(key.to_sym)
|
65
|
+
shell_quote(vars[key.to_sym])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def shell_quote(string)
|
70
|
+
return "" if string.nil? or string !~ /\S/
|
71
|
+
if self.class.unix?
|
72
|
+
string.split("'").map{|m| "'#{m}'" }.join("\\'")
|
73
|
+
else
|
74
|
+
%{"#{string}"}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def bit_bucket
|
79
|
+
self.class.unix? ? "2>/dev/null" : "2>NUL"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.unix?
|
83
|
+
File.exist?("/dev/null")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Ext
|
3
|
+
# Determines whether the specified +value+ is blank.
|
4
|
+
#
|
5
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
6
|
+
# For example, "", " ", +nil+, [], and {} are blank.
|
7
|
+
#
|
8
|
+
# @api semipublic
|
9
|
+
def self.blank?(value)
|
10
|
+
case value
|
11
|
+
when ::NilClass, ::FalseClass
|
12
|
+
true
|
13
|
+
when ::TrueClass, ::Numeric
|
14
|
+
false
|
15
|
+
when ::Array, ::Hash
|
16
|
+
value.empty?
|
17
|
+
when ::String
|
18
|
+
value !~ /\S/
|
19
|
+
else
|
20
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Paperclip; module Ext
|
2
|
+
module Class
|
3
|
+
def self.inheritable_attributes(klass)
|
4
|
+
unless klass.instance_variable_defined?(IVar)
|
5
|
+
klass.instance_variable_set(IVar, EMPTY_INHERITABLE_ATTRIBUTES)
|
6
|
+
end
|
7
|
+
klass.instance_variable_get(IVar)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.write_inheritable_attribute(klass, key, value)
|
11
|
+
if inheritable_attributes(klass).equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
12
|
+
klass.instance_variable_set(IVar, {})
|
13
|
+
end
|
14
|
+
inheritable_attributes(klass)[key] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.read_inheritable_attribute(klass, key)
|
18
|
+
inheritable_attributes(klass)[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.reset_inheritable_attributes(klass)
|
22
|
+
klass.instance_variable_set(IVar, EMPTY_INHERITABLE_ATTRIBUTES)
|
23
|
+
end
|
24
|
+
|
25
|
+
module Hook
|
26
|
+
def inherited(base)
|
27
|
+
super
|
28
|
+
|
29
|
+
attributes = ::Paperclip::Ext::Class.inheritable_attributes(self)
|
30
|
+
new_attributes =
|
31
|
+
if attributes.equal?(::Paperclip::Ext::Class::EMPTY_INHERITABLE_ATTRIBUTES)
|
32
|
+
::Paperclip::Ext::Class::EMPTY_INHERITABLE_ATTRIBUTES
|
33
|
+
else
|
34
|
+
attributes.inject({}) do |memo, (key, value)|
|
35
|
+
memo[key] = ::Paperclip::Ext.try_dup(value.dup)
|
36
|
+
memo
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
base.instance_variable_set(::Paperclip::Ext::Class::IVar, new_attributes)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
IVar = "@_C2DE8FA4_FDA9_45A9_8952_0AEFB571DCC1_inheritable_attributes"
|
46
|
+
|
47
|
+
# Prevent this constant from being created multiple times
|
48
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze
|
49
|
+
end
|
50
|
+
end; end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Kernel
|
2
|
+
def singleton_class
|
3
|
+
(class << self; self; end)
|
4
|
+
end unless method_defined?(:singleton_class)
|
5
|
+
end
|
6
|
+
|
7
|
+
class Class
|
8
|
+
def define_singleton_method(*args, &block)
|
9
|
+
singleton_class.module_eval { define_method(*args, &block) }
|
10
|
+
end unless method_defined?(:define_singleton_method)
|
11
|
+
end
|
@@ -6,10 +6,8 @@ module Paperclip
|
|
6
6
|
|
7
7
|
# Gives a Geometry representing the given height and width
|
8
8
|
def initialize width = nil, height = nil, modifier = nil
|
9
|
-
height =
|
10
|
-
width =
|
11
|
-
@height = (height || width).to_f
|
12
|
-
@width = (width || height).to_f
|
9
|
+
@height = height.to_f
|
10
|
+
@width = width.to_f
|
13
11
|
@modifier = modifier
|
14
12
|
end
|
15
13
|
|
@@ -18,7 +16,7 @@ module Paperclip
|
|
18
16
|
def self.from_file file
|
19
17
|
file = file.path if file.respond_to? "path"
|
20
18
|
geometry = begin
|
21
|
-
Paperclip.run("identify",
|
19
|
+
Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
|
22
20
|
rescue PaperclipCommandLineError
|
23
21
|
""
|
24
22
|
end
|
@@ -27,6 +27,7 @@ module Paperclip
|
|
27
27
|
# and the arguments to pass, which are the attachment and style name.
|
28
28
|
def self.interpolate pattern, *args
|
29
29
|
all.reverse.inject( pattern.dup ) do |result, tag|
|
30
|
+
|
30
31
|
result.gsub(/:#{tag}/) do |match|
|
31
32
|
send( tag, *args )
|
32
33
|
end
|
@@ -34,41 +35,45 @@ module Paperclip
|
|
34
35
|
end
|
35
36
|
|
36
37
|
# Returns the filename, the same way as ":basename.:extension" would.
|
37
|
-
def filename attachment,
|
38
|
-
"#{basename(attachment,
|
38
|
+
def filename attachment, style_name
|
39
|
+
"#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
|
39
40
|
end
|
40
41
|
|
41
42
|
# Returns the interpolated URL. Will raise an error if the url itself
|
42
43
|
# contains ":url" to prevent infinite recursion. This interpolation
|
43
44
|
# is used in the default :path to ease default specifications.
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}"
|
46
|
+
def url attachment, style_name
|
47
|
+
raise InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
|
48
|
+
attachment.url(style_name, false)
|
47
49
|
end
|
48
50
|
|
49
51
|
# Returns the timestamp as defined by the <attachment>_updated_at field
|
50
|
-
def timestamp attachment,
|
52
|
+
def timestamp attachment, style_name
|
51
53
|
attachment.instance_read(:updated_at).to_s
|
52
54
|
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
rails_root(attachment, style)
|
59
|
-
else
|
60
|
-
""
|
61
|
-
end
|
56
|
+
# Returns an integer timestamp that is time zone-neutral, so that paths
|
57
|
+
# remain valid even if a server's time zone changes.
|
58
|
+
def updated_at attachment, style_name
|
59
|
+
attachment.updated_at
|
62
60
|
end
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
def web_root attachment, style
|
63
|
+
Paperclip.config.root ||
|
64
|
+
merb_root(attachment, style) ||
|
65
|
+
rails_root(attachment, style) ||
|
66
|
+
""
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the Rails.root constant.
|
70
|
+
def rails_root attachment, style_name
|
71
|
+
Object.const_defined?('Rails') ? Rails.root : nil
|
67
72
|
end
|
68
73
|
|
69
|
-
# Returns the
|
70
|
-
def rails_env attachment,
|
71
|
-
Object.const_defined?('
|
74
|
+
# Returns the Rails.env constant.
|
75
|
+
def rails_env attachment, style_name
|
76
|
+
Object.const_defined?('Rails') ? Rails.env : nil
|
72
77
|
end
|
73
78
|
|
74
79
|
def merb_root attachment, style
|
@@ -79,45 +84,65 @@ module Paperclip
|
|
79
84
|
Object.const_defined?('Merb') ? Merb.env : nil
|
80
85
|
end
|
81
86
|
|
82
|
-
# Returns the
|
87
|
+
# Returns the underscored, pluralized version of the class name.
|
83
88
|
# e.g. "users" for the User class.
|
84
|
-
|
85
|
-
|
89
|
+
# NOTE: The arguments need to be optional, because some tools fetch
|
90
|
+
# all class names. Calling #class will return the expected class.
|
91
|
+
def class attachment = nil, style_name = nil
|
92
|
+
return super() if attachment.nil? && style_name.nil?
|
93
|
+
name = DataMapper::Inflector.underscore(attachment.instance.class.to_s)
|
94
|
+
DataMapper::Inflector.pluralize(name)
|
86
95
|
end
|
87
96
|
|
88
97
|
# Returns the basename of the file. e.g. "file" for "file.jpg"
|
89
|
-
def basename attachment,
|
98
|
+
def basename attachment, style_name
|
90
99
|
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
|
91
100
|
end
|
92
101
|
|
93
102
|
# Returns the extension of the file. e.g. "jpg" for "file.jpg"
|
94
103
|
# If the style has a format defined, it will return the format instead
|
95
104
|
# of the actual extension.
|
96
|
-
def extension attachment,
|
97
|
-
((style = attachment.styles[
|
105
|
+
def extension attachment, style_name
|
106
|
+
((style = attachment.styles[style_name]) && style[:format]) ||
|
98
107
|
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
99
108
|
end
|
100
109
|
|
101
110
|
# Returns the id of the instance.
|
102
|
-
def id attachment,
|
111
|
+
def id attachment, style_name
|
103
112
|
attachment.instance.id
|
104
113
|
end
|
105
114
|
|
115
|
+
# Returns the fingerprint of the instance.
|
116
|
+
def fingerprint attachment, style_name
|
117
|
+
attachment.fingerprint
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns a the attachment hash. See Paperclip::Attachment#hash for
|
121
|
+
# more details.
|
122
|
+
def hash attachment, style_name
|
123
|
+
attachment.hash(style_name)
|
124
|
+
end
|
125
|
+
|
106
126
|
# Returns the id of the instance in a split path form. e.g. returns
|
107
127
|
# 000/001/234 for an id of 1234.
|
108
|
-
def id_partition attachment,
|
128
|
+
def id_partition attachment, style_name
|
109
129
|
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
|
110
130
|
end
|
111
131
|
|
112
132
|
# Returns the pluralized form of the attachment name. e.g.
|
113
133
|
# "avatars" for an attachment of :avatar
|
114
|
-
def attachment attachment,
|
115
|
-
attachment.name.to_s.downcase
|
134
|
+
def attachment attachment, style_name
|
135
|
+
DataMapper::Inflector.pluralize(attachment.name.to_s.downcase)
|
116
136
|
end
|
117
137
|
|
118
138
|
# Returns the style, or the default style if nil is supplied.
|
119
|
-
def style attachment,
|
120
|
-
|
139
|
+
def style attachment, style_name
|
140
|
+
style_name || attachment.default_style
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the uuid of the instance.
|
144
|
+
def uuid attachment, style_name
|
145
|
+
attachment.instance.uuid
|
121
146
|
end
|
122
147
|
end
|
123
148
|
end
|
@@ -1,48 +1,34 @@
|
|
1
1
|
# Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
|
2
2
|
# and Tempfile conversion.
|
3
3
|
module IOStream
|
4
|
-
|
5
4
|
# Returns a Tempfile containing the contents of the readable object.
|
6
|
-
def to_tempfile
|
7
|
-
|
8
|
-
|
5
|
+
def to_tempfile(object)
|
6
|
+
return object.to_tempfile if object.respond_to?(:to_tempfile)
|
7
|
+
name = object.respond_to?(:original_filename) ? object.original_filename : (object.respond_to?(:path) ? object.path : "stream")
|
8
|
+
tempfile = Paperclip::Tempfile.new(["stream", File.extname(name)])
|
9
9
|
tempfile.binmode
|
10
|
-
|
10
|
+
stream_to(object, tempfile)
|
11
11
|
end
|
12
12
|
|
13
13
|
# Copies one read-able object from one place to another in blocks, obviating the need to load
|
14
|
-
# the whole thing into memory. Defaults to 8k blocks.
|
15
|
-
#
|
16
|
-
|
17
|
-
# and returns the IO or Tempfile as passed in if one is sent as the destination.
|
18
|
-
def stream_to path_or_file, in_blocks_of = 8192
|
14
|
+
# the whole thing into memory. Defaults to 8k blocks. Returns a File if a String is passed
|
15
|
+
# in as the destination and returns the IO or Tempfile as passed in if one is sent as the destination.
|
16
|
+
def stream_to object, path_or_file, in_blocks_of = 8192
|
19
17
|
dstio = case path_or_file
|
20
18
|
when String then File.new(path_or_file, "wb+")
|
21
19
|
when IO then path_or_file
|
22
20
|
when Tempfile then path_or_file
|
23
21
|
end
|
24
22
|
buffer = ""
|
25
|
-
|
26
|
-
while
|
23
|
+
object.rewind
|
24
|
+
while object.read(in_blocks_of, buffer) do
|
27
25
|
dstio.write(buffer)
|
28
26
|
end
|
29
|
-
dstio.rewind
|
27
|
+
dstio.rewind
|
30
28
|
dstio
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
|
-
class IO #:nodoc:
|
35
|
-
include IOStream
|
36
|
-
end
|
37
|
-
|
38
|
-
%w( Tempfile StringIO ).each do |klass|
|
39
|
-
if Object.const_defined? klass
|
40
|
-
Object.const_get(klass).class_eval do
|
41
|
-
include IOStream
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
32
|
# Corrects a bug in Windows when asking for Tempfile size.
|
47
33
|
if defined? Tempfile
|
48
34
|
class Tempfile
|
@@ -56,4 +42,4 @@ if defined? Tempfile
|
|
56
42
|
end
|
57
43
|
end
|
58
44
|
end
|
59
|
-
end
|
45
|
+
end
|