openstreetmap-image_optim 0.21.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +42 -0
  5. data/CHANGELOG.markdown +272 -0
  6. data/CONTRIBUTING.markdown +10 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.markdown +344 -0
  10. data/Vagrantfile +33 -0
  11. data/bin/image_optim +28 -0
  12. data/image_optim.gemspec +29 -0
  13. data/lib/image_optim.rb +228 -0
  14. data/lib/image_optim/bin_resolver.rb +144 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +105 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  17. data/lib/image_optim/bin_resolver/error.rb +6 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  19. data/lib/image_optim/cmd.rb +49 -0
  20. data/lib/image_optim/config.rb +205 -0
  21. data/lib/image_optim/configuration_error.rb +3 -0
  22. data/lib/image_optim/handler.rb +57 -0
  23. data/lib/image_optim/hash_helpers.rb +45 -0
  24. data/lib/image_optim/image_meta.rb +25 -0
  25. data/lib/image_optim/image_path.rb +68 -0
  26. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  27. data/lib/image_optim/option_definition.rb +32 -0
  28. data/lib/image_optim/option_helpers.rb +17 -0
  29. data/lib/image_optim/railtie.rb +38 -0
  30. data/lib/image_optim/runner.rb +139 -0
  31. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  32. data/lib/image_optim/runner/option_parser.rb +227 -0
  33. data/lib/image_optim/space.rb +29 -0
  34. data/lib/image_optim/true_false_nil.rb +16 -0
  35. data/lib/image_optim/worker.rb +159 -0
  36. data/lib/image_optim/worker/advpng.rb +35 -0
  37. data/lib/image_optim/worker/class_methods.rb +91 -0
  38. data/lib/image_optim/worker/gifsicle.rb +63 -0
  39. data/lib/image_optim/worker/jhead.rb +43 -0
  40. data/lib/image_optim/worker/jpegoptim.rb +58 -0
  41. data/lib/image_optim/worker/jpegrecompress.rb +44 -0
  42. data/lib/image_optim/worker/jpegtran.rb +46 -0
  43. data/lib/image_optim/worker/optipng.rb +45 -0
  44. data/lib/image_optim/worker/pngcrush.rb +54 -0
  45. data/lib/image_optim/worker/pngout.rb +38 -0
  46. data/lib/image_optim/worker/pngquant.rb +51 -0
  47. data/lib/image_optim/worker/svgo.rb +32 -0
  48. data/script/template/jquery-2.1.3.min.js +4 -0
  49. data/script/template/sortable-0.6.0.min.js +2 -0
  50. data/script/template/worker_analysis.erb +254 -0
  51. data/script/update_worker_options_in_readme +60 -0
  52. data/script/worker_analysis +599 -0
  53. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  54. data/spec/image_optim/bin_resolver/simple_version_spec.rb +57 -0
  55. data/spec/image_optim/bin_resolver_spec.rb +272 -0
  56. data/spec/image_optim/cmd_spec.rb +66 -0
  57. data/spec/image_optim/config_spec.rb +217 -0
  58. data/spec/image_optim/handler_spec.rb +95 -0
  59. data/spec/image_optim/hash_helpers_spec.rb +76 -0
  60. data/spec/image_optim/image_path_spec.rb +54 -0
  61. data/spec/image_optim/railtie_spec.rb +121 -0
  62. data/spec/image_optim/runner/glob_helpers_spec.rb +25 -0
  63. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  64. data/spec/image_optim/space_spec.rb +25 -0
  65. data/spec/image_optim/worker_spec.rb +192 -0
  66. data/spec/image_optim_spec.rb +242 -0
  67. data/spec/images/comparison.png +0 -0
  68. data/spec/images/decompressed.jpeg +0 -0
  69. data/spec/images/icecream.gif +0 -0
  70. data/spec/images/image.jpg +0 -0
  71. data/spec/images/invisiblepixels/generate +24 -0
  72. data/spec/images/invisiblepixels/image.png +0 -0
  73. data/spec/images/lena.jpg +0 -0
  74. data/spec/images/orient/0.jpg +0 -0
  75. data/spec/images/orient/1.jpg +0 -0
  76. data/spec/images/orient/2.jpg +0 -0
  77. data/spec/images/orient/3.jpg +0 -0
  78. data/spec/images/orient/4.jpg +0 -0
  79. data/spec/images/orient/5.jpg +0 -0
  80. data/spec/images/orient/6.jpg +0 -0
  81. data/spec/images/orient/7.jpg +0 -0
  82. data/spec/images/orient/8.jpg +0 -0
  83. data/spec/images/orient/generate +23 -0
  84. data/spec/images/orient/original.jpg +0 -0
  85. data/spec/images/quant/64.png +0 -0
  86. data/spec/images/quant/generate +25 -0
  87. data/spec/images/rails.png +0 -0
  88. data/spec/images/test.svg +3 -0
  89. data/spec/images/transparency1.png +0 -0
  90. data/spec/images/transparency2.png +0 -0
  91. data/spec/images/vergroessert.jpg +0 -0
  92. data/spec/spec_helper.rb +64 -0
  93. data/vendor/jpegrescan +143 -0
  94. metadata +308 -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
+ 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,57 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/bin_resolver/simple_version'
3
+
4
+ describe ImageOptim::BinResolver::SimpleVersion do
5
+ helpers = Module.new do
6
+ def v(str)
7
+ ImageOptim::BinResolver::SimpleVersion.new(str)
8
+ end
9
+ end
10
+ include helpers
11
+ extend helpers
12
+
13
+ describe 'compares version 1.17' do
14
+ subject{ v '1.17' }
15
+
16
+ it{ is_expected.to be > '0' }
17
+ it{ is_expected.to be > '0.1' }
18
+ it{ is_expected.to be > '0.9' }
19
+ it{ is_expected.to be > '1.9' }
20
+ it{ is_expected.to be < '1.17.1' }
21
+ it{ is_expected.to be < '1.99' }
22
+ it{ is_expected.to be < '2.1' }
23
+ end
24
+
25
+ describe 'normalization' do
26
+ %w[
27
+ 1
28
+ 01
29
+ 1.0
30
+ 1.00
31
+ 1.0.0
32
+ 1.0.0.0
33
+ ].each do |variation|
34
+ it "normalizes #{variation}" do
35
+ expect(v variation).to eq(1)
36
+ end
37
+ end
38
+ end
39
+
40
+ describe 'conversion' do
41
+ it 'converts Integer' do
42
+ expect(v 117).to eq('117')
43
+ end
44
+
45
+ it 'converts Float' do
46
+ expect(v 1.17).to eq('1.17')
47
+ end
48
+
49
+ it 'converts String' do
50
+ expect(v '1.17').to eq('1.17')
51
+ end
52
+
53
+ it 'converts self' do
54
+ expect(v(v 1.17)).to eq('1.17')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,272 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/bin_resolver'
3
+ require 'image_optim/cmd'
4
+
5
+ describe ImageOptim::BinResolver do
6
+ def with_env(key, value)
7
+ saved, ENV[key] = ENV[key], value
8
+ yield
9
+ ensure
10
+ ENV[key] = saved
11
+ end
12
+
13
+ before do
14
+ stub_const('BinResolver', ImageOptim::BinResolver)
15
+ stub_const('Bin', BinResolver::Bin)
16
+ stub_const('SimpleVersion', BinResolver::SimpleVersion)
17
+ stub_const('Cmd', ImageOptim::Cmd)
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
+ def command_v(name)
29
+ path = Cmd.capture("sh -c 'command -v #{name}' 2> /dev/null").strip
30
+ path unless path.empty?
31
+ end
32
+
33
+ it 'finds binary in path' do
34
+ with_env 'PATH', 'bin' do
35
+ expect(full_path('image_optim')).
36
+ to eq(File.expand_path('bin/image_optim'))
37
+ end
38
+ end
39
+
40
+ it 'finds bin in vendor' do
41
+ with_env 'PATH', nil do
42
+ expect(full_path('jpegrescan')).
43
+ to eq(File.expand_path('vendor/jpegrescan'))
44
+ end
45
+ end
46
+
47
+ it 'finds bin in pack' do
48
+ allow(image_optim).to receive(:pack).and_return(true)
49
+ stub_const('ImageOptim::Pack', Class.new do
50
+ def self.path
51
+ 'script'
52
+ end
53
+ end)
54
+
55
+ with_env 'PATH', nil do
56
+ expect(full_path('update_worker_options_in_readme')).
57
+ to eq(File.expand_path('script/update_worker_options_in_readme'))
58
+ end
59
+ end
60
+
61
+ it 'works with different path separator' do
62
+ stub_const('File::PATH_SEPARATOR', 'O_o')
63
+ with_env 'PATH', 'bin' do
64
+ expect(full_path('image_optim')).
65
+ to eq(File.expand_path('bin/image_optim'))
66
+ end
67
+ end
68
+
69
+ it 'returns nil on failure' do
70
+ with_env 'PATH', 'lib' do
71
+ expect(full_path('image_optim')).to be_nil
72
+ end
73
+ end
74
+
75
+ %w[ls sh which bash image_optim does_not_exist].each do |name|
76
+ it "returns same path as `command -v` for #{name}" do
77
+ expect(full_path(name)).to eq(command_v(name))
78
+ end
79
+ end
80
+ end
81
+
82
+ it 'combines path in order dir:pack:path:vendor' do
83
+ allow(image_optim).to receive(:pack).and_return(true)
84
+ stub_const('ImageOptim::Pack', Class.new do
85
+ def self.path
86
+ 'pack_path'
87
+ end
88
+ end)
89
+ allow(resolver).to receive(:dir).and_return('temp_dir')
90
+
91
+ expect(resolver.env_path).to eq([
92
+ 'temp_dir',
93
+ 'pack_path',
94
+ ENV['PATH'],
95
+ BinResolver::VENDOR_PATH,
96
+ ].join(':'))
97
+ end
98
+
99
+ it 'resolves bin in path and returns instance of Bin' do
100
+ with_env 'LS_BIN', nil do
101
+ expect(FSPath).not_to receive(:temp_dir)
102
+ expect(resolver).to receive(:full_path).with(:ls).and_return('/bin/ls')
103
+ bin = double
104
+ expect(Bin).to receive(:new).with(:ls, '/bin/ls').and_return(bin)
105
+ expect(bin).to receive(:check!).once
106
+ expect(bin).to receive(:check_fail!).exactly(5).times
107
+
108
+ 5.times do
109
+ expect(resolver.resolve!(:ls)).to eq(bin)
110
+ end
111
+ expect(resolver.env_path).to eq([
112
+ ENV['PATH'],
113
+ BinResolver::VENDOR_PATH,
114
+ ].join(':'))
115
+ end
116
+ end
117
+
118
+ it 'raises on failure to resolve bin' do
119
+ with_env 'LS_BIN', nil do
120
+ expect(FSPath).not_to receive(:temp_dir)
121
+ expect(resolver).to receive(:full_path).with(:ls).and_return(nil)
122
+ expect(Bin).not_to receive(:new)
123
+
124
+ 5.times do
125
+ expect do
126
+ resolver.resolve!(:ls)
127
+ end.to raise_error BinResolver::BinNotFound
128
+ end
129
+ expect(resolver.env_path).to eq([
130
+ ENV['PATH'],
131
+ BinResolver::VENDOR_PATH,
132
+ ].join(':'))
133
+ end
134
+ end
135
+
136
+ it 'resolves bin specified in ENV' do
137
+ path = 'bin/image_optim'
138
+ with_env 'IMAGE_OPTIM_BIN', path do
139
+ tmpdir = double(:tmpdir, :to_str => 'tmpdir')
140
+ symlink = double(:symlink)
141
+
142
+ expect(FSPath).to receive(:temp_dir).
143
+ once.and_return(tmpdir)
144
+ expect(tmpdir).to receive(:/).
145
+ with(:image_optim).once.and_return(symlink)
146
+ expect(symlink).to receive(:make_symlink).
147
+ with(File.expand_path(path)).once
148
+
149
+ expect(resolver).not_to receive(:full_path)
150
+ bin = double
151
+ expect(Bin).to receive(:new).
152
+ with(:image_optim, File.expand_path(path)).and_return(bin)
153
+ expect(bin).to receive(:check!).once
154
+ expect(bin).to receive(:check_fail!).exactly(5).times
155
+
156
+ at_exit_blocks = []
157
+ expect(resolver).to receive(:at_exit).once do |&block|
158
+ at_exit_blocks.unshift(block)
159
+ end
160
+
161
+ 5.times do
162
+ resolver.resolve!(:image_optim)
163
+ end
164
+ expect(resolver.env_path).to eq([
165
+ tmpdir,
166
+ ENV['PATH'],
167
+ BinResolver::VENDOR_PATH,
168
+ ].join(':'))
169
+
170
+ expect(FileUtils).to receive(:remove_entry_secure).with(tmpdir)
171
+ at_exit_blocks.each(&:call)
172
+ end
173
+ end
174
+
175
+ {
176
+ 'some/path/does/not/exist' => 'doesn\'t exist',
177
+ '.' => 'is not a file',
178
+ __FILE__ => 'is not executable',
179
+ }.each do |path, error_message|
180
+ it "raises when bin specified in ENV #{error_message}" do
181
+ with_env 'IMAGE_OPTIM_BIN', path do
182
+ expect(FSPath).not_to receive(:temp_dir)
183
+ expect(resolver).not_to receive(:at_exit)
184
+
185
+ 5.times do
186
+ expect do
187
+ resolver.resolve!(:image_optim)
188
+ end.to raise_error RuntimeError, /#{Regexp.escape(error_message)}/
189
+ end
190
+ expect(resolver.env_path).to eq([
191
+ ENV['PATH'],
192
+ BinResolver::VENDOR_PATH,
193
+ ].join(':'))
194
+ end
195
+ end
196
+ end
197
+
198
+ it 'resolves bin only once, but checks every time' do
199
+ with_env 'LS_BIN', nil do
200
+ expect(resolver).to receive(:full_path).once.with(:ls) do
201
+ sleep 0.1
202
+ '/bin/ls'
203
+ end
204
+ bin = double
205
+ expect(Bin).to receive(:new).once.with(:ls, '/bin/ls').and_return(bin)
206
+
207
+ count = 0
208
+ mutex = Mutex.new
209
+ allow(bin).to receive(:check!).once
210
+ allow(bin).to receive(:check_fail!){ mutex.synchronize{ count += 1 } }
211
+
212
+ 10.times.map do
213
+ Thread.new do
214
+ resolver.resolve!(:ls)
215
+ end
216
+ end.each(&:join)
217
+
218
+ expect(count).to eq(10)
219
+ end
220
+ end
221
+
222
+ describe 'checking version' do
223
+ before do
224
+ allow(resolver).to receive(:full_path){ |name| "/bin/#{name}" }
225
+ end
226
+
227
+ it 'raises every time if did not get bin version' do
228
+ with_env 'PNGCRUSH_BIN', nil do
229
+ bin = Bin.new(:pngcrush, '/bin/pngcrush')
230
+
231
+ expect(Bin).to receive(:new).and_return(bin)
232
+ allow(bin).to receive(:version).and_return(nil)
233
+
234
+ 5.times do
235
+ expect do
236
+ resolver.resolve!(:pngcrush)
237
+ end.to raise_error Bin::UnknownVersion
238
+ end
239
+ end
240
+ end
241
+
242
+ it 'raises every time on detection of misbehaving version' do
243
+ with_env 'PNGCRUSH_BIN', nil do
244
+ bin = Bin.new(:pngcrush, '/bin/pngcrush')
245
+
246
+ expect(Bin).to receive(:new).and_return(bin)
247
+ allow(bin).to receive(:version).and_return(SimpleVersion.new('1.7.60'))
248
+
249
+ 5.times do
250
+ expect do
251
+ resolver.resolve!(:pngcrush)
252
+ end.to raise_error Bin::BadVersion
253
+ end
254
+ end
255
+ end
256
+
257
+ it 'warns once on detection of problematic version' do
258
+ with_env 'ADVPNG_BIN', nil do
259
+ bin = Bin.new(:advpng, '/bin/advpng')
260
+
261
+ expect(Bin).to receive(:new).and_return(bin)
262
+ allow(bin).to receive(:version).and_return(SimpleVersion.new('1.15'))
263
+
264
+ expect(bin).to receive(:warn).once
265
+
266
+ 5.times do
267
+ resolver.resolve!(:pngcrush)
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/cmd'
3
+
4
+ describe ImageOptim::Cmd do
5
+ before do
6
+ stub_const('Cmd', ImageOptim::Cmd)
7
+ end
8
+
9
+ def expect_int_exception(&block)
10
+ expect(&block).to raise_error(SignalException) do |error|
11
+ expect(error.message.to_s).to match(/INT|#{Signal.list['INT']}/)
12
+ end
13
+ end
14
+
15
+ describe :run do
16
+ it 'calls system and returns result' do
17
+ status = double
18
+ expect(Cmd).to receive(:system).with('cmd', 'arg').and_return(status)
19
+ allow(Cmd).to receive(:check_status!)
20
+ expect(Cmd.run('cmd', 'arg')).to eq(status)
21
+ end
22
+
23
+ it 'returns process success status' do
24
+ expect(Cmd.run('sh -c exit\ 0')).to eq(true)
25
+ expect($CHILD_STATUS.exitstatus).to eq(0)
26
+
27
+ expect(Cmd.run('sh -c exit\ 1')).to eq(false)
28
+ expect($CHILD_STATUS.exitstatus).to eq(1)
29
+
30
+ expect(Cmd.run('sh -c exit\ 66')).to eq(false)
31
+ expect($CHILD_STATUS.exitstatus).to eq(66)
32
+ end
33
+
34
+ it 'raises SignalException if process terminates after signal' do
35
+ expect_int_exception do
36
+ Cmd.run('kill -s INT $$')
37
+ end
38
+ end
39
+ end
40
+
41
+ describe :capture do
42
+ it 'calls ` and returns result' do
43
+ output = double
44
+ expect(Cmd).to receive(:`).with('cmd arg arg+').and_return(output)
45
+ allow(Cmd).to receive(:check_status!)
46
+ expect(Cmd.capture('cmd arg arg+')).to eq(output)
47
+ end
48
+
49
+ it 'returns output' do
50
+ expect(Cmd.capture('echo test')).to eq("test\n")
51
+ expect($CHILD_STATUS.exitstatus).to eq(0)
52
+
53
+ expect(Cmd.capture('printf more; sh -c exit\ 1')).to eq('more')
54
+ expect($CHILD_STATUS.exitstatus).to eq(1)
55
+
56
+ expect(Cmd.capture('sh -c exit\ 66')).to eq('')
57
+ expect($CHILD_STATUS.exitstatus).to eq(66)
58
+ end
59
+
60
+ it 'raises SignalException if process terminates after signal' do
61
+ expect_int_exception do
62
+ Cmd.capture('kill -s INT $$')
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/config'
3
+
4
+ describe ImageOptim::Config do
5
+ before do
6
+ stub_const('IOConfig', ImageOptim::Config)
7
+ end
8
+
9
+ describe :assert_no_unused_options! do
10
+ before do
11
+ allow(IOConfig).to receive(:read_options).and_return({})
12
+ end
13
+
14
+ it 'does not raise when no unused options' do
15
+ config = IOConfig.new({})
16
+ config.assert_no_unused_options!
17
+ end
18
+
19
+ it 'raises when there are unused options' do
20
+ config = IOConfig.new(:unused => true)
21
+ expect do
22
+ config.assert_no_unused_options!
23
+ end.to raise_error(ImageOptim::ConfigurationError)
24
+ end
25
+ end
26
+
27
+ describe :nice do
28
+ before do
29
+ allow(IOConfig).to receive(:read_options).and_return({})
30
+ end
31
+
32
+ it 'is 10 by default' do
33
+ config = IOConfig.new({})
34
+ expect(config.nice).to eq(10)
35
+ end
36
+
37
+ it 'is 0 if disabled' do
38
+ config = IOConfig.new(:nice => false)
39
+ expect(config.nice).to eq(0)
40
+ end
41
+
42
+ it 'converts value to number' do
43
+ config = IOConfig.new(:nice => '13')
44
+ expect(config.nice).to eq(13)
45
+ end
46
+ end
47
+
48
+ describe :threads do
49
+ before do
50
+ allow(IOConfig).to receive(:read_options).and_return({})
51
+ end
52
+
53
+ it 'is processor_count by default' do
54
+ config = IOConfig.new({})
55
+ allow(config).to receive(:processor_count).and_return(13)
56
+ expect(config.threads).to eq(13)
57
+ end
58
+
59
+ it 'is 1 if disabled' do
60
+ config = IOConfig.new(:threads => false)
61
+ expect(config.threads).to eq(1)
62
+ end
63
+
64
+ it 'converts value to number' do
65
+ config = IOConfig.new(:threads => '616')
66
+ expect(config.threads).to eq(616)
67
+ end
68
+ end
69
+
70
+ describe :for_worker do
71
+ before do
72
+ allow(IOConfig).to receive(:read_options).and_return({})
73
+ stub_const('Abc', Class.new do
74
+ def self.bin_sym
75
+ :abc
76
+ end
77
+
78
+ def image_formats
79
+ []
80
+ end
81
+ end)
82
+ end
83
+
84
+ it 'returns empty hash by default' do
85
+ config = IOConfig.new({})
86
+ expect(config.for_worker(Abc)).to eq({})
87
+ end
88
+
89
+ it 'returns passed hash' do
90
+ config = IOConfig.new(:abc => {:option => true})
91
+ expect(config.for_worker(Abc)).to eq(:option => true)
92
+ end
93
+
94
+ it 'returns {:disable => true} for false' do
95
+ config = IOConfig.new(:abc => false)
96
+ expect(config.for_worker(Abc)).to eq(:disable => true)
97
+ end
98
+
99
+ it 'raises on unknown option' do
100
+ config = IOConfig.new(:abc => 13)
101
+ expect do
102
+ config.for_worker(Abc)
103
+ end.to raise_error(ImageOptim::ConfigurationError)
104
+ end
105
+ end
106
+
107
+ describe 'config' do
108
+ it 'reads options from default locations' do
109
+ expect(IOConfig).to receive(:read_options).
110
+ with(IOConfig::GLOBAL_PATH).and_return(:a => 1, :b => 2, :c => 3)
111
+ expect(IOConfig).to receive(:read_options).
112
+ with(IOConfig::LOCAL_PATH).and_return(:a => 10, :b => 20)
113
+
114
+ config = IOConfig.new(:a => 100)
115
+ expect(config.get!(:a)).to eq(100)
116
+ expect(config.get!(:b)).to eq(20)
117
+ expect(config.get!(:c)).to eq(3)
118
+ config.assert_no_unused_options!
119
+ end
120
+
121
+ it 'does not read options with empty config_paths' do
122
+ expect(IOConfig).not_to receive(:read_options)
123
+
124
+ config = IOConfig.new(:config_paths => [])
125
+ config.assert_no_unused_options!
126
+ end
127
+
128
+ it 'reads options from specified paths' do
129
+ expect(IOConfig).to receive(:read_options).
130
+ with('/etc/image_optim.yml').and_return(:a => 1, :b => 2, :c => 3)
131
+ expect(IOConfig).to receive(:read_options).
132
+ with('config/image_optim.yml').and_return(:a => 10, :b => 20)
133
+
134
+ config = IOConfig.new(:a => 100, :config_paths => %w[
135
+ /etc/image_optim.yml
136
+ config/image_optim.yml
137
+ ])
138
+ expect(config.get!(:a)).to eq(100)
139
+ expect(config.get!(:b)).to eq(20)
140
+ expect(config.get!(:c)).to eq(3)
141
+ config.assert_no_unused_options!
142
+ end
143
+
144
+ it 'converts config_paths to array' do
145
+ expect(IOConfig).to receive(:read_options).
146
+ with('config/image_optim.yml').and_return({})
147
+
148
+ config = IOConfig.new(:config_paths => 'config/image_optim.yml')
149
+ config.assert_no_unused_options!
150
+ end
151
+ end
152
+
153
+ describe 'class methods' do
154
+ describe :read_options do
155
+ let(:path){ double(:path) }
156
+ let(:full_path){ double(:full_path) }
157
+
158
+ it 'warns if expand path fails' do
159
+ expect(IOConfig).to receive(:warn)
160
+ expect(File).to receive(:expand_path).
161
+ with(path).and_raise(ArgumentError)
162
+ expect(File).not_to receive(:file?)
163
+
164
+ expect(IOConfig.read_options(path)).to eq({})
165
+ end
166
+
167
+ it 'returns empty hash if path is not a file' do
168
+ expect(IOConfig).not_to receive(:warn)
169
+ expect(File).to receive(:expand_path).
170
+ with(path).and_return(full_path)
171
+ expect(File).to receive(:file?).
172
+ with(full_path).and_return(false)
173
+
174
+ expect(IOConfig.read_options(path)).to eq({})
175
+ end
176
+
177
+ it 'returns hash with deep symbolised keys from reader' do
178
+ stringified = {'config' => {'this' => true}}
179
+ symbolized = {:config => {:this => true}}
180
+
181
+ expect(IOConfig).not_to receive(:warn)
182
+ expect(File).to receive(:expand_path).
183
+ with(path).and_return(full_path)
184
+ expect(File).to receive(:file?).
185
+ with(full_path).and_return(true)
186
+ expect(YAML).to receive(:load_file).
187
+ with(full_path).and_return(stringified)
188
+
189
+ expect(IOConfig.read_options(path)).to eq(symbolized)
190
+ end
191
+
192
+ it 'warns and returns an empty hash if reader returns non hash' do
193
+ expect(IOConfig).to receive(:warn)
194
+ expect(File).to receive(:expand_path).
195
+ with(path).and_return(full_path)
196
+ expect(File).to receive(:file?).
197
+ with(full_path).and_return(true)
198
+ expect(YAML).to receive(:load_file).
199
+ with(full_path).and_return([:config])
200
+
201
+ expect(IOConfig.read_options(path)).to eq({})
202
+ end
203
+
204
+ it 'warns and returns an empty hash if reader raises exception' do
205
+ expect(IOConfig).to receive(:warn)
206
+ expect(File).to receive(:expand_path).
207
+ with(path).and_return(full_path)
208
+ expect(File).to receive(:file?).
209
+ with(full_path).and_return(true)
210
+ expect(YAML).to receive(:load_file).
211
+ with(full_path).and_raise
212
+
213
+ expect(IOConfig.read_options(path)).to eq({})
214
+ end
215
+ end
216
+ end
217
+ end