image_optim 0.22.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +8 -8
  2. data/.appveyor.yml +95 -0
  3. data/.rubocop.yml +3 -0
  4. data/.travis.yml +27 -22
  5. data/CHANGELOG.markdown +10 -0
  6. data/CONTRIBUTING.markdown +2 -1
  7. data/Gemfile +1 -1
  8. data/README.markdown +10 -2
  9. data/image_optim.gemspec +4 -4
  10. data/lib/image_optim.rb +32 -16
  11. data/lib/image_optim/bin_resolver/bin.rb +11 -4
  12. data/lib/image_optim/cache.rb +71 -0
  13. data/lib/image_optim/cache_path.rb +16 -0
  14. data/lib/image_optim/config.rb +12 -2
  15. data/lib/image_optim/handler.rb +1 -1
  16. data/lib/image_optim/image_meta.rb +5 -10
  17. data/lib/image_optim/optimized_path.rb +25 -0
  18. data/lib/image_optim/path.rb +70 -0
  19. data/lib/image_optim/runner/option_parser.rb +13 -0
  20. data/lib/image_optim/worker.rb +5 -8
  21. data/lib/image_optim/worker/class_methods.rb +3 -1
  22. data/lib/image_optim/worker/jpegoptim.rb +3 -0
  23. data/lib/image_optim/worker/jpegrecompress.rb +3 -0
  24. data/lib/image_optim/worker/pngquant.rb +3 -0
  25. data/script/worker_analysis +10 -9
  26. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +1 -1
  27. data/spec/image_optim/bin_resolver/simple_version_spec.rb +48 -40
  28. data/spec/image_optim/bin_resolver_spec.rb +190 -172
  29. data/spec/image_optim/cache_path_spec.rb +59 -0
  30. data/spec/image_optim/cache_spec.rb +159 -0
  31. data/spec/image_optim/cmd_spec.rb +11 -7
  32. data/spec/image_optim/config_spec.rb +92 -71
  33. data/spec/image_optim/handler_spec.rb +3 -6
  34. data/spec/image_optim/image_meta_spec.rb +61 -0
  35. data/spec/image_optim/optimized_path_spec.rb +58 -0
  36. data/spec/image_optim/option_helpers_spec.rb +25 -0
  37. data/spec/image_optim/path_spec.rb +105 -0
  38. data/spec/image_optim/railtie_spec.rb +6 -6
  39. data/spec/image_optim/runner/glob_helpers_spec.rb +2 -6
  40. data/spec/image_optim/runner/option_parser_spec.rb +3 -3
  41. data/spec/image_optim/space_spec.rb +16 -18
  42. data/spec/image_optim/worker/optipng_spec.rb +3 -3
  43. data/spec/image_optim/worker/pngquant_spec.rb +47 -7
  44. data/spec/image_optim/worker_spec.rb +114 -17
  45. data/spec/image_optim_spec.rb +58 -69
  46. data/spec/images/broken_jpeg +1 -0
  47. data/spec/spec_helper.rb +40 -10
  48. metadata +30 -8
  49. data/lib/image_optim/image_path.rb +0 -68
  50. data/spec/image_optim/image_path_spec.rb +0 -54
@@ -6,12 +6,36 @@ describe ImageOptim::Worker do
6
6
  before do
7
7
  stub_const('Worker', ImageOptim::Worker)
8
8
  stub_const('BinResolver', ImageOptim::BinResolver)
9
+
10
+ # don't add to list of wokers
11
+ allow(ImageOptim::Worker).to receive(:inherited)
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'expects first argument to be an instanace of ImageOptim' do
16
+ expect do
17
+ Worker.new(double)
18
+ end.to raise_error ArgumentError
19
+ end
20
+ end
21
+
22
+ describe '#options' do
23
+ it 'returns a Hash with options' do
24
+ worker_class = Class.new(Worker) do
25
+ option(:one, 1, 'One')
26
+ option(:two, 2, 'Two')
27
+ option(:three, 3, 'Three')
28
+ end
29
+
30
+ worker = worker_class.new(ImageOptim.new, :three => '...')
31
+
32
+ expect(worker.options).to eq(:one => 1, :two => 2, :three => '...')
33
+ end
9
34
  end
10
35
 
11
- describe :optimize do
36
+ describe '#optimize' do
12
37
  it 'raises NotImplementedError' do
13
- image_optim = ImageOptim.new
14
- worker = Worker.new(image_optim, {})
38
+ worker = Worker.new(ImageOptim.new, {})
15
39
 
16
40
  expect do
17
41
  worker.optimize(double, double)
@@ -19,7 +43,50 @@ describe ImageOptim::Worker do
19
43
  end
20
44
  end
21
45
 
22
- describe :create_all_by_format do
46
+ describe '#image_formats' do
47
+ {
48
+ 'GifOptim' => :gif,
49
+ 'JpegOptim' => :jpeg,
50
+ 'PngOptim' => :png,
51
+ 'SvgOptim' => :svg,
52
+ }.each do |class_name, image_format|
53
+ it "detects if class name contains #{image_format}" do
54
+ worker = stub_const(class_name, Class.new(Worker)).new(ImageOptim.new)
55
+ expect(worker.image_formats).to eq([image_format])
56
+ end
57
+ end
58
+
59
+ it 'fails if class name does not contain known type' do
60
+ worker = stub_const('TiffOptim', Class.new(Worker)).new(ImageOptim.new)
61
+ expect{ worker.image_formats }.to raise_error(/can't guess/)
62
+ end
63
+ end
64
+
65
+ describe '#inspect' do
66
+ it 'returns inspect String containing options' do
67
+ stub_const('DefOptim', Class.new(Worker) do
68
+ option(:one, 1, 'One')
69
+ option(:two, 2, 'Two')
70
+ option(:three, 3, 'Three')
71
+ end)
72
+
73
+ worker = DefOptim.new(ImageOptim.new, :three => '...')
74
+
75
+ expect(worker.inspect).to eq('#<DefOptim @one=1, @two=2, @three="...">')
76
+ end
77
+ end
78
+
79
+ describe '.inherited' do
80
+ it 'adds subclasses to klasses' do
81
+ base_class = Class.new{ extend ImageOptim::Worker::ClassMethods }
82
+ expect(base_class.klasses.to_a).to eq([])
83
+
84
+ worker_class = Class.new(base_class)
85
+ expect(base_class.klasses.to_a).to eq([worker_class])
86
+ end
87
+ end
88
+
89
+ describe '.create_all_by_format' do
23
90
  it 'passes arguments to create_all' do
24
91
  image_optim = double
25
92
  options_proc = proc{ true }
@@ -52,17 +119,21 @@ describe ImageOptim::Worker do
52
119
  end
53
120
  end
54
121
 
55
- describe :create_all do
122
+ describe '.create_all' do
56
123
  def worker_double(override = {})
57
124
  stubs = {:resolve_used_bins! => nil, :run_order => 0}.merge(override)
58
125
  instance_double(Worker, stubs)
59
126
  end
60
127
 
128
+ def worker_class_doubles(workers)
129
+ workers.map{ |worker| class_double(Worker, :init => worker) }
130
+ end
131
+
61
132
  let(:image_optim){ double(:allow_lossy => false) }
62
133
 
63
134
  it 'creates all workers for which options_proc returns true' do
64
135
  workers = Array.new(3){ worker_double }
65
- klasses = workers.map{ |worker| double(:init => worker) }
136
+ klasses = worker_class_doubles(workers)
66
137
  options_proc = proc do |klass|
67
138
  klass == klasses[1] ? {:disable => true} : {}
68
139
  end
@@ -79,7 +150,7 @@ describe ImageOptim::Worker do
79
150
  [worker_double, worker_double, worker_double],
80
151
  worker_double,
81
152
  ]
82
- klasses = workers.map{ |worker| double(:init => worker) }
153
+ klasses = worker_class_doubles(workers)
83
154
 
84
155
  allow(Worker).to receive(:klasses).and_return(klasses)
85
156
 
@@ -98,7 +169,7 @@ describe ImageOptim::Worker do
98
169
  worker
99
170
  end
100
171
  end
101
- let(:klasses){ workers.map{ |worker| double(:init => worker) } }
172
+ let(:klasses){ worker_class_doubles(workers) }
102
173
 
103
174
  before do
104
175
  allow(Worker).to receive(:klasses).and_return(klasses)
@@ -136,12 +207,11 @@ describe ImageOptim::Worker do
136
207
  end
137
208
 
138
209
  it 'orders workers by run_order' do
139
- image_optim = double(:allow_lossy => false)
140
210
  run_orders = [10, -10, 0, 0, 0, 10, -10]
141
211
  workers = run_orders.map do |run_order|
142
212
  worker_double(:run_order => run_order)
143
213
  end
144
- klasses_list = workers.map{ |worker| double(:init => worker) }
214
+ klasses_list = worker_class_doubles(workers)
145
215
 
146
216
  [
147
217
  klasses_list,
@@ -157,13 +227,43 @@ describe ImageOptim::Worker do
157
227
  expect(Worker.create_all(image_optim){ {} }).to eq(expected_order)
158
228
  end
159
229
  end
230
+
231
+ describe 'passing allow_lossy' do
232
+ it 'passes allow_lossy if worker has such attribute' do
233
+ klasses = worker_class_doubles([worker_double, worker_double])
234
+
235
+ allow(Worker).to receive(:klasses).and_return(klasses)
236
+
237
+ klasses[0].send(:attr_reader, :allow_lossy)
238
+ expect(klasses[0]).to receive(:init).
239
+ with(image_optim, hash_including(:allow_lossy))
240
+ expect(klasses[1]).to receive(:init).
241
+ with(image_optim, hash_not_including(:allow_lossy))
242
+
243
+ Worker.create_all(image_optim){ {} }
244
+ end
245
+
246
+ it 'allows overriding per worker' do
247
+ klasses = worker_class_doubles([worker_double, worker_double])
248
+ options_proc = proc do |klass|
249
+ klass == klasses[1] ? {:allow_lossy => :b} : {}
250
+ end
251
+
252
+ allow(Worker).to receive(:klasses).and_return(klasses)
253
+
254
+ klasses.each{ |klass| klass.send(:attr_reader, :allow_lossy) }
255
+ expect(klasses[0]).to receive(:init).
256
+ with(image_optim, hash_including(:allow_lossy => false))
257
+ expect(klasses[1]).to receive(:init).
258
+ with(image_optim, hash_including(:allow_lossy => :b))
259
+
260
+ Worker.create_all(image_optim, &options_proc)
261
+ end
262
+ end
160
263
  end
161
264
 
162
- describe :option do
265
+ describe '.option' do
163
266
  it 'runs option block in context of worker' do
164
- # don't add Abc to list of wokers
165
- allow(ImageOptim::Worker).to receive(:inherited)
166
-
167
267
  stub_const('Abc', Class.new(Worker) do
168
268
  option(:test, 1, 'Test context') do |_v|
169
269
  some_instance_method
@@ -176,9 +276,6 @@ describe ImageOptim::Worker do
176
276
  end
177
277
 
178
278
  it 'returns instance of OptionDefinition' do
179
- # don't add Abc to list of wokers
180
- allow(ImageOptim::Worker).to receive(:inherited)
181
-
182
279
  definition = nil
183
280
  Class.new(Worker) do
184
281
  definition = option(:test, 1, 'Test'){ |v| v }
@@ -5,7 +5,7 @@ require 'tempfile'
5
5
  require 'English'
6
6
 
7
7
  describe ImageOptim do
8
- root_dir = ImageOptim::ImagePath.new(__FILE__).dirname.dirname
8
+ root_dir = ImageOptim::Path.new(__FILE__).dirname.dirname
9
9
  images_dir = root_dir / 'spec/images'
10
10
  test_images = images_dir.glob('**/*.*').freeze
11
11
 
@@ -17,15 +17,8 @@ describe ImageOptim do
17
17
  include helpers
18
18
  extend helpers
19
19
 
20
- matcher :be_in_range do |expected|
21
- match{ |actual| expected.include?(actual) }
22
- end
23
-
24
20
  before do
25
21
  stub_const('Cmd', ImageOptim::Cmd)
26
-
27
- allow(ImageOptim::Config).to receive(:global).and_return({})
28
- allow(ImageOptim::Config).to receive(:local).and_return({})
29
22
  end
30
23
 
31
24
  isolated_options_base = {:skip_missing_workers => false}
@@ -50,7 +43,7 @@ describe ImageOptim do
50
43
  end
51
44
  end
52
45
 
53
- describe :optimize_image do
46
+ describe '#optimize_image' do
54
47
  define :have_same_data_as do |expected|
55
48
  match{ |actual| actual.binread == expected.binread }
56
49
  end
@@ -68,30 +61,28 @@ describe ImageOptim do
68
61
  ['lossless', base_options, 0],
69
62
  ['lossy', base_options.merge(:allow_lossy => true), 0.001],
70
63
  ].each do |type, options, max_difference|
71
- image_optim = ImageOptim.new(options)
72
- describe type do
64
+ it "does it #{type}" do
65
+ image_optim = ImageOptim.new(options)
73
66
  copies = test_images.map{ |image| temp_copy(image) }
74
67
  pairs = image_optim.optimize_images(copies)
75
68
  test_images.zip(*pairs.transpose).each do |original, copy, optimized|
76
- it "optimizes #{original.relative_path_from(root_dir)}" do
77
- expect(copy).to have_same_data_as(original)
78
-
79
- expect(optimized).not_to be_nil
80
- expect(optimized).to be_a(ImageOptim::ImagePath::Optimized)
81
- expect(optimized).to have_size
82
- expect(optimized).to be_smaller_than(original)
83
- expect(optimized).not_to have_same_data_as(original)
84
-
85
- compare_to = rotate_images.include?(original) ? rotated : original
86
- expect(optimized).to be_similar_to(compare_to, max_difference)
87
- end
69
+ expect(copy).to have_same_data_as(original)
70
+
71
+ expect(optimized).not_to be_nil
72
+ expect(optimized).to be_a(ImageOptim::OptimizedPath)
73
+ expect(optimized).to have_size
74
+ expect(optimized).to be_smaller_than(original)
75
+ expect(optimized).not_to have_same_data_as(original)
76
+
77
+ compare_to = rotate_images.include?(original) ? rotated : original
78
+ expect(optimized).to be_similar_to(compare_to, max_difference)
88
79
  end
89
80
  end
90
81
  end
91
82
  end
92
83
 
93
84
  it 'ignores text file' do
94
- original = ImageOptim::ImagePath.new(__FILE__)
85
+ original = ImageOptim::Path.new(__FILE__)
95
86
  copy = temp_copy(original)
96
87
 
97
88
  expect(Tempfile).not_to receive(:new)
@@ -106,27 +97,27 @@ describe ImageOptim do
106
97
  }.each do |type, data|
107
98
  it "ingores broken #{type}" do
108
99
  path = FSPath.temp_file_path
109
- path.write(data)
100
+ path.binwrite(data)
110
101
  expect(ImageOptim::ImageMeta).to receive(:warn)
111
102
  expect(ImageOptim.optimize_image(path)).to be_nil
112
103
  end
113
104
  end
114
105
  end
115
106
 
116
- describe :optimize_image! do
107
+ describe '#optimize_image!' do
117
108
  it 'optimizes image and replaces original' do
118
109
  original = double
119
110
  optimized = double(:original_size => 12_345)
120
111
  optimized_wrap = double
121
112
  image_optim = ImageOptim.new
122
113
 
123
- allow(ImageOptim::ImagePath).to receive(:convert).
114
+ allow(ImageOptim::Path).to receive(:convert).
124
115
  with(original).and_return(original)
125
116
 
126
117
  expect(image_optim).to receive(:optimize_image).
127
118
  with(original).and_return(optimized)
128
119
  expect(optimized).to receive(:replace).with(original)
129
- expect(ImageOptim::ImagePath::Optimized).to receive(:new).
120
+ expect(ImageOptim::OptimizedPath).to receive(:new).
130
121
  with(original, 12_345).and_return(optimized_wrap)
131
122
 
132
123
  expect(image_optim.optimize_image!(original)).to eq(optimized_wrap)
@@ -136,18 +127,18 @@ describe ImageOptim do
136
127
  original = double
137
128
  image_optim = ImageOptim.new
138
129
 
139
- allow(ImageOptim::ImagePath).to receive(:convert).
130
+ allow(ImageOptim::Path).to receive(:convert).
140
131
  with(original).and_return(original)
141
132
 
142
133
  expect(image_optim).to receive(:optimize_image).
143
134
  with(original).and_return(nil)
144
- expect(ImageOptim::ImagePath::Optimized).not_to receive(:new)
135
+ expect(ImageOptim::OptimizedPath).not_to receive(:new)
145
136
 
146
137
  expect(image_optim.optimize_image!(original)).to eq(nil)
147
138
  end
148
139
  end
149
140
 
150
- describe :optimize_image_data do
141
+ describe '#optimize_image_data' do
151
142
  it 'create temp file, optimizes image and returns data' do
152
143
  data = double
153
144
  temp = double(:path => double)
@@ -155,10 +146,10 @@ describe ImageOptim do
155
146
  optimized_data = double
156
147
  image_optim = ImageOptim.new
157
148
 
158
- allow(ImageOptim::ImageMeta).to receive(:for_data).
159
- with(data).and_return(double(:format => 'xxx'))
149
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
150
+ with(data).and_return('xxx')
160
151
 
161
- expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
152
+ expect(ImageOptim::Path).to receive(:temp_file).and_yield(temp)
162
153
  expect(temp).to receive(:binmode)
163
154
  expect(temp).to receive(:write).with(data)
164
155
  expect(temp).to receive(:close)
@@ -174,10 +165,10 @@ describe ImageOptim do
174
165
  temp = double(:path => double)
175
166
  image_optim = ImageOptim.new
176
167
 
177
- allow(ImageOptim::ImageMeta).to receive(:for_data).
178
- with(data).and_return(double(:format => 'xxx'))
168
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
169
+ with(data).and_return('xxx')
179
170
 
180
- expect(ImageOptim::ImagePath).to receive(:temp_file).and_yield(temp)
171
+ expect(ImageOptim::Path).to receive(:temp_file).and_yield(temp)
181
172
  expect(temp).to receive(:binmode)
182
173
  expect(temp).to receive(:write).with(data)
183
174
  expect(temp).to receive(:close)
@@ -191,49 +182,47 @@ describe ImageOptim do
191
182
  data = double
192
183
  image_optim = ImageOptim.new
193
184
 
194
- allow(ImageOptim::ImageMeta).to receive(:for_data).
195
- with(data).and_return(double(:format => nil))
185
+ allow(ImageOptim::ImageMeta).to receive(:format_for_data).
186
+ with(data).and_return(nil)
196
187
 
197
- expect(ImageOptim::ImagePath).not_to receive(:temp_file)
188
+ expect(ImageOptim::Path).not_to receive(:temp_file)
198
189
  expect(image_optim).not_to receive(:optimize_image)
199
190
 
200
191
  expect(image_optim.optimize_image_data(data)).to eq(nil)
201
192
  end
202
193
  end
203
194
 
204
- describe 'optimize multiple' do
205
- %w[
206
- optimize_images
207
- optimize_images!
208
- optimize_images_data
209
- ].each do |list_method|
210
- describe list_method do
211
- method = list_method.sub('images', 'image')
212
- describe 'without block' do
213
- it 'optimizes images and returns array of results' do
214
- image_optim = ImageOptim.new
215
- results = test_images.map do |src|
216
- dst = double
217
- expect(image_optim).to receive(method).with(src).and_return(dst)
218
- [src, dst]
219
- end
220
- expect(image_optim.send(list_method, test_images)).to eq(results)
195
+ %w[
196
+ optimize_images
197
+ optimize_images!
198
+ optimize_images_data
199
+ ].each do |list_method|
200
+ describe "##{list_method}" do
201
+ method = list_method.sub('images', 'image')
202
+ describe 'without block' do
203
+ it 'optimizes images and returns array of results' do
204
+ image_optim = ImageOptim.new
205
+ results = test_images.map do |src|
206
+ dst = double
207
+ expect(image_optim).to receive(method).with(src).and_return(dst)
208
+ [src, dst]
221
209
  end
210
+ expect(image_optim.send(list_method, test_images)).to eq(results)
222
211
  end
212
+ end
223
213
 
224
- describe 'given block' do
225
- it 'optimizes images, yields path and result for each and '\
226
- 'returns array of yield results' do
227
- image_optim = ImageOptim.new
228
- results = test_images.map do |src|
229
- dst = double
230
- expect(image_optim).to receive(method).with(src).and_return(dst)
231
- [src, dst, :test]
232
- end
233
- expect(image_optim.send(list_method, test_images) do |src, dst|
234
- [src, dst, :test]
235
- end).to eq(results)
214
+ describe 'given block' do
215
+ it 'optimizes images, yields path and result for each and '\
216
+ 'returns array of yield results' do
217
+ image_optim = ImageOptim.new
218
+ results = test_images.map do |src|
219
+ dst = double
220
+ expect(image_optim).to receive(method).with(src).and_return(dst)
221
+ [src, dst, :test]
236
222
  end
223
+ expect(image_optim.send(list_method, test_images) do |src, dst|
224
+ [src, dst, :test]
225
+ end).to eq(results)
237
226
  end
238
227
  end
239
228
  end
@@ -0,0 +1 @@
1
+ ��
@@ -1,18 +1,28 @@
1
- if ENV['CODECLIMATE_REPO_TOKEN']
2
- begin
3
- require 'codeclimate-test-reporter'
4
- CodeClimate::TestReporter.start
5
- rescue LoadError => e
6
- $stderr.puts "Got following while loading codeclimate-test-reporter: #{e}"
7
- end
1
+ if ENV['CODECLIMATE'] && ENV['CODECLIMATE_REPO_TOKEN']
2
+ require 'codeclimate-test-reporter'
3
+ CodeClimate::TestReporter.start
8
4
  end
9
5
 
6
+ require 'image_optim/pack'
7
+ require 'image_optim/path'
8
+
9
+ ENV['PATH'] = [
10
+ ImageOptim::Pack.path,
11
+ ENV['PATH'],
12
+ ].compact.join File::PATH_SEPARATOR
13
+
10
14
  RSpec.configure do |c|
11
- c.alias_example_to :they
15
+ c.before do
16
+ stub_const('ImageOptim::Config::GLOBAL_PATH', ImageOptim::Path::NULL)
17
+ stub_const('ImageOptim::Config::LOCAL_PATH', ImageOptim::Path::NULL)
18
+ ImageOptim.class_eval{ def pack; end }
19
+ end
20
+
21
+ c.order = :random
12
22
  end
13
23
 
14
24
  def flatten_animation(image)
15
- if image.format == :gif
25
+ if image.image_format == :gif
16
26
  flattened = image.temp_path
17
27
  command = %W[
18
28
  convert
@@ -37,7 +47,7 @@ def mepp(image_a, image_b)
37
47
  -alpha Background
38
48
  #{coalesce_a.to_s.shellescape}
39
49
  #{coalesce_b.to_s.shellescape}
40
- /dev/null
50
+ #{ImageOptim::Path::NULL}
41
51
  2>&1
42
52
  ].join(' ')
43
53
  output = ImageOptim::Cmd.capture(command)
@@ -63,3 +73,23 @@ RSpec::Matchers.define :be_similar_to do |expected, max_difference|
63
73
  "#{expected}, got normalized root-mean-square error of #{@diff}"
64
74
  end
65
75
  end
76
+
77
+ module CapabilityCheckHelpers
78
+ def any_file_modes_allowed?
79
+ Tempfile.open 'posix' do |f|
80
+ File.chmod(0, f.path)
81
+ File.stat(f.path).mode & 0o777 == 0
82
+ end
83
+ end
84
+
85
+ def inodes_supported?
86
+ File.stat(__FILE__).ino != 0
87
+ end
88
+
89
+ def signals_supported?
90
+ Process.kill(0, 0)
91
+ true
92
+ rescue
93
+ false
94
+ end
95
+ end