mini_magick 4.12.0 → 5.2.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.
@@ -5,12 +5,11 @@ module MiniMagick
5
5
  module Configuration
6
6
 
7
7
  ##
8
- # If you don't have the CLI tools in your PATH, you can set the path to the
9
- # executables.
8
+ # Uses [GraphicsMagick](http://www.graphicsmagick.org/) instead of
9
+ # ImageMagick, by prefixing commands with `gm` instead of `magick`.
10
10
  #
11
- attr_writer :cli_path
12
- # @private (for backwards compatibility)
13
- attr_accessor :processor_path
11
+ # @return [Boolean]
12
+ attr_accessor :graphicsmagick
14
13
 
15
14
  ##
16
15
  # Adds a prefix to the CLI command.
@@ -24,23 +23,35 @@ module MiniMagick
24
23
  attr_accessor :cli_prefix
25
24
 
26
25
  ##
27
- # If you don't want commands to take too long, you can set a timeout (in
28
- # seconds).
26
+ # Adds environment variables to every CLI command call.
27
+ # For example, you could use it to set `LD_PRELOAD="/path/to/libsomething.so"`.
28
+ # Must be a hash of strings keyed to valid environment variable name strings.
29
+ # e.g. {'MY_ENV' => 'my value'}
29
30
  #
30
- # @return [Integer]
31
+ # @return [Hash]
31
32
  #
32
- attr_accessor :timeout
33
+ attr_accessor :cli_env
34
+
33
35
  ##
34
- # When get to `true`, it outputs each command to STDOUT in their shell
35
- # version.
36
+ # If set to true, Open3 will restrict system calls to access only
37
+ # environment variables defined in :cli_env, plus HOME, PATH, and LANG
38
+ # since those are required for such system calls. It will not pass on any
39
+ # other environment variables from the system.
36
40
  #
37
41
  # @return [Boolean]
38
42
  #
39
- attr_reader :debug
43
+ attr_accessor :restricted_env
44
+
40
45
  ##
41
- # Logger for {#debug}, default is `MiniMagick::Logger.new(STDOUT)`, but
42
- # you can override it, for example if you want the logs to be written to
43
- # a file.
46
+ # If you don't want commands to take too long, you can set a timeout (in
47
+ # seconds).
48
+ #
49
+ # @return [Integer]
50
+ #
51
+ attr_accessor :timeout
52
+ ##
53
+ # Logger for commands, default is `Logger.new($stdout)`, but you can
54
+ # override it, for example if you want the logs to be written to a file.
44
55
  #
45
56
  # @return [Logger]
46
57
  #
@@ -53,146 +64,38 @@ module MiniMagick
53
64
  #
54
65
  attr_accessor :tmpdir
55
66
 
56
- ##
57
- # If set to `true`, it will `identify` every newly created image, and raise
58
- # `MiniMagick::Invalid` if the image is not valid. Useful for validating
59
- # user input, although it adds a bit of overhead. Defaults to `true`.
60
- #
61
- # @return [Boolean]
62
- #
63
- attr_accessor :validate_on_create
64
- ##
65
- # If set to `true`, it will `identify` every image that gets written (with
66
- # {MiniMagick::Image#write}), and raise `MiniMagick::Invalid` if the image
67
- # is not valid. Useful for validating that processing was sucessful,
68
- # although it adds a bit of overhead. Defaults to `true`.
69
- #
70
- # @return [Boolean]
71
- #
72
- attr_accessor :validate_on_write
73
-
74
67
  ##
75
68
  # If set to `false`, it will not raise errors when ImageMagick returns
76
69
  # status code different than 0. Defaults to `true`.
77
70
  #
78
71
  # @return [Boolean]
79
72
  #
80
- attr_accessor :whiny
73
+ attr_accessor :errors
81
74
 
82
75
  ##
83
- # Instructs MiniMagick how to execute the shell commands. Available
84
- # APIs are "open3" (default) and "posix-spawn" (requires the "posix-spawn"
85
- # gem).
86
- #
87
- # @return [String]
88
- #
89
- attr_accessor :shell_api
76
+ # If set to `false`, it will not forward warnings from ImageMagick to
77
+ # standard error.
78
+ attr_accessor :warnings
90
79
 
91
80
  def self.extended(base)
92
81
  base.tmpdir = Dir.tmpdir
93
- base.validate_on_create = true
94
- base.validate_on_write = true
95
- base.whiny = true
96
- base.shell_api = "open3"
82
+ base.errors = true
97
83
  base.logger = Logger.new($stdout).tap { |l| l.level = Logger::INFO }
84
+ base.warnings = true
85
+ base.cli_env = {}.freeze
86
+ base.restricted_env = false
87
+ base.graphicsmagick = false
98
88
  end
99
89
 
100
90
  ##
101
91
  # @yield [self]
102
92
  # @example
103
93
  # MiniMagick.configure do |config|
104
- # config.cli = :graphicsmagick
105
94
  # config.timeout = 5
106
95
  # end
107
96
  #
108
97
  def configure
109
98
  yield self
110
99
  end
111
-
112
- CLI_DETECTION = {
113
- imagemagick7: "magick",
114
- imagemagick: "mogrify",
115
- graphicsmagick: "gm",
116
- }
117
-
118
- # @private (for backwards compatibility)
119
- def processor
120
- @processor ||= CLI_DETECTION.values.detect do |processor|
121
- MiniMagick::Utilities.which(processor)
122
- end
123
- end
124
-
125
- # @private (for backwards compatibility)
126
- def processor=(processor)
127
- @processor = processor.to_s
128
-
129
- unless CLI_DETECTION.value?(@processor)
130
- raise ArgumentError,
131
- "processor has to be set to either \"magick\", \"mogrify\" or \"gm\"" \
132
- ", was set to #{@processor.inspect}"
133
- end
134
- end
135
-
136
- ##
137
- # Get [ImageMagick](http://www.imagemagick.org) or
138
- # [GraphicsMagick](http://www.graphicsmagick.org).
139
- #
140
- # @return [Symbol] `:imagemagick` or `:graphicsmagick`
141
- #
142
- def cli
143
- if instance_variable_defined?("@cli")
144
- instance_variable_get("@cli")
145
- else
146
- cli = CLI_DETECTION.key(processor) or
147
- fail MiniMagick::Error, "You must have ImageMagick or GraphicsMagick installed"
148
-
149
- instance_variable_set("@cli", cli)
150
- end
151
- end
152
-
153
- ##
154
- # Set whether you want to use [ImageMagick](http://www.imagemagick.org) or
155
- # [GraphicsMagick](http://www.graphicsmagick.org).
156
- #
157
- def cli=(value)
158
- @cli = value
159
-
160
- if not CLI_DETECTION.key?(@cli)
161
- raise ArgumentError,
162
- "CLI has to be set to either :imagemagick, :imagemagick7 or :graphicsmagick" \
163
- ", was set to #{@cli.inspect}"
164
- end
165
- end
166
-
167
- ##
168
- # If you set the path of CLI tools, you can get the path of the
169
- # executables.
170
- #
171
- # @return [String]
172
- #
173
- def cli_path
174
- if instance_variable_defined?("@cli_path")
175
- instance_variable_get("@cli_path")
176
- else
177
- processor_path = instance_variable_get("@processor_path") if instance_variable_defined?("@processor_path")
178
-
179
- instance_variable_set("@cli_path", processor_path)
180
- end
181
- end
182
-
183
- ##
184
- # When set to `true`, it outputs each command to STDOUT in their shell
185
- # version.
186
- #
187
- def debug=(value)
188
- warn "MiniMagick.debug is deprecated and will be removed in MiniMagick 5. Use `MiniMagick.logger.level = Logger::DEBUG` instead."
189
- logger.level = value ? Logger::DEBUG : Logger::INFO
190
- end
191
-
192
- # Backwards compatibility
193
- def reload_tools
194
- warn "MiniMagick.reload_tools is deprecated because it is no longer necessary"
195
- end
196
-
197
100
  end
198
101
  end
@@ -17,8 +17,6 @@ module MiniMagick
17
17
  cheap_info(value)
18
18
  when "colorspace"
19
19
  colorspace
20
- when "mime_type"
21
- mime_type
22
20
  when "resolution"
23
21
  resolution(*args)
24
22
  when "signature"
@@ -27,8 +25,6 @@ module MiniMagick
27
25
  raw_exif(value)
28
26
  when "exif"
29
27
  exif
30
- when "details"
31
- details
32
28
  when "data"
33
29
  data
34
30
  else
@@ -76,10 +72,6 @@ module MiniMagick
76
72
  @info["colorspace"] ||= self["%r"]
77
73
  end
78
74
 
79
- def mime_type
80
- "image/#{self["format"].downcase}"
81
- end
82
-
83
75
  def resolution(unit = nil)
84
76
  output = identify do |b|
85
77
  b.units unit if unit
@@ -100,20 +92,12 @@ module MiniMagick
100
92
  output.each_line do |line|
101
93
  line = line.chomp("\n")
102
94
 
103
- case MiniMagick.cli
104
- when :imagemagick, :imagemagick7
105
- if match = line.match(/^exif:/)
106
- key, value = match.post_match.split("=", 2)
107
- value = decode_comma_separated_ascii_characters(value) if ASCII_ENCODED_EXIF_KEYS.include?(key)
108
- hash[key] = value
109
- else
110
- hash[hash.keys.last] << "\n#{line}"
111
- end
112
- when :graphicsmagick
113
- next if line == "unknown"
114
- key, value = line.split("=", 2)
115
- value.gsub!("\\012", "\n") # convert "\012" characters to newlines
95
+ if match = line.match(/^exif:/)
96
+ key, value = match.post_match.split("=", 2)
97
+ value = decode_comma_separated_ascii_characters(value) if ASCII_ENCODED_EXIF_KEYS.include?(key)
116
98
  hash[key] = value
99
+ else
100
+ hash[hash.keys.last] << "\n#{line}"
117
101
  end
118
102
  end
119
103
 
@@ -129,44 +113,9 @@ module MiniMagick
129
113
  @info["signature"] ||= self["%#"]
130
114
  end
131
115
 
132
- def details
133
- warn "[MiniMagick] MiniMagick::Image#details has been deprecated, as it was causing too many parsing errors. You should use MiniMagick::Image#data instead, which differs in a way that the keys are in camelcase." if MiniMagick.imagemagick? || MiniMagick.imagemagick7?
134
-
135
- @info["details"] ||= (
136
- details_string = identify(&:verbose)
137
- key_stack = []
138
- details_string.lines.to_a[1..-1].each_with_object({}) do |line, details_hash|
139
- next if !line.valid_encoding? || line.strip.length.zero?
140
-
141
- level = line[/^\s*/].length / 2 - 1
142
- if level >= 0
143
- key_stack.pop until key_stack.size <= level
144
- else
145
- # Some metadata, such as SVG clipping paths, will be saved without
146
- # indentation, resulting in a level of -1
147
- last_key = details_hash.keys.last
148
- details_hash[last_key] = '' if details_hash[last_key].empty?
149
- details_hash[last_key] << line
150
- next
151
- end
152
-
153
- key, _, value = line.partition(/:[\s]/).map(&:strip)
154
- hash = key_stack.inject(details_hash) { |_hash, _key| _hash.fetch(_key) }
155
- if value.empty?
156
- hash[key] = {}
157
- key_stack.push key
158
- else
159
- hash[key] = value
160
- end
161
- end
162
- )
163
- end
164
-
165
116
  def data
166
- raise Error, "MiniMagick::Image#data isn't supported on GraphicsMagick. Use MiniMagick::Image#details instead." if MiniMagick.graphicsmagick?
167
-
168
117
  @info["data"] ||= (
169
- json = MiniMagick::Tool::Convert.new do |convert|
118
+ json = MiniMagick.convert do |convert|
170
119
  convert << path
171
120
  convert << "json:"
172
121
  end
@@ -178,7 +127,7 @@ module MiniMagick
178
127
  end
179
128
 
180
129
  def identify
181
- MiniMagick::Tool::Identify.new do |builder|
130
+ MiniMagick.identify do |builder|
182
131
  yield builder if block_given?
183
132
  builder << path
184
133
  end
@@ -51,11 +51,11 @@ module MiniMagick
51
51
  #
52
52
  def self.import_pixels(blob, columns, rows, depth, map, format = 'png')
53
53
  # Create an image object with the raw pixel data string:
54
- create(".dat", false) { |f| f.write(blob) }.tap do |image|
54
+ read(blob, ".dat").tap do |image|
55
55
  output_path = image.path.sub(/\.\w+$/, ".#{format}")
56
56
  # Use ImageMagick to convert the raw data file to an image file of the
57
57
  # desired format:
58
- MiniMagick::Tool::Convert.new do |convert|
58
+ MiniMagick.convert do |convert|
59
59
  convert.size "#{columns}x#{rows}"
60
60
  convert.depth depth
61
61
  convert << "#{map}:#{image.path}"
@@ -79,33 +79,15 @@ module MiniMagick
79
79
  # @param options [Hash] Specify options for the open method
80
80
  # @return [MiniMagick::Image] The loaded image
81
81
  #
82
- def self.open(path_or_url, ext = nil, options = {})
83
- options, ext = ext, nil if ext.is_a?(Hash)
84
-
85
- # Don't use Kernel#open, but reuse its logic
86
- openable =
87
- if path_or_url.respond_to?(:open)
88
- path_or_url
89
- elsif path_or_url.respond_to?(:to_str) &&
90
- %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ path_or_url &&
91
- (uri = URI.parse(path_or_url)).respond_to?(:open)
92
- uri
93
- else
94
- options = { binmode: true }.merge(options)
95
- Pathname(path_or_url)
96
- end
97
-
98
- if openable.is_a?(URI::Generic)
99
- ext ||= File.extname(openable.path)
100
- else
101
- ext ||= File.extname(openable.to_s)
102
- end
103
- ext.sub!(/:.*/, '') # hack for filenames or URLs that include a colon
104
-
105
- if openable.is_a?(URI::Generic)
106
- openable.open(options) { |file| read(file, ext) }
82
+ def self.open(path_or_url, ext = nil, **options)
83
+ if path_or_url.to_s =~ %r{\A(https?|ftp)://}
84
+ uri = URI(path_or_url)
85
+ ext ||= File.extname(uri.path).sub(/:.*/, '') # handle URL including a colon
86
+ uri.open(options) { |file| read(file, ext) }
107
87
  else
108
- openable.open(**options) { |file| read(file, ext) }
88
+ pathname = Pathname(path_or_url)
89
+ ext ||= File.extname(pathname.to_s)
90
+ pathname.open(binmode: true, **options) { |file| read(file, ext) }
109
91
  end
110
92
  end
111
93
 
@@ -118,18 +100,14 @@ module MiniMagick
118
100
  # we have a good tempfile.
119
101
  #
120
102
  # @param ext [String] Specify the extension you want to read it as
121
- # @param validate [Boolean] If false, skips validation of the created
122
- # image. Defaults to true.
123
103
  # @yield [Tempfile] You can #write bits to this object to create the new
124
104
  # Image
125
105
  # @return [MiniMagick::Image] The created image
126
106
  #
127
- def self.create(ext = nil, validate = MiniMagick.validate_on_create, &block)
107
+ def self.create(ext = nil, &block)
128
108
  tempfile = MiniMagick::Utilities.tempfile(ext.to_s.downcase, &block)
129
109
 
130
- new(tempfile.path, tempfile).tap do |image|
131
- image.validate! if validate
132
- end
110
+ new(tempfile.path, tempfile)
133
111
  end
134
112
 
135
113
  ##
@@ -164,7 +142,7 @@ module MiniMagick
164
142
  # which creates a temporary file for you and protects your original.
165
143
  #
166
144
  # @param input_path [String, Pathname] The location of an image file
167
- # @yield [MiniMagick::Tool::Mogrify] If block is given, {#combine_options}
145
+ # @yield [MiniMagick::Tool] If block is given, {#combine_options}
168
146
  # is called.
169
147
  #
170
148
  def initialize(input_path, tempfile = nil, &block)
@@ -228,10 +206,6 @@ module MiniMagick
228
206
  #
229
207
  attribute :type, "format"
230
208
  ##
231
- # @return [String]
232
- #
233
- attribute :mime_type
234
- ##
235
209
  # @return [Integer]
236
210
  #
237
211
  attribute :width
@@ -274,7 +248,7 @@ module MiniMagick
274
248
  #
275
249
  attribute :resolution
276
250
  ##
277
- # Returns the message digest of this image as a SHA-256, hexidecimal
251
+ # Returns the message digest of this image as a SHA-256, hexadecimal
278
252
  # encoded string. This signature uniquely identifies the image and is
279
253
  # convenient for determining if an image has been modified or whether two
280
254
  # images are identical.
@@ -286,17 +260,10 @@ module MiniMagick
286
260
  #
287
261
  attribute :signature
288
262
  ##
289
- # Returns the information from `identify -verbose` in a Hash format, for
290
- # ImageMagick.
263
+ # Returns the result of converting the image to JSON format.
291
264
  #
292
265
  # @return [Hash]
293
266
  attribute :data
294
- ##
295
- # Returns the information from `identify -verbose` in a Hash format, for
296
- # GraphicsMagick.
297
- #
298
- # @return [Hash]
299
- attribute :details
300
267
 
301
268
  ##
302
269
  # Use this method if you want to access raw Identify's format API.
@@ -367,7 +334,7 @@ module MiniMagick
367
334
  # @return [Array] Matrix of each color of each pixel
368
335
  def get_pixels(map="RGB")
369
336
  raise ArgumentError, "Invalid map value" unless ["RGB", "RGBA"].include?(map)
370
- convert = MiniMagick::Tool::Convert.new
337
+ convert = MiniMagick.convert
371
338
  convert << path
372
339
  convert.depth(8)
373
340
  convert << "#{map}:-"
@@ -397,10 +364,10 @@ module MiniMagick
397
364
  # @example
398
365
  # # It is given in readme.md file
399
366
  ##
400
- def self.get_image_from_pixels(pixels, dimension, map, depth, mime_type)
367
+ def self.get_image_from_pixels(pixels, dimension, map, depth, format)
401
368
  pixels = pixels.flatten
402
369
  blob = pixels.pack('C*')
403
- import_pixels(blob, *dimension, depth, map, mime_type)
370
+ import_pixels(blob, *dimension, depth, map, format)
404
371
  end
405
372
 
406
373
  ##
@@ -426,7 +393,7 @@ module MiniMagick
426
393
  # will convert all pages.
427
394
  # @param read_opts [Hash] Any read options to be passed to ImageMagick
428
395
  # for example: image.format('jpg', page, {density: '300'})
429
- # @yield [MiniMagick::Tool::Convert] It optionally yields the command,
396
+ # @yield [MiniMagick::Tool] It optionally yields the command,
430
397
  # if you want to add something.
431
398
  # @return [self]
432
399
  #
@@ -441,7 +408,7 @@ module MiniMagick
441
408
  input_path = path.dup
442
409
  input_path << "[#{page}]" if page && !layer?
443
410
 
444
- MiniMagick::Tool::Convert.new do |convert|
411
+ MiniMagick.convert do |convert|
445
412
  read_opts.each do |opt, val|
446
413
  convert.send(opt.to_s, val)
447
414
  end
@@ -477,7 +444,7 @@ module MiniMagick
477
444
  # c.background "blue"
478
445
  # end
479
446
  #
480
- # @yield [MiniMagick::Tool::Mogrify]
447
+ # @yield [MiniMagick::Command]
481
448
  # @see http://www.imagemagick.org/script/mogrify.php
482
449
  # @return [self]
483
450
  #
@@ -498,8 +465,11 @@ module MiniMagick
498
465
  end
499
466
  end
500
467
 
501
- def respond_to_missing?(method_name, include_private = false)
502
- MiniMagick::Tool::Mogrify.option_methods.include?(method_name.to_s)
468
+ ##
469
+ # Prevents ruby from calling `#to_ary` on the image when checking if it's a
470
+ # splattable data structure in certain cases.
471
+ def respond_to_missing?(name, include_all)
472
+ false
503
473
  end
504
474
 
505
475
  ##
@@ -514,7 +484,7 @@ module MiniMagick
514
484
  case output_to
515
485
  when String, Pathname
516
486
  if layer?
517
- MiniMagick::Tool::Convert.new do |builder|
487
+ MiniMagick.convert do |builder|
518
488
  builder << path
519
489
  builder << output_to
520
490
  end
@@ -541,7 +511,7 @@ module MiniMagick
541
511
  def composite(other_image, output_extension = type.downcase, mask = nil)
542
512
  output_tempfile = MiniMagick::Utilities.tempfile(".#{output_extension}")
543
513
 
544
- MiniMagick::Tool::Composite.new do |composite|
514
+ MiniMagick.composite do |composite|
545
515
  yield composite if block_given?
546
516
  composite << other_image.path
547
517
  composite << path
@@ -583,27 +553,21 @@ module MiniMagick
583
553
  # b.verbose
584
554
  # end # runs `identify -verbose image.jpg`
585
555
  # @return [String] Output from `identify`
586
- # @yield [MiniMagick::Tool::Identify]
556
+ # @yield [MiniMagick::Tool]
587
557
  #
588
558
  def identify
589
- MiniMagick::Tool::Identify.new do |builder|
559
+ MiniMagick.identify do |builder|
590
560
  yield builder if block_given?
591
561
  builder << path
592
562
  end
593
563
  end
594
564
 
595
- # @private
596
- def run_command(tool_name, *args)
597
- MiniMagick::Tool.const_get(tool_name.capitalize).new do |builder|
598
- args.each do |arg|
599
- builder << arg
600
- end
601
- end
602
- end
603
-
604
565
  def mogrify(page = nil)
605
- MiniMagick::Tool::MogrifyRestricted.new do |builder|
566
+ MiniMagick.mogrify do |builder|
606
567
  yield builder if block_given?
568
+ if builder.args.include?("-format")
569
+ fail MiniMagick::Error, "you must call #format on a MiniMagick::Image directly"
570
+ end
607
571
  builder << (page ? "#{path}[#{page}]" : path)
608
572
  end
609
573
 
@@ -1,4 +1,4 @@
1
- require "timeout"
1
+ require "open3"
2
2
  require "benchmark"
3
3
 
4
4
  module MiniMagick
@@ -10,64 +10,38 @@ module MiniMagick
10
10
  #
11
11
  class Shell
12
12
 
13
- def run(command, options = {})
14
- stdout, stderr, status = execute(command, stdin: options[:stdin])
13
+ def run(command, errors: MiniMagick.errors, warnings: MiniMagick.warnings, **options)
14
+ stdout, stderr, status = execute(command, **options)
15
15
 
16
- if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
17
- fail MiniMagick::Error, "`#{command.join(" ")}` failed with status: #{status} and error:\n#{stderr}"
16
+ if status != 0
17
+ if stderr.include?("time limit exceeded")
18
+ fail MiniMagick::TimeoutError, "`#{command.join(" ")}` has timed out"
19
+ elsif errors
20
+ fail MiniMagick::Error, "`#{command.join(" ")}` failed with status: #{status.inspect} and error:\n#{stderr}"
21
+ end
18
22
  end
19
23
 
20
- $stderr.print(stderr) unless options[:stderr] == false
24
+ $stderr.print(stderr) if warnings
21
25
 
22
26
  [stdout, stderr, status]
23
27
  end
24
28
 
25
- def execute(command, options = {})
26
- stdout, stderr, status =
27
- log(command.join(" ")) do
28
- send("execute_#{MiniMagick.shell_api.tr("-", "_")}", command, options)
29
- end
29
+ def execute(command, stdin: "", timeout: MiniMagick.timeout)
30
+ env = MiniMagick.restricted_env ? ENV.slice("HOME", "PATH", "LANG") : {}
31
+ env.merge!(MiniMagick.cli_env)
32
+ env["MAGICK_TIME_LIMIT"] = timeout.to_s if timeout
33
+
34
+ stdout, stderr, status = log(command.join(" ")) do
35
+ Open3.capture3(env, *command, stdin_data: stdin, unsetenv_others: MiniMagick.restricted_env)
36
+ end
30
37
 
31
- [stdout, stderr, status.exitstatus]
38
+ [stdout, stderr, status&.exitstatus]
32
39
  rescue Errno::ENOENT, IOError
33
40
  ["", "executable not found: \"#{command.first}\"", 127]
34
41
  end
35
42
 
36
43
  private
37
44
 
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
- unless thread.join(MiniMagick.timeout)
54
- Process.kill("TERM", thread.pid) rescue nil
55
- Process.waitpid(thread.pid) rescue nil
56
- raise Timeout::Error, "MiniMagick command timed out: #{command}"
57
- end
58
-
59
- [stdout_reader.value, stderr_reader.value, thread.value]
60
- end
61
- end
62
-
63
- def execute_posix_spawn(command, options = {})
64
- require "posix-spawn"
65
- child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: MiniMagick.timeout)
66
- [child.out, child.err, child.status]
67
- rescue POSIX::Spawn::TimeoutExceeded
68
- raise Timeout::Error, "MiniMagick command timed out: #{command}"
69
- end
70
-
71
45
  def log(command, &block)
72
46
  value = nil
73
47
  duration = Benchmark.realtime { value = block.call }