image_optim 0.26.5 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.appveyor.yml +2 -0
  3. data/.pre-commit-hooks.yaml +9 -0
  4. data/.rubocop.yml +33 -14
  5. data/.travis.yml +17 -15
  6. data/CHANGELOG.markdown +26 -0
  7. data/CONTRIBUTING.markdown +4 -1
  8. data/LICENSE.txt +1 -1
  9. data/README.markdown +9 -3
  10. data/Vagrantfile +1 -1
  11. data/image_optim.gemspec +7 -4
  12. data/lib/image_optim.rb +15 -9
  13. data/lib/image_optim/bin_resolver/bin.rb +10 -8
  14. data/lib/image_optim/bin_resolver/comparable_condition.rb +1 -0
  15. data/lib/image_optim/cache.rb +6 -0
  16. data/lib/image_optim/cache_path.rb +19 -2
  17. data/lib/image_optim/cmd.rb +45 -6
  18. data/lib/image_optim/config.rb +13 -7
  19. data/lib/image_optim/elapsed_time.rb +26 -0
  20. data/lib/image_optim/errors.rb +9 -0
  21. data/lib/image_optim/optimized_path.rb +1 -1
  22. data/lib/image_optim/path.rb +29 -6
  23. data/lib/image_optim/runner/option_parser.rb +6 -0
  24. data/lib/image_optim/timer.rb +25 -0
  25. data/lib/image_optim/worker.rb +29 -26
  26. data/lib/image_optim/worker/advpng.rb +2 -2
  27. data/lib/image_optim/worker/class_methods.rb +2 -0
  28. data/lib/image_optim/worker/gifsicle.rb +3 -3
  29. data/lib/image_optim/worker/jhead.rb +2 -2
  30. data/lib/image_optim/worker/jpegoptim.rb +8 -6
  31. data/lib/image_optim/worker/jpegrecompress.rb +17 -2
  32. data/lib/image_optim/worker/jpegtran.rb +3 -3
  33. data/lib/image_optim/worker/optipng.rb +4 -4
  34. data/lib/image_optim/worker/pngcrush.rb +4 -4
  35. data/lib/image_optim/worker/pngout.rb +2 -2
  36. data/lib/image_optim/worker/pngquant.rb +3 -2
  37. data/lib/image_optim/worker/svgo.rb +2 -2
  38. data/script/update_worker_options_in_readme +1 -1
  39. data/script/worker_analysis +20 -19
  40. data/spec/image_optim/bin_resolver_spec.rb +5 -5
  41. data/spec/image_optim/cache_path_spec.rb +67 -28
  42. data/spec/image_optim/cache_spec.rb +10 -8
  43. data/spec/image_optim/cmd_spec.rb +58 -6
  44. data/spec/image_optim/config_spec.rb +36 -20
  45. data/spec/image_optim/elapsed_time_spec.rb +14 -0
  46. data/spec/image_optim/hash_helpers_spec.rb +18 -18
  47. data/spec/image_optim/option_definition_spec.rb +6 -6
  48. data/spec/image_optim/path_spec.rb +61 -26
  49. data/spec/image_optim/runner/option_parser_spec.rb +4 -4
  50. data/spec/image_optim/timer_spec.rb +32 -0
  51. data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
  52. data/spec/image_optim/worker/optipng_spec.rb +11 -11
  53. data/spec/image_optim/worker/pngquant_spec.rb +5 -5
  54. data/spec/image_optim/worker_spec.rb +17 -17
  55. data/spec/image_optim_spec.rb +50 -12
  56. data/spec/images/quant/generate +2 -2
  57. data/spec/spec_helper.rb +16 -15
  58. metadata +36 -12
@@ -37,7 +37,7 @@ describe ImageOptim::Runner::OptionParser do
37
37
  %w[-r -R --recursive].each do |flag|
38
38
  it "is parsed from #{flag}" do
39
39
  args = %W[#{flag} foo bar]
40
- expect(OptionParser.parse!(args)).to eq(:recursive => true)
40
+ expect(OptionParser.parse!(args)).to eq(recursive: true)
41
41
  expect(args).to eq(%w[foo bar])
42
42
  end
43
43
  end
@@ -46,19 +46,19 @@ describe ImageOptim::Runner::OptionParser do
46
46
  describe 'numeric option threads' do
47
47
  it 'is parsed with space separator' do
48
48
  args = %w[--threads 616 foo bar]
49
- expect(OptionParser.parse!(args)).to eq(:threads => 616)
49
+ expect(OptionParser.parse!(args)).to eq(threads: 616)
50
50
  expect(args).to eq(%w[foo bar])
51
51
  end
52
52
 
53
53
  it 'is parsed with equal separator' do
54
54
  args = %w[--threads=616 foo bar]
55
- expect(OptionParser.parse!(args)).to eq(:threads => 616)
55
+ expect(OptionParser.parse!(args)).to eq(threads: 616)
56
56
  expect(args).to eq(%w[foo bar])
57
57
  end
58
58
 
59
59
  it 'is parsed with no- prefix' do
60
60
  args = %w[--no-threads 616 foo bar]
61
- expect(OptionParser.parse!(args)).to eq(:threads => false)
61
+ expect(OptionParser.parse!(args)).to eq(threads: false)
62
62
  expect(args).to eq(%w[616 foo bar])
63
63
  end
64
64
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'image_optim/timer'
5
+
6
+ describe ImageOptim::Timer do
7
+ let!(:timer){ described_class.new(1) }
8
+
9
+ describe '#elapsed' do
10
+ it 'returns elapsed time' do
11
+ sleep 0.01
12
+
13
+ expect(timer.elapsed).to be >= 0.01
14
+ end
15
+ end
16
+
17
+ describe '#left' do
18
+ it 'returns time left' do
19
+ sleep 0.01
20
+
21
+ expect(timer.left).to be <= 0.99
22
+ end
23
+ end
24
+
25
+ describe '#to_f' do
26
+ it 'returns time left' do
27
+ sleep 0.01
28
+
29
+ expect(timer.to_f).to be <= 0.99
30
+ end
31
+ end
32
+ 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
@@ -10,7 +10,7 @@ describe ImageOptim::Worker::Optipng do
10
10
 
11
11
  let(:options){ {} }
12
12
  let(:optipng_version){ '0.7' }
13
- let(:src){ instance_double(ImageOptim::Path, :copy => nil) }
13
+ let(:src){ instance_double(ImageOptim::Path, copy: nil) }
14
14
  let(:dst){ instance_double(ImageOptim::Path) }
15
15
 
16
16
  before do
@@ -34,7 +34,7 @@ describe ImageOptim::Worker::Optipng do
34
34
  end
35
35
 
36
36
  context 'when strip is disabled' do
37
- let(:options){ {:strip => false} }
37
+ let(:options){ {strip: false} }
38
38
 
39
39
  it 'should not add -strip all to arguments' do
40
40
  expect(subject).to receive(:execute) do |_bin, *args|
@@ -61,42 +61,42 @@ describe ImageOptim::Worker::Optipng do
61
61
  describe '#optimized?' do
62
62
  let(:src){ instance_double(ImageOptim::Path, src_options) }
63
63
  let(:dst){ instance_double(ImageOptim::Path, dst_options) }
64
- let(:src_options){ {:size => 10} }
65
- let(:dst_options){ {:size? => 9} }
64
+ let(:src_options){ {size: 10} }
65
+ let(:dst_options){ {size?: 9} }
66
66
  let(:instance){ described_class.new(ImageOptim.new, instance_options) }
67
67
  let(:instance_options){ {} }
68
68
 
69
69
  subject{ instance.optimized?(src, dst) }
70
70
 
71
71
  context 'when interlace option is enabled' do
72
- let(:instance_options){ {:interlace => true} }
72
+ let(:instance_options){ {interlace: true} }
73
73
 
74
74
  context 'when dst is empty' do
75
- let(:dst_options){ {:size? => nil} }
75
+ let(:dst_options){ {size?: nil} }
76
76
  it{ is_expected.to be_falsy }
77
77
  end
78
78
 
79
79
  context 'when dst is not empty' do
80
- let(:dst_options){ {:size? => 20} }
80
+ let(:dst_options){ {size?: 20} }
81
81
  it{ is_expected.to be_truthy }
82
82
  end
83
83
  end
84
84
 
85
85
  context 'when interlace option is disabled' do
86
- let(:instance_options){ {:interlace => false} }
86
+ let(:instance_options){ {interlace: false} }
87
87
 
88
88
  context 'when dst is empty' do
89
- let(:dst_options){ {:size? => nil} }
89
+ let(:dst_options){ {size?: nil} }
90
90
  it{ is_expected.to be_falsy }
91
91
  end
92
92
 
93
93
  context 'when dst is greater than or equal to src' do
94
- let(:dst_options){ {:size? => 10} }
94
+ let(:dst_options){ {size?: 10} }
95
95
  it{ is_expected.to be_falsy }
96
96
  end
97
97
 
98
98
  context 'when dst is less than src' do
99
- let(:dst_options){ {:size? => 9} }
99
+ let(:dst_options){ {size?: 9} }
100
100
  it{ is_expected.to be_truthy }
101
101
  end
102
102
  end
@@ -22,7 +22,7 @@ describe ImageOptim::Worker::Pngquant do
22
22
  end
23
23
 
24
24
  context 'when value is passed through options' do
25
- let(:options){ {:quality => 10..90} }
25
+ let(:options){ {quality: 10..90} }
26
26
 
27
27
  it 'warns and keeps default' do
28
28
  expect_any_instance_of(described_class).
@@ -34,13 +34,13 @@ describe ImageOptim::Worker::Pngquant do
34
34
 
35
35
  context 'when lossy allowed' do
36
36
  context 'by default' do
37
- let(:options){ {:allow_lossy => true} }
37
+ let(:options){ {allow_lossy: true} }
38
38
 
39
39
  it{ is_expected.to eq(0..100) }
40
40
  end
41
41
 
42
42
  context 'when value is passed through options' do
43
- let(:options){ {:allow_lossy => true, :quality => 10..90} }
43
+ let(:options){ {allow_lossy: true, quality: 10..90} }
44
44
 
45
45
  it 'sets the value without warning' do
46
46
  expect_any_instance_of(described_class).not_to receive(:warn)
@@ -49,7 +49,7 @@ describe ImageOptim::Worker::Pngquant do
49
49
  end
50
50
 
51
51
  context 'when passed range begin is less than 0' do
52
- let(:options){ {:allow_lossy => true, :quality => -50..50} }
52
+ let(:options){ {allow_lossy: true, quality: -50..50} }
53
53
 
54
54
  it 'sets begin to 0' do
55
55
  is_expected.to eq(0..50)
@@ -57,7 +57,7 @@ describe ImageOptim::Worker::Pngquant do
57
57
  end
58
58
 
59
59
  context 'when passed range end is more than 100' do
60
- let(:options){ {:allow_lossy => true, :quality => 50..150} }
60
+ let(:options){ {allow_lossy: true, quality: 50..150} }
61
61
 
62
62
  it 'sets end to 100' do
63
63
  is_expected.to eq(50..100)
@@ -29,9 +29,9 @@ describe ImageOptim::Worker do
29
29
  option(:three, 3, 'Three')
30
30
  end
31
31
 
32
- worker = worker_class.new(ImageOptim.new, :three => '...')
32
+ worker = worker_class.new(ImageOptim.new, three: '...')
33
33
 
34
- expect(worker.options).to eq(:one => 1, :two => 2, :three => '...')
34
+ expect(worker.options).to eq(one: 1, two: 2, three: '...')
35
35
  end
36
36
  end
37
37
 
@@ -72,7 +72,7 @@ describe ImageOptim::Worker do
72
72
  option(:three, 3, 'Three')
73
73
  end)
74
74
 
75
- worker = DefOptim.new(ImageOptim.new, :three => '...')
75
+ worker = DefOptim.new(ImageOptim.new, three: '...')
76
76
 
77
77
  expect(worker.inspect).to eq('#<DefOptim @one=1, @two=2, @three="...">')
78
78
  end
@@ -104,17 +104,17 @@ describe ImageOptim::Worker do
104
104
 
105
105
  it 'create hash by format' do
106
106
  workers = [
107
- double(:image_formats => [:a]),
108
- double(:image_formats => [:a, :b]),
109
- double(:image_formats => [:b, :c]),
107
+ double(image_formats: [:a]),
108
+ double(image_formats: [:a, :b]),
109
+ double(image_formats: [:b, :c]),
110
110
  ]
111
111
 
112
112
  expect(Worker).to receive(:create_all).and_return(workers)
113
113
 
114
114
  worker_by_format = {
115
- :a => [workers[0], workers[1]],
116
- :b => [workers[1], workers[2]],
117
- :c => [workers[2]],
115
+ a: [workers[0], workers[1]],
116
+ b: [workers[1], workers[2]],
117
+ c: [workers[2]],
118
118
  }
119
119
 
120
120
  expect(Worker.create_all_by_format(double)).to eq(worker_by_format)
@@ -123,21 +123,21 @@ describe ImageOptim::Worker do
123
123
 
124
124
  describe '.create_all' do
125
125
  def worker_double(override = {})
126
- stubs = {:resolve_used_bins! => nil, :run_order => 0}.merge(override)
126
+ stubs = {resolve_used_bins!: nil, run_order: 0}.merge(override)
127
127
  instance_double(Worker, stubs)
128
128
  end
129
129
 
130
130
  def worker_class_doubles(workers)
131
- workers.map{ |worker| class_double(Worker, :init => worker) }
131
+ workers.map{ |worker| class_double(Worker, init: worker) }
132
132
  end
133
133
 
134
- let(:image_optim){ double(:allow_lossy => false) }
134
+ let(:image_optim){ double(allow_lossy: false) }
135
135
 
136
136
  it 'creates all workers for which options_proc returns true' do
137
137
  workers = Array.new(3){ worker_double }
138
138
  klasses = worker_class_doubles(workers)
139
139
  options_proc = proc do |klass|
140
- klass == klasses[1] ? {:disable => true} : {}
140
+ klass == klasses[1] ? {disable: true} : {}
141
141
  end
142
142
 
143
143
  allow(Worker).to receive(:klasses).and_return(klasses)
@@ -206,7 +206,7 @@ describe ImageOptim::Worker do
206
206
  it 'orders workers by run_order' do
207
207
  run_orders = [10, -10, 0, 0, 0, 10, -10]
208
208
  workers = run_orders.map do |run_order|
209
- worker_double(:run_order => run_order)
209
+ worker_double(run_order: run_order)
210
210
  end
211
211
  klasses_list = worker_class_doubles(workers)
212
212
 
@@ -243,16 +243,16 @@ describe ImageOptim::Worker do
243
243
  it 'allows overriding per worker' do
244
244
  klasses = worker_class_doubles([worker_double, worker_double])
245
245
  options_proc = proc do |klass|
246
- klass == klasses[1] ? {:allow_lossy => :b} : {}
246
+ klass == klasses[1] ? {allow_lossy: :b} : {}
247
247
  end
248
248
 
249
249
  allow(Worker).to receive(:klasses).and_return(klasses)
250
250
 
251
251
  klasses.each{ |klass| klass.send(:attr_reader, :allow_lossy) }
252
252
  expect(klasses[0]).to receive(:init).
253
- with(image_optim, hash_including(:allow_lossy => false))
253
+ with(image_optim, hash_including(allow_lossy: false))
254
254
  expect(klasses[1]).to receive(:init).
255
- with(image_optim, hash_including(:allow_lossy => :b))
255
+ with(image_optim, hash_including(allow_lossy: :b))
256
256
 
257
257
  Worker.create_all(image_optim, &options_proc)
258
258
  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
@@ -35,7 +36,7 @@ describe ImageOptim do
35
36
 
36
37
  image_optim = ImageOptim.new(options)
37
38
  if Array(worker_klass.init(image_optim)).empty?
38
- image_optim = ImageOptim.new(options.merge(:allow_lossy => true))
39
+ image_optim = ImageOptim.new(options.merge(allow_lossy: true))
39
40
  end
40
41
 
41
42
  expect(test_images.any? do |original|
@@ -58,10 +59,10 @@ describe ImageOptim do
58
59
  rotated = images_dir / 'orient/original.jpg'
59
60
  rotate_images = images_dir.glob('orient/?.jpg')
60
61
 
61
- base_options = {:skip_missing_workers => false}
62
+ base_options = {skip_missing_workers: false}
62
63
  [
63
64
  ['lossless', base_options, 0],
64
- ['lossy', base_options.merge(:allow_lossy => true), 0.001],
65
+ ['lossy', base_options.merge(allow_lossy: true), 0.001],
65
66
  ].each do |type, options, max_difference|
66
67
  it "does it #{type}" do
67
68
  image_optim = ImageOptim.new(options)
@@ -94,8 +95,8 @@ describe ImageOptim do
94
95
  end
95
96
 
96
97
  {
97
- :png => "\211PNG\r\n\032\n",
98
- :jpeg => "\377\330",
98
+ png: "\211PNG\r\n\032\n",
99
+ jpeg: "\377\330",
99
100
  }.each do |type, data|
100
101
  it "ingores broken #{type}" do
101
102
  path = FSPath.temp_file_path
@@ -104,12 +105,49 @@ describe ImageOptim do
104
105
  expect(ImageOptim.optimize_image(path)).to be_nil
105
106
  end
106
107
  end
108
+
109
+ context 'using timeout' do
110
+ let(:timeout){ 123 }
111
+ let(:image_optim){ ImageOptim.new(isolated_options_base.merge(timeout: timeout)) }
112
+ let(:timer){ instance_double(ImageOptim::Timer) }
113
+ let(:workers){ Array.new(3){ instance_double(ImageOptim::Worker) } }
114
+ let(:path){ test_images.first }
115
+
116
+ before do
117
+ allow(ImageOptim::Timer).to receive(:new).with(timeout).and_return(timer)
118
+ allow(image_optim).to receive(:workers_for_image).and_return(workers)
119
+ end
120
+
121
+ it 'sends timeout to every worker' do
122
+ some_path = instance_of(ImageOptim::Path)
123
+
124
+ expect(workers[0]).to receive(:optimize).with(some_path, some_path, timeout: timer)
125
+ expect(workers[1]).to receive(:optimize).with(some_path, some_path, timeout: timer)
126
+ expect(workers[2]).to receive(:optimize).with(some_path, some_path, timeout: timer)
127
+
128
+ image_optim.optimize_image(path)
129
+ end
130
+
131
+ it 'returns nil if there was no success before worker times out' do
132
+ allow(workers[0]).to receive(:optimize).and_return(false)
133
+ allow(workers[1]).to receive(:optimize).and_raise(ImageOptim::Errors::TimeoutExceeded)
134
+
135
+ expect(image_optim.optimize_image(path)).to be_nil
136
+ end
137
+
138
+ it 'returns result if there was success before worker times out' do
139
+ allow(workers[0]).to receive(:optimize).and_return(true)
140
+ allow(workers[1]).to receive(:optimize).and_raise(ImageOptim::Errors::TimeoutExceeded)
141
+
142
+ expect(image_optim.optimize_image(path)).to be_an(ImageOptim::OptimizedPath)
143
+ end
144
+ end
107
145
  end
108
146
 
109
147
  describe '#optimize_image!' do
110
148
  it 'optimizes image and replaces original' do
111
149
  original = double
112
- optimized = double(:original_size => 12_345)
150
+ optimized = double(original_size: 12_345)
113
151
  optimized_wrap = double
114
152
  image_optim = ImageOptim.new
115
153
 
@@ -143,7 +181,7 @@ describe ImageOptim do
143
181
  describe '#optimize_image_data' do
144
182
  it 'create temp file, optimizes image and returns data' do
145
183
  data = double
146
- temp = double(:path => double)
184
+ temp = double(path: double)
147
185
  optimized = double
148
186
  optimized_data = double
149
187
  image_optim = ImageOptim.new
@@ -164,7 +202,7 @@ describe ImageOptim do
164
202
 
165
203
  it 'returns nil if optimization fails' do
166
204
  data = double
167
- temp = double(:path => double)
205
+ temp = double(path: double)
168
206
  image_optim = ImageOptim.new
169
207
 
170
208
  allow(ImageOptim::ImageMeta).to receive(:format_for_data).
@@ -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
data/spec/spec_helper.rb CHANGED
@@ -75,22 +75,23 @@ RSpec::Matchers.define :be_similar_to do |expected, max_difference|
75
75
  end
76
76
  end
77
77
 
78
- module CapabilityCheckHelpers
79
- def any_file_modes_allowed?
80
- Tempfile.open 'posix' do |f|
78
+ SkipConditions = Hash.new do |cache, name|
79
+ cache[name] = case name
80
+ when :any_file_mode_allowed
81
+ Tempfile.open('posix') do |f|
81
82
  File.chmod(0, f.path)
82
- return (File.stat(f.path).mode & 0o777).zero?
83
+ 'full file modes are not support' unless (File.stat(f.path).mode & 0o777).zero?
83
84
  end
84
- end
85
-
86
- def inodes_supported?
87
- !File.stat(__FILE__).ino.zero?
88
- end
89
-
90
- def signals_supported?
91
- Process.kill(0, 0)
92
- true
93
- rescue
94
- false
85
+ when :inodes_support
86
+ 'inodes are not supported' if File.stat(__FILE__).ino.zero?
87
+ when :signals_support
88
+ begin
89
+ Process.kill(0, 0)
90
+ nil
91
+ rescue
92
+ 'signals are not supported'
93
+ end
94
+ else
95
+ fail "Unknown check #{name}"
95
96
  end
96
97
  end