image_optim 0.27.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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)