image_optim 0.28.0 → 0.31.1

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/.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