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.
Files changed (48) hide show
  1. data/Gemfile +29 -0
  2. data/Gemfile.lock +100 -0
  3. data/README.md +145 -0
  4. data/Rakefile +37 -71
  5. data/VERSION +1 -0
  6. data/dm-paperclip.gemspec +103 -0
  7. data/lib/dm-paperclip.rb +88 -74
  8. data/lib/dm-paperclip/attachment.rb +139 -102
  9. data/lib/dm-paperclip/callbacks.rb +55 -0
  10. data/lib/dm-paperclip/command_line.rb +86 -0
  11. data/lib/dm-paperclip/ext/blank.rb +24 -0
  12. data/lib/dm-paperclip/ext/class.rb +50 -0
  13. data/lib/dm-paperclip/ext/compatibility.rb +11 -0
  14. data/lib/dm-paperclip/ext/try_dup.rb +12 -0
  15. data/lib/dm-paperclip/geometry.rb +3 -5
  16. data/lib/dm-paperclip/interpolations.rb +57 -32
  17. data/lib/dm-paperclip/iostream.rb +12 -26
  18. data/lib/dm-paperclip/processor.rb +14 -4
  19. data/lib/dm-paperclip/storage.rb +2 -257
  20. data/lib/dm-paperclip/storage/filesystem.rb +73 -0
  21. data/lib/dm-paperclip/storage/s3.rb +209 -0
  22. data/lib/dm-paperclip/storage/s3/aws_library.rb +41 -0
  23. data/lib/dm-paperclip/storage/s3/aws_s3_library.rb +60 -0
  24. data/lib/dm-paperclip/style.rb +90 -0
  25. data/lib/dm-paperclip/thumbnail.rb +33 -24
  26. data/lib/dm-paperclip/upfile.rb +13 -5
  27. data/lib/dm-paperclip/validations.rb +40 -37
  28. data/lib/dm-paperclip/version.rb +4 -0
  29. data/test/attachment_test.rb +510 -67
  30. data/test/command_line_test.rb +138 -0
  31. data/test/fixtures/s3.yml +8 -0
  32. data/test/fixtures/twopage.pdf +0 -0
  33. data/test/fixtures/uppercase.PNG +0 -0
  34. data/test/geometry_test.rb +54 -19
  35. data/test/helper.rb +91 -28
  36. data/test/integration_test.rb +252 -79
  37. data/test/interpolations_test.rb +150 -0
  38. data/test/iostream_test.rb +8 -15
  39. data/test/paperclip_test.rb +222 -69
  40. data/test/processor_test.rb +10 -0
  41. data/test/storage_test.rb +102 -23
  42. data/test/style_test.rb +141 -0
  43. data/test/thumbnail_test.rb +106 -18
  44. data/test/upfile_test.rb +36 -0
  45. metadata +136 -121
  46. data/README.rdoc +0 -116
  47. data/init.rb +0 -1
  48. 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
@@ -0,0 +1,12 @@
1
+ module Paperclip
2
+ module Ext
3
+ def self.try_dup(value)
4
+ case value
5
+ when ::TrueClass, ::FalseClass, ::NilClass, ::Module, ::Numeric, ::Symbol
6
+ value
7
+ else
8
+ value.dup
9
+ end
10
+ end
11
+ end
12
+ 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 = nil if height == ''
10
- width = nil if 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", %Q[-format "%wx%h" "#{file}"[0]])
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, style
38
- "#{basename(attachment, style)}.#{extension(attachment, style)}"
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
- def url attachment, style
45
- raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
46
- attachment.url(style, false)
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, style
52
+ def timestamp attachment, style_name
51
53
  attachment.instance_read(:updated_at).to_s
52
54
  end
53
55
 
54
- def web_root attachment, style
55
- if Object.const_defined?('Merb')
56
- merb_root(attachment, style)
57
- elsif Object.const_defined("RAILS_ROOT")
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
- # Returns the RAILS_ROOT constant.
65
- def rails_root attachment, style
66
- Object.const_defined?('RAILS_ROOT') ? RAILS_ROOT : nil
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 RAILS_ENV constant.
70
- def rails_env attachment, style
71
- Object.const_defined?('RAILS_ENV') ? RAILS_ENV : nil
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 snake cased, pluralized version of the class name.
87
+ # Returns the underscored, pluralized version of the class name.
83
88
  # e.g. "users" for the User class.
84
- def class attachment, style
85
- attachment.instance.class.to_s.snake_case.pluralize
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, style
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, style
97
- ((style = attachment.styles[style]) && style[:format]) ||
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, style
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, style
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, style
115
- attachment.name.to_s.downcase.pluralize
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, style
120
- style || attachment.default_style
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
- name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream")
8
- tempfile = Paperclip::Tempfile.new(File.basename(name))
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
- self.stream_to(tempfile)
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. If this module is included in both
15
- # StringIO and Tempfile, then either can have its data copied anywhere else without typing
16
- # worries or memory overhead worries. Returns a File if a String is passed in as the destination
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
- self.rewind
26
- while self.read(in_blocks_of, buffer) do
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