jr-paperclip 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/custom.md +10 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/reviewdog.yml +23 -0
  7. data/.github/workflows/test.yml +46 -0
  8. data/.gitignore +19 -0
  9. data/.qlty/.gitignore +7 -0
  10. data/.qlty/qlty.toml +89 -0
  11. data/.rubocop.yml +1060 -0
  12. data/Appraisals +29 -0
  13. data/CONTRIBUTING.md +85 -0
  14. data/Gemfile +17 -0
  15. data/LICENSE +25 -0
  16. data/NEWS +567 -0
  17. data/README.md +1083 -0
  18. data/RELEASING.md +17 -0
  19. data/Rakefile +52 -0
  20. data/bin/console +11 -0
  21. data/features/basic_integration.feature +85 -0
  22. data/features/migration.feature +29 -0
  23. data/features/rake_tasks.feature +62 -0
  24. data/features/step_definitions/attachment_steps.rb +121 -0
  25. data/features/step_definitions/html_steps.rb +15 -0
  26. data/features/step_definitions/rails_steps.rb +271 -0
  27. data/features/step_definitions/s3_steps.rb +16 -0
  28. data/features/step_definitions/web_steps.rb +106 -0
  29. data/features/support/env.rb +12 -0
  30. data/features/support/file_helpers.rb +34 -0
  31. data/features/support/fixtures/boot_config.txt +15 -0
  32. data/features/support/fixtures/gemfile.txt +5 -0
  33. data/features/support/fixtures/preinitializer.txt +20 -0
  34. data/features/support/paths.rb +28 -0
  35. data/features/support/rails.rb +39 -0
  36. data/features/support/selectors.rb +19 -0
  37. data/features/support/webmock_setup.rb +8 -0
  38. data/gemfiles/7.0.gemfile +20 -0
  39. data/gemfiles/7.1.gemfile +20 -0
  40. data/gemfiles/7.2.gemfile +20 -0
  41. data/gemfiles/8.0.gemfile +20 -0
  42. data/gemfiles/8.1.gemfile +20 -0
  43. data/lib/generators/paperclip/USAGE +8 -0
  44. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  45. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  46. data/lib/jr-paperclip.rb +1 -0
  47. data/lib/paperclip/attachment.rb +634 -0
  48. data/lib/paperclip/attachment_registry.rb +60 -0
  49. data/lib/paperclip/callbacks.rb +42 -0
  50. data/lib/paperclip/content_type_detector.rb +85 -0
  51. data/lib/paperclip/errors.rb +34 -0
  52. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  53. data/lib/paperclip/filename_cleaner.rb +15 -0
  54. data/lib/paperclip/geometry.rb +157 -0
  55. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  56. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  57. data/lib/paperclip/glue.rb +18 -0
  58. data/lib/paperclip/has_attached_file.rb +116 -0
  59. data/lib/paperclip/helpers.rb +60 -0
  60. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  61. data/lib/paperclip/interpolations.rb +205 -0
  62. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  63. data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
  64. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  65. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  66. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  67. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  68. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  69. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  70. data/lib/paperclip/io_adapters/registry.rb +36 -0
  71. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  72. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  73. data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
  74. data/lib/paperclip/locales/en.yml +18 -0
  75. data/lib/paperclip/locales/fr.yml +18 -0
  76. data/lib/paperclip/locales/gd.yml +20 -0
  77. data/lib/paperclip/logger.rb +21 -0
  78. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  79. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  80. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  81. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  82. data/lib/paperclip/matchers.rb +64 -0
  83. data/lib/paperclip/media_type_spoof_detector.rb +93 -0
  84. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  85. data/lib/paperclip/processor.rb +56 -0
  86. data/lib/paperclip/processor_helpers.rb +52 -0
  87. data/lib/paperclip/rails_environment.rb +21 -0
  88. data/lib/paperclip/railtie.rb +31 -0
  89. data/lib/paperclip/schema.rb +104 -0
  90. data/lib/paperclip/storage/filesystem.rb +99 -0
  91. data/lib/paperclip/storage/fog.rb +262 -0
  92. data/lib/paperclip/storage/s3.rb +497 -0
  93. data/lib/paperclip/storage.rb +3 -0
  94. data/lib/paperclip/style.rb +106 -0
  95. data/lib/paperclip/tempfile.rb +42 -0
  96. data/lib/paperclip/tempfile_factory.rb +22 -0
  97. data/lib/paperclip/thumbnail.rb +131 -0
  98. data/lib/paperclip/url_generator.rb +83 -0
  99. data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
  100. data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
  101. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  102. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  103. data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
  104. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  105. data/lib/paperclip/validators.rb +73 -0
  106. data/lib/paperclip/version.rb +3 -0
  107. data/lib/paperclip.rb +215 -0
  108. data/lib/tasks/paperclip.rake +140 -0
  109. data/paperclip.gemspec +51 -0
  110. data/shoulda_macros/paperclip.rb +134 -0
  111. data/spec/database.yml +4 -0
  112. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  113. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  114. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  115. data/spec/paperclip/attachment_spec.rb +1617 -0
  116. data/spec/paperclip/content_type_detector_spec.rb +58 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +47 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +267 -0
  122. data/spec/paperclip/glue_spec.rb +63 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  124. data/spec/paperclip/integration_spec.rb +702 -0
  125. data/spec/paperclip/interpolations_spec.rb +270 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -0
  132. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  133. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  134. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  135. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  136. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  137. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +231 -0
  138. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  139. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  140. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  141. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  142. data/spec/paperclip/media_type_spoof_detector_spec.rb +126 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  145. data/spec/paperclip/paperclip_spec.rb +196 -0
  146. data/spec/paperclip/plural_cache_spec.rb +37 -0
  147. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  148. data/spec/paperclip/processor_spec.rb +26 -0
  149. data/spec/paperclip/rails_environment_spec.rb +30 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +298 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +102 -0
  153. data/spec/paperclip/storage/fog_spec.rb +606 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1974 -0
  156. data/spec/paperclip/style_spec.rb +251 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/tempfile_spec.rb +35 -0
  159. data/spec/paperclip/thumbnail_spec.rb +504 -0
  160. data/spec/paperclip/url_generator_spec.rb +231 -0
  161. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
  162. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
  163. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  164. data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
  165. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  166. data/spec/paperclip/validators_spec.rb +179 -0
  167. data/spec/spec_helper.rb +52 -0
  168. data/spec/support/assertions.rb +84 -0
  169. data/spec/support/fake_model.rb +24 -0
  170. data/spec/support/fake_rails.rb +12 -0
  171. data/spec/support/fixtures/12k.png +0 -0
  172. data/spec/support/fixtures/50x50.png +0 -0
  173. data/spec/support/fixtures/5k.png +0 -0
  174. data/spec/support/fixtures/animated +0 -0
  175. data/spec/support/fixtures/animated.gif +0 -0
  176. data/spec/support/fixtures/animated.unknown +0 -0
  177. data/spec/support/fixtures/aws_s3.yml +13 -0
  178. data/spec/support/fixtures/bad.png +1 -0
  179. data/spec/support/fixtures/empty.html +1 -0
  180. data/spec/support/fixtures/empty.xlsx +0 -0
  181. data/spec/support/fixtures/fog.yml +8 -0
  182. data/spec/support/fixtures/rotated.jpg +0 -0
  183. data/spec/support/fixtures/s3.yml +8 -0
  184. data/spec/support/fixtures/sample.xlsm +0 -0
  185. data/spec/support/fixtures/spaced file.jpg +0 -0
  186. data/spec/support/fixtures/spaced file.png +0 -0
  187. data/spec/support/fixtures/text.txt +1 -0
  188. data/spec/support/fixtures/twopage.pdf +0 -0
  189. data/spec/support/fixtures/uppercase.PNG +0 -0
  190. data/spec/support/matchers/accept.rb +5 -0
  191. data/spec/support/matchers/exist.rb +5 -0
  192. data/spec/support/matchers/have_column.rb +23 -0
  193. data/spec/support/mock_attachment.rb +24 -0
  194. data/spec/support/mock_interpolator.rb +24 -0
  195. data/spec/support/mock_url_generator_builder.rb +26 -0
  196. data/spec/support/model_reconstruction.rb +72 -0
  197. data/spec/support/reporting.rb +11 -0
  198. data/spec/support/test_data.rb +13 -0
  199. data/spec/support/version_helper.rb +9 -0
  200. metadata +702 -0
@@ -0,0 +1,42 @@
1
+ module Paperclip
2
+ module Callbacks
3
+ def self.included(base)
4
+ base.extend(Defining)
5
+ base.send(:include, Running)
6
+ end
7
+
8
+ module Defining
9
+ def define_paperclip_callbacks(*callbacks)
10
+ define_callbacks(*[callbacks, { terminator: hasta_la_vista_baby }].flatten)
11
+ callbacks.each do |callback|
12
+ eval <<-end_callbacks
13
+ def before_#{callback}(*args, &blk)
14
+ set_callback(:#{callback}, :before, *args, &blk)
15
+ end
16
+ def after_#{callback}(*args, &blk)
17
+ set_callback(:#{callback}, :after, *args, &blk)
18
+ end
19
+ end_callbacks
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def hasta_la_vista_baby
26
+ lambda do |_, result|
27
+ if result.respond_to?(:call)
28
+ result.call == false
29
+ else
30
+ result == false
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module Running
37
+ def run_paperclip_callbacks(callback, &block)
38
+ run_callbacks(callback, &block)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,85 @@
1
+ module Paperclip
2
+ class ContentTypeDetector
3
+ # The content-type detection strategy is as follows:
4
+ #
5
+ # 1. Blank/Empty files: If there's no filepath or the file is empty,
6
+ # provide a sensible default (application/octet-stream or inode/x-empty)
7
+ #
8
+ # 2. Calculated match: Return the first result that is found by both the
9
+ # `file` command and MIME::Types.
10
+ #
11
+ # 3. Standard types: Return the first standard (without an x- prefix) entry
12
+ # in MIME::Types
13
+ #
14
+ # 4. Experimental types: If there were no standard types in MIME::Types
15
+ # list, try to return the first experimental one
16
+ #
17
+ # 5. Raw `file` command: Just use the output of the `file` command raw, or
18
+ # a sensible default. This is cached from Step 2.
19
+
20
+ EMPTY_TYPE = "inode/x-empty"
21
+ SENSIBLE_DEFAULT = "application/octet-stream"
22
+
23
+ def initialize(filepath)
24
+ @filepath = filepath
25
+ end
26
+
27
+ # Returns a String describing the file's content type
28
+ def detect
29
+ if blank_name?
30
+ SENSIBLE_DEFAULT
31
+ elsif empty_file?
32
+ EMPTY_TYPE
33
+ elsif calculated_type_matches.any?
34
+ calculated_type_matches.first
35
+ else
36
+ type_from_file_contents || SENSIBLE_DEFAULT
37
+ end.to_s
38
+ end
39
+
40
+ private
41
+
42
+ def blank_name?
43
+ @filepath.nil? || @filepath.empty?
44
+ end
45
+
46
+ def empty_file?
47
+ File.exist?(@filepath) && File.size(@filepath) == 0
48
+ end
49
+
50
+ alias :empty? :empty_file?
51
+
52
+ def calculated_type_matches
53
+ possible_types.select do |content_type|
54
+ content_type == type_from_file_contents
55
+ end
56
+ end
57
+
58
+ def possible_types
59
+ MIME::Types.type_for(@filepath).collect(&:content_type)
60
+ end
61
+
62
+ def type_from_file_contents
63
+ type_from_marcel || type_from_file_command
64
+ rescue Errno::ENOENT => e
65
+ Paperclip.log("Error while determining content type: #{e}")
66
+ SENSIBLE_DEFAULT
67
+ end
68
+
69
+ def type_from_marcel
70
+ return @type_from_marcel if defined? @type_from_marcel
71
+
72
+ @type_from_marcel = Marcel::MimeType.for Pathname.new(@filepath),
73
+ name: @filepath
74
+ # Marcel::MineType returns 'application/octet-stream' if it can't find
75
+ # a valid type.
76
+ @type_from_marcel = nil if @type_from_marcel == SENSIBLE_DEFAULT
77
+ @type_from_marcel
78
+ end
79
+
80
+ def type_from_file_command
81
+ @type_from_file_command ||=
82
+ FileCommandContentTypeDetector.new(@filepath).detect
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,34 @@
1
+ module Paperclip
2
+ # A base error class for Paperclip. Most of the error that will be thrown
3
+ # from Paperclip will inherits from this class.
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Errors
8
+ # Will be thrown when a storage method is not found.
9
+ class StorageMethodNotFound < Paperclip::Error
10
+ end
11
+
12
+ # Will be thrown when a command or executable is not found.
13
+ class CommandNotFoundError < Paperclip::Error
14
+ end
15
+
16
+ # Attachments require a content_type or file_name validator,
17
+ # or to have explicitly opted out of them.
18
+ class MissingRequiredValidatorError < Paperclip::Error
19
+ end
20
+
21
+ # Will be thrown when ImageMagic cannot determine the uploaded file's
22
+ # metadata, usually this would mean the file is not an image. If you are
23
+ # consistently receiving this error on PDFs make sure that you have
24
+ # installed Ghostscript.
25
+ class NotIdentifiedByImageMagickError < Paperclip::Error
26
+ end
27
+
28
+ # Will be thrown if the interpolation is creating an infinite loop. If you
29
+ # are creating an interpolator which might cause an infinite loop, you
30
+ # should be throwing this error upon the infinite loop as well.
31
+ class InfiniteInterpolationError < Paperclip::Error
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module Paperclip
2
+ class FileCommandContentTypeDetector
3
+ SENSIBLE_DEFAULT = "application/octet-stream"
4
+
5
+ def initialize(filename)
6
+ @filename = filename
7
+ end
8
+
9
+ def detect
10
+ type_from_file_command
11
+ end
12
+
13
+ private
14
+
15
+ def type_from_file_command
16
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
17
+ type = begin
18
+ Paperclip.run("file", "-b --mime :file", file: @filename)
19
+ rescue Terrapin::CommandLineError => e
20
+ Paperclip.log("Error while determining content type: #{e}")
21
+ SENSIBLE_DEFAULT
22
+ end
23
+
24
+ type = SENSIBLE_DEFAULT if type.nil? || type.match(/\(.*?\)/)
25
+ type.split(/[:;\s]+/)[0]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Paperclip
2
+ class FilenameCleaner
3
+ def initialize(invalid_character_regex)
4
+ @invalid_character_regex = invalid_character_regex
5
+ end
6
+
7
+ def call(filename)
8
+ if @invalid_character_regex
9
+ filename.gsub(@invalid_character_regex, "_")
10
+ else
11
+ filename
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,157 @@
1
+ module Paperclip
2
+ # Defines the geometry of an image.
3
+ class Geometry
4
+ attr_accessor :height, :width, :modifier
5
+
6
+ EXIF_ROTATED_ORIENTATION_VALUES = [5, 6, 7, 8].freeze
7
+
8
+ # Gives a Geometry representing the given height and width
9
+ def initialize(width = nil, height = nil, modifier = nil)
10
+ if width.is_a?(Hash)
11
+ options = width
12
+ @height = options[:height].to_f
13
+ @width = options[:width].to_f
14
+ @modifier = options[:modifier]
15
+ @orientation = options[:orientation].to_i
16
+ else
17
+ @height = height.to_f
18
+ @width = width.to_f
19
+ @modifier = modifier
20
+ end
21
+ end
22
+
23
+ # Extracts the Geometry from a file (or path to a file)
24
+ def self.from_file(file)
25
+ GeometryDetector.new(file).make
26
+ end
27
+
28
+ # Extracts the Geometry from a "WxH,O" string
29
+ # Where W is the width, H is the height,
30
+ # and O is the EXIF orientation
31
+ def self.parse(string)
32
+ GeometryParser.new(string).make
33
+ end
34
+
35
+ # Swaps the height and width if necessary
36
+ def auto_orient
37
+ if EXIF_ROTATED_ORIENTATION_VALUES.include?(@orientation)
38
+ @height, @width = @width, @height
39
+ @orientation -= 4
40
+ end
41
+ end
42
+
43
+ # True if the dimensions represent a square
44
+ def square?
45
+ height == width
46
+ end
47
+
48
+ # True if the dimensions represent a horizontal rectangle
49
+ def horizontal?
50
+ height < width
51
+ end
52
+
53
+ # True if the dimensions represent a vertical rectangle
54
+ def vertical?
55
+ height > width
56
+ end
57
+
58
+ # The aspect ratio of the dimensions.
59
+ def aspect
60
+ width / height
61
+ end
62
+
63
+ # Returns the larger of the two dimensions
64
+ def larger
65
+ [height, width].max
66
+ end
67
+
68
+ # Returns the smaller of the two dimensions
69
+ def smaller
70
+ [height, width].min
71
+ end
72
+
73
+ # Returns the width and height in a format suitable to be passed to Geometry.parse
74
+ def to_s
75
+ s = String.new
76
+ s << width.to_i.to_s if width > 0
77
+ s << "x#{height.to_i}" if height > 0
78
+ s << modifier.to_s
79
+ s
80
+ end
81
+
82
+ # Same as to_s
83
+ def inspect
84
+ to_s
85
+ end
86
+
87
+ # Returns the scaling and cropping geometries (in string-based ImageMagick format)
88
+ # neccessary to transform this Geometry into the Geometry given. If crop is true,
89
+ # then it is assumed the destination Geometry will be the exact final resolution.
90
+ # In this case, the source Geometry is scaled so that an image containing the
91
+ # destination Geometry would be completely filled by the source image, and any
92
+ # overhanging image would be cropped. Useful for square thumbnail images. The cropping
93
+ # is weighted at the center of the Geometry.
94
+ def transformation_to(dst, crop = false)
95
+ if crop
96
+ ratio = Geometry.new(dst.width / width, dst.height / height)
97
+ scale_geometry, scale = scaling(dst, ratio)
98
+ crop_geometry = cropping(dst, ratio, scale)
99
+ else
100
+ scale_geometry = dst.to_s
101
+ end
102
+
103
+ [scale_geometry, crop_geometry]
104
+ end
105
+
106
+ # resize to a new geometry
107
+ # @param geometry [String] the Paperclip geometry definition to resize to
108
+ # @example
109
+ # Paperclip::Geometry.new(150, 150).resize_to('50x50!')
110
+ # #=> Paperclip::Geometry(50, 50)
111
+ def resize_to(geometry)
112
+ new_geometry = Paperclip::Geometry.parse geometry
113
+ case new_geometry.modifier
114
+ when "!", "#"
115
+ new_geometry
116
+ when ">"
117
+ if new_geometry.width >= width && new_geometry.height >= height
118
+ self
119
+ else
120
+ scale_to new_geometry
121
+ end
122
+ when "<"
123
+ if new_geometry.width <= width || new_geometry.height <= height
124
+ self
125
+ else
126
+ scale_to new_geometry
127
+ end
128
+ else
129
+ scale_to new_geometry
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def scaling(dst, ratio)
136
+ if ratio.horizontal? || ratio.square?
137
+ ["%dx" % dst.width, ratio.width]
138
+ else
139
+ ["x%d" % dst.height, ratio.height]
140
+ end
141
+ end
142
+
143
+ def cropping(dst, ratio, scale)
144
+ if ratio.horizontal? || ratio.square?
145
+ "%dx%d+%d+%d" % [dst.width, dst.height, 0, (height * scale - dst.height) / 2]
146
+ else
147
+ "%dx%d+%d+%d" % [dst.width, dst.height, (width * scale - dst.width) / 2, 0]
148
+ end
149
+ end
150
+
151
+ # scale to the requested geometry and preserve the aspect ratio
152
+ def scale_to(new_geometry)
153
+ scale = [new_geometry.width.to_f / width.to_f, new_geometry.height.to_f / height.to_f].min
154
+ Paperclip::Geometry.new((width * scale).round, (height * scale).round)
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,45 @@
1
+ module Paperclip
2
+ class GeometryDetector
3
+ def initialize(file)
4
+ @file = file
5
+ raise_if_blank_file
6
+ end
7
+
8
+ def make
9
+ geometry = GeometryParser.new(geometry_string.strip).make
10
+ geometry || raise(Errors::NotIdentifiedByImageMagickError.new("Could not identify image size"))
11
+ end
12
+
13
+ private
14
+
15
+ def geometry_string
16
+ orientation = Paperclip.options[:use_exif_orientation] ?
17
+ "%[exif:orientation]" : "1"
18
+ Paperclip.run(
19
+ Paperclip.options[:is_windows] ? "magick identify" : "identify",
20
+ "-format '%wx%h,#{orientation}' :file", {
21
+ file: "#{path}[0]"
22
+ },
23
+ swallow_stderr: true
24
+ )
25
+ rescue Terrapin::ExitStatusError
26
+ ""
27
+ rescue Terrapin::CommandNotFoundError => e
28
+ raise_because_imagemagick_missing
29
+ end
30
+
31
+ def path
32
+ @file.respond_to?(:path) ? @file.path : @file
33
+ end
34
+
35
+ def raise_if_blank_file
36
+ if path.blank?
37
+ raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
38
+ end
39
+ end
40
+
41
+ def raise_because_imagemagick_missing
42
+ raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ module Paperclip
2
+ class GeometryParser
3
+ FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i.freeze
4
+ def initialize(string)
5
+ @string = string
6
+ end
7
+
8
+ def make
9
+ if match
10
+ Geometry.new(
11
+ height: @height,
12
+ width: @width,
13
+ modifier: @modifier,
14
+ orientation: @orientation
15
+ )
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def match
22
+ if actual_match = @string && @string.match(FORMAT)
23
+ @width = actual_match[1]
24
+ @height = actual_match[2]
25
+ @orientation = actual_match[3]
26
+ @modifier = actual_match[4]
27
+ end
28
+ actual_match
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ require "paperclip/callbacks"
2
+ require "paperclip/validators"
3
+ require "paperclip/schema"
4
+
5
+ module Paperclip
6
+ module Glue
7
+ LOCALE_PATHS = Dir.glob("#{File.dirname(__FILE__)}/locales/*.{rb,yml}")
8
+
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ base.send :include, Callbacks
12
+ base.send :include, Validators
13
+ base.send :include, Schema if defined? ActiveRecord::Base
14
+
15
+ I18n.load_path += LOCALE_PATHS unless (LOCALE_PATHS - I18n.load_path).empty?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,116 @@
1
+ module Paperclip
2
+ class HasAttachedFile
3
+ def self.define_on(klass, name, options)
4
+ new(klass, name, options).define
5
+ end
6
+
7
+ def initialize(klass, name, options)
8
+ @klass = klass
9
+ @name = name
10
+ @options = options
11
+ end
12
+
13
+ def define
14
+ define_flush_errors
15
+ define_getters
16
+ define_setter
17
+ define_query
18
+ register_new_attachment
19
+ add_active_record_callbacks
20
+ add_paperclip_callbacks
21
+ add_required_validations
22
+ end
23
+
24
+ private
25
+
26
+ def define_flush_errors
27
+ @klass.send(:validates_each, @name) do |record, _attr, _value|
28
+ attachment = record.send(@name)
29
+ attachment.send(:flush_errors)
30
+ end
31
+ end
32
+
33
+ def define_getters
34
+ define_instance_getter
35
+ define_class_getter
36
+ end
37
+
38
+ def define_instance_getter
39
+ name = @name
40
+ options = @options
41
+
42
+ @klass.send :define_method, @name do |*args|
43
+ ivar = "@attachment_#{name}"
44
+ attachment = instance_variable_get(ivar)
45
+
46
+ if attachment.nil?
47
+ attachment = Attachment.new(name, self, options)
48
+ instance_variable_set(ivar, attachment)
49
+ end
50
+
51
+ if !args.empty?
52
+ attachment.to_s(args.first)
53
+ else
54
+ attachment
55
+ end
56
+ end
57
+ end
58
+
59
+ def define_class_getter
60
+ @klass.extend(ClassMethods)
61
+ end
62
+
63
+ def define_setter
64
+ name = @name
65
+ @klass.send :define_method, "#{@name}=" do |file|
66
+ send(name).assign(file)
67
+ end
68
+ end
69
+
70
+ def define_query
71
+ name = @name
72
+ @klass.send :define_method, "#{@name}?" do
73
+ send(name).file?
74
+ end
75
+ end
76
+
77
+ def register_new_attachment
78
+ Paperclip::AttachmentRegistry.register(@klass, @name, @options)
79
+ end
80
+
81
+ def add_required_validations
82
+ options = Paperclip::Attachment.default_options.deep_merge(@options)
83
+ if options[:validate_media_type] != false
84
+ name = @name
85
+ @klass.validates_media_type_spoof_detection name,
86
+ if: ->(instance) { instance.send(name).dirty? }
87
+ end
88
+ end
89
+
90
+ def add_active_record_callbacks
91
+ name = @name
92
+ @klass.send(:after_save) { send(name).send(:save) }
93
+ @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
94
+ if @klass.respond_to?(:after_commit)
95
+ @klass.send(:after_commit, on: :destroy) do
96
+ send(name).send(:flush_deletes)
97
+ end
98
+ else
99
+ @klass.send(:after_destroy) { send(name).send(:flush_deletes) }
100
+ end
101
+ end
102
+
103
+ def add_paperclip_callbacks
104
+ @klass.send(
105
+ :define_paperclip_callbacks,
106
+ :post_process, :"#{@name}_post_process", :"#{@name}_validate"
107
+ )
108
+ end
109
+
110
+ module ClassMethods
111
+ def attachment_definitions
112
+ Paperclip::AttachmentRegistry.definitions_for(self)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,60 @@
1
+ module Paperclip
2
+ module Helpers
3
+ def configure
4
+ yield(self) if block_given?
5
+ end
6
+
7
+ def interpolates(key, &block)
8
+ Paperclip::Interpolations[key] = block
9
+ end
10
+
11
+ # The run method takes the name of a binary to run, the arguments
12
+ # to that binary, the values to interpolate and some local options.
13
+ #
14
+ # :cmd -> The name of a binary to run.
15
+ #
16
+ # :arguments -> The command line arguments to that binary.
17
+ #
18
+ # :interpolation_values -> Values to be interpolated into the arguments.
19
+ #
20
+ # :local_options -> The options to be used by Cocain::CommandLine.
21
+ # These could be: runner
22
+ # logger
23
+ # swallow_stderr
24
+ # expected_outcodes
25
+ # environment
26
+ # runner_options
27
+ #
28
+ def run(cmd, arguments = "", interpolation_values = {}, local_options = {})
29
+ command_path = options[:command_path]
30
+ terrapin_path_array = Terrapin::CommandLine.path.try(:split, Terrapin::OS.path_separator)
31
+ Terrapin::CommandLine.path = [terrapin_path_array, command_path].flatten.compact.uniq
32
+ if logging? && (options[:log_command] || local_options[:log_command])
33
+ local_options = local_options.merge(logger: logger)
34
+ end
35
+ Terrapin::CommandLine.new(cmd, arguments, local_options).run(interpolation_values)
36
+ end
37
+
38
+ # Find all instances of the given Active Record model +klass+ with attachment +name+.
39
+ # This method is used by the refresh rake tasks.
40
+ def each_instance_with_attachment(klass, name)
41
+ class_for(klass).unscoped.where("#{name}_file_name IS NOT NULL").find_each do |instance|
42
+ yield(instance)
43
+ end
44
+ end
45
+
46
+ def class_for(class_name)
47
+ class_name.split("::").inject(Object) do |klass, partial_class_name|
48
+ if klass.const_defined?(partial_class_name)
49
+ klass.const_get(partial_class_name, false)
50
+ else
51
+ klass.const_missing(partial_class_name)
52
+ end
53
+ end
54
+ end
55
+
56
+ def reset_duplicate_clash_check!
57
+ @names_url = nil
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ module Paperclip
2
+ module Interpolations
3
+ class PluralCache
4
+ def initialize
5
+ @symbol_cache = {}.compare_by_identity
6
+ @klass_cache = {}.compare_by_identity
7
+ end
8
+
9
+ def pluralize_symbol(symbol)
10
+ @symbol_cache[symbol] ||= symbol.to_s.downcase.pluralize
11
+ end
12
+
13
+ def underscore_and_pluralize_class(klass)
14
+ @klass_cache[klass] ||= klass.name.underscore.pluralize
15
+ end
16
+ end
17
+ end
18
+ end