image_optim 0.27.1 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.appveyor.yml +2 -0
  3. data/.github/workflows/check.yml +59 -0
  4. data/.pre-commit-hooks.yaml +9 -0
  5. data/.rubocop.yml +6 -3
  6. data/CHANGELOG.markdown +22 -0
  7. data/CONTRIBUTING.markdown +5 -2
  8. data/Gemfile +1 -7
  9. data/LICENSE.txt +1 -1
  10. data/README.markdown +21 -10
  11. data/Vagrantfile +1 -1
  12. data/image_optim.gemspec +7 -4
  13. data/lib/image_optim/bin_resolver/bin.rb +10 -9
  14. data/lib/image_optim/cache.rb +6 -0
  15. data/lib/image_optim/cmd.rb +45 -6
  16. data/lib/image_optim/config.rb +14 -8
  17. data/lib/image_optim/elapsed_time.rb +26 -0
  18. data/lib/image_optim/errors.rb +9 -0
  19. data/lib/image_optim/path.rb +1 -1
  20. data/lib/image_optim/runner/option_parser.rb +24 -18
  21. data/lib/image_optim/runner.rb +1 -1
  22. data/lib/image_optim/timer.rb +25 -0
  23. data/lib/image_optim/worker/advpng.rb +7 -7
  24. data/lib/image_optim/worker/gifsicle.rb +11 -11
  25. data/lib/image_optim/worker/jhead.rb +2 -2
  26. data/lib/image_optim/worker/jpegoptim.rb +13 -11
  27. data/lib/image_optim/worker/jpegrecompress.rb +17 -2
  28. data/lib/image_optim/worker/jpegtran.rb +4 -4
  29. data/lib/image_optim/worker/optipng.rb +7 -7
  30. data/lib/image_optim/worker/oxipng.rb +53 -0
  31. data/lib/image_optim/worker/pngcrush.rb +6 -6
  32. data/lib/image_optim/worker/pngout.rb +7 -7
  33. data/lib/image_optim/worker/pngquant.rb +10 -9
  34. data/lib/image_optim/worker/svgo.rb +2 -2
  35. data/lib/image_optim/worker.rb +32 -29
  36. data/lib/image_optim.rb +16 -10
  37. data/script/update_worker_options_in_readme +2 -2
  38. data/script/worker_analysis +16 -18
  39. data/spec/image_optim/bin_resolver_spec.rb +5 -5
  40. data/spec/image_optim/cache_path_spec.rb +7 -10
  41. data/spec/image_optim/cache_spec.rb +8 -8
  42. data/spec/image_optim/cmd_spec.rb +64 -6
  43. data/spec/image_optim/config_spec.rb +36 -20
  44. data/spec/image_optim/elapsed_time_spec.rb +14 -0
  45. data/spec/image_optim/handler_spec.rb +1 -1
  46. data/spec/image_optim/hash_helpers_spec.rb +18 -18
  47. data/spec/image_optim/option_definition_spec.rb +6 -6
  48. data/spec/image_optim/path_spec.rb +8 -11
  49. data/spec/image_optim/runner/option_parser_spec.rb +4 -4
  50. data/spec/image_optim/timer_spec.rb +32 -0
  51. data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
  52. data/spec/image_optim/worker/optipng_spec.rb +11 -11
  53. data/spec/image_optim/worker/oxipng_spec.rb +89 -0
  54. data/spec/image_optim/worker/pngquant_spec.rb +5 -5
  55. data/spec/image_optim/worker_spec.rb +17 -17
  56. data/spec/image_optim_spec.rb +47 -10
  57. data/spec/images/invisiblepixels/generate +1 -1
  58. data/spec/spec_helper.rb +18 -17
  59. metadata +36 -15
  60. data/.travis.yml +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8d7c83b1d36f1cb0c41866cf421a8477632c43137749f2011b2520854de859d
4
- data.tar.gz: 3030f2377d510cc964025e3497dfb2bc860bd560fa88e33acc97e3caaa56614c
3
+ metadata.gz: e80e82d67e3434476f0bdf332b14b6369601010aaeef7ab7b901515d0c03565c
4
+ data.tar.gz: 16f304385c879cf1afc0073d7d037fb548d6c260f57e2b94cdcdd974f3cd4b4e
5
5
  SHA512:
6
- metadata.gz: 3df3a7cd3606e03dc16fd7aed023e40f61e5c4b688707a39240cc67b5432b9705fbb2c33df9ec8a1da96e4408d7a5270b6a181415647e53c686565f45b5c2bd4
7
- data.tar.gz: b19684e4d13d29e6ac52e3ba5cddb38a6716f9acfae9946962ab45582a38e49c6c42018387d66e75a5f52d7153e12cf4a9e9dc10388f6d36bd4834d5a5e78ee0
6
+ metadata.gz: b0395980a804453e3273f779c1217d5dd5b9b7c522e74964070b43f2b3ac433f9f39d5f997a3e9c6594f22d244752910eea1697ac2e570de34e6b0641555b9e8
7
+ data.tar.gz: 9de0bebe89071d3aa7bd506b02dc684b53d998dcfccab1debad72bdc244e6fbae6eb0ac2d5871359fe2ffa5cb9e7c5ae8a7dc98829e00e8abef49c017ac3b00c
data/.appveyor.yml CHANGED
@@ -6,6 +6,8 @@ install:
6
6
  - gem --version
7
7
  - bundle package --all
8
8
 
9
+ - ps: Install-Product node
10
+
9
11
  - ps: git --work-tree=tmp\bin checkout origin/windows-binaries -- '*.exe'
10
12
 
11
13
  - ps: | # svgo
@@ -0,0 +1,59 @@
1
+ name: check
2
+ on:
3
+ push:
4
+ pull_request:
5
+ schedule:
6
+ - cron: 45 4 * * 2
7
+ jobs:
8
+ check:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ ruby:
13
+ - '2.0'
14
+ - '2.1'
15
+ - '2.2'
16
+ - '2.3'
17
+ - '2.4'
18
+ - '2.5'
19
+ - '2.6'
20
+ - '2.7'
21
+ - '3.0'
22
+ - jruby-9.2
23
+ fail-fast: false
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: "${{ matrix.ruby }}"
29
+ bundler-cache: true
30
+ - run: sudo npm install -g svgo
31
+ - run: curl -L "https://www.jonof.id.au/files/kenutils/pngout-20200115-linux.tar.gz" | sudo tar -xz -C /usr/local/bin --strip-components 2 --wildcards '*/amd64/pngout'
32
+ - run: curl -L "https://github.com/shssoichiro/oxipng/releases/download/v4.0.3/oxipng-4.0.3-x86_64-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin --strip-components 1 --wildcards '*oxipng'
33
+ - run: bundle exec image_optim --info
34
+ - run: bundle exec rspec
35
+ coverage:
36
+ runs-on: ubuntu-latest
37
+ env:
38
+ CC_TEST_REPORTER_ID: b433c6540d220a2da0663670c9b260806bafdb3a43c6f22b2e81bfb1f87b12fe
39
+ steps:
40
+ - uses: actions/checkout@v2
41
+ - uses: ruby/setup-ruby@v1
42
+ with:
43
+ ruby-version: '3.0'
44
+ bundler-cache: true
45
+ - run: sudo npm install -g svgo
46
+ - run: curl -L "https://www.jonof.id.au/files/kenutils/pngout-20200115-linux.tar.gz" | sudo tar -xz -C /usr/local/bin --strip-components 2 --wildcards '*/amd64/pngout'
47
+ - run: curl -L "https://github.com/shssoichiro/oxipng/releases/download/v4.0.3/oxipng-4.0.3-x86_64-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin --strip-components 1 --wildcards '*oxipng'
48
+ - uses: paambaati/codeclimate-action@v2.7.5
49
+ with:
50
+ coverageCommand: bundle exec rspec
51
+ rubocop:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v2
55
+ - uses: ruby/setup-ruby@v1
56
+ with:
57
+ ruby-version: '3.0'
58
+ bundler-cache: true
59
+ - run: bundle exec rubocop
@@ -0,0 +1,9 @@
1
+ - id: image_optim
2
+ name: image_optim
3
+ entry: image_optim
4
+ language: ruby
5
+ types:
6
+ - image
7
+ args: []
8
+ additional_dependencies:
9
+ - image_optim_pack
data/.rubocop.yml CHANGED
@@ -103,12 +103,12 @@ Style/ExpandPathArguments:
103
103
  Style/FormatStringToken:
104
104
  Enabled: false
105
105
 
106
+ Style/HashConversion:
107
+ Enabled: false
108
+
106
109
  Style/HashEachMethods:
107
110
  Enabled: true
108
111
 
109
- Style/HashSyntax:
110
- EnforcedStyle: hash_rockets
111
-
112
112
  Style/HashTransformKeys:
113
113
  Enabled: false
114
114
 
@@ -127,6 +127,9 @@ Style/OptionalBooleanParameter:
127
127
  Style/ParallelAssignment:
128
128
  Enabled: false
129
129
 
130
+ Style/RedundantBegin:
131
+ Enabled: false
132
+
130
133
  Style/RescueStandardError:
131
134
  EnforcedStyle: implicit
132
135
 
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v0.31.0 (2021-10-03)
6
+
7
+ * Add support for Oxipng [#167](https://github.com/toy/image_optim/issues/167) [#190](https://github.com/toy/image_optim/pull/190) [@oblakeerickson](https://github.com/oblakeerickson)
8
+ * Fix `TypeError: can't convert ImageOptim::Timer into Float` with Ruby 3 [#194](https://github.com/toy/image_optim/pull/194) [@yahonda](https://github.com/yahonda)
9
+
10
+ ## v0.30.0 (2021-05-11)
11
+
12
+ * Add `timeout` option to restrict maximum time spent on every image [#21](https://github.com/toy/image_optim/issues/21) [#148](https://github.com/toy/image_optim/pull/148) [#149](https://github.com/toy/image_optim/pull/149) [#162](https://github.com/toy/image_optim/pull/162) [#184](https://github.com/toy/image_optim/pull/184) [#189](https://github.com/toy/image_optim/pull/189) [@tgxworld](https://github.com/tgxworld) [@oblakeerickson](https://github.com/oblakeerickson) [@toy](https://github.com/toy)
13
+
14
+ ## v0.29.0 (2021-04-28)
15
+
16
+ * Require at least ruby 1.9.3 [@toy](https://github.com/toy)
17
+ * Add support for use as [pre-commit](https://pre-commit.com/) hook [#192](https://github.com/toy/image_optim/pull/192) [@proinsias](https://github.com/proinsias)
18
+ * Fix `Path#copy_metadata` by rescuing also `Errno::EACCES` as it is done in `fileutils` [#187](https://github.com/toy/image_optim/issues/187) [@toy](https://github.com/toy)
19
+ * More precise regular expression for capturing svgo version [@toy](https://github.com/toy)
20
+
21
+ ## v0.28.0 (2020-12-18)
22
+
23
+ * Fix and update list of markers in jpegoptim worker: allow to pass `com` instead of incorrect `comments` and add missing `xmp` and `none` [#188](https://github.com/toy/image_optim/issues/188) [@toy](https://github.com/toy)
24
+ * Add `--skip-if-larger` flag to pngquant worker. The pngquant worker already does this, but this will make it fail faster. [#125](https://github.com/toy/image_optim/pull/125) [#181](https://github.com/toy/image_optim/pull/181) [@iggant](https://github.com/iggant) [@oblakeerickson](https://github.com/oblakeerickson)
25
+ * Add `method` option for jpegrecompress, default to `ssim` [#102](https://github.com/toy/image_optim/issues/102) [#103](https://github.com/toy/image_optim/pull/103) [#182](https://github.com/toy/image_optim/pull/182) [@ramiroaraujo](https://github.com/ramiroaraujo) [@oblakeerickson](https://github.com/oblakeerickson)
26
+
5
27
  ## v0.27.1 (2020-09-30)
6
28
 
7
29
  * Fixed atomic replacement for case when equal `File::Stat#dev` doesn't mean that file can be linked [#180](https://github.com/toy/image_optim/issues/180) [@toy](https://github.com/toy)
@@ -2,10 +2,13 @@
2
2
 
3
3
  * Create topic/feature branch: `git checkout -b awesome-changes`
4
4
  * Commit…
5
- * Add entry at the top of [ChangeLog](CHANGELOG.markdown)
5
+ * Add an entry at the top (after ## unreleased) of [ChangeLog](CHANGELOG.markdown), include:
6
+ * Issues (`[#123](https://github.com/toy/image_optim/issues/123)`)
7
+ * Pull requests (`[#123](https://github.com/toy/image_optim/pull/123)`)
8
+ * Authors (`[@octocat](https://github.com/octocat)`)
6
9
  * Run tests: `bundle exec rspec`
7
10
  * Check code style: `bundle exec rubocop`
8
11
  * Rebase on master and squash commits to logical units
9
12
  * Push your branch: `git push origin awesome-changes`
10
13
  * Create pull request
11
- * Check if [travis is happy](https://travis-ci.org/toy/image_optim/pull_requests)
14
+ * Check if [github actions workflow is happy](https://github.com/toy/image_optim_pack/actions/workflows/check.yml)
data/Gemfile CHANGED
@@ -4,14 +4,8 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- if ENV['CODECLIMATE']
7
+ if ENV['CC_TEST_REPORTER_ID']
8
8
  group :test do
9
9
  gem 'simplecov'
10
-
11
- gem 'codeclimate-test-reporter'
12
10
  end
13
11
  end
14
-
15
- if ENV['CHECK_RUBIES']
16
- gem 'travis_check_rubies', '~> 0.2'
17
- end
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2020 Ivan Kuchin
1
+ Copyright (c) 2012-2021 Ivan Kuchin
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.markdown CHANGED
@@ -1,10 +1,10 @@
1
- [![Gem Version](https://img.shields.io/gem/v/image_optim.svg?style=flat)](https://rubygems.org/gems/image_optim)
2
- [![Build Status](https://img.shields.io/travis/toy/image_optim/master.svg?style=flat)](https://travis-ci.org/toy/image_optim)
3
- [![AppVeyor Status](https://img.shields.io/appveyor/ci/toy/image-optim/master.svg?style=flat&label=windows)](https://ci.appveyor.com/project/toy/image-optim)
4
- [![Code Climate](https://img.shields.io/codeclimate/maintainability/toy/image_optim.svg?style=flat)](https://codeclimate.com/github/toy/image_optim)
5
- [![Code Climate Coverage](https://img.shields.io/codeclimate/c/toy/image_optim.svg?style=flat)](https://codeclimate.com/github/toy/image_optim)
6
- [![Depfu](https://badges.depfu.com/badges/221b4832fa96f613aa5401f7cb4030ac/overview.svg)](https://depfu.com/github/toy/image_optim)
7
- [![Inch CI](https://inch-ci.org/github/toy/image_optim.svg?branch=master&style=flat)](https://inch-ci.org/github/toy/image_optim)
1
+ [![Gem Version](https://img.shields.io/gem/v/image_optim?logo=rubygems)](https://rubygems.org/gems/image_optim)
2
+ [![Build Status](https://img.shields.io/github/workflow/status/toy/image_optim/check/master?logo=github)](https://github.com/toy/image_optim/actions/workflows/check.yml)
3
+ [![AppVeyor Status](https://img.shields.io/appveyor/build/toy/image-optim/master?label=windows&logo=appveyor)](https://ci.appveyor.com/project/toy/image-optim)
4
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/toy/image_optim?logo=codeclimate)](https://codeclimate.com/github/toy/image_optim)
5
+ [![Code Climate Coverage](https://img.shields.io/codeclimate/coverage/toy/image_optim?logo=codeclimate)](https://codeclimate.com/github/toy/image_optim)
6
+ [![Depfu](https://img.shields.io/depfu/toy/image_optim)](https://depfu.com/github/toy/image_optim)
7
+ [![Inch CI](https://inch-ci.org/github/toy/image_optim.svg?branch=master)](https://inch-ci.org/github/toy/image_optim)
8
8
 
9
9
  # image_optim
10
10
 
@@ -18,6 +18,7 @@ Command line tool and ruby interface to optimize (lossless compress, optionally
18
18
  * [jpeg-recompress](https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress)
19
19
  * jpegtran from [Independent JPEG Group's JPEG library](http://www.ijg.org/)
20
20
  * [optipng](http://optipng.sourceforge.net/)
21
+ * [oxipng](https://github.com/shssoichiro/oxipng)
21
22
  * [pngcrush](http://pmt.sourceforge.net/pngcrush/)
22
23
  * [pngout](http://www.advsys.net/ken/util/pngout.htm)
23
24
  * [pngquant](http://pngquant.org/)
@@ -60,7 +61,7 @@ With version:
60
61
 
61
62
  <!---<update-version>-->
62
63
  ```ruby
63
- gem 'image_optim', '~> 0.27'
64
+ gem 'image_optim', '~> 0.31'
64
65
  ```
65
66
  <!---</update-version>-->
66
67
 
@@ -166,6 +167,14 @@ sudo port install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush png
166
167
  brew install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush pngquant jonof/kenutils/pngout
167
168
  ```
168
169
 
170
+ ### oxipng installation (optional)
171
+
172
+ Unless it is available in your chosen package manager, can be installed using cargo:
173
+
174
+ ```bash
175
+ cargo install oxipng
176
+ ```
177
+
169
178
  ### pngout installation (optional)
170
179
 
171
180
  If you installed the dependencies via brew, pngout should be installed already. Otherwise, you can install `pngout` by downloading and installing the [binary versions](http://www.jonof.id.au/kenutils).
@@ -291,6 +300,7 @@ optipng:
291
300
  * `:allow_lossy` — Allow lossy workers and optimizations *(defaults to `false`)*
292
301
  * `:cache_dir` — Configure cache directory
293
302
  * `:cache_worker_digests` - Also cache worker digests along with original file digest and worker options: updating workers invalidates cache
303
+ * `:timeout` — Maximum time in seconds to spend on one image, note multithreading and cache *(defaults to unlimited)*
294
304
 
295
305
  Worker can be disabled by passing `false` instead of options hash or by setting option `:disable` to `true`.
296
306
 
@@ -310,12 +320,13 @@ Worker has no options
310
320
 
311
321
  ### jpegoptim:
312
322
  * `:allow_lossy` — Allow limiting maximum quality *(defaults to `false`)*
313
- * `:strip` — List of extra markers to strip: `:comments`, `:exif`, `:iptc`, `:icc` or `:all` *(defaults to `:all`)*
323
+ * `:strip` — List of markers to strip: `:com`, `:exif`, `:iptc`, `:icc`, `:xmp`, `:none` or `:all` *(defaults to `:all`)*
314
324
  * `:max_quality` — Maximum image quality factor `0`..`100`, ignored in default/lossless mode *(defaults to `100`)*
315
325
 
316
326
  ### jpegrecompress:
317
327
  * `:allow_lossy` — Allow worker, it is always lossy *(defaults to `false`)*
318
328
  * `:quality` — JPEG quality preset: `0` - low, `1` - medium, `2` - high, `3` - veryhigh *(defaults to `3`)*
329
+ * `:method` — Comparison Metric: `mpe` - Mean pixel error, `ssim` - Structural similarity, `ms-ssim` - Multi-scale structural similarity (slow!), `smallfry` - Linear-weighted BBCQ-like (may be patented) *(defaults to ssim)*
319
330
 
320
331
  ### jpegtran:
321
332
  * `:copy_chunks` — Copy all chunks *(defaults to `false`)*
@@ -361,4 +372,4 @@ In separate file [CHANGELOG.markdown](CHANGELOG.markdown).
361
372
 
362
373
  ## Copyright
363
374
 
364
- Copyright (c) 2012-2020 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
375
+ Copyright (c) 2012-2021 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
data/Vagrantfile CHANGED
@@ -3,7 +3,7 @@
3
3
  Vagrant.configure('2') do |config|
4
4
  config.vm.box = 'ubuntu/precise64'
5
5
 
6
- config.vm.provision 'shell', :inline => <<-SH
6
+ config.vm.provision 'shell', inline: <<-SH
7
7
  set -e
8
8
 
9
9
  cd /vagrant
data/image_optim.gemspec CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_optim'
5
- s.version = '0.27.1'
6
- s.summary = %q{Command line tool and ruby interface to optimize (lossless compress, optionally lossy) jpeg, png, gif and svg images using external utilities (advpng, gifsicle, jhead, jpeg-recompress, jpegoptim, jpegrescan, jpegtran, optipng, pngcrush, pngout, pngquant, svgo)}
5
+ s.version = '0.31.0'
6
+ s.summary = %q{Command line tool and ruby interface to optimize (lossless compress, optionally lossy) jpeg, png, gif and svg images using external utilities (advpng, gifsicle, jhead, jpeg-recompress, jpegoptim, jpegrescan, jpegtran, optipng, oxipng, pngcrush, pngout, pngquant, svgo)}
7
7
  s.homepage = "https://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
9
9
  s.license = 'MIT'
10
10
 
11
+ s.required_ruby_version = '>= 1.9.3'
12
+
11
13
  s.metadata = {
12
14
  'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
13
15
  'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
@@ -33,7 +35,8 @@ EOF
33
35
 
34
36
  s.add_development_dependency 'image_optim_pack', '~> 0.2', '>= 0.2.2'
35
37
  s.add_development_dependency 'rspec', '~> 3.0'
36
- if RUBY_VERSION >= '2.2' && !Gem.win_platform? && !defined?(JRUBY_VERSION)
37
- s.add_development_dependency 'rubocop', '~> 0.59', '!= 0.78.0'
38
+ if RUBY_VERSION >= '2.4' && !Gem.win_platform? && !defined?(JRUBY_VERSION)
39
+ s.add_development_dependency 'rubocop', '~> 1.0'
40
+ s.add_development_dependency 'rubocop-rspec', '~> 2.0'
38
41
  end
39
42
  end
@@ -14,6 +14,7 @@ class ImageOptim
14
14
  # Holds bin name and path, gets version
15
15
  class Bin
16
16
  class UnknownVersion < Error; end
17
+
17
18
  class BadVersion < Error; end
18
19
 
19
20
  attr_reader :name, :path, :version
@@ -37,30 +38,30 @@ class ImageOptim
37
38
  is = ComparableCondition.is
38
39
 
39
40
  FAIL_CHECKS = {
40
- :pngcrush => [
41
+ pngcrush: [
41
42
  [is.between?('1.7.60', '1.7.65'), 'is known to produce broken pngs'],
42
43
  [is == '1.7.80', 'loses one color in indexed images'],
43
44
  ],
44
- :pngquant => [
45
+ pngquant: [
45
46
  [is < '2.0', 'is not supported'],
46
47
  ],
47
48
  }.freeze
48
49
 
49
50
  WARN_CHECKS = {
50
- :advpng => [
51
+ advpng: [
51
52
  [is == 'none', 'is of unknown version'],
52
53
  [is < '1.17', 'does not use zopfli'],
53
54
  ],
54
- :gifsicle => [
55
+ gifsicle: [
55
56
  [is < '1.85', 'does not support removing extension blocks'],
56
57
  ],
57
- :pngcrush => [
58
+ pngcrush: [
58
59
  [is < '1.7.38', 'does not have blacken flag'],
59
60
  ],
60
- :pngquant => [
61
+ pngquant: [
61
62
  [is < '2.1', 'may be lossy even with quality `100-`'],
62
63
  ],
63
- :optipng => [
64
+ optipng: [
64
65
  [is < '0.7', 'does not support -strip option'],
65
66
  ],
66
67
  }.freeze
@@ -108,10 +109,10 @@ class ImageOptim
108
109
  case name
109
110
  when :advpng
110
111
  capture("#{escaped_path} --version 2> #{Path::NULL}")[/\bv(\d+(\.\d+)+|none)/, 1]
111
- when :gifsicle, :jpegoptim, :optipng
112
+ when :gifsicle, :jpegoptim, :optipng, :oxipng
112
113
  capture("#{escaped_path} --version 2> #{Path::NULL}")[/\d+(\.\d+)+/]
113
114
  when :svgo, :pngquant
114
- capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+)+/]
115
+ capture("#{escaped_path} --version 2>&1")[/\A\d+(\.\d+)+/]
115
116
  when :jhead, :'jpeg-recompress'
116
117
  capture("#{escaped_path} -V 2> #{Path::NULL}")[/\d+(\.\d+)+/]
117
118
  when :jpegtran
@@ -21,6 +21,11 @@ class ImageOptim
21
21
  "#{bin.name}[#{bin.digest}]"
22
22
  end.sort!.uniq.join(', ')]
23
23
  end]
24
+ @global_options = begin
25
+ options = {}
26
+ options[:timeout] = image_optim.timeout if image_optim.timeout
27
+ options.empty? ? '' : options.inspect
28
+ end
24
29
  end
25
30
 
26
31
  def fetch(original)
@@ -68,6 +73,7 @@ class ImageOptim
68
73
  digest = Digest::SHA1.file(path)
69
74
  digest.update options_by_format(format)
70
75
  digest.update bins_by_format(format) if @cache_worker_digests
76
+ digest.update @global_options
71
77
  s = digest.hexdigest
72
78
  "#{s[0..1]}/#{s[2..-1]}"
73
79
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'image_optim/errors'
3
4
  require 'English'
4
5
 
5
6
  class ImageOptim
@@ -10,11 +11,30 @@ class ImageOptim
10
11
  # Return success status
11
12
  # Will raise SignalException if process was interrupted
12
13
  def run(*args)
13
- success = system(*args)
14
+ if args.last.is_a?(Hash) && (timeout = args.last.delete(:timeout))
15
+ args.last[Gem.win_platform? ? :new_pgroup : :pgroup] = true
14
16
 
15
- check_status!
17
+ pid = Process.spawn(*args)
18
+
19
+ waiter = Process.detach(pid)
20
+ if waiter.join(timeout.to_f)
21
+ status = waiter.value
22
+
23
+ check_status!(status)
24
+
25
+ status.success?
26
+ else
27
+ cleanup(pid, waiter)
16
28
 
17
- success
29
+ fail Errors::TimeoutExceeded
30
+ end
31
+ else
32
+ success = system(*args)
33
+
34
+ check_status!
35
+
36
+ success
37
+ end
18
38
  end
19
39
 
20
40
  # Run using backtick
@@ -30,9 +50,7 @@ class ImageOptim
30
50
 
31
51
  private
32
52
 
33
- def check_status!
34
- status = $CHILD_STATUS
35
-
53
+ def check_status!(status = $CHILD_STATUS)
36
54
  return unless status.signaled?
37
55
 
38
56
  # jruby incorrectly returns true for `signaled?` if process exits with
@@ -46,6 +64,27 @@ class ImageOptim
46
64
 
47
65
  fail SignalException, status.termsig
48
66
  end
67
+
68
+ def cleanup(pid, waiter)
69
+ if Gem.win_platform?
70
+ kill('KILL', pid)
71
+ else
72
+ kill('-TERM', pid)
73
+
74
+ # Allow 10 seconds for the process to exit
75
+ waiter.join(10)
76
+
77
+ kill('-KILL', pid)
78
+ end
79
+
80
+ waiter.join
81
+ end
82
+
83
+ def kill(signal, pid)
84
+ Process.kill(signal, pid)
85
+ rescue Errno::ESRCH, Errno::EPERM
86
+ # expected
87
+ end
49
88
  end
50
89
  end
51
90
  end
@@ -15,9 +15,7 @@ class ImageOptim
15
15
 
16
16
  # Global config path at `$XDG_CONFIG_HOME/image_optim.yml` (by default
17
17
  # `~/.config/image_optim.yml`)
18
- GLOBAL_PATH = begin
19
- File.join(ENV['XDG_CONFIG_HOME'] || '~/.config', 'image_optim.yml')
20
- end
18
+ GLOBAL_PATH = File.join(ENV['XDG_CONFIG_HOME'] || '~/.config', 'image_optim.yml')
21
19
 
22
20
  # Local config path at `./.image_optim.yml`
23
21
  LOCAL_PATH = './.image_optim.yml'
@@ -119,6 +117,14 @@ class ImageOptim
119
117
  end
120
118
  end
121
119
 
120
+ # Timeout in seconds for each image:
121
+ # * not set by default and for `nil`
122
+ # * otherwise converted to float
123
+ def timeout
124
+ timeout = get!(:timeout)
125
+ timeout ? timeout.to_f : nil
126
+ end
127
+
122
128
  # Verbose mode, converted to boolean
123
129
  def verbose
124
130
  !!get!(:verbose)
@@ -177,10 +183,10 @@ class ImageOptim
177
183
  when true, nil
178
184
  {}
179
185
  when false
180
- {:disable => true}
186
+ {disable: true}
181
187
  else
182
188
  fail ConfigurationError, "Got #{worker_options.inspect} for "\
183
- "#{klass.name} options"
189
+ "#{klass.name} options"
184
190
  end
185
191
  end
186
192
 
@@ -197,10 +203,10 @@ class ImageOptim
197
203
  when /darwin9/
198
204
  Cmd.capture 'hwprefs cpu_count'
199
205
  when /darwin/
200
- if (Cmd.capture 'which hwprefs') != ''
201
- Cmd.capture 'hwprefs thread_count'
202
- else
206
+ if (Cmd.capture 'which hwprefs') == ''
203
207
  Cmd.capture 'sysctl -n hw.ncpu'
208
+ else
209
+ Cmd.capture 'hwprefs thread_count'
204
210
  end
205
211
  when /linux/
206
212
  Cmd.capture 'grep -c processor /proc/cpuinfo'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ImageOptim
4
+ # Use Process.clock_gettime if available to get time more fitting to calculate elapsed time
5
+ module ElapsedTime
6
+ CLOCK_NAME = %w[
7
+ CLOCK_UPTIME_RAW
8
+ CLOCK_UPTIME
9
+ CLOCK_MONOTONIC_RAW
10
+ CLOCK_MONOTONIC
11
+ CLOCK_REALTIME
12
+ ].find{ |name| Process.const_defined?(name) }
13
+
14
+ CLOCK_ID = CLOCK_NAME && Process.const_get(CLOCK_NAME)
15
+
16
+ module_function
17
+
18
+ def now
19
+ if CLOCK_ID
20
+ Process.clock_gettime(CLOCK_ID)
21
+ else
22
+ Time.now.to_f
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ImageOptim
4
+ class Error < StandardError; end
5
+
6
+ module Errors
7
+ class TimeoutExceeded < Error; end
8
+ end
9
+ end
@@ -41,7 +41,7 @@ class ImageOptim
41
41
  dst.utime(stat.atime, stat.mtime) if time
42
42
  begin
43
43
  dst.chown(stat.uid, stat.gid)
44
- rescue Errno::EPERM
44
+ rescue Errno::EPERM, Errno::EACCES
45
45
  dst.chmod(stat.mode & 0o1777)
46
46
  else
47
47
  dst.chmod(stat.mode)