mini_magick 3.7.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ require "timeout"
2
+ require "benchmark"
3
+
4
+ module MiniMagick
5
+ ##
6
+ # Sends commands to the shell (more precisely, it sends commands directly to
7
+ # the operating system).
8
+ #
9
+ # @private
10
+ #
11
+ class Shell
12
+
13
+ def run(command, options = {})
14
+ stdout, stderr, status = execute(command, stdin: options[:stdin])
15
+
16
+ if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
17
+ fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
18
+ end
19
+
20
+ $stderr.print(stderr) unless options[:stderr] == false
21
+
22
+ [stdout, stderr, status]
23
+ end
24
+
25
+ def execute(command, options = {})
26
+ stdout, stderr, status =
27
+ log(command.join(" ")) do
28
+ send("execute_#{MiniMagick.shell_api.gsub("-", "_")}", command, options)
29
+ end
30
+
31
+ [stdout, stderr, status.exitstatus]
32
+ rescue Errno::ENOENT, IOError
33
+ ["", "executable not found: \"#{command.first}\"", 127]
34
+ end
35
+
36
+ private
37
+
38
+ def execute_open3(command, options = {})
39
+ require "open3"
40
+
41
+ # We would ideally use Open3.capture3, but it wouldn't allow us to
42
+ # terminate the command after timing out.
43
+ Open3.popen3(*command) do |in_w, out_r, err_r, thread|
44
+ [in_w, out_r, err_r].each(&:binmode)
45
+ stdout_reader = Thread.new { out_r.read }
46
+ stderr_reader = Thread.new { err_r.read }
47
+ begin
48
+ in_w.write options[:stdin].to_s
49
+ rescue Errno::EPIPE
50
+ end
51
+ in_w.close
52
+
53
+ begin
54
+ Timeout.timeout(MiniMagick.timeout) { thread.join }
55
+ rescue Timeout::Error
56
+ Process.kill("TERM", thread.pid) rescue nil
57
+ Process.waitpid(thread.pid) rescue nil
58
+ raise Timeout::Error, "MiniMagick command timed out: #{command}"
59
+ end
60
+
61
+ [stdout_reader.value, stderr_reader.value, thread.value]
62
+ end
63
+ end
64
+
65
+ def execute_posix_spawn(command, options = {})
66
+ require "posix-spawn"
67
+ child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: MiniMagick.timeout)
68
+ [child.out, child.err, child.status]
69
+ rescue POSIX::Spawn::TimeoutExceeded
70
+ raise Timeout::Error, "MiniMagick command timed out: #{command}"
71
+ end
72
+
73
+ def log(command, &block)
74
+ value = nil
75
+ duration = Benchmark.realtime { value = block.call }
76
+ MiniMagick.logger.debug "[%.2fs] %s" % [duration, command]
77
+ value
78
+ end
79
+
80
+ end
81
+ 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/command-line-processing.php
5
+ #
6
+ class Magick < MiniMagick::Tool
7
+
8
+ def initialize(*args)
9
+ super("magick", *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,15 @@
1
+ require "mini_magick/tool/mogrify"
2
+
3
+ module MiniMagick
4
+ class Tool
5
+ ##
6
+ # @see http://www.imagemagick.org/script/mogrify.php
7
+ #
8
+ class MogrifyRestricted < Mogrify
9
+ def format(*args)
10
+ fail NoMethodError,
11
+ "you must call #format on a MiniMagick::Image directly"
12
+ end
13
+ end
14
+ end
15
+ 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,307 @@
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
+ CREATION_OPERATORS = %w[xc canvas logo rose gradient radial-gradient plasma
19
+ pattern text pango]
20
+
21
+ ##
22
+ # Aside from classic instantiation, it also accepts a block, and then
23
+ # executes the command in the end.
24
+ #
25
+ # @example
26
+ # version = MiniMagick::Tool::Identify.new { |b| b.version }
27
+ # puts version
28
+ #
29
+ # @return [MiniMagick::Tool, String] If no block is given, returns an
30
+ # instance of the tool, if block is given, returns the output of the
31
+ # command.
32
+ #
33
+ def self.new(*args)
34
+ instance = super(*args)
35
+
36
+ if block_given?
37
+ yield instance
38
+ instance.call
39
+ else
40
+ instance
41
+ end
42
+ end
43
+
44
+ # @private
45
+ attr_reader :name, :args
46
+
47
+ # @param name [String]
48
+ # @param options [Hash]
49
+ # @option options [Boolean] :whiny Whether to raise errors on non-zero
50
+ # exit codes.
51
+ # @example
52
+ # MiniMagick::Tool::Identify.new(whiny: false) do |identify|
53
+ # identify.help # returns exit status 1, which would otherwise throw an error
54
+ # end
55
+ def initialize(name, options = {})
56
+ warn "MiniMagick::Tool.new(false) is deprecated and will be removed in MiniMagick 5, use MiniMagick::Tool.new(whiny: false) instead." if !options.is_a?(Hash)
57
+
58
+ @name = name
59
+ @args = []
60
+ @whiny = options.is_a?(Hash) ? options.fetch(:whiny, MiniMagick.whiny) : options
61
+ end
62
+
63
+ ##
64
+ # Executes the command that has been built up.
65
+ #
66
+ # @example
67
+ # mogrify = MiniMagick::Tool::Mogrify.new
68
+ # mogrify.resize("500x500")
69
+ # mogrify << "path/to/image.jpg"
70
+ # mogrify.call # executes `mogrify -resize 500x500 path/to/image.jpg`
71
+ #
72
+ # @example
73
+ # mogrify = MiniMagick::Tool::Mogrify.new
74
+ # # build the command
75
+ # mogrify.call do |stdout, stderr, status|
76
+ # # ...
77
+ # end
78
+ #
79
+ # @yield [Array] Optionally yields stdout, stderr, and exit status
80
+ #
81
+ # @return [String] Returns the output of the command
82
+ #
83
+ def call(*args)
84
+ options = args[-1].is_a?(Hash) ? args.pop : {}
85
+ warn "Passing whiny to MiniMagick::Tool#call is deprecated and will be removed in MiniMagick 5, use MiniMagick::Tool.new(whiny: false) instead." if args.any?
86
+ whiny = args.fetch(0, @whiny)
87
+
88
+ options[:whiny] = whiny
89
+ options[:stderr] = false if block_given?
90
+
91
+ shell = MiniMagick::Shell.new
92
+ stdout, stderr, status = shell.run(command, options)
93
+ yield stdout, stderr, status if block_given?
94
+
95
+ stdout.chomp("\n")
96
+ end
97
+
98
+ ##
99
+ # The currently built-up command.
100
+ #
101
+ # @return [Array<String>]
102
+ #
103
+ # @example
104
+ # mogrify = MiniMagick::Tool::Mogrify.new
105
+ # mogrify.resize "500x500"
106
+ # mogrify.contrast
107
+ # mogrify.command #=> ["mogrify", "-resize", "500x500", "-contrast"]
108
+ #
109
+ def command
110
+ [*executable, *args]
111
+ end
112
+
113
+ ##
114
+ # The executable used for this tool. Respects
115
+ # {MiniMagick::Configuration#cli}, {MiniMagick::Configuration#cli_path},
116
+ # and {MiniMagick::Configuration#cli_prefix}.
117
+ #
118
+ # @return [Array<String>]
119
+ #
120
+ # @example
121
+ # MiniMagick.configure { |config| config.cli = :graphicsmagick }
122
+ # identify = MiniMagick::Tool::Identify.new
123
+ # identify.executable #=> ["gm", "identify"]
124
+ #
125
+ # @example
126
+ # MiniMagick.configure do |config|
127
+ # config.cli = :graphicsmagick
128
+ # config.cli_prefix = ['firejail', '--force']
129
+ # end
130
+ # identify = MiniMagick::Tool::Identify.new
131
+ # identify.executable #=> ["firejail", "--force", "gm", "identify"]
132
+ #
133
+ def executable
134
+ exe = [name]
135
+ exe.unshift "magick" if MiniMagick.imagemagick7? && name != "magick"
136
+ exe.unshift "gm" if MiniMagick.graphicsmagick?
137
+ exe.unshift File.join(MiniMagick.cli_path, exe.shift) if MiniMagick.cli_path
138
+ Array(MiniMagick.cli_prefix).reverse_each { |p| exe.unshift p } if MiniMagick.cli_prefix
139
+ exe
140
+ end
141
+
142
+ ##
143
+ # Appends raw options, useful for appending image paths.
144
+ #
145
+ # @return [self]
146
+ #
147
+ def <<(arg)
148
+ args << arg.to_s
149
+ self
150
+ end
151
+
152
+ ##
153
+ # Merges a list of raw options.
154
+ #
155
+ # @return [self]
156
+ #
157
+ def merge!(new_args)
158
+ new_args.each { |arg| self << arg }
159
+ self
160
+ end
161
+
162
+ ##
163
+ # Changes the last operator to its "plus" form.
164
+ #
165
+ # @example
166
+ # MiniMagick::Tool::Mogrify.new do |mogrify|
167
+ # mogrify.antialias.+
168
+ # mogrify.distort.+("Perspective", "0,0,4,5 89,0,45,46")
169
+ # end
170
+ # # executes `mogrify +antialias +distort Perspective '0,0,4,5 89,0,45,46'`
171
+ #
172
+ # @return [self]
173
+ #
174
+ def +(*values)
175
+ args[-1] = args[-1].sub(/^-/, '+')
176
+ self.merge!(values)
177
+ self
178
+ end
179
+
180
+ ##
181
+ # Create an ImageMagick stack in the command (surround.
182
+ #
183
+ # @example
184
+ # MiniMagick::Tool::Convert.new do |convert|
185
+ # convert << "wand.gif"
186
+ # convert.stack do |stack|
187
+ # stack << "wand.gif"
188
+ # stack.rotate(30)
189
+ # end
190
+ # convert.append.+
191
+ # convert << "images.gif"
192
+ # end
193
+ # # executes `convert wand.gif \( wizard.gif -rotate 30 \) +append images.gif`
194
+ #
195
+ def stack(*args)
196
+ self << "("
197
+ args.each do |value|
198
+ case value
199
+ when Hash then value.each { |key, value| send(key, *value) }
200
+ when String then self << value
201
+ end
202
+ end
203
+ yield self if block_given?
204
+ self << ")"
205
+ end
206
+
207
+ ##
208
+ # Adds ImageMagick's pseudo-filename `-` for standard input.
209
+ #
210
+ # @example
211
+ # identify = MiniMagick::Tool::Identify.new
212
+ # identify.stdin
213
+ # identify.call(stdin: image_content)
214
+ # # executes `identify -` with the given standard input
215
+ #
216
+ def stdin
217
+ self << "-"
218
+ end
219
+
220
+ ##
221
+ # Adds ImageMagick's pseudo-filename `-` for standard output.
222
+ #
223
+ # @example
224
+ # content = MiniMagick::Tool::Convert.new do |convert|
225
+ # convert << "input.jpg"
226
+ # convert.auto_orient
227
+ # convert.stdout
228
+ # end
229
+ # # executes `convert input.jpg -auto-orient -` which returns file contents
230
+ #
231
+ def stdout
232
+ self << "-"
233
+ end
234
+
235
+ ##
236
+ # Define creator operator methods
237
+ #
238
+ # @example
239
+ # mogrify = MiniMagick::Tool.new("mogrify")
240
+ # mogrify.canvas("khaki")
241
+ # mogrify.command.join(" ") #=> "mogrify canvas:khaki"
242
+ #
243
+ CREATION_OPERATORS.each do |operator|
244
+ define_method(operator.gsub('-', '_')) do |value = nil|
245
+ self << "#{operator}:#{value}"
246
+ self
247
+ end
248
+ end
249
+
250
+ ##
251
+ # This option is a valid ImageMagick option, but it's also a Ruby method,
252
+ # so we need to override it so that it correctly acts as an option method.
253
+ #
254
+ def clone(*args)
255
+ self << '-clone'
256
+ self.merge!(args)
257
+ self
258
+ end
259
+
260
+ ##
261
+ # Any undefined method will be transformed into a CLI option
262
+ #
263
+ # @example
264
+ # mogrify = MiniMagick::Tool.new("mogrify")
265
+ # mogrify.adaptive_blur("...")
266
+ # mogrify.foo_bar
267
+ # mogrify.command.join(" ") # => "mogrify -adaptive-blur ... -foo-bar"
268
+ #
269
+ def method_missing(name, *args)
270
+ option = "-#{name.to_s.tr('_', '-')}"
271
+ self << option
272
+ self.merge!(args)
273
+ self
274
+ end
275
+
276
+ def self.option_methods
277
+ @option_methods ||= (
278
+ tool = new(whiny: false)
279
+ tool << "-help"
280
+ help_page = tool.call(stderr: false)
281
+
282
+ cli_options = help_page.scan(/^\s+-[a-z\-]+/).map(&:strip)
283
+ if tool.name == "mogrify" && MiniMagick.graphicsmagick?
284
+ # These options were undocumented before 2015-06-14 (see gm bug 302)
285
+ cli_options |= %w[-box -convolve -gravity -linewidth -mattecolor -render -shave]
286
+ end
287
+
288
+ cli_options.map { |o| o[1..-1].tr('-','_') }
289
+ )
290
+ end
291
+
292
+ end
293
+ end
294
+
295
+ require "mini_magick/tool/animate"
296
+ require "mini_magick/tool/compare"
297
+ require "mini_magick/tool/composite"
298
+ require "mini_magick/tool/conjure"
299
+ require "mini_magick/tool/convert"
300
+ require "mini_magick/tool/display"
301
+ require "mini_magick/tool/identify"
302
+ require "mini_magick/tool/import"
303
+ require "mini_magick/tool/magick"
304
+ require "mini_magick/tool/mogrify"
305
+ require "mini_magick/tool/mogrify_restricted"
306
+ require "mini_magick/tool/montage"
307
+ require "mini_magick/tool/stream"
@@ -1,31 +1,35 @@
1
- require 'rbconfig'
1
+ require "tempfile"
2
2
 
3
3
  module MiniMagick
4
+ # @private
4
5
  module Utilities
5
- class << self
6
- # Cross-platform way of finding an executable in the $PATH.
7
- #
8
- # which('ruby') #=> /usr/bin/ruby
9
- def which(cmd)
10
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
11
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
12
- exts.each { |ext|
13
- exe = File.join(path, "#{cmd}#{ext}")
14
- return exe if File.executable? exe
15
- }
16
- end
17
- return nil
18
- end
19
6
 
20
- # Finds out if the host OS is windows
21
- def windows?
22
- RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
7
+ module_function
8
+
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.fetch('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
21
+ end
23
22
  end
23
+ nil
24
+ end
24
25
 
25
- def windows_escape(cmdline)
26
- '"' + cmdline.gsub(/\\(?=\\*\")/, "\\\\\\").gsub(/\"/, "\\\"").gsub(/\\$/, "\\\\\\").gsub("%", "%%") + '"'
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
27
31
  end
28
32
  end
33
+
29
34
  end
30
35
  end
31
-
@@ -1,3 +1,17 @@
1
1
  module MiniMagick
2
- VERSION = "3.7.0"
2
+ ##
3
+ # @return [Gem::Version]
4
+ #
5
+ def self.version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 4
11
+ MINOR = 11
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
+ end
3
17
  end