mini_magick 3.8.1 → 4.0.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mini_gmagick.rb +2 -1
  3. data/lib/mini_magick/configuration.rb +136 -0
  4. data/lib/mini_magick/image/info.rb +122 -0
  5. data/lib/mini_magick/image.rb +380 -334
  6. data/lib/mini_magick/logger.rb +40 -0
  7. data/lib/mini_magick/shell.rb +48 -0
  8. data/lib/mini_magick/tool/animate.rb +14 -0
  9. data/lib/mini_magick/tool/compare.rb +14 -0
  10. data/lib/mini_magick/tool/composite.rb +14 -0
  11. data/lib/mini_magick/tool/conjure.rb +14 -0
  12. data/lib/mini_magick/tool/convert.rb +14 -0
  13. data/lib/mini_magick/tool/display.rb +14 -0
  14. data/lib/mini_magick/tool/identify.rb +14 -0
  15. data/lib/mini_magick/tool/import.rb +14 -0
  16. data/lib/mini_magick/tool/mogrify.rb +14 -0
  17. data/lib/mini_magick/tool/montage.rb +14 -0
  18. data/lib/mini_magick/tool/stream.rb +14 -0
  19. data/lib/mini_magick/tool.rb +250 -0
  20. data/lib/mini_magick/utilities.rb +23 -50
  21. data/lib/mini_magick/version.rb +5 -5
  22. data/lib/mini_magick.rb +43 -65
  23. data/spec/fixtures/animation.gif +0 -0
  24. data/spec/fixtures/default.jpg +0 -0
  25. data/spec/fixtures/exif.jpg +0 -0
  26. data/spec/fixtures/image.psd +0 -0
  27. data/spec/fixtures/not_an_image.rb +1 -0
  28. data/spec/lib/mini_magick/configuration_spec.rb +66 -0
  29. data/spec/lib/mini_magick/image_spec.rb +367 -406
  30. data/spec/lib/mini_magick/shell_spec.rb +66 -0
  31. data/spec/lib/mini_magick/tool_spec.rb +107 -0
  32. data/spec/lib/mini_magick/utilities_spec.rb +17 -0
  33. data/spec/lib/mini_magick_spec.rb +23 -47
  34. data/spec/spec_helper.rb +17 -25
  35. data/spec/support/helpers.rb +37 -0
  36. metadata +40 -74
  37. data/lib/mini_magick/command_builder.rb +0 -94
  38. data/lib/mini_magick/errors.rb +0 -4
  39. data/spec/files/actually_a_gif.jpg +0 -0
  40. data/spec/files/animation.gif +0 -0
  41. data/spec/files/composited.jpg +0 -0
  42. data/spec/files/erroneous.jpg +0 -0
  43. data/spec/files/layers.psd +0 -0
  44. data/spec/files/leaves (spaced).tiff +0 -0
  45. data/spec/files/not_an_image.php +0 -1
  46. data/spec/files/png.png +0 -0
  47. data/spec/files/simple-minus.gif +0 -0
  48. data/spec/files/simple.gif +0 -0
  49. data/spec/files/trogdor.jpg +0 -0
  50. data/spec/files/trogdor_capitalized.JPG +0 -0
  51. data/spec/lib/mini_magick/command_builder_spec.rb +0 -153
@@ -0,0 +1,40 @@
1
+ require "benchmark"
2
+
3
+ module MiniMagick
4
+ ##
5
+ # Responsible for logging commands to stdout (activated when
6
+ # `MiniMagick.debug` is set to `true`). Implements a simplified Logger
7
+ # interface.
8
+ #
9
+ # @private
10
+ #
11
+ class Logger
12
+
13
+ attr_accessor :format
14
+
15
+ def initialize(io)
16
+ @io = io
17
+ @format = "[%<duration>.2fs] %<command>s"
18
+ end
19
+
20
+ def debug(command, &action)
21
+ benchmark(action) do |duration|
22
+ output(duration: duration, command: command) if MiniMagick.debug
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def output(data)
29
+ printf @io, "#{format}\n", data
30
+ end
31
+
32
+ def benchmark(action)
33
+ return_value = nil
34
+ duration = Benchmark.realtime { return_value = action.call }
35
+ yield duration
36
+ return_value
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ require "mini_magick/logger"
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ module MiniMagick
7
+ ##
8
+ # Sends commands to the shell (more precisely, it sends commands directly to
9
+ # the operating system).
10
+ #
11
+ # @private
12
+ #
13
+ class Shell
14
+
15
+ def initialize(whiny = true)
16
+ @whiny = whiny
17
+ end
18
+
19
+ def run(command)
20
+ stdout, stderr, code = execute(command)
21
+
22
+ case code
23
+ when 1
24
+ fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
25
+ when 127
26
+ fail MiniMagick::Error, stderr
27
+ end if @whiny
28
+
29
+ $stderr.print(stderr)
30
+
31
+ stdout
32
+ end
33
+
34
+ def execute(command)
35
+ stdout, stderr, status =
36
+ MiniMagick.logger.debug(command.join(" ")) do
37
+ Timeout.timeout(MiniMagick.timeout) do
38
+ Open3.capture3(*command)
39
+ end
40
+ end
41
+
42
+ [stdout, stderr, status.exitstatus]
43
+ rescue Errno::ENOENT
44
+ ["", "executable not found: \"#{command.first}\"", 127]
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/animate.php
5
+ #
6
+ class Animate < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("animate", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/compare.php
5
+ #
6
+ class Compare < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("compare", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/composite.php
5
+ #
6
+ class Composite < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("composite", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/conjure.php
5
+ #
6
+ class Conjure < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("conjure", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/convert.php
5
+ #
6
+ class Convert < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("convert", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/display.php
5
+ #
6
+ class Display < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("display", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/identify.php
5
+ #
6
+ class Identify < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("identify", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/import.php
5
+ #
6
+ class Import < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("import", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/mogrify.php
5
+ #
6
+ class Mogrify < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("mogrify", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/montage.php
5
+ #
6
+ class Montage < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("montage", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module MiniMagick
2
+ class Tool
3
+ ##
4
+ # @see http://www.imagemagick.org/script/stream.php
5
+ #
6
+ class Stream < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("stream", *args)
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,250 @@
1
+ require "mini_magick/shell"
2
+
3
+ module MiniMagick
4
+ ##
5
+ # Abstract class that wraps command-line tools. It shouldn't be used directly,
6
+ # but through one of its subclasses (e.g. {MiniMagick::Tool::Mogrify}). Use
7
+ # this class if you want to be closer to the metal and execute ImageMagick
8
+ # commands directly, but still with a nice Ruby interface.
9
+ #
10
+ # @example
11
+ # MiniMagick::Tool::Mogrify.new do |builder|
12
+ # builder.resize "500x500"
13
+ # builder << "path/to/image.jpg"
14
+ # end
15
+ #
16
+ class Tool
17
+
18
+ autoload :Animate, "mini_magick/tool/animate"
19
+ autoload :Compare, "mini_magick/tool/compare"
20
+ autoload :Composite, "mini_magick/tool/composite"
21
+ autoload :Conjure, "mini_magick/tool/conjure"
22
+ autoload :Convert, "mini_magick/tool/convert"
23
+ autoload :Display, "mini_magick/tool/display"
24
+ autoload :Identify, "mini_magick/tool/identify"
25
+ autoload :Import, "mini_magick/tool/import"
26
+ autoload :Mogrify, "mini_magick/tool/mogrify"
27
+ autoload :Montage, "mini_magick/tool/montage"
28
+ autoload :Stream, "mini_magick/tool/stream"
29
+
30
+ # @private
31
+ def self.inherited(child)
32
+ child_name = child.name.split("::").last.downcase
33
+ child.send :include, MiniMagick::Tool::OptionMethods.new(child_name)
34
+ end
35
+
36
+ ##
37
+ # Aside from classic instantiation, it also accepts a block, and then
38
+ # executes the command in the end.
39
+ #
40
+ # @example
41
+ # version = MiniMagick::Tool::Identify.new { |b| b.version }
42
+ # puts version
43
+ #
44
+ # @return [MiniMagick::Tool, String] If no block is given, returns an
45
+ # instance of the tool, if block is given, returns the output of the
46
+ # command.
47
+ #
48
+ def self.new(*args)
49
+ instance = super(*args)
50
+
51
+ if block_given?
52
+ yield instance
53
+ instance.call
54
+ else
55
+ instance
56
+ end
57
+ end
58
+
59
+ # @private
60
+ attr_reader :name, :args
61
+
62
+ # @param whiny [Boolean] Whether to raise errors on exit codes different
63
+ # than 0.
64
+ # @example
65
+ # MiniMagick::Tool::Identify.new(false) do |identify|
66
+ # identify.help # returns exit status 1, which would otherwise throw an error
67
+ # end
68
+ def initialize(name, whiny = true)
69
+ @name = name
70
+ @whiny = whiny
71
+ @args = []
72
+ end
73
+
74
+ ##
75
+ # Executes the command that has been built up.
76
+ #
77
+ # @example
78
+ # mogrify = MiniMagick::Tool::Mogrify.new
79
+ # mogrify.resize("500x500")
80
+ # mogrify << "path/to/image.jpg"
81
+ # mogirfy.call # executes `mogrify -resize 500x500 path/to/image.jpg`
82
+ #
83
+ # @param whiny [Boolean] Whether you want an error to be raised when
84
+ # ImageMagick returns an exit code of 1. You may want this because
85
+ # some ImageMagick's commands (`identify -help`) return exit code 1,
86
+ # even though no error happened.
87
+ #
88
+ # @return [String] Output of the command
89
+ #
90
+ def call(whiny = @whiny)
91
+ shell = MiniMagick::Shell.new(whiny)
92
+ shell.run(command).strip
93
+ end
94
+
95
+ ##
96
+ # The currently built-up command.
97
+ #
98
+ # @return [Array<String>]
99
+ #
100
+ # @example
101
+ # mogrify = MiniMagick::Tool::Mogrify.new
102
+ # mogrify.resize "500x500"
103
+ # mogrify.contrast
104
+ # mogrify.command #=> ["mogrify", "-resize", "500x500", "-contrast"]
105
+ #
106
+ def command
107
+ [*executable, *args]
108
+ end
109
+
110
+ ##
111
+ # The executable used for this tool. Respects
112
+ # {MiniMagick::Configuration#cli} and {MiniMagick::Configuration#cli_path}.
113
+ #
114
+ # @return [Array<String>]
115
+ #
116
+ # @example
117
+ # MiniMagick.configure { |config| config.cli = :graphicsmagick }
118
+ # identify = MiniMagick::Tool::Identify.new
119
+ # identify.executable #=> ["gm", "identify"]
120
+ #
121
+ def executable
122
+ exe = [name]
123
+ exe.unshift "gm" if MiniMagick.graphicsmagick?
124
+ exe.unshift File.join(MiniMagick.cli_path, exe.shift) if MiniMagick.cli_path
125
+ exe
126
+ end
127
+
128
+ ##
129
+ # Appends raw options, useful for appending image paths.
130
+ #
131
+ # @return [self]
132
+ #
133
+ def <<(arg)
134
+ args << arg.to_s
135
+ self
136
+ end
137
+
138
+ ##
139
+ # Merges a list of raw options.
140
+ #
141
+ # @return [self]
142
+ #
143
+ def merge!(new_args)
144
+ new_args.each { |arg| self << arg }
145
+ self
146
+ end
147
+
148
+ ##
149
+ # Changes the last operator to its "plus" form.
150
+ #
151
+ # @example
152
+ # mogrify = MiniMagick::Tool::Mogrify.new
153
+ # mogrify.antialias.+
154
+ # mogrify.distort.+("Perspective '0,0,4,5'")
155
+ # mogrify.command #=> ["mogrify", "+antialias", "+distort", "Perspective '0,0,4,5'"]
156
+ #
157
+ # @return [self]
158
+ #
159
+ def +(*values)
160
+ args[-1] = args[-1].sub(/^-/, '+')
161
+ self.merge!(values)
162
+ self
163
+ end
164
+
165
+ private
166
+
167
+ ##
168
+ # Dynamically generates modules with dynamically generated option methods
169
+ # for each command-line tool. It uses the `-help` page of a command-line
170
+ # tool and generates methods from it. It then includes the generated
171
+ # module into the tool class.
172
+ #
173
+ # @private
174
+ #
175
+ class OptionMethods < Module # think about it for a minute
176
+
177
+ def self.instances
178
+ @instances ||= []
179
+ end
180
+
181
+ def initialize(tool_name)
182
+ @tool_name = tool_name
183
+ reload_methods
184
+ self.class.instances << self
185
+ end
186
+
187
+ def to_s
188
+ "OptionMethods(#{@tool_name})"
189
+ end
190
+
191
+ ##
192
+ # Dynamically generates operator methods from the "-help" page.
193
+ #
194
+ def reload_methods
195
+ instance_methods(false).each { |method| undef_method(method) }
196
+ creation_operator *creation_operators
197
+ option *cli_options
198
+ end
199
+
200
+ private
201
+
202
+ ##
203
+ # Creates method based on command-line option's name.
204
+ #
205
+ # mogrify = MiniMagick::Tool.new("mogrify")
206
+ # mogrify.antialias
207
+ # mogrify.depth(8)
208
+ # mogrify.resize("500x500")
209
+ # mogirfy.command.join(" ") #=> "mogrify -antialias -depth "8" -resize "500x500""
210
+ #
211
+ def option(*options)
212
+ options.each do |option|
213
+ define_method(option[1..-1].gsub('-', '_')) do |*values|
214
+ self << option
215
+ self.merge!(values)
216
+ self
217
+ end
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Creates method based on creation operator's name.
223
+ #
224
+ # mogrify = MiniMagick::Tool.new("mogrify")
225
+ # mogrify.canvas("khaki")
226
+ # mogrify.command.join(" ") #=> "mogrify canvas:khaki"
227
+ #
228
+ def creation_operator(*operators)
229
+ operators.each do |operator|
230
+ define_method(operator.gsub('-', '_')) do |value = nil|
231
+ self << "#{operator}:#{value}"
232
+ self
233
+ end
234
+ end
235
+ end
236
+
237
+ def creation_operators
238
+ %w[xc canvas logo rose gradient radial-gradient
239
+ plasma tile pattern label caption text]
240
+ end
241
+
242
+ def cli_options
243
+ help = MiniMagick::Tool.new(@tool_name, false) { |b| b << "-help" }
244
+ cli_options = help.scan(/^\s+-[a-z\-]+/).map(&:strip)
245
+ end
246
+
247
+ end
248
+
249
+ end
250
+ end
@@ -1,62 +1,35 @@
1
- require 'rbconfig'
2
- require 'shellwords'
3
- require 'pathname'
1
+ require "tempfile"
4
2
 
5
3
  module MiniMagick
4
+ # @private
6
5
  module Utilities
7
- class << self
8
- # Cross-platform way of finding an executable in the $PATH.
9
- #
10
- # which('ruby') #=> /usr/bin/ruby
11
- def which(cmd)
12
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
13
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
14
- exts.each do |ext|
15
- exe = File.join(path, "#{cmd}#{ext}")
16
- return exe if File.executable? exe
17
- end
18
- end
19
- nil
20
- end
21
6
 
22
- # Finds out if the host OS is windows
23
- def windows?
24
- RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
25
- end
7
+ module_function
26
8
 
27
- def escape(value)
28
- if windows?
29
- windows_escape(value)
30
- else
31
- shell_escape(value)
32
- end
33
- end
34
-
35
- def shell_escape(value)
36
- Shellwords.escape(value)
37
- end
38
-
39
- def windows_escape(value)
40
- # For Windows, ^ is the escape char, equivalent to \ in Unix.
41
- escaped = value.gsub(/\^/, '^^').gsub(/>/, '^>')
42
- if escaped !~ /^".+"$/ && escaped.include?("'")
43
- escaped.inspect
44
- else
45
- escaped
9
+ ##
10
+ # Cross-platform way of finding an executable in the $PATH.
11
+ #
12
+ # @example
13
+ # MiniMagick::Utilities.which('ruby') #=> "/usr/bin/ruby"
14
+ #
15
+ def which(cmd)
16
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
17
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
18
+ exts.each do |ext|
19
+ exe = File.join(path, "#{cmd}#{ext}")
20
+ return exe if File.executable? exe
46
21
  end
47
22
  end
23
+ nil
24
+ end
48
25
 
49
- def path(path)
50
- if windows?
51
- # For Windows, if a path contains space char, you need to quote it,
52
- # otherwise you SHOULD NOT quote it. If you quote a path that does
53
- # not contains space, it will not work.
54
- pathname = Pathname.new(path).to_s
55
- path.include?(' ') ? pathname.inspect : pathname
56
- else
57
- path
58
- end
26
+ def tempfile(extension)
27
+ Tempfile.new(["mini_magick", extension]).tap do |tempfile|
28
+ tempfile.binmode
29
+ yield tempfile if block_given?
30
+ tempfile.close
59
31
  end
60
32
  end
33
+
61
34
  end
62
35
  end
@@ -1,15 +1,15 @@
1
1
  module MiniMagick
2
2
  ##
3
- # Returns the version of the currently loaded MiniMagick as
4
- # a <tt>Gem::Version</tt>.
3
+ # @return [Gem::Version]
4
+ #
5
5
  def self.version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 3
11
- MINOR = 8
12
- TINY = 1
10
+ MAJOR = 4
11
+ MINOR = 0
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')