image_optim 0.26.4 → 0.29.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00401e7aa20de4af81903c0c1ddac9d9cbb9eda7d17377f1fdf69dbe3280e346
4
- data.tar.gz: 748fcdbedccbb2bb67082c1fd97b678a9aa1c383d1d91f9089a9a3f84ff1eb3e
3
+ metadata.gz: 9035f1f635ab728b4622dc5e99d2f5e9f96eab86ea35038c4037587fd3ca90d7
4
+ data.tar.gz: d24f6305c1461483d9a67a9338c4e9385ef9282070aecdc53cf0b79e7bffb5ed
5
5
  SHA512:
6
- metadata.gz: 33e4f3be57b23b3752b6992f50b6e8400c43f3273b404c9fe390107ccab85d947e4c69687f12d5e361df6f439568c7233eafa03bd3b7a6d329b068326b16476f
7
- data.tar.gz: 19b8dd22e717e0cd06bf32b11fd4a048f6a1543299bb741980ec7c2e06c35b087b1f142570afb07e1471b33aeef5d041a77c4f2b3488e808a848b45673278ed7
6
+ metadata.gz: 3501154596158ee61209215d7c3a4df1b82d7384d8b54248a16992e5897701eb1e1c10bca9bbbe92312f728e403544dfa6f0dd3c3dfc33918026497550f72ef4
7
+ data.tar.gz: 72ef8a7dacc6a7cc8850d996a716a5df2098cbe47e91f58a051e95664a83494346c0daa03f61384f4bd0c6674a65e4c383ac931863ef31ffed2cc6a8d57ef4a3
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,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
@@ -2,6 +2,7 @@ AllCops:
2
2
  Exclude:
3
3
  - '*.gemspec'
4
4
  - 'vendor/**/*'
5
+ NewCops: enable
5
6
 
6
7
  Bundler/OrderedGems:
7
8
  Enabled: false
@@ -9,6 +10,9 @@ Bundler/OrderedGems:
9
10
  Layout/AccessModifierIndentation:
10
11
  EnforcedStyle: outdent
11
12
 
13
+ Layout/AssignmentIndentation:
14
+ Enabled: false
15
+
12
16
  Layout/CaseIndentation:
13
17
  EnforcedStyle: end
14
18
 
@@ -18,18 +22,18 @@ Layout/DotPosition:
18
22
  Layout/EndAlignment:
19
23
  EnforcedStyleAlignWith: variable
20
24
 
21
- Layout/IndentFirstArrayElement:
25
+ Layout/FirstArrayElementIndentation:
22
26
  EnforcedStyle: consistent
23
27
 
24
- Layout/IndentAssignment:
25
- Enabled: false
26
-
27
- Layout/IndentFirstHashElement:
28
+ Layout/FirstHashElementIndentation:
28
29
  EnforcedStyle: consistent
29
30
 
30
- Layout/IndentHeredoc:
31
+ Layout/HeredocIndentation:
31
32
  Enabled: false
32
33
 
34
+ Layout/LineLength:
35
+ Max: 120
36
+
33
37
  Layout/RescueEnsureAlignment:
34
38
  Enabled: false
35
39
 
@@ -47,10 +51,10 @@ Lint/AmbiguousBlockAssociation:
47
51
  Lint/NestedPercentLiteral:
48
52
  Enabled: false
49
53
 
50
- Lint/UnneededRequireStatement:
54
+ Lint/RedundantRequireStatement:
51
55
  Enabled: false
52
56
 
53
- Lint/UnneededSplatExpansion:
57
+ Lint/RedundantSplatExpansion:
54
58
  Enabled: false
55
59
 
56
60
  Metrics/AbcSize:
@@ -68,19 +72,19 @@ Metrics/ClassLength:
68
72
  Metrics/CyclomaticComplexity:
69
73
  Max: 11
70
74
 
71
- Metrics/LineLength:
72
- Max: 120
73
-
74
75
  Metrics/MethodLength:
75
76
  Max: 25
76
77
 
77
78
  Metrics/PerceivedComplexity:
78
- Max: 8
79
+ Max: 10
79
80
 
80
81
  Security/MarshalLoad:
81
82
  Exclude:
82
83
  - 'script/worker_analysis'
83
84
 
85
+ Style/AccessorGrouping:
86
+ Enabled: false
87
+
84
88
  Style/Alias:
85
89
  EnforcedStyle: prefer_alias_method
86
90
 
@@ -99,15 +103,30 @@ Style/ExpandPathArguments:
99
103
  Style/FormatStringToken:
100
104
  Enabled: false
101
105
 
106
+ Style/HashConversion:
107
+ Enabled: false
108
+
109
+ Style/HashEachMethods:
110
+ Enabled: true
111
+
102
112
  Style/HashSyntax:
103
113
  EnforcedStyle: hash_rockets
104
114
 
115
+ Style/HashTransformKeys:
116
+ Enabled: false
117
+
118
+ Style/HashTransformValues:
119
+ Enabled: false
120
+
105
121
  Style/IfUnlessModifier:
106
122
  Enabled: false
107
123
 
108
124
  Style/NumericPredicate:
109
125
  EnforcedStyle: comparison
110
126
 
127
+ Style/OptionalBooleanParameter:
128
+ Enabled: false
129
+
111
130
  Style/ParallelAssignment:
112
131
  Enabled: false
113
132
 
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
- sudo: false
1
+ dist: xenial
2
2
  language: ruby
3
3
  cache:
4
4
  bundler: true
@@ -6,38 +6,40 @@ cache:
6
6
  - $(npm root)
7
7
  - ~/bin
8
8
  rvm:
9
- - '1.8.7-p371'
10
9
  - '1.9.3-p551'
11
10
  - '2.0.0-p648'
12
11
  - '2.1.10'
13
12
  - '2.2.10'
14
13
  - '2.3.8'
15
- - '2.4.6'
16
- - '2.5.5'
17
- - '2.6.3'
18
- - 'jruby-9.1.9.0'
14
+ - '2.4.10'
15
+ - '2.5.9'
16
+ - '2.6.7'
17
+ - '2.7.3'
18
+ - '3.0.1'
19
+ - 'jruby-9.2.14.0'
19
20
  script:
20
21
  - bundle exec image_optim --info
21
22
  - bundle exec rspec
22
23
  before_install:
24
+ - 'echo "gem: --no-ri --no-rdoc --no-document" > ~/.gemrc'
23
25
  - gem install rubygems-update || gem install rubygems-update --version '< 3'
24
- - gem update --system
26
+ - update_rubygems
25
27
  - gem install bundler || gem install bundler --version '< 2'
26
28
  - nvm install stable
27
- - mkdir -p ~/bin
28
29
  - command -v svgo || npm install -g svgo
29
- - command -v pngout || curl -L "http://static.jonof.id.au/dl/kenutils/pngout-20150319-linux.tar.gz" | tar -xz -C ~/bin --strip-components 2 --wildcards '*/x86_64/pngout'
30
+ - mkdir -p ~/bin
31
+ - command -v pngout || curl -L "https://www.jonof.id.au/files/kenutils/pngout-20200115-linux.tar.gz" | tar -xz -C ~/bin --strip-components 2 --wildcards '*/amd64/pngout'
30
32
  matrix:
31
33
  include:
32
- - env: CODECLIMATE=✓
33
- rvm: '2.4.6'
34
+ - env: CODECLIMATE=1
35
+ rvm: '2.4.10'
34
36
  after_success: bundle exec codeclimate-test-reporter
35
- - env: RUBOCOP=✓
36
- rvm: '2.4.6'
37
+ - env: RUBOCOP=1
38
+ rvm: '2.4.10'
37
39
  script: bundle exec rubocop
38
40
  before_install: gem update --system && gem install bundler
39
- - env: CHECK_RUBIES=✓
40
- rvm: '2.4.6'
41
+ - env: CHECK_RUBIES=1
42
+ rvm: '2.4.10'
41
43
  script: bundle exec travis_check_rubies
42
44
  before_install: gem update --system && gem install bundler
43
45
  addons:
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v0.29.0 (2021-04-28)
6
+
7
+ * Require at least ruby 1.9.3 [@toy](https://github.com/toy)
8
+ * 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)
9
+ * 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)
10
+ * More precise regular expression for capturing svgo version [@toy](https://github.com/toy)
11
+
12
+ ## v0.28.0 (2020-12-18)
13
+
14
+ * 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)
15
+ * 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)
16
+ * 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)
17
+
18
+ ## v0.27.1 (2020-09-30)
19
+
20
+ * 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)
21
+
22
+ ## v0.27.0 (2020-08-27)
23
+
24
+ * Use `.tmp` as the extension for temporary file if it needs to be created for atomic replacement [#178](https://github.com/toy/image_optim/issues/178) [@toy](https://github.com/toy)
25
+ * Don't create a temporary file in destination directory for atomic replacement if temporary directory is on same device as destination [#178](https://github.com/toy/image_optim/issues/178) [@toy](https://github.com/toy)
26
+
27
+ ## v0.26.5 (2019-07-14)
28
+
29
+ * Remove deprecated `rubyforge_project` attribute from gemspec [rubygems/rubygems#2436](https://github.com/rubygems/rubygems/pull/2436) [@toy](https://github.com/toy)
30
+
5
31
  ## v0.26.4 (2019-05-23)
6
32
 
7
33
  * Enable frozen string literals [@toy](https://github.com/toy)
@@ -2,7 +2,10 @@
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
data/Gemfile CHANGED
@@ -15,5 +15,3 @@ end
15
15
  if ENV['CHECK_RUBIES']
16
16
  gem 'travis_check_rubies', '~> 0.2'
17
17
  end
18
-
19
- gem 'rspec-expectations', '!= 3.8.3' # https://github.com/rspec/rspec-expectations/issues/1113
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2019 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
@@ -60,7 +60,7 @@ With version:
60
60
 
61
61
  <!---<update-version>-->
62
62
  ```ruby
63
- gem 'image_optim', '~> 0.26'
63
+ gem 'image_optim', '~> 0.29'
64
64
  ```
65
65
  <!---</update-version>-->
66
66
 
@@ -277,6 +277,10 @@ optipng:
277
277
  level: 5
278
278
  ```
279
279
 
280
+ ### Temporary directory
281
+
282
+ `image_optim` uses standard ruby library for creating temporary files. Temporary directory can be changed using one of `TMPDIR`, `TMP` or `TEMP` environment variables.
283
+
280
284
  ## Options
281
285
 
282
286
  * `:nice` — Nice level, priority of all used tools with higher value meaning lower priority, in range `-20..19`, negative values can be set only if run by root user *(defaults to `10`)*
@@ -306,12 +310,13 @@ Worker has no options
306
310
 
307
311
  ### jpegoptim:
308
312
  * `:allow_lossy` — Allow limiting maximum quality *(defaults to `false`)*
309
- * `:strip` — List of extra markers to strip: `:comments`, `:exif`, `:iptc`, `:icc` or `:all` *(defaults to `:all`)*
313
+ * `:strip` — List of markers to strip: `:com`, `:exif`, `:iptc`, `:icc`, `:xmp`, `:none` or `:all` *(defaults to `:all`)*
310
314
  * `:max_quality` — Maximum image quality factor `0`..`100`, ignored in default/lossless mode *(defaults to `100`)*
311
315
 
312
316
  ### jpegrecompress:
313
317
  * `:allow_lossy` — Allow worker, it is always lossy *(defaults to `false`)*
314
318
  * `:quality` — JPEG quality preset: `0` - low, `1` - medium, `2` - high, `3` - veryhigh *(defaults to `3`)*
319
+ * `: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)*
315
320
 
316
321
  ### jpegtran:
317
322
  * `:copy_chunks` — Copy all chunks *(defaults to `false`)*
@@ -357,4 +362,4 @@ In separate file [CHANGELOG.markdown](CHANGELOG.markdown).
357
362
 
358
363
  ## Copyright
359
364
 
360
- Copyright (c) 2012-2019 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
365
+ Copyright (c) 2012-2021 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
data/image_optim.gemspec CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_optim'
5
- s.version = '0.26.4'
5
+ s.version = '0.29.0'
6
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)}
7
- s.homepage = "http://github.com/toy/#{s.name}"
7
+ s.homepage = "https://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
9
9
  s.license = 'MIT'
10
10
 
11
- s.rubyforge_project = s.name
11
+ s.required_ruby_version = '>= 1.9.3'
12
12
 
13
13
  s.metadata = {
14
14
  'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
@@ -35,7 +35,8 @@ EOF
35
35
 
36
36
  s.add_development_dependency 'image_optim_pack', '~> 0.2', '>= 0.2.2'
37
37
  s.add_development_dependency 'rspec', '~> 3.0'
38
- if RUBY_VERSION >= '2.2' && !Gem.win_platform? && !defined?(JRUBY_VERSION)
39
- s.add_development_dependency 'rubocop', '~> 0.59'
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'
40
41
  end
41
42
  end
data/lib/image_optim.rb CHANGED
@@ -188,12 +188,6 @@ class ImageOptim
188
188
  optimize_image_method?(method) || super
189
189
  end
190
190
 
191
- if RUBY_VERSION < '1.9'
192
- def respond_to?(method, include_private = false)
193
- optimize_image_method?(method) || super
194
- end
195
- end
196
-
197
191
  # Version of image_optim gem spec loaded
198
192
  def version
199
193
  Gem.loaded_specs['image_optim'].version.to_s
@@ -14,9 +14,11 @@ 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
21
+
20
22
  def initialize(name, path)
21
23
  @name = name.to_sym
22
24
  @path = path.to_s
@@ -110,7 +112,7 @@ class ImageOptim
110
112
  when :gifsicle, :jpegoptim, :optipng
111
113
  capture("#{escaped_path} --version 2> #{Path::NULL}")[/\d+(\.\d+)+/]
112
114
  when :svgo, :pngquant
113
- capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+)+/]
115
+ capture("#{escaped_path} --version 2>&1")[/\A\d+(\.\d+)+/]
114
116
  when :jhead, :'jpeg-recompress'
115
117
  capture("#{escaped_path} -V 2> #{Path::NULL}")[/\d+(\.\d+)+/]
116
118
  when :jpegtran
@@ -26,6 +26,7 @@ class ImageOptim
26
26
  end
27
27
 
28
28
  attr_reader :method, :args
29
+
29
30
  def initialize(method, *args)
30
31
  @method, @args = method.to_sym, args
31
32
 
@@ -7,8 +7,25 @@ class ImageOptim
7
7
  class CachePath < Path
8
8
  # Atomic replace dst with self
9
9
  def replace(dst)
10
- dst = self.class.new(dst)
11
- dst.temp_path(dst.dirname) do |temp|
10
+ dst = self.class.convert(dst)
11
+ tmpdir = [dirname, Path.new(Dir.tmpdir)].find do |dir|
12
+ dir.same_dev?(dst.dirname)
13
+ end
14
+ if tmpdir
15
+ begin
16
+ replace_using_tmp_file(dst, tmpdir)
17
+ rescue Errno::EXDEV
18
+ replace_using_tmp_file(dst, dst.dirname)
19
+ end
20
+ else
21
+ replace_using_tmp_file(dst, dst.dirname)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def replace_using_tmp_file(dst, tmpdir)
28
+ dst.temp_path_with_tmp_ext(tmpdir) do |temp|
12
29
  copy(temp)
13
30
  dst.copy_metadata(temp)
14
31
  temp.rename(dst.to_s)
@@ -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'
@@ -197,10 +195,10 @@ class ImageOptim
197
195
  when /darwin9/
198
196
  Cmd.capture 'hwprefs cpu_count'
199
197
  when /darwin/
200
- if (Cmd.capture 'which hwprefs') != ''
201
- Cmd.capture 'hwprefs thread_count'
202
- else
198
+ if (Cmd.capture 'which hwprefs') == ''
203
199
  Cmd.capture 'sysctl -n hw.ncpu'
200
+ else
201
+ Cmd.capture 'hwprefs thread_count'
204
202
  end
205
203
  when /linux/
206
204
  Cmd.capture 'grep -c processor /proc/cpuinfo'
@@ -7,7 +7,7 @@ class ImageOptim
7
7
  class OptimizedPath < DelegateClass(Path)
8
8
  def initialize(path, original_or_size = nil)
9
9
  path = Path.convert(path)
10
- __setobj__(path)
10
+ super(path)
11
11
  if original_or_size.is_a?(Integer)
12
12
  @original = path
13
13
  @original_size = original_or_size
@@ -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)
@@ -50,11 +50,16 @@ class ImageOptim
50
50
 
51
51
  # Atomic replace dst with self
52
52
  def replace(dst)
53
- dst = self.class.new(dst)
54
- dst.temp_path(dst.dirname) do |temp|
55
- move(temp)
56
- dst.copy_metadata(temp)
57
- temp.rename(dst.to_s)
53
+ dst = self.class.convert(dst)
54
+ if same_dev?(dst.dirname)
55
+ dst.copy_metadata(self)
56
+ begin
57
+ rename(dst.to_s)
58
+ rescue Errno::EXDEV
59
+ replace_using_tmp_file(dst)
60
+ end
61
+ else
62
+ replace_using_tmp_file(dst)
58
63
  end
59
64
  end
60
65
 
@@ -68,5 +73,23 @@ class ImageOptim
68
73
  def self.convert(path)
69
74
  path.is_a?(self) ? path : new(path)
70
75
  end
76
+
77
+ protected
78
+
79
+ def same_dev?(other)
80
+ stat.dev == other.stat.dev
81
+ end
82
+
83
+ def replace_using_tmp_file(dst)
84
+ dst.temp_path_with_tmp_ext(dst.dirname) do |temp|
85
+ move(temp)
86
+ dst.copy_metadata(temp)
87
+ temp.rename(dst.to_s)
88
+ end
89
+ end
90
+
91
+ def temp_path_with_tmp_ext(*args, &block)
92
+ self.class.temp_file_path([basename.to_s, '.tmp'], *args, &block)
93
+ end
71
94
  end
72
95
  end
@@ -202,6 +202,8 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
202
202
  [Integer, 'N']
203
203
  when Array >= type
204
204
  [Array, 'a,b,c']
205
+ when String >= type
206
+ [String, 'S']
205
207
  when ImageOptim::NonNegativeIntegerRange == type
206
208
  [type, 'M-N']
207
209
  else
@@ -142,21 +142,12 @@ class ImageOptim
142
142
  # Run command defining environment, setting nice level, removing output and
143
143
  # reraising signal exception
144
144
  def run_command(cmd_args)
145
- args = if RUBY_VERSION < '1.9' || defined?(JRUBY_VERSION)
146
- %W[
147
- env PATH=#{@image_optim.env_path.shellescape}
148
- nice -n #{@image_optim.nice}
149
- #{cmd_args.shelljoin}
150
- > #{Path::NULL} 2>&1
151
- ].join(' ')
152
- else
153
- [
154
- {'PATH' => @image_optim.env_path},
155
- %W[nice -n #{@image_optim.nice}],
156
- cmd_args,
157
- {:out => Path::NULL, :err => Path::NULL},
158
- ].flatten
159
- end
145
+ args = [
146
+ {'PATH' => @image_optim.env_path},
147
+ *%W[nice -n #{@image_optim.nice}],
148
+ *cmd_args,
149
+ {:out => Path::NULL, :err => Path::NULL},
150
+ ]
160
151
  Cmd.run(*args)
161
152
  end
162
153
  end
@@ -18,6 +18,7 @@ class ImageOptim
18
18
 
19
19
  # Remember all classes inheriting from this one
20
20
  def inherited(base)
21
+ super
21
22
  @klasses << base
22
23
  end
23
24
 
@@ -36,6 +37,7 @@ class ImageOptim
36
37
 
37
38
  def option(name, default, type, description = nil, &proc)
38
39
  attr_reader name
40
+
39
41
  OptionDefinition.new(name, default, type, description, &proc).
40
42
  tap{ |option_definition| option_definitions << option_definition }
41
43
  end
@@ -11,14 +11,16 @@ class ImageOptim
11
11
  option(:allow_lossy, false, 'Allow limiting maximum quality'){ |v| !!v }
12
12
 
13
13
  STRIP_OPTION =
14
- option(:strip, :all, Array, 'List of extra markers to strip: '\
15
- '`:comments`, '\
14
+ option(:strip, :all, Array, 'List of markers to strip: '\
15
+ '`:com`, '\
16
16
  '`:exif`, '\
17
17
  '`:iptc`, '\
18
- '`:icc` or '\
18
+ '`:icc`, '\
19
+ '`:xmp`, '\
20
+ '`:none` or '\
19
21
  '`:all`') do |v|
20
22
  values = Array(v).map(&:to_s)
21
- known_values = %w[all comments exif iptc icc]
23
+ known_values = %w[com exif iptc icc xmp none all]
22
24
  unknown_values = values - known_values
23
25
  unless unknown_values.empty?
24
26
  warn "Unknown markers for jpegoptim: #{unknown_values.join(', ')}"
@@ -26,6 +26,20 @@ class ImageOptim
26
26
  OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
27
27
  end
28
28
 
29
+ METHOD_OPTION =
30
+ option(:method, 'ssim', 'Comparison Metric: '\
31
+ '`mpe` - Mean pixel error, '\
32
+ '`ssim` - Structural similarity, '\
33
+ '`ms-ssim` - Multi-scale structural similarity (slow!), '\
34
+ '`smallfry` - Linear-weighted BBCQ-like (may be patented)') do |v, opt_def|
35
+ if %w[mpe ssim ms-ssim smallfry].include? v
36
+ v
37
+ else
38
+ warn "Unknown method for jpegrecompress: #{v}"
39
+ opt_def.default
40
+ end
41
+ end
42
+
29
43
  def used_bins
30
44
  [:'jpeg-recompress']
31
45
  end
@@ -38,6 +52,7 @@ class ImageOptim
38
52
  def optimize(src, dst)
39
53
  args = %W[
40
54
  --quality #{QUALITY_NAMES[quality]}
55
+ --method #{method}
41
56
  --no-copy
42
57
  #{src}
43
58
  #{dst}
@@ -39,8 +39,8 @@ class ImageOptim
39
39
  #{dst}
40
40
  ]
41
41
  args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
42
- if resolve_bin!(:optipng).version >= '0.7'
43
- args.unshift '-strip', 'all' if strip
42
+ if strip && resolve_bin!(:optipng).version >= '0.7'
43
+ args.unshift '-strip', 'all'
44
44
  end
45
45
  execute(:optipng, *args) && optimized?(src, dst)
46
46
  end
@@ -39,8 +39,8 @@ class ImageOptim
39
39
  end
40
40
  flags.push '-fix' if fix
41
41
  flags.push '-brute' if brute
42
- if resolve_bin!(:pngcrush).version >= '1.7.38'
43
- flags.push '-blacken' if blacken
42
+ if blacken && resolve_bin!(:pngcrush).version >= '1.7.38'
43
+ flags.push '-blacken'
44
44
  end
45
45
 
46
46
  args = flags + %W[
@@ -55,6 +55,7 @@ class ImageOptim
55
55
  --quality=#{quality.begin}-#{quality.end}
56
56
  --speed=#{speed}
57
57
  --output=#{dst}
58
+ --skip-if-larger
58
59
  --force
59
60
  #{max_colors}
60
61
  --
@@ -18,7 +18,7 @@ def write_worker_options(io, klass)
18
18
  klass.option_definitions.each do |option_definition|
19
19
  line = "* `:#{option_definition.name}` — #{option_definition.description}"
20
20
  unless line['(defaults']
21
- line << " *(defaults to #{option_definition.default_description})*"
21
+ line += " *(defaults to #{option_definition.default_description})*"
22
22
  end
23
23
  io.puts line
24
24
  end
@@ -137,13 +137,14 @@ class Analyser
137
137
  # Delegate to worker with short id
138
138
  class WorkerVariant < DelegateClass(ImageOptim::Worker)
139
139
  attr_reader :name, :id, :cons_id, :required
140
+
140
141
  def initialize(klass, image_optim, options)
141
142
  @required = options.delete(:required)
142
143
  @run_order = options.delete(:run_order)
143
144
  allow_consecutive_on = Array(options.delete(:allow_consecutive_on))
144
145
  @image_optim = image_optim
145
146
  @name = klass.bin_sym.to_s + options_string(options)
146
- __setobj__(klass.new(image_optim, options))
147
+ super(klass.new(image_optim, options))
147
148
  @id = klass.bin_sym.to_s + options_string(self.options)
148
149
  @cons_id = [klass, allow_consecutive_on.map{ |key| [key, send(key)] }]
149
150
  end
@@ -357,20 +358,18 @@ class Analyser
357
358
  end
358
359
 
359
360
  def flatten_animation(image)
360
- run_cache[:flatten][image.digest] ||= begin
361
- if image.image_format == :gif
362
- flattened = image.temp_path
363
- Cmd.run(*%W[
364
- convert
365
- #{image.image_format}:#{image}
366
- -coalesce
367
- -append
368
- #{image.image_format}:#{flattened}
369
- ]) || fail("failed flattening of #{image}")
370
- flattened
371
- else
372
- image
373
- end
361
+ run_cache[:flatten][image.digest] ||= if image.image_format == :gif
362
+ flattened = image.temp_path
363
+ Cmd.run(*%W[
364
+ convert
365
+ #{image.image_format}:#{image}
366
+ -coalesce
367
+ -append
368
+ #{image.image_format}:#{flattened}
369
+ ]) || fail("failed flattening of #{image}")
370
+ flattened
371
+ else
372
+ image
374
373
  end
375
374
  end
376
375
 
@@ -452,6 +451,7 @@ class Analyser
452
451
  attr_reader :name
453
452
  attr_reader :success_count
454
453
  attr_reader :time, :avg_time
454
+
455
455
  def initialize(name, steps)
456
456
  @name = name
457
457
  @success_count = steps.count(&:success)
@@ -465,6 +465,7 @@ class Analyser
465
465
  end
466
466
 
467
467
  attr_reader :name, :results, :ids2names
468
+
468
469
  def initialize(name, results, ids2names)
469
470
  @name = name.to_s
470
471
  @results = results
@@ -8,52 +8,95 @@ describe ImageOptim::CachePath do
8
8
  include CapabilityCheckHelpers
9
9
 
10
10
  before do
11
+ stub_const('Path', ImageOptim::Path)
11
12
  stub_const('CachePath', ImageOptim::CachePath)
12
13
  end
13
14
 
14
15
  describe '#replace' do
15
- let(:src){ CachePath.temp_file_path }
16
- let(:dst){ CachePath.temp_file_path }
16
+ let(:src_dir){ Path.temp_dir }
17
+ let(:src){ CachePath.temp_file_path(nil, src_dir) }
18
+ let(:dst){ Path.temp_file_path }
17
19
 
18
- it 'moves data to destination' do
19
- src.write('src')
20
+ shared_examples 'replaces file' do
21
+ it 'moves data to destination' do
22
+ src.write('src')
20
23
 
21
- src.replace(dst)
24
+ src.replace(dst)
22
25
 
23
- expect(dst.read).to eq('src')
24
- end
26
+ expect(dst.read).to eq('src')
27
+ end
25
28
 
26
- it 'does not remove original file' do
27
- src.replace(dst)
29
+ it 'does not remove original file' do
30
+ src.replace(dst)
28
31
 
29
- expect(src).to exist
30
- end
32
+ expect(src).to exist
33
+ end
34
+
35
+ it 'preserves attributes of destination file' do
36
+ skip 'full file modes are not support' unless any_file_modes_allowed?
37
+ mode = 0o666
38
+
39
+ dst.chmod(mode)
40
+
41
+ src.replace(dst)
31
42
 
32
- it 'preserves attributes of destination file' do
33
- skip 'full file modes are not support' unless any_file_modes_allowed?
34
- mode = 0o666
43
+ got = dst.stat.mode & 0o777
44
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
45
+ end
35
46
 
36
- dst.chmod(mode)
47
+ it 'does not preserve mtime of destination file' do
48
+ time = src.mtime
37
49
 
38
- src.replace(dst)
50
+ dst.utime(time - 1000, time - 1000)
39
51
 
40
- got = dst.stat.mode & 0o777
41
- expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
52
+ src.replace(dst)
53
+
54
+ expect(dst.mtime).to be >= time
55
+ end
56
+
57
+ it 'changes inode of destination' do
58
+ skip 'inodes are not supported' unless inodes_supported?
59
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
60
+ end
61
+
62
+ it 'is using temporary file with .tmp extension' do
63
+ expect(src).to receive(:copy).with(having_attributes(:extname => '.tmp')).at_least(:once)
64
+
65
+ src.replace(dst)
66
+ end
42
67
  end
43
68
 
44
- it 'does not preserve mtime of destination file' do
45
- time = src.mtime
69
+ context 'when src and dst are on same device' do
70
+ before do
71
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
72
+ end
46
73
 
47
- dst.utime(time - 1000, time - 1000)
74
+ include_examples 'replaces file'
75
+ end
48
76
 
49
- src.replace(dst)
77
+ context 'when src and dst are on different devices' do
78
+ before do
79
+ allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
80
+ end
50
81
 
51
- expect(dst.mtime).to be >= time
82
+ include_examples 'replaces file'
52
83
  end
53
84
 
54
- it 'changes inode of destination' do
55
- skip 'inodes are not supported' unless inodes_supported?
56
- expect{ src.replace(dst) }.to change{ dst.stat.ino }
85
+ context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
86
+ before do
87
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
88
+ allow(described_class).to receive(:temp_file_path).and_call_original
89
+ expect(described_class).to receive(:temp_file_path).
90
+ with([dst.basename.to_s, '.tmp'], src.dirname).
91
+ and_wrap_original do |m, *args, &block|
92
+ m.call(*args) do |tmp|
93
+ expect(tmp).to receive(:rename).with(dst.to_s).and_raise(Errno::EXDEV)
94
+ block.call(tmp)
95
+ end
96
+ end
97
+ end
98
+
99
+ include_examples 'replaces file'
57
100
  end
58
101
  end
59
102
  end
@@ -64,6 +64,7 @@ describe ImageOptim::Cache do
64
64
  end
65
65
  end
66
66
 
67
+ # rubocop:disable Style/RedundantFetchBlock
67
68
  shared_examples 'an enabled cache' do
68
69
  context 'when cached file does not exist' do
69
70
  describe :fetch do
@@ -101,7 +102,7 @@ describe ImageOptim::Cache do
101
102
  expect(FileUtils).not_to receive(:mv)
102
103
  expect(File).not_to receive(:rename)
103
104
 
104
- expect(cache.fetch(original){}).to eq(cached)
105
+ expect(cache.fetch(original){ nil }).to eq(cached)
105
106
  end
106
107
 
107
108
  it 'returns nil when file is already optimized' do
@@ -116,6 +117,7 @@ describe ImageOptim::Cache do
116
117
  end
117
118
  end
118
119
  end
120
+ # rubocop:enable Style/RedundantFetchBlock
119
121
 
120
122
  context 'when cache is enabled (without worker digests)' do
121
123
  let(:image_optim) do
@@ -61,45 +61,84 @@ describe ImageOptim::Path do
61
61
  let(:src){ Path.temp_file_path }
62
62
  let(:dst){ Path.temp_file_path }
63
63
 
64
- it 'moves data to destination' do
65
- src.write('src')
64
+ shared_examples 'replaces file' do
65
+ it 'moves data to destination' do
66
+ src.write('src')
66
67
 
67
- src.replace(dst)
68
+ src.replace(dst)
68
69
 
69
- expect(dst.read).to eq('src')
70
- end
70
+ expect(dst.read).to eq('src')
71
+ end
71
72
 
72
- it 'removes original file' do
73
- src.replace(dst)
73
+ it 'removes original file' do
74
+ src.replace(dst)
74
75
 
75
- expect(src).to_not exist
76
- end
76
+ expect(src).to_not exist
77
+ end
78
+
79
+ it 'preserves attributes of destination file' do
80
+ skip 'full file modes are not support' unless any_file_modes_allowed?
81
+ mode = 0o666
82
+
83
+ dst.chmod(mode)
77
84
 
78
- it 'preserves attributes of destination file' do
79
- skip 'full file modes are not support' unless any_file_modes_allowed?
80
- mode = 0o666
85
+ src.replace(dst)
81
86
 
82
- dst.chmod(mode)
87
+ got = dst.stat.mode & 0o777
88
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
89
+ end
90
+
91
+ it 'does not preserve mtime of destination file' do
92
+ time = src.mtime
93
+
94
+ dst.utime(time - 1000, time - 1000)
83
95
 
84
- src.replace(dst)
96
+ src.replace(dst)
85
97
 
86
- got = dst.stat.mode & 0o777
87
- expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
98
+ expect(dst.mtime).to be >= time
99
+ end
100
+
101
+ it 'changes inode of destination' do
102
+ skip 'inodes are not supported' unless inodes_supported?
103
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
104
+ end
88
105
  end
89
106
 
90
- it 'does not preserve mtime of destination file' do
91
- time = src.mtime
107
+ context 'when src and dst are on same device' do
108
+ before do
109
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
110
+ end
111
+
112
+ include_examples 'replaces file'
113
+ end
92
114
 
93
- dst.utime(time - 1000, time - 1000)
115
+ context 'when src and dst are on different devices' do
116
+ before do
117
+ allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
118
+ end
119
+
120
+ include_examples 'replaces file'
94
121
 
95
- src.replace(dst)
122
+ it 'is using temporary file with .tmp extension' do
123
+ expect(src).to receive(:move).with(having_attributes(:extname => '.tmp'))
96
124
 
97
- expect(dst.mtime).to be >= time
125
+ src.replace(dst)
126
+ end
98
127
  end
99
128
 
100
- it 'changes inode of destination' do
101
- skip 'inodes are not supported' unless inodes_supported?
102
- expect{ src.replace(dst) }.to change{ dst.stat.ino }
129
+ context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
130
+ before do
131
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
132
+ expect(src).to receive(:rename).with(dst.to_s).once.and_raise(Errno::EXDEV)
133
+ end
134
+
135
+ include_examples 'replaces file'
136
+
137
+ it 'is using temporary file with .tmp extension' do
138
+ expect(src).to receive(:move).with(having_attributes(:extname => '.tmp'))
139
+
140
+ src.replace(dst)
141
+ end
103
142
  end
104
143
  end
105
144
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'image_optim/worker/jpegrecompress'
5
+
6
+ describe ImageOptim::Worker::Jpegrecompress do
7
+ describe 'method value' do
8
+ let(:subject){ described_class.new(ImageOptim.new, method).method }
9
+
10
+ context 'default' do
11
+ let(:method){ {} }
12
+
13
+ it{ is_expected.to eq('ssim') }
14
+ end
15
+
16
+ context 'uses default when invalid' do
17
+ let(:method){ {:method => 'invalid'} }
18
+
19
+ it 'warns and keeps default' do
20
+ expect_any_instance_of(described_class).
21
+ to receive(:warn).with('Unknown method for jpegrecompress: invalid')
22
+ is_expected.to eq('ssim')
23
+ end
24
+ end
25
+
26
+ context 'can use a valid option' do
27
+ let(:method){ {:method => 'smallfry'} }
28
+
29
+ it{ is_expected.to eq('smallfry') }
30
+ end
31
+ end
32
+ end
@@ -23,10 +23,11 @@ describe ImageOptim do
23
23
  stub_const('Cmd', ImageOptim::Cmd)
24
24
  end
25
25
 
26
- isolated_options_base = {:skip_missing_workers => false}
27
- ImageOptim::Worker.klasses.each do |klass|
28
- isolated_options_base[klass.bin_sym] = false
29
- end
26
+ isolated_options_base = Hash[
27
+ ImageOptim::Worker.klasses.map do |klass|
28
+ [klass.bin_sym, false]
29
+ end
30
+ ].merge(:skip_missing_workers => false)
30
31
 
31
32
  ImageOptim::Worker.klasses.each do |worker_klass|
32
33
  describe "#{worker_klass.bin_sym} worker" do
@@ -17,8 +17,8 @@ palettes.each do |palette|
17
17
  rgb:-
18
18
  PNG24:#{palette}.png
19
19
  ].shelljoin, 'w') do |f|
20
- (side * side).times do |i|
21
- color = i * palette / (side * side) * 0x10000 / palette
20
+ (side**2).times do |i|
21
+ color = i * palette / (side**2) * 0x10000 / palette
22
22
  f << [color / 0x100, color % 0x100, 0].pack('C*')
23
23
  end
24
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_optim
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.4
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Kuchin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-23 00:00:00.000000000 Z
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fspath
@@ -138,16 +138,30 @@ dependencies:
138
138
  requirements:
139
139
  - - "~>"
140
140
  - !ruby/object:Gem::Version
141
- version: '0.59'
141
+ version: '1.0'
142
142
  type: :development
143
143
  prerelease: false
144
144
  version_requirements: !ruby/object:Gem::Requirement
145
145
  requirements:
146
146
  - - "~>"
147
147
  - !ruby/object:Gem::Version
148
- version: '0.59'
149
- description:
150
- email:
148
+ version: '1.0'
149
+ - !ruby/object:Gem::Dependency
150
+ name: rubocop-rspec
151
+ requirement: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '2.0'
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: '2.0'
163
+ description:
164
+ email:
151
165
  executables:
152
166
  - image_optim
153
167
  extensions: []
@@ -155,6 +169,7 @@ extra_rdoc_files: []
155
169
  files:
156
170
  - ".appveyor.yml"
157
171
  - ".gitignore"
172
+ - ".pre-commit-hooks.yaml"
158
173
  - ".rubocop.yml"
159
174
  - ".travis.yml"
160
175
  - CHANGELOG.markdown
@@ -224,6 +239,7 @@ files:
224
239
  - spec/image_optim/runner/glob_helpers_spec.rb
225
240
  - spec/image_optim/runner/option_parser_spec.rb
226
241
  - spec/image_optim/space_spec.rb
242
+ - spec/image_optim/worker/jpegrecompress_spec.rb
227
243
  - spec/image_optim/worker/optipng_spec.rb
228
244
  - spec/image_optim/worker/pngquant_spec.rb
229
245
  - spec/image_optim/worker_spec.rb
@@ -257,13 +273,13 @@ files:
257
273
  - spec/spec_helper.rb
258
274
  - vendor/jpegrescan
259
275
  - vendor/jpegrescan.bat
260
- homepage: http://github.com/toy/image_optim
276
+ homepage: https://github.com/toy/image_optim
261
277
  licenses:
262
278
  - MIT
263
279
  metadata:
264
280
  bug_tracker_uri: https://github.com/toy/image_optim/issues
265
281
  changelog_uri: https://github.com/toy/image_optim/blob/master/CHANGELOG.markdown
266
- documentation_uri: https://www.rubydoc.info/gems/image_optim/0.26.4
282
+ documentation_uri: https://www.rubydoc.info/gems/image_optim/0.29.0
267
283
  source_code_uri: https://github.com/toy/image_optim
268
284
  post_install_message: |
269
285
  Rails image assets optimization is extracted into image_optim_rails gem
@@ -275,15 +291,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
275
291
  requirements:
276
292
  - - ">="
277
293
  - !ruby/object:Gem::Version
278
- version: '0'
294
+ version: 1.9.3
279
295
  required_rubygems_version: !ruby/object:Gem::Requirement
280
296
  requirements:
281
297
  - - ">="
282
298
  - !ruby/object:Gem::Version
283
299
  version: '0'
284
300
  requirements: []
285
- rubygems_version: 3.0.3
286
- signing_key:
301
+ rubygems_version: 3.1.5
302
+ signing_key:
287
303
  specification_version: 4
288
304
  summary: Command line tool and ruby interface to optimize (lossless compress, optionally
289
305
  lossy) jpeg, png, gif and svg images using external utilities (advpng, gifsicle,
@@ -307,6 +323,7 @@ test_files:
307
323
  - spec/image_optim/runner/glob_helpers_spec.rb
308
324
  - spec/image_optim/runner/option_parser_spec.rb
309
325
  - spec/image_optim/space_spec.rb
326
+ - spec/image_optim/worker/jpegrecompress_spec.rb
310
327
  - spec/image_optim/worker/optipng_spec.rb
311
328
  - spec/image_optim/worker/pngquant_spec.rb
312
329
  - spec/image_optim/worker_spec.rb