discourse_image_optim 0.26.1 → 0.26.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.markdown +4 -5
- data/README.markdown +36 -60
- data/image_optim.gemspec +1 -1
- data/lib/image_optim.rb +48 -3
- data/lib/image_optim/cmd.rb +19 -72
- data/lib/image_optim/config.rb +5 -5
- data/lib/image_optim/runner/option_parser.rb +2 -2
- data/lib/image_optim/worker.rb +3 -13
- data/lib/image_optim/worker/advpng.rb +0 -2
- data/lib/image_optim/worker/class_methods.rb +2 -15
- data/lib/image_optim/worker/gifsicle.rb +0 -2
- data/lib/image_optim/worker/jhead.rb +0 -2
- data/lib/image_optim/worker/jpegoptim.rb +0 -2
- data/lib/image_optim/worker/jpegrecompress.rb +0 -2
- data/lib/image_optim/worker/jpegtran.rb +0 -2
- data/lib/image_optim/worker/optipng.rb +0 -2
- data/lib/image_optim/worker/pngcrush.rb +0 -2
- data/lib/image_optim/worker/pngout.rb +0 -2
- data/lib/image_optim/worker/pngquant.rb +0 -2
- data/lib/image_optim/worker/svgo.rb +0 -2
- data/spec/image_optim/cmd_spec.rb +8 -30
- data/spec/image_optim/config_spec.rb +16 -16
- data/spec/image_optim/worker_spec.rb +12 -31
- data/spec/image_optim_spec.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79257562b4927a5ec9d03fbbbc649b45dd060a3567897b0b0c10e29fbb759561
|
4
|
+
data.tar.gz: 524acbb993b15449b795f1aaa1c49bd477dbf18f102c2c516f3eafbb1a9dc194
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aa22f9ea0ee4fb7a2aaaca83a577a842105b77fbe9f8399239d051a687b5c181a99f4d8cd4208b1dba8a6eb84d996a0980cd9b3182c11a4963bddf5979ed99c
|
7
|
+
data.tar.gz: 7e74d060fc2d3977441a99dbb971a5961f9d414cf286de48702bec28ae259230540994497b95a8d45ba5e129a554f8393032bbbbb1ba6952d0e64762045bdfde
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -2,13 +2,12 @@
|
|
2
2
|
|
3
3
|
## unreleased
|
4
4
|
|
5
|
+
* Add `timeout` option to ImageOptim [#21](https://github.com/toy/image_optim/issues/21) [@tgxworld](http://github.com/tgxworld)
|
6
|
+
|
5
7
|
## v0.26.1 (2017-12-14)
|
6
8
|
|
7
9
|
* Require `'date'` which is used in parsing pngout version [toy/image_optim_pack#14](https://github.com/toy/image_optim_pack/issues/14) [@toy](https://github.com/toy)
|
8
10
|
|
9
|
-
* Add `timeout` option to workers [#21](https://github.com/toy/image_optim/issues/21)
|
10
|
-
[@tgxworld](http://github.com/tgxworld)
|
11
|
-
|
12
11
|
## v0.26.0 (2017-11-13)
|
13
12
|
|
14
13
|
* Enable `jpegrescan` by default after removing its dependency on `File::Slurp` and fixing for windows [#153](https://github.com/toy/image_optim/issues/153) [@toy](https://github.com/toy)
|
@@ -36,7 +35,7 @@
|
|
36
35
|
|
37
36
|
## v0.24.0 (2016-08-14)
|
38
37
|
|
39
|
-
* Rails image assets optimization is extracted into [
|
38
|
+
* Rails image assets optimization is extracted into [image\_optim\_rails gem](https://github.com/toy/image_optim_rails) [#127](https://github.com/toy/image_optim/issues/127) [@toy](https://github.com/toy)
|
40
39
|
* Add proper handling of `ImageOptim.respond_to?` [@toy](https://github.com/toy)
|
41
40
|
* Fix an issue not working OptiPNG `interlace` option [#136](https://github.com/toy/image_optim/pull/136) [@mrk21](https://github.com/mrk21)
|
42
41
|
* Minimize number of file system calls in default implementation of `optimized?` [#137](https://github.com/toy/image_optim/issues/137) [@toy](https://github.com/toy)
|
@@ -283,7 +282,7 @@
|
|
283
282
|
|
284
283
|
* Warn if directly added files are not images or are not optimizable [@toy](https://github.com/toy)
|
285
284
|
* Recursively scan directories for images [#4](https://github.com/toy/image_optim/issues/4) [@toy](https://github.com/toy)
|
286
|
-
* Typo in bin/image_optim
|
285
|
+
* Typo in bin/image_optim [#3](https://github.com/toy/image_optim/issues/3) [@fabiomcosta](https://github.com/fabiomcosta)
|
287
286
|
|
288
287
|
## v0.5.1 (2012-08-07)
|
289
288
|
|
data/README.markdown
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
Optimize (lossless compress, optionally lossy) images (jpeg, png, gif, svg) using external utilities:
|
12
12
|
|
13
13
|
* [advpng](http://advancemame.sourceforge.net/doc-advpng.html) from [AdvanceCOMP](http://advancemame.sourceforge.net/comp-readme.html)
|
14
|
-
|
14
|
+
(will use [zopfli](https://code.google.com/p/zopfli/) on default/maximum level 4)
|
15
15
|
* [gifsicle](http://www.lcdf.org/gifsicle/)
|
16
16
|
* [jhead](http://www.sentex.net/~mwandel/jhead/)
|
17
17
|
* [jpegoptim](http://www.kokkonen.net/tjko/projects.html)
|
@@ -57,11 +57,9 @@ gem 'image_optim_pack'
|
|
57
57
|
With version:
|
58
58
|
|
59
59
|
<!---<update-version>-->
|
60
|
-
|
61
60
|
```ruby
|
62
61
|
gem 'image_optim', '~> 0.26'
|
63
62
|
```
|
64
|
-
|
65
63
|
<!---</update-version>-->
|
66
64
|
|
67
65
|
If you want to check latest changes:
|
@@ -243,7 +241,7 @@ image_optim.optimize_images_data(datas)
|
|
243
241
|
|
244
242
|
### From rails
|
245
243
|
|
246
|
-
Rails image assets optimization is extracted into [
|
244
|
+
Rails image assets optimization is extracted into [image\_optim\_rails gem](https://github.com/toy/image_optim_rails).
|
247
245
|
|
248
246
|
## Configuration
|
249
247
|
|
@@ -265,92 +263,70 @@ optipng:
|
|
265
263
|
|
266
264
|
## Options
|
267
265
|
|
268
|
-
* `:nice` — Nice level, priority of all used tools with higher value meaning lower priority, in range `-20..19`, negative values can be set only if run by root user
|
269
|
-
* `:threads` — Number of threads or disable
|
270
|
-
* `:verbose` — Verbose output
|
271
|
-
* `:pack` — Require
|
272
|
-
* `:skip_missing_workers` — Skip workers with missing or problematic binaries
|
273
|
-
* `:allow_lossy` — Allow lossy workers and optimizations
|
266
|
+
* `:nice` — Nice level, priority of all used tools with higher value meaning lower priority, in range `-20..19`, negative values can be set only if run by root user *(defaults to `10`)*
|
267
|
+
* `:threads` — Number of threads or disable *(defaults to number of processors)*
|
268
|
+
* `:verbose` — Verbose output *(defaults to `false`)*
|
269
|
+
* `:pack` — Require image\_optim\_pack or disable it, by default image\_optim\_pack will be used if available, will turn on `:skip-missing-workers` unless explicitly disabled *(defaults to `nil`)*
|
270
|
+
* `:skip_missing_workers` — Skip workers with missing or problematic binaries *(defaults to `false`)*
|
271
|
+
* `:allow_lossy` — Allow lossy workers and optimizations *(defaults to `false`)*
|
274
272
|
* `:cache_dir` — Configure cache directory
|
275
273
|
* `:cache_worker_digests` - Also cache worker digests along with original file digest and worker options: updating workers invalidates cache
|
276
|
-
* `:timeout`
|
274
|
+
* `:timeout` - Number of seconds before ImageOptim is timed out.
|
277
275
|
|
278
276
|
Worker can be disabled by passing `false` instead of options hash or by setting option `:disable` to `true`.
|
279
277
|
|
280
278
|
<!---<worker-options>-->
|
281
|
-
|
282
279
|
<!-- markdown for worker options is generated by `script/update_worker_options_in_readme` -->
|
283
280
|
|
284
281
|
### advpng:
|
285
|
-
|
286
|
-
* `:level` — Compression level: `0` - don't compress, `1` - fast, `2` - normal, `3` - extra, `4` - extreme _(defaults to `4`)_
|
287
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
282
|
+
* `:level` — Compression level: `0` - don't compress, `1` - fast, `2` - normal, `3` - extra, `4` - extreme *(defaults to `4`)*
|
288
283
|
|
289
284
|
### gifsicle:
|
290
|
-
|
291
285
|
* `:interlace` — Interlace: `true` - interlace on, `false` - interlace off, `nil` - as is in original image (defaults to running two instances, one with interlace off and one with on)
|
292
|
-
* `:level` — Compression level: `1` - light and fast, `2` - normal, `3` - heavy (slower)
|
293
|
-
* `:careful` — Avoid bugs with some software
|
294
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
286
|
+
* `:level` — Compression level: `1` - light and fast, `2` - normal, `3` - heavy (slower) *(defaults to `3`)*
|
287
|
+
* `:careful` — Avoid bugs with some software *(defaults to `false`)*
|
295
288
|
|
296
289
|
### jhead:
|
297
|
-
|
298
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
290
|
+
Worker has no options
|
299
291
|
|
300
292
|
### jpegoptim:
|
301
|
-
|
302
|
-
* `:
|
303
|
-
* `:
|
304
|
-
* `:max_quality` — Maximum image quality factor `0`..`100`, ignored in default/lossless mode _(defaults to `100`)_
|
305
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
293
|
+
* `:allow_lossy` — Allow limiting maximum quality *(defaults to `false`)*
|
294
|
+
* `:strip` — List of extra markers to strip: `:comments`, `:exif`, `:iptc`, `:icc` or `:all` *(defaults to `:all`)*
|
295
|
+
* `:max_quality` — Maximum image quality factor `0`..`100`, ignored in default/lossless mode *(defaults to `100`)*
|
306
296
|
|
307
297
|
### jpegrecompress:
|
308
|
-
|
309
|
-
* `:
|
310
|
-
* `:quality` — JPEG quality preset: `0` - low, `1` - medium, `2` - high, `3` - veryhigh _(defaults to `3`)_
|
311
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
298
|
+
* `:allow_lossy` — Allow worker, it is always lossy *(defaults to `false`)*
|
299
|
+
* `:quality` — JPEG quality preset: `0` - low, `1` - medium, `2` - high, `3` - veryhigh *(defaults to `3`)*
|
312
300
|
|
313
301
|
### jpegtran:
|
314
|
-
|
315
|
-
* `:
|
316
|
-
* `:
|
317
|
-
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option _(defaults to `false`)_
|
318
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
302
|
+
* `:copy_chunks` — Copy all chunks *(defaults to `false`)*
|
303
|
+
* `:progressive` — Create progressive JPEG file *(defaults to `true`)*
|
304
|
+
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to `true`)*
|
319
305
|
|
320
306
|
### optipng:
|
321
|
-
|
322
|
-
* `:
|
323
|
-
* `:
|
324
|
-
* `:strip` — Remove all auxiliary chunks _(defaults to `true`)_
|
325
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
307
|
+
* `:level` — Optimization level preset: `0` is least, `7` is best *(defaults to `6`)*
|
308
|
+
* `:interlace` — Interlace: `true` - interlace on, `false` - interlace off, `nil` - as is in original image *(defaults to `false`)*
|
309
|
+
* `:strip` — Remove all auxiliary chunks *(defaults to `true`)*
|
326
310
|
|
327
311
|
### pngcrush:
|
328
|
-
|
329
|
-
* `:
|
330
|
-
* `:
|
331
|
-
* `:
|
332
|
-
* `:blacken` — Blacken fully transparent pixels _(defaults to `true`)_
|
333
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
312
|
+
* `:chunks` — List of chunks to remove or `:alla` - all except tRNS/transparency or `:allb` - all except tRNS and gAMA/gamma *(defaults to `:alla`)*
|
313
|
+
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to `false`)*
|
314
|
+
* `:brute` — Brute force try all methods, very time-consuming and generally not worthwhile *(defaults to `false`)*
|
315
|
+
* `:blacken` — Blacken fully transparent pixels *(defaults to `true`)*
|
334
316
|
|
335
317
|
### pngout:
|
336
|
-
|
337
|
-
* `:
|
338
|
-
* `:strategy` — Strategy: `0` - xtreme, `1` - intense, `2` - longest Match, `3` - huffman Only, `4` - uncompressed _(defaults to `0`)_
|
339
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
318
|
+
* `:copy_chunks` — Copy optional chunks *(defaults to `false`)*
|
319
|
+
* `:strategy` — Strategy: `0` - xtreme, `1` - intense, `2` - longest Match, `3` - huffman Only, `4` - uncompressed *(defaults to `0`)*
|
340
320
|
|
341
321
|
### pngquant:
|
342
|
-
|
343
|
-
* `:
|
344
|
-
* `:
|
345
|
-
* `:
|
346
|
-
* `:speed` — speed/quality trade-off: `1` - slow, `3` - default, `11` - fast & rough _(defaults to `3`)_
|
347
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
322
|
+
* `:allow_lossy` — Allow quality option *(defaults to `false`)*
|
323
|
+
* `:max_colors` — Maximum number of colors to use *(defaults to `256`)*
|
324
|
+
* `:quality` — min..max - don't save below min, use less colors below max (both in range `0..100`; in yaml - `!ruby/range 0..100`), ignored in default/lossless mode *(defaults to `100..100`, `0..100` in lossy mode)*
|
325
|
+
* `:speed` — speed/quality trade-off: `1` - slow, `3` - default, `11` - fast & rough *(defaults to `3`)*
|
348
326
|
|
349
327
|
### svgo:
|
350
|
-
|
351
|
-
* `:
|
352
|
-
* `:enable_plugins` — List of plugins to enable _(defaults to `[]`)_
|
353
|
-
* `:timeout` — Number of seconds before worker is timed out. Must be greater than 0 to enable timeout. _(defaults to `0`)_
|
328
|
+
* `:disable_plugins` — List of plugins to disable *(defaults to `[]`)*
|
329
|
+
* `:enable_plugins` — List of plugins to enable *(defaults to `[]`)*
|
354
330
|
|
355
331
|
<!---</worker-options>-->
|
356
332
|
|
data/image_optim.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'discourse_image_optim'
|
5
|
-
s.version = '0.26.
|
5
|
+
s.version = '0.26.2'
|
6
6
|
s.summary = %q{Optimize (lossless compress, optionally lossy) images (jpeg, png, gif, svg) using external utilities (advpng, gifsicle, jhead, jpeg-recompress, jpegoptim, jpegrescan, jpegtran, optipng, pngcrush, pngout, pngquant, svgo)}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
data/lib/image_optim.rb
CHANGED
@@ -20,6 +20,8 @@ end
|
|
20
20
|
|
21
21
|
# Main interface
|
22
22
|
class ImageOptim
|
23
|
+
class TimeoutExceeded < StandardError; end
|
24
|
+
|
23
25
|
# Nice level
|
24
26
|
attr_reader :nice
|
25
27
|
|
@@ -111,10 +113,22 @@ class ImageOptim
|
|
111
113
|
|
112
114
|
optimized = @cache.fetch(original) do
|
113
115
|
Handler.for(original) do |handler|
|
114
|
-
|
115
|
-
|
116
|
-
|
116
|
+
current_worker = nil
|
117
|
+
|
118
|
+
begin
|
119
|
+
with_timeout(@timeout) do
|
120
|
+
workers.each do |worker|
|
121
|
+
current_worker = worker
|
122
|
+
handler.process{ |src, dst| worker.optimize(src, dst) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
rescue TimeoutExceeded => e
|
126
|
+
if current_worker && current_worker.pid
|
127
|
+
pid = current_worker.pid
|
128
|
+
cleanup_process(pid)
|
117
129
|
end
|
130
|
+
|
131
|
+
raise e
|
118
132
|
end
|
119
133
|
end
|
120
134
|
end
|
@@ -228,6 +242,37 @@ class ImageOptim
|
|
228
242
|
|
229
243
|
private
|
230
244
|
|
245
|
+
def with_timeout(timeout)
|
246
|
+
if timeout && timeout >= 0
|
247
|
+
thread = Thread.new{ yield if block_given? }
|
248
|
+
if thread.respond_to?(:report_on_exception)
|
249
|
+
thread.report_on_exception = false
|
250
|
+
end
|
251
|
+
fail TimeoutExceeded if thread.join(timeout).nil?
|
252
|
+
elsif block_given?
|
253
|
+
yield
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def cleanup_process(pid)
|
258
|
+
Process.detach(pid)
|
259
|
+
Process.kill('-TERM', pid)
|
260
|
+
now = Time.now
|
261
|
+
|
262
|
+
while Time.now - now < 10
|
263
|
+
begin
|
264
|
+
Process.getpgid(pid)
|
265
|
+
sleep 0.1
|
266
|
+
rescue Errno::ESRCH
|
267
|
+
break
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
Process.kill('-KILL', pid) if Process.getpgid(pid)
|
272
|
+
rescue Errno::ESRCH
|
273
|
+
pid
|
274
|
+
end
|
275
|
+
|
231
276
|
def log_workers_by_format
|
232
277
|
$stderr << "Workers by format:\n"
|
233
278
|
@workers_by_format.each do |format, workers|
|
data/lib/image_optim/cmd.rb
CHANGED
@@ -1,60 +1,39 @@
|
|
1
1
|
require 'English'
|
2
|
-
require 'open3'
|
3
2
|
|
4
3
|
class ImageOptim
|
5
4
|
# Helper for running commands
|
6
5
|
module Cmd
|
7
|
-
class TimeoutExceeded < StandardError; end
|
8
|
-
|
9
6
|
class << self
|
10
7
|
# Run using `system`
|
11
8
|
# Return success status
|
12
9
|
# Will raise SignalException if process was interrupted
|
13
10
|
def run(*args)
|
14
|
-
|
11
|
+
if Process.respond_to?(:spawn)
|
12
|
+
if args.last.is_a?(Hash)
|
13
|
+
args.last[Gem.win_platform? ? :new_pgroup : :pgroup] = true
|
14
|
+
end
|
15
15
|
|
16
|
-
|
16
|
+
begin
|
17
|
+
pid = Process.spawn(*args)
|
18
|
+
ensure
|
19
|
+
yield pid if block_given?
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
begin
|
23
|
+
Process.waitpid(pid)
|
24
|
+
rescue Errno::ECHILD
|
25
|
+
return
|
26
|
+
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
JRUBY_VERSION >= '9.0.0.0'
|
28
|
+
check_status!
|
29
|
+
$CHILD_STATUS.success?
|
24
30
|
else
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Run the specified command, and kill it off if it runs longer
|
30
|
-
# than `timeout` seconds.
|
31
|
-
#
|
32
|
-
# Return success status
|
33
|
-
# Will raise an error when command timeouts
|
34
|
-
def run_with_timeout(timeout, *args)
|
35
|
-
return run(*args) unless timeout > 0 && support_timeout?
|
31
|
+
success = system(*args)
|
36
32
|
|
37
|
-
|
38
|
-
init_options!(args)
|
33
|
+
check_status!
|
39
34
|
|
40
|
-
|
41
|
-
stdin, stdout, thread = Open3.popen2(*args)
|
42
|
-
stdin.close
|
43
|
-
stdout.close
|
44
|
-
|
45
|
-
pid = thread[:pid]
|
46
|
-
|
47
|
-
if thread.join(timeout).nil?
|
48
|
-
cleanup_process(pid)
|
49
|
-
thread.kill
|
50
|
-
fail TimeoutExceeded
|
51
|
-
else
|
52
|
-
status = thread.value.exitstatus
|
53
|
-
success = status.zero? if status
|
54
|
-
end
|
35
|
+
success
|
55
36
|
end
|
56
|
-
|
57
|
-
success
|
58
37
|
end
|
59
38
|
|
60
39
|
# Run using backtick
|
@@ -86,38 +65,6 @@ class ImageOptim
|
|
86
65
|
|
87
66
|
fail SignalException, status.termsig
|
88
67
|
end
|
89
|
-
|
90
|
-
def init_options!(args)
|
91
|
-
pgroup_opt = Gem.win_platform? ? :new_pgroup : :pgroup
|
92
|
-
|
93
|
-
if args.last.is_a?(Hash)
|
94
|
-
args.last[pgroup_opt] = true
|
95
|
-
else
|
96
|
-
args.push(pgroup_opt => true)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def cleanup_process(pid)
|
101
|
-
Thread.new do
|
102
|
-
Process.kill('-TERM', pid)
|
103
|
-
Process.detach(pid)
|
104
|
-
now = Time.now
|
105
|
-
|
106
|
-
while Time.now - now < 10
|
107
|
-
begin
|
108
|
-
Process.kill(0, pid)
|
109
|
-
sleep 0.001
|
110
|
-
next
|
111
|
-
rescue Errno::ESRCH
|
112
|
-
break
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
if Process.getpgid(pid)
|
117
|
-
Process.kill('-KILL', pid)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
68
|
end
|
122
69
|
end
|
123
70
|
end
|
data/lib/image_optim/config.rb
CHANGED
@@ -114,6 +114,11 @@ class ImageOptim
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
def timeout
|
118
|
+
timeout = get!(:timeout)
|
119
|
+
timeout ? timeout.to_f : nil
|
120
|
+
end
|
121
|
+
|
117
122
|
# Verbose mode, converted to boolean
|
118
123
|
def verbose
|
119
124
|
!!get!(:verbose)
|
@@ -157,11 +162,6 @@ class ImageOptim
|
|
157
162
|
!!get!(:cache_worker_digests)
|
158
163
|
end
|
159
164
|
|
160
|
-
def timeout
|
161
|
-
timeout = get!(:timeout)
|
162
|
-
timeout ? timeout.to_i : 0
|
163
|
-
end
|
164
|
-
|
165
165
|
# Options for worker class by its `bin_sym`:
|
166
166
|
# * `Hash` passed as is
|
167
167
|
# * `{}` for `true` or `nil`
|
@@ -181,8 +181,8 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
|
|
181
181
|
options[:allow_lossy] = allow_lossy
|
182
182
|
end
|
183
183
|
|
184
|
-
op.on('--timeout N', 'Sets a timeout for
|
185
|
-
options[:timeout] = timeout
|
184
|
+
op.on('--timeout N', Integer, 'Sets a timeout for optimization') do |timeout|
|
185
|
+
options[:timeout] = timeout
|
186
186
|
end
|
187
187
|
|
188
188
|
op.separator nil
|
data/lib/image_optim/worker.rb
CHANGED
@@ -12,14 +12,14 @@ class ImageOptim
|
|
12
12
|
class Worker
|
13
13
|
extend ClassMethods
|
14
14
|
|
15
|
-
class TimeoutExceeded < StandardError; end
|
16
|
-
|
17
15
|
class << self
|
18
16
|
# Default init for worker is new
|
19
17
|
# Check example of override in gifsicle worker
|
20
18
|
alias_method :init, :new
|
21
19
|
end
|
22
20
|
|
21
|
+
attr_accessor :pid
|
22
|
+
|
23
23
|
# Configure (raises on extra options)
|
24
24
|
def initialize(image_optim, options = {})
|
25
25
|
unless image_optim.is_a?(ImageOptim)
|
@@ -155,17 +155,7 @@ class ImageOptim
|
|
155
155
|
].flatten
|
156
156
|
end
|
157
157
|
|
158
|
-
|
159
|
-
|
160
|
-
if seconds_to_timeout > 0
|
161
|
-
begin
|
162
|
-
Cmd.run_with_timeout(seconds_to_timeout, *args)
|
163
|
-
rescue Cmd::TimeoutExceeded
|
164
|
-
raise ImageOptim::Worker::TimeoutExceeded
|
165
|
-
end
|
166
|
-
else
|
167
|
-
Cmd.run(*args)
|
168
|
-
end
|
158
|
+
Cmd.run(*args){ |pid| self.pid = pid }
|
169
159
|
end
|
170
160
|
end
|
171
161
|
end
|
@@ -32,15 +32,6 @@ class ImageOptim
|
|
32
32
|
@option_definitions ||= []
|
33
33
|
end
|
34
34
|
|
35
|
-
def timeout_option
|
36
|
-
option(
|
37
|
-
:timeout,
|
38
|
-
0,
|
39
|
-
'Number of seconds before worker is timed out. Must be greater than' \
|
40
|
-
'0 to enable timeout.'
|
41
|
-
){ |v| v }
|
42
|
-
end
|
43
|
-
|
44
35
|
def option(name, default, type, description = nil, &proc)
|
45
36
|
attr_reader name
|
46
37
|
OptionDefinition.new(name, default, type, description, &proc).
|
@@ -92,13 +83,9 @@ class ImageOptim
|
|
92
83
|
klasses.map do |klass|
|
93
84
|
options = options_proc[klass]
|
94
85
|
next if options[:disable]
|
95
|
-
|
96
|
-
|
97
|
-
if !options.key?(option_key) && klass.method_defined?(option_key)
|
98
|
-
options[option_key] = image_optim.send(option_key)
|
99
|
-
end
|
86
|
+
if !options.key?(:allow_lossy) && klass.method_defined?(:allow_lossy)
|
87
|
+
options[:allow_lossy] = image_optim.allow_lossy
|
100
88
|
end
|
101
|
-
|
102
89
|
klass.init(image_optim, options)
|
103
90
|
end.compact.flatten
|
104
91
|
end
|
@@ -15,13 +15,6 @@ describe ImageOptim::Cmd do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
describe '.run' do
|
18
|
-
it 'calls system and returns result' do
|
19
|
-
status = double
|
20
|
-
expect(Cmd).to receive(:system).with('cmd', 'arg').and_return(status)
|
21
|
-
allow(Cmd).to receive(:check_status!)
|
22
|
-
expect(Cmd.run('cmd', 'arg')).to eq(status)
|
23
|
-
end
|
24
|
-
|
25
18
|
it 'returns process success status' do
|
26
19
|
expect(Cmd.run('sh -c "exit 0"')).to eq(true)
|
27
20
|
expect($CHILD_STATUS.exitstatus).to eq(0)
|
@@ -33,6 +26,14 @@ describe ImageOptim::Cmd do
|
|
33
26
|
expect($CHILD_STATUS.exitstatus).to eq(66)
|
34
27
|
end
|
35
28
|
|
29
|
+
it 'accepts a block that yields the pid' do
|
30
|
+
expect(
|
31
|
+
Cmd.run('sh -c "exit 66"') do |pid|
|
32
|
+
expect(pid.is_a?(Integer)).to eq(true)
|
33
|
+
end
|
34
|
+
).to eq(false)
|
35
|
+
end
|
36
|
+
|
36
37
|
it 'raises SignalException if process terminates after signal' do
|
37
38
|
skip 'signals are not supported' unless signals_supported?
|
38
39
|
expect_int_exception do
|
@@ -41,29 +42,6 @@ describe ImageOptim::Cmd do
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
|
-
describe '.run_with_timeout', :if => ImageOptim::Cmd.support_timeout? do
|
45
|
-
it 'spawns a process in a thread and returns status' do
|
46
|
-
expect(Cmd.run_with_timeout(20, 'sh -c "exit 0"')).to eq(true)
|
47
|
-
|
48
|
-
[1, 66].each do |status|
|
49
|
-
expect(Cmd.run_with_timeout(20, "sh -c \"exit #{status}\"")).
|
50
|
-
to eq(false)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'raises Timeout::Error if process timeout' do
|
55
|
-
expect{ Cmd.run_with_timeout(0.001, 'sleep 1') }.
|
56
|
-
to raise_error(Cmd::TimeoutExceeded)
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'calls system if timeout is <= zero' do
|
60
|
-
expect(Cmd.run_with_timeout(0, 'sh -c "sleep 0.001; exit 0"')).to eq(true)
|
61
|
-
|
62
|
-
expect(Cmd.run_with_timeout(-1, 'sh -c "sleep 0.001; exit 1"')).
|
63
|
-
to eq(false)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
45
|
describe '.capture' do
|
68
46
|
it 'calls ` and returns result' do
|
69
47
|
output = double
|
@@ -94,22 +94,6 @@ describe ImageOptim::Config do
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
describe '#timeout' do
|
98
|
-
before do
|
99
|
-
allow(IOConfig).to receive(:read_options).and_return({})
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'is 0 by default' do
|
103
|
-
config = IOConfig.new({})
|
104
|
-
expect(config.timeout).to eq(0)
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'converts value to number' do
|
108
|
-
config = IOConfig.new(:timeout => '15')
|
109
|
-
expect(config.timeout).to eq(15)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
97
|
describe '#for_worker' do
|
114
98
|
before do
|
115
99
|
allow(IOConfig).to receive(:read_options).and_return({})
|
@@ -251,4 +235,20 @@ describe ImageOptim::Config do
|
|
251
235
|
expect(IOConfig.read_options(path)).to eq({})
|
252
236
|
end
|
253
237
|
end
|
238
|
+
|
239
|
+
describe '#timeout' do
|
240
|
+
before do
|
241
|
+
allow(IOConfig).to receive(:read_options).and_return({})
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'is nil by default' do
|
245
|
+
config = IOConfig.new({})
|
246
|
+
expect(config.timeout).to eq(nil)
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'converts value to a float' do
|
250
|
+
config = IOConfig.new(:timeout => '15.1')
|
251
|
+
expect(config.timeout).to eq(15.1)
|
252
|
+
end
|
253
|
+
end
|
254
254
|
end
|
@@ -129,7 +129,7 @@ describe ImageOptim::Worker do
|
|
129
129
|
workers.map{ |worker| class_double(Worker, :init => worker) }
|
130
130
|
end
|
131
131
|
|
132
|
-
let(:image_optim){ double(:allow_lossy => false
|
132
|
+
let(:image_optim){ double(:allow_lossy => false) }
|
133
133
|
|
134
134
|
it 'creates all workers for which options_proc returns true' do
|
135
135
|
workers = Array.new(3){ worker_double }
|
@@ -223,53 +223,34 @@ describe ImageOptim::Worker do
|
|
223
223
|
end
|
224
224
|
end
|
225
225
|
|
226
|
-
describe '
|
227
|
-
|
228
|
-
it "passes #{option} if worker has such attribute" do
|
229
|
-
klasses = worker_class_doubles([worker_double, worker_double])
|
230
|
-
|
231
|
-
allow(Worker).to receive(:klasses).and_return(klasses)
|
232
|
-
|
233
|
-
klasses[0].send(:attr_reader, option)
|
234
|
-
expect(klasses[0]).to receive(:init).
|
235
|
-
with(image_optim, hash_including(option))
|
236
|
-
expect(klasses[1]).to receive(:init).
|
237
|
-
with(image_optim, hash_not_including(option))
|
238
|
-
|
239
|
-
Worker.create_all(image_optim){ {} }
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
it 'allows overriding allow_lossy per worker' do
|
226
|
+
describe 'passing allow_lossy' do
|
227
|
+
it 'passes allow_lossy if worker has such attribute' do
|
244
228
|
klasses = worker_class_doubles([worker_double, worker_double])
|
245
|
-
options_proc = proc do |klass|
|
246
|
-
klass == klasses[1] ? {:allow_lossy => :b} : {}
|
247
|
-
end
|
248
229
|
|
249
230
|
allow(Worker).to receive(:klasses).and_return(klasses)
|
250
231
|
|
251
|
-
klasses.
|
232
|
+
klasses[0].send(:attr_reader, :allow_lossy)
|
252
233
|
expect(klasses[0]).to receive(:init).
|
253
|
-
with(image_optim, hash_including(:allow_lossy
|
234
|
+
with(image_optim, hash_including(:allow_lossy))
|
254
235
|
expect(klasses[1]).to receive(:init).
|
255
|
-
with(image_optim,
|
236
|
+
with(image_optim, hash_not_including(:allow_lossy))
|
256
237
|
|
257
|
-
Worker.create_all(image_optim
|
238
|
+
Worker.create_all(image_optim){ {} }
|
258
239
|
end
|
259
240
|
|
260
|
-
it 'allows overriding
|
241
|
+
it 'allows overriding per worker' do
|
261
242
|
klasses = worker_class_doubles([worker_double, worker_double])
|
262
243
|
options_proc = proc do |klass|
|
263
|
-
klass == klasses[1] ? {:
|
244
|
+
klass == klasses[1] ? {:allow_lossy => :b} : {}
|
264
245
|
end
|
265
246
|
|
266
247
|
allow(Worker).to receive(:klasses).and_return(klasses)
|
267
248
|
|
268
|
-
klasses.each{ |klass| klass.send(:attr_reader, :
|
249
|
+
klasses.each{ |klass| klass.send(:attr_reader, :allow_lossy) }
|
269
250
|
expect(klasses[0]).to receive(:init).
|
270
|
-
with(image_optim, hash_including(:
|
251
|
+
with(image_optim, hash_including(:allow_lossy => false))
|
271
252
|
expect(klasses[1]).to receive(:init).
|
272
|
-
with(image_optim, hash_including(:
|
253
|
+
with(image_optim, hash_including(:allow_lossy => :b))
|
273
254
|
|
274
255
|
Worker.create_all(image_optim, &options_proc)
|
275
256
|
end
|
data/spec/image_optim_spec.rb
CHANGED
@@ -102,6 +102,21 @@ describe ImageOptim do
|
|
102
102
|
expect(ImageOptim.optimize_image(path)).to be_nil
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
it 'allows timeout to be configured' do
|
107
|
+
base_options = {:timeout => 0.001}
|
108
|
+
image_optim = ImageOptim.new(base_options)
|
109
|
+
|
110
|
+
original_threads = Thread.list
|
111
|
+
|
112
|
+
expect do
|
113
|
+
image_optim.optimize_image(test_images.first)
|
114
|
+
end.to raise_error(ImageOptim::TimeoutExceeded)
|
115
|
+
|
116
|
+
# Ensure we don't leak any threads
|
117
|
+
(Thread.list - original_threads).each(&:join)
|
118
|
+
expect(Thread.list.count).to eq(original_threads.count)
|
119
|
+
end
|
105
120
|
end
|
106
121
|
|
107
122
|
describe '#optimize_image!' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discourse_image_optim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.26.
|
4
|
+
version: 0.26.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Kuchin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-07-
|
11
|
+
date: 2018-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fspath
|