image_optim 0.26.4 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
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