image_optim 0.28.0 → 0.31.1

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/.github/workflows/check.yml +89 -0
  3. data/.pre-commit-hooks.yaml +9 -0
  4. data/.rubocop.yml +6 -3
  5. data/CHANGELOG.markdown +20 -0
  6. data/CONTRIBUTING.markdown +1 -1
  7. data/Gemfile +1 -7
  8. data/LICENSE.txt +1 -1
  9. data/README.markdown +18 -9
  10. data/Vagrantfile +1 -1
  11. data/image_optim.gemspec +6 -3
  12. data/lib/image_optim/bin_resolver/bin.rb +9 -9
  13. data/lib/image_optim/cache.rb +6 -0
  14. data/lib/image_optim/cmd.rb +45 -6
  15. data/lib/image_optim/config.rb +11 -5
  16. data/lib/image_optim/elapsed_time.rb +26 -0
  17. data/lib/image_optim/errors.rb +9 -0
  18. data/lib/image_optim/path.rb +1 -1
  19. data/lib/image_optim/runner/option_parser.rb +21 -17
  20. data/lib/image_optim/runner.rb +1 -1
  21. data/lib/image_optim/timer.rb +25 -0
  22. data/lib/image_optim/worker/advpng.rb +7 -7
  23. data/lib/image_optim/worker/gifsicle.rb +11 -11
  24. data/lib/image_optim/worker/jhead.rb +2 -2
  25. data/lib/image_optim/worker/jpegoptim.rb +11 -11
  26. data/lib/image_optim/worker/jpegrecompress.rb +6 -6
  27. data/lib/image_optim/worker/jpegtran.rb +4 -4
  28. data/lib/image_optim/worker/optipng.rb +7 -7
  29. data/lib/image_optim/worker/oxipng.rb +53 -0
  30. data/lib/image_optim/worker/pngcrush.rb +6 -6
  31. data/lib/image_optim/worker/pngout.rb +7 -7
  32. data/lib/image_optim/worker/pngquant.rb +9 -9
  33. data/lib/image_optim/worker/svgo.rb +2 -2
  34. data/lib/image_optim/worker.rb +32 -29
  35. data/lib/image_optim.rb +16 -10
  36. data/script/update_worker_options_in_readme +1 -1
  37. data/script/worker_analysis +16 -18
  38. data/spec/image_optim/bin_resolver_spec.rb +5 -5
  39. data/spec/image_optim/cache_path_spec.rb +7 -10
  40. data/spec/image_optim/cache_spec.rb +7 -7
  41. data/spec/image_optim/cmd_spec.rb +64 -6
  42. data/spec/image_optim/config_spec.rb +36 -20
  43. data/spec/image_optim/elapsed_time_spec.rb +14 -0
  44. data/spec/image_optim/handler_spec.rb +1 -1
  45. data/spec/image_optim/hash_helpers_spec.rb +18 -18
  46. data/spec/image_optim/option_definition_spec.rb +6 -6
  47. data/spec/image_optim/path_spec.rb +8 -11
  48. data/spec/image_optim/runner/option_parser_spec.rb +4 -4
  49. data/spec/image_optim/timer_spec.rb +32 -0
  50. data/spec/image_optim/worker/jpegrecompress_spec.rb +2 -2
  51. data/spec/image_optim/worker/optipng_spec.rb +11 -11
  52. data/spec/image_optim/worker/oxipng_spec.rb +89 -0
  53. data/spec/image_optim/worker/pngquant_spec.rb +5 -5
  54. data/spec/image_optim/worker_spec.rb +17 -17
  55. data/spec/image_optim_spec.rb +47 -10
  56. data/spec/images/invisiblepixels/generate +1 -1
  57. data/spec/spec_helper.rb +24 -21
  58. metadata +35 -11
  59. data/.appveyor.yml +0 -53
  60. data/.travis.yml +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 818160db3036f1fd4305077d943e59991aebe5fb1023b3c7e46db02e63c5fb37
4
- data.tar.gz: 76c76ec43a5c05b9e35de21757928ec8793709a3db27e8a982cc46612f8d491e
3
+ metadata.gz: 481b027ab78658543ea138ff4d7547a609dca8b31eea02f8cb003318f10c67eb
4
+ data.tar.gz: e11f9f69a28e6a3e7a0028579cb36e78b5dd1457943a884f8071d6de17e7031e
5
5
  SHA512:
6
- metadata.gz: f30e6f493be6ff4e98407bb611d8fd868733af14da078b225b6708c7f6aabaa41d00b08e82c3801c788bd70c4d7278ee91a186a3dc54bc7bdcc16e685d27c7a8
7
- data.tar.gz: b3402efe19eeb298518cfeb117f38c7aca1e0bbabe68313663107269d3ab87f8900f238db182042377adb5ebc12f69dd133ee5c922fcbde54c4b647bc9efa836
6
+ metadata.gz: 961044af3b8ef65acbdc6bbce6a278e8aefe246a1ea53a6eceea5bb025f02c7adeecf457843fd2244a5bc21fa0f303094a16f4947cda41fc9ac7372dbcce5f46
7
+ data.tar.gz: ea8dcb08f8fffd1aad10506bdb3ab2bdf0043f30d08b68e2cb0d2195562492d6477619e3bdcf830a9bec9ead04adfe36b195b136fba61a1e7d4f96835c61c854
@@ -0,0 +1,89 @@
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
+ windows:
36
+ runs-on: windows-latest
37
+ strategy:
38
+ matrix:
39
+ ruby:
40
+ - '2.6'
41
+ - '2.7'
42
+ - '3.0'
43
+ fail-fast: false
44
+ steps:
45
+ - uses: actions/checkout@v2
46
+ - uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: "${{ matrix.ruby }}"
49
+ bundler-cache: true
50
+ - uses: actions/cache@v2
51
+ with:
52
+ path: "$HOME/bin"
53
+ key: ${{ runner.os }}
54
+ - run: |
55
+ mkdir "$HOME/bin"
56
+ git fetch origin windows-binaries
57
+ git --work-tree="$HOME/bin" checkout origin/windows-binaries -- '*.exe'
58
+ echo "$HOME/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
59
+ - run: |
60
+ choco install --no-progress imagemagick
61
+ ls C:\'Program Files'\ImageMagick* | % FullName | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
62
+ - run: npm install -g svgo
63
+ - run: bundle exec image_optim --info
64
+ - run: bundle exec rspec
65
+ coverage:
66
+ runs-on: ubuntu-latest
67
+ env:
68
+ CC_TEST_REPORTER_ID: b433c6540d220a2da0663670c9b260806bafdb3a43c6f22b2e81bfb1f87b12fe
69
+ steps:
70
+ - uses: actions/checkout@v2
71
+ - uses: ruby/setup-ruby@v1
72
+ with:
73
+ ruby-version: '3.0'
74
+ bundler-cache: true
75
+ - run: sudo npm install -g svgo
76
+ - 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'
77
+ - 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'
78
+ - uses: paambaati/codeclimate-action@v2.7.5
79
+ with:
80
+ coverageCommand: bundle exec rspec
81
+ rubocop:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v2
85
+ - uses: ruby/setup-ruby@v1
86
+ with:
87
+ ruby-version: '3.0'
88
+ bundler-cache: true
89
+ - 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,26 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v0.31.1 (2021-10-21)
6
+
7
+ * Allow image_size 3 [@toy](https://github.com/toy)
8
+
9
+ ## v0.31.0 (2021-10-03)
10
+
11
+ * 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)
12
+ * 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)
13
+
14
+ ## v0.30.0 (2021-05-11)
15
+
16
+ * 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)
17
+
18
+ ## v0.29.0 (2021-04-28)
19
+
20
+ * Require at least ruby 1.9.3 [@toy](https://github.com/toy)
21
+ * 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)
22
+ * 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)
23
+ * More precise regular expression for capturing svgo version [@toy](https://github.com/toy)
24
+
5
25
  ## v0.28.0 (2020-12-18)
6
26
 
7
27
  * 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)
@@ -11,4 +11,4 @@
11
11
  * Rebase on master and squash commits to logical units
12
12
  * Push your branch: `git push origin awesome-changes`
13
13
  * Create pull request
14
- * 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,9 @@
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
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/toy/image_optim?logo=codeclimate)](https://codeclimate.com/github/toy/image_optim)
4
+ [![Code Climate Coverage](https://img.shields.io/codeclimate/coverage/toy/image_optim?logo=codeclimate)](https://codeclimate.com/github/toy/image_optim)
5
+ [![Depfu](https://img.shields.io/depfu/toy/image_optim)](https://depfu.com/github/toy/image_optim)
6
+ [![Inch CI](https://inch-ci.org/github/toy/image_optim.svg?branch=master)](https://inch-ci.org/github/toy/image_optim)
8
7
 
9
8
  # image_optim
10
9
 
@@ -18,6 +17,7 @@ Command line tool and ruby interface to optimize (lossless compress, optionally
18
17
  * [jpeg-recompress](https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress)
19
18
  * jpegtran from [Independent JPEG Group's JPEG library](http://www.ijg.org/)
20
19
  * [optipng](http://optipng.sourceforge.net/)
20
+ * [oxipng](https://github.com/shssoichiro/oxipng)
21
21
  * [pngcrush](http://pmt.sourceforge.net/pngcrush/)
22
22
  * [pngout](http://www.advsys.net/ken/util/pngout.htm)
23
23
  * [pngquant](http://pngquant.org/)
@@ -60,7 +60,7 @@ With version:
60
60
 
61
61
  <!---<update-version>-->
62
62
  ```ruby
63
- gem 'image_optim', '~> 0.28'
63
+ gem 'image_optim', '~> 0.31'
64
64
  ```
65
65
  <!---</update-version>-->
66
66
 
@@ -166,6 +166,14 @@ sudo port install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush png
166
166
  brew install advancecomp gifsicle jhead jpegoptim jpeg optipng pngcrush pngquant jonof/kenutils/pngout
167
167
  ```
168
168
 
169
+ ### oxipng installation (optional)
170
+
171
+ Unless it is available in your chosen package manager, can be installed using cargo:
172
+
173
+ ```bash
174
+ cargo install oxipng
175
+ ```
176
+
169
177
  ### pngout installation (optional)
170
178
 
171
179
  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 +299,7 @@ optipng:
291
299
  * `:allow_lossy` — Allow lossy workers and optimizations *(defaults to `false`)*
292
300
  * `:cache_dir` — Configure cache directory
293
301
  * `:cache_worker_digests` - Also cache worker digests along with original file digest and worker options: updating workers invalidates cache
302
+ * `:timeout` — Maximum time in seconds to spend on one image, note multithreading and cache *(defaults to unlimited)*
294
303
 
295
304
  Worker can be disabled by passing `false` instead of options hash or by setting option `:disable` to `true`.
296
305
 
@@ -362,4 +371,4 @@ In separate file [CHANGELOG.markdown](CHANGELOG.markdown).
362
371
 
363
372
  ## Copyright
364
373
 
365
- Copyright (c) 2012-2020 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
374
+ 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.28.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, pngcrush, pngout, pngquant, svgo)}
5
+ s.version = '0.31.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, 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",
@@ -26,7 +28,7 @@ You can safely remove `config.assets.image_optim = false` if you are not going t
26
28
  EOF
27
29
 
28
30
  s.add_dependency 'fspath', '~> 3.0'
29
- s.add_dependency 'image_size', '>= 1.5', '< 3'
31
+ s.add_dependency 'image_size', '>= 1.5', '< 4'
30
32
  s.add_dependency 'exifr', '~> 1.2', '>= 1.2.2'
31
33
  s.add_dependency 'progress', '~> 3.0', '>= 3.0.1'
32
34
  s.add_dependency 'in_threads', '~> 1.3'
@@ -35,5 +37,6 @@ EOF
35
37
  s.add_development_dependency 'rspec', '~> 3.0'
36
38
  if RUBY_VERSION >= '2.4' && !Gem.win_platform? && !defined?(JRUBY_VERSION)
37
39
  s.add_development_dependency 'rubocop', '~> 1.0'
40
+ s.add_development_dependency 'rubocop-rspec', '~> 2.0'
38
41
  end
39
42
  end
@@ -38,30 +38,30 @@ class ImageOptim
38
38
  is = ComparableCondition.is
39
39
 
40
40
  FAIL_CHECKS = {
41
- :pngcrush => [
41
+ pngcrush: [
42
42
  [is.between?('1.7.60', '1.7.65'), 'is known to produce broken pngs'],
43
43
  [is == '1.7.80', 'loses one color in indexed images'],
44
44
  ],
45
- :pngquant => [
45
+ pngquant: [
46
46
  [is < '2.0', 'is not supported'],
47
47
  ],
48
48
  }.freeze
49
49
 
50
50
  WARN_CHECKS = {
51
- :advpng => [
51
+ advpng: [
52
52
  [is == 'none', 'is of unknown version'],
53
53
  [is < '1.17', 'does not use zopfli'],
54
54
  ],
55
- :gifsicle => [
55
+ gifsicle: [
56
56
  [is < '1.85', 'does not support removing extension blocks'],
57
57
  ],
58
- :pngcrush => [
58
+ pngcrush: [
59
59
  [is < '1.7.38', 'does not have blacken flag'],
60
60
  ],
61
- :pngquant => [
61
+ pngquant: [
62
62
  [is < '2.1', 'may be lossy even with quality `100-`'],
63
63
  ],
64
- :optipng => [
64
+ optipng: [
65
65
  [is < '0.7', 'does not support -strip option'],
66
66
  ],
67
67
  }.freeze
@@ -109,10 +109,10 @@ class ImageOptim
109
109
  case name
110
110
  when :advpng
111
111
  capture("#{escaped_path} --version 2> #{Path::NULL}")[/\bv(\d+(\.\d+)+|none)/, 1]
112
- when :gifsicle, :jpegoptim, :optipng
112
+ when :gifsicle, :jpegoptim, :optipng, :oxipng
113
113
  capture("#{escaped_path} --version 2> #{Path::NULL}")[/\d+(\.\d+)+/]
114
114
  when :svgo, :pngquant
115
- capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+)+/]
115
+ capture("#{escaped_path} --version 2>&1")[/\A\d+(\.\d+)+/]
116
116
  when :jhead, :'jpeg-recompress'
117
117
  capture("#{escaped_path} -V 2> #{Path::NULL}")[/\d+(\.\d+)+/]
118
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
 
@@ -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)
@@ -38,7 +38,7 @@ class ImageOptim
38
38
  columns = terminal_columns - 1
39
39
  # 1 for distance between summary and description
40
40
  # 2 for additional indent
41
- wrapped_indent = summary_indent + ' ' * (summary_width + 1 + 2)
41
+ wrapped_indent = summary_indent + (' ' * (summary_width + 1 + 2))
42
42
  wrapped_width = columns - wrapped_indent.length
43
43
  # don't try to wrap if there is too little space for description
44
44
  return text if wrapped_width < 20
@@ -97,29 +97,29 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
97
97
  TEXT
98
98
 
99
99
  op.on('--config-paths PATH1,PATH2', Array, 'Config paths to use instead of '\
100
- 'default ones') do |paths|
100
+ 'default ones') do |paths|
101
101
  options[:config_paths] = paths
102
102
  end
103
103
 
104
104
  op.separator nil
105
105
 
106
106
  op.on('-r', '-R', '--recursive', 'Recursively scan directories '\
107
- 'for images') do |recursive|
107
+ 'for images') do |recursive|
108
108
  options[:recursive] = recursive
109
109
  end
110
110
 
111
111
  op.on("--exclude-dir 'GLOB'", 'Glob for excluding directories '\
112
- '(defaults to .*)') do |glob|
112
+ '(defaults to .*)') do |glob|
113
113
  options[:exclude_dir_glob] = glob
114
114
  end
115
115
 
116
116
  op.on("--exclude-file 'GLOB'", 'Glob for excluding files '\
117
- '(defaults to .*)') do |glob|
117
+ '(defaults to .*)') do |glob|
118
118
  options[:exclude_file_glob] = glob
119
119
  end
120
120
 
121
121
  op.on("--exclude 'GLOB'", 'Set glob for excluding both directories and '\
122
- 'files') do |glob|
122
+ 'files') do |glob|
123
123
  options[:exclude_file_glob] = options[:exclude_dir_glob] = glob
124
124
  end
125
125
 
@@ -130,19 +130,19 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
130
130
  end
131
131
 
132
132
  op.on('--[no-]threads N', Integer, 'Number of threads or disable '\
133
- '(defaults to number of processors)') do |threads|
133
+ '(defaults to number of processors)') do |threads|
134
134
  options[:threads] = threads
135
135
  end
136
136
 
137
137
  op.on('--[no-]nice N', Integer, 'Nice level, priority of all used tools '\
138
- 'with higher value meaning lower priority, in range -20..19, negative '\
139
- 'values can be set only if run by root user (defaults to 10)') do |nice|
138
+ 'with higher value meaning lower priority, in range -20..19, negative '\
139
+ 'values can be set only if run by root user (defaults to 10)') do |nice|
140
140
  options[:nice] = nice
141
141
  end
142
142
 
143
143
  op.on('--[no-]pack', 'Require image_optim_pack or disable it, '\
144
- 'by default image_optim_pack will be used if available, '\
145
- 'will turn on skip-missing-workers unless explicitly disabled') do |pack|
144
+ 'by default image_optim_pack will be used if available, '\
145
+ 'will turn on skip-missing-workers unless explicitly disabled') do |pack|
146
146
  options[:pack] = pack
147
147
  end
148
148
 
@@ -150,12 +150,12 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
150
150
  op.separator ' Caching:'
151
151
 
152
152
  op.on('--cache-dir DIR', 'Cache optimized images '\
153
- 'into the specified directory') do |cache_dir|
153
+ 'into the specified directory') do |cache_dir|
154
154
  options[:cache_dir] = cache_dir
155
155
  end
156
156
 
157
157
  op.on('--cache-worker-digests', 'Cache worker digests '\
158
- '(updating workers invalidates cache)') do |cache_worker_digests|
158
+ '(updating workers invalidates cache)') do |cache_worker_digests|
159
159
  options[:cache_worker_digests] = cache_worker_digests
160
160
  end
161
161
 
@@ -163,7 +163,7 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
163
163
  op.separator ' Disabling workers:'
164
164
 
165
165
  op.on('--[no-]skip-missing-workers', 'Skip workers with missing or '\
166
- 'problematic binaries') do |skip|
166
+ 'problematic binaries') do |skip|
167
167
  options[:skip_missing_workers] = skip
168
168
  end
169
169
 
@@ -178,10 +178,14 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
178
178
  op.separator ' Worker options:'
179
179
 
180
180
  op.on('--allow-lossy', 'Allow lossy workers and '\
181
- 'optimizations') do |allow_lossy|
181
+ 'optimizations') do |allow_lossy|
182
182
  options[:allow_lossy] = allow_lossy
183
183
  end
184
184
 
185
+ op.on('--timeout N', Float, 'Maximum time in seconds to spend on one image') do |timeout|
186
+ options[:timeout] = timeout
187
+ end
188
+
185
189
  op.separator nil
186
190
 
187
191
  ImageOptim::Worker.klasses.each_with_index do |klass, i|
@@ -226,8 +230,8 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
226
230
  op.separator ' Common options:'
227
231
 
228
232
  op.on_tail('-v', '--verbose', 'Verbose output (show global and worker '\
229
- 'config, binary resolution log, information about each tool invocation, '\
230
- 'backtrace of exception)') do
233
+ 'config, binary resolution log, information about each tool invocation, '\
234
+ 'backtrace of exception)') do
231
235
  options[:verbose] = true
232
236
  end
233
237