discourse_image_optim 0.24.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/bin_resolver/comparable_condition'
3
+
4
+ describe ImageOptim::BinResolver::ComparableCondition do
5
+ let(:is){ ImageOptim::BinResolver::ComparableCondition.is }
6
+
7
+ it 'builds conditions' do
8
+ expect(is.between?(10, 20).method).to eq(:between?)
9
+ expect(is.between?(10, 20).args).to eq([10, 20])
10
+
11
+ expect((is >= 15).method).to eq(:>=)
12
+ expect((is >= 15).args).to eq([15])
13
+
14
+ expect((is < 30).method).to eq(:<)
15
+ expect((is < 30).args).to eq([30])
16
+ end
17
+
18
+ it 'stringifies conditions' do
19
+ expect(is.between?(10, 20).to_s).to eq('10..20')
20
+ expect((is >= 15).to_s).to eq('>= 15')
21
+ expect((is < 30).to_s).to eq('< 30')
22
+ end
23
+
24
+ it 'matches conditions' do
25
+ expect(is.between?(10, 20)).not_to match 9
26
+ expect(is.between?(10, 20)).to match 15
27
+ expect(is.between?(10, 20)).not_to match 21
28
+
29
+ expect(is >= 15).not_to match 14
30
+ expect(is >= 15).to match 15
31
+ expect(is >= 15).to match 16
32
+
33
+ expect(is < 30).to match 29
34
+ expect(is < 30).not_to match 30
35
+ expect(is < 30).not_to match 31
36
+ end
37
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/bin_resolver/simple_version'
3
+
4
+ describe ImageOptim::BinResolver::SimpleVersion do
5
+ def v(str)
6
+ described_class.new(str)
7
+ end
8
+
9
+ describe '#initialize' do
10
+ describe 'conversion' do
11
+ it 'converts Integer' do
12
+ expect(v(117)).to eq('117')
13
+ end
14
+
15
+ it 'converts Float' do
16
+ expect(v(1.17)).to eq('1.17')
17
+ end
18
+
19
+ it 'converts String' do
20
+ expect(v('1.17')).to eq('1.17')
21
+ end
22
+
23
+ it 'converts self' do
24
+ expect(v(v(1.17))).to eq('1.17')
25
+ end
26
+ end
27
+
28
+ describe 'normalization' do
29
+ %w[
30
+ 1
31
+ 01
32
+ 1.0
33
+ 1.00
34
+ 1.0.0
35
+ 1.0.0.0
36
+ ].each do |variation|
37
+ it "normalizes #{variation}" do
38
+ expect(v(variation)).to eq(1)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#to_s' do
45
+ it 'returns the original value converted to String' do
46
+ expect(v(117).to_s).to eq('117')
47
+ expect(v(1.17).to_s).to eq('1.17')
48
+ expect(v('0117').to_s).to eq('0117')
49
+ end
50
+ end
51
+
52
+ describe '#<=>' do
53
+ describe 'comparing version 1.17' do
54
+ subject{ v '1.17' }
55
+
56
+ it{ is_expected.to be > '0' }
57
+ it{ is_expected.to be > '0.1' }
58
+ it{ is_expected.to be > '0.9' }
59
+ it{ is_expected.to be > '1.9' }
60
+ it{ is_expected.to be < '1.17.1' }
61
+ it{ is_expected.to be < '1.99' }
62
+ it{ is_expected.to be < '2.1' }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,290 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/bin_resolver'
3
+ require 'image_optim/cmd'
4
+ require 'image_optim/path'
5
+
6
+ describe ImageOptim::BinResolver do
7
+ def stub_env(key, value)
8
+ allow(ENV).to receive(:[]).with(key).and_return(value)
9
+ end
10
+
11
+ before do
12
+ stub_const('BinResolver', ImageOptim::BinResolver)
13
+ stub_const('Bin', BinResolver::Bin)
14
+ stub_const('SimpleVersion', BinResolver::SimpleVersion)
15
+ stub_const('Cmd', ImageOptim::Cmd)
16
+
17
+ allow(ENV).to receive(:[]).and_call_original
18
+ end
19
+
20
+ let(:image_optim){ double(:image_optim, :verbose => false, :pack => false) }
21
+ let(:resolver){ BinResolver.new(image_optim) }
22
+
23
+ describe '#full_path' do
24
+ def full_path(name)
25
+ resolver.instance_eval{ full_path(name) }
26
+ end
27
+
28
+ context 'when PATHEXT is not set' do
29
+ it 'finds binary without ext in combined path' do
30
+ stub_env 'PATH', %w[/a /b /c /d].join(File::PATH_SEPARATOR)
31
+ stub_env 'PATHEXT', nil
32
+
33
+ [
34
+ [:file?, '/a/abc', false],
35
+ [:file?, '/b/abc', true],
36
+ [:executable?, '/b/abc', false],
37
+ [:file?, '/c/abc', true],
38
+ [:executable?, '/c/abc', true],
39
+ ].each do |method, path, result|
40
+ allow(File).to receive(method).
41
+ with(File.expand_path(path)).and_return(result)
42
+ end
43
+
44
+ expect(full_path('abc')).
45
+ to eq(File.expand_path('/c/abc'))
46
+ end
47
+ end
48
+
49
+ context 'when PATHEXT is set' do
50
+ it 'finds binary with ext in combined path' do
51
+ stub_env 'PATH', %w[/a /b].join(File::PATH_SEPARATOR)
52
+ stub_env 'PATHEXT', '.com;.bat'
53
+
54
+ [
55
+ [:file?, '/a/abc.com', false],
56
+ [:file?, '/a/abc.bat', true],
57
+ [:executable?, '/a/abc.bat', false],
58
+ [:file?, '/b/abc.com', true],
59
+ [:executable?, '/b/abc.com', true],
60
+ ].each do |method, path, result|
61
+ allow(File).to receive(method).
62
+ with(File.expand_path(path)).and_return(result)
63
+ end
64
+
65
+ expect(full_path('abc')).
66
+ to eq(File.expand_path('/b/abc.com'))
67
+ end
68
+ end
69
+
70
+ it 'returns nil on failure' do
71
+ stub_env 'PATH', ''
72
+ expect(full_path('image_optim')).to be_nil
73
+ end
74
+ end
75
+
76
+ it 'combines path in order dir, pack, path, vendor' do
77
+ allow(image_optim).to receive(:pack).and_return(true)
78
+ stub_const('ImageOptim::Pack', Class.new do
79
+ def self.path
80
+ 'pack_path'
81
+ end
82
+ end)
83
+ allow(resolver).to receive(:dir).and_return('temp_dir')
84
+
85
+ expect(resolver.env_path).to eq([
86
+ 'temp_dir',
87
+ 'pack_path',
88
+ ENV['PATH'],
89
+ BinResolver::VENDOR_PATH,
90
+ ].join(File::PATH_SEPARATOR))
91
+ end
92
+
93
+ it 'resolves bin in path and returns instance of Bin' do
94
+ stub_env 'LS_BIN', nil
95
+ expect(FSPath).not_to receive(:temp_dir)
96
+ expect(resolver).to receive(:full_path).with(:ls).and_return('/bin/ls')
97
+ bin = double
98
+ expect(Bin).to receive(:new).with(:ls, '/bin/ls').and_return(bin)
99
+ expect(bin).to receive(:check!).once
100
+ expect(bin).to receive(:check_fail!).exactly(5).times
101
+
102
+ 5.times do
103
+ expect(resolver.resolve!(:ls)).to eq(bin)
104
+ end
105
+ expect(resolver.env_path).to eq([
106
+ ENV['PATH'],
107
+ BinResolver::VENDOR_PATH,
108
+ ].join(File::PATH_SEPARATOR))
109
+ end
110
+
111
+ it 'raises on failure to resolve bin' do
112
+ stub_env 'LS_BIN', nil
113
+ expect(FSPath).not_to receive(:temp_dir)
114
+ expect(resolver).to receive(:full_path).with(:ls).and_return(nil)
115
+ expect(Bin).not_to receive(:new)
116
+
117
+ 5.times do
118
+ expect do
119
+ resolver.resolve!(:ls)
120
+ end.to raise_error BinResolver::BinNotFound
121
+ end
122
+ expect(resolver.env_path).to eq([
123
+ ENV['PATH'],
124
+ BinResolver::VENDOR_PATH,
125
+ ].join(File::PATH_SEPARATOR))
126
+ end
127
+
128
+ it 'resolves bin specified in ENV' do
129
+ path = 'bin/the_optimizer'
130
+ stub_env 'THE_OPTIMIZER_BIN', path
131
+ tmpdir = double(:tmpdir, :to_str => 'tmpdir')
132
+ symlink = double(:symlink)
133
+
134
+ full_path = File.expand_path(path)
135
+ allow(File).to receive(:exist?).with(full_path).and_return(true)
136
+ allow(File).to receive(:file?).with(full_path).and_return(true)
137
+ allow(File).to receive(:executable?).with(full_path).and_return(true)
138
+
139
+ expect(FSPath).to receive(:temp_dir).
140
+ once.and_return(tmpdir)
141
+ expect(tmpdir).to receive(:/).
142
+ with(:the_optimizer).once.and_return(symlink)
143
+ expect(symlink).to receive(:make_symlink).
144
+ with(File.expand_path(path)).once
145
+
146
+ expect(resolver).not_to receive(:full_path)
147
+ bin = double
148
+ expect(Bin).to receive(:new).
149
+ with(:the_optimizer, File.expand_path(path)).and_return(bin)
150
+ expect(bin).to receive(:check!).once
151
+ expect(bin).to receive(:check_fail!).exactly(5).times
152
+
153
+ at_exit_blocks = []
154
+ expect(resolver).to receive(:at_exit).once do |&block|
155
+ at_exit_blocks.unshift(block)
156
+ end
157
+
158
+ 5.times do
159
+ resolver.resolve!(:the_optimizer)
160
+ end
161
+ expect(resolver.env_path).to eq([
162
+ tmpdir,
163
+ ENV['PATH'],
164
+ BinResolver::VENDOR_PATH,
165
+ ].join(File::PATH_SEPARATOR))
166
+
167
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir)
168
+ at_exit_blocks.each(&:call)
169
+ end
170
+
171
+ describe 'checking bin' do
172
+ let(:path){ 'the_optimizer' }
173
+ let(:exist?){ true }
174
+ let(:file?){ true }
175
+ let(:executable?){ true }
176
+
177
+ before do
178
+ stub_env 'THE_OPTIMIZER_BIN', path
179
+ expect(FSPath).not_to receive(:temp_dir)
180
+ expect(resolver).not_to receive(:at_exit)
181
+ allow(File).to receive_messages(:exist? => exist?,
182
+ :file? => file?,
183
+ :executable? => executable?)
184
+ end
185
+
186
+ after do
187
+ expect(resolver.env_path).to eq([
188
+ ENV['PATH'],
189
+ BinResolver::VENDOR_PATH,
190
+ ].join(File::PATH_SEPARATOR))
191
+ end
192
+
193
+ def raises_error(error_message)
194
+ 5.times do
195
+ expect do
196
+ resolver.resolve!(:the_optimizer)
197
+ end.to raise_error RuntimeError, /#{Regexp.escape(error_message)}/
198
+ end
199
+ end
200
+
201
+ context 'presence' do
202
+ let(:exist?){ false }
203
+
204
+ it{ raises_error('doesn\'t exist') }
205
+ end
206
+
207
+ context 'been a file' do
208
+ let(:file?){ false }
209
+
210
+ it{ raises_error('is not a file') }
211
+ end
212
+
213
+ context 'been a file' do
214
+ let(:executable?){ false }
215
+
216
+ it{ raises_error('is not executable') }
217
+ end
218
+ end
219
+
220
+ it 'resolves bin only once, but checks every time' do
221
+ stub_env 'LS_BIN', nil
222
+ expect(resolver).to receive(:full_path).once.with(:ls) do
223
+ sleep 0.1
224
+ '/bin/ls'
225
+ end
226
+ bin = double
227
+ expect(Bin).to receive(:new).once.with(:ls, '/bin/ls').and_return(bin)
228
+
229
+ count = 0
230
+ mutex = Mutex.new
231
+ allow(bin).to receive(:check!).once
232
+ allow(bin).to receive(:check_fail!){ mutex.synchronize{ count += 1 } }
233
+
234
+ Array.new(10) do
235
+ Thread.new do
236
+ resolver.resolve!(:ls)
237
+ end
238
+ end.each(&:join)
239
+
240
+ expect(count).to eq(10)
241
+ end
242
+
243
+ describe 'checking version' do
244
+ before do
245
+ allow(resolver).to receive(:full_path){ |name| "/bin/#{name}" }
246
+ end
247
+
248
+ it 'raises every time if did not get bin version' do
249
+ stub_env 'PNGCRUSH_BIN', nil
250
+ bin = Bin.new(:pngcrush, '/bin/pngcrush')
251
+
252
+ expect(Bin).to receive(:new).and_return(bin)
253
+ allow(bin).to receive(:version).and_return(nil)
254
+
255
+ 5.times do
256
+ expect do
257
+ resolver.resolve!(:pngcrush)
258
+ end.to raise_error Bin::UnknownVersion, %r{pngcrush at /bin/pngcrush}
259
+ end
260
+ end
261
+
262
+ it 'raises every time on detection of misbehaving version' do
263
+ stub_env 'PNGCRUSH_BIN', nil
264
+ bin = Bin.new(:pngcrush, '/bin/pngcrush')
265
+
266
+ expect(Bin).to receive(:new).and_return(bin)
267
+ allow(bin).to receive(:version).and_return(SimpleVersion.new('1.7.60'))
268
+
269
+ 5.times do
270
+ expect do
271
+ resolver.resolve!(:pngcrush)
272
+ end.to raise_error Bin::BadVersion, /is known to produce broken pngs/
273
+ end
274
+ end
275
+
276
+ it 'warns once on detection of problematic version' do
277
+ stub_env 'ADVPNG_BIN', nil
278
+ bin = Bin.new(:advpng, '/bin/advpng')
279
+
280
+ expect(Bin).to receive(:new).and_return(bin)
281
+ allow(bin).to receive(:version).and_return(SimpleVersion.new('1.15'))
282
+
283
+ expect(bin).to receive(:warn).once.with(match(/does not use zopfli/))
284
+
285
+ 5.times do
286
+ resolver.resolve!(:pngcrush)
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/cache_path'
3
+ require 'tempfile'
4
+
5
+ describe ImageOptim::CachePath do
6
+ include CapabilityCheckHelpers
7
+
8
+ before do
9
+ stub_const('CachePath', ImageOptim::CachePath)
10
+ end
11
+
12
+ describe '#replace' do
13
+ let(:src){ CachePath.temp_file_path }
14
+ let(:dst){ CachePath.temp_file_path }
15
+
16
+ it 'moves data to destination' do
17
+ src.write('src')
18
+
19
+ src.replace(dst)
20
+
21
+ expect(dst.read).to eq('src')
22
+ end
23
+
24
+ it 'does not remove original file' do
25
+ src.replace(dst)
26
+
27
+ expect(src).to exist
28
+ end
29
+
30
+ it 'preserves attributes of destination file' do
31
+ skip 'full file modes are not support' unless any_file_modes_allowed?
32
+ mode = 0o666
33
+
34
+ dst.chmod(mode)
35
+
36
+ src.replace(dst)
37
+
38
+ got = dst.stat.mode & 0o777
39
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
40
+ end
41
+
42
+ it 'does not preserve mtime of destination file' do
43
+ time = src.mtime
44
+
45
+ dst.utime(time - 1000, time - 1000)
46
+
47
+ src.replace(dst)
48
+
49
+ expect(dst.mtime).to be >= time
50
+ end
51
+
52
+ it 'changes inode of destination' do
53
+ skip 'inodes are not supported' unless inodes_supported?
54
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
55
+ end
56
+ end
57
+ end