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
@@ -9,15 +9,12 @@ describe ImageOptim::Handler do
9
9
  it 'uses original as source for first conversion '\
10
10
  'and two temp files for further conversions' do
11
11
  original = double(:original)
12
- allow(original).to receive(:temp_path) do
13
- fail 'temp_path called unexpectedly'
14
- end
12
+ allow(original).to receive(:respond_to?).with(:temp_path).and_return(true)
15
13
 
16
14
  handler = Handler.new(original)
17
15
  temp_a = double(:temp_a)
18
16
  temp_b = double(:temp_b)
19
- expect(original).to receive(:temp_path).once.and_return(temp_a)
20
- expect(original).to receive(:temp_path).once.and_return(temp_b)
17
+ expect(original).to receive(:temp_path).and_return(temp_a, temp_b)
21
18
 
22
19
  # first unsuccessful run
23
20
  handler.process do |src, dst|
@@ -60,7 +57,7 @@ describe ImageOptim::Handler do
60
57
  handler.cleanup
61
58
  end
62
59
 
63
- describe :open do
60
+ describe '.for' do
64
61
  it 'yields instance, runs cleanup and returns result' do
65
62
  original = double
66
63
  handler = double
@@ -0,0 +1,61 @@
1
+ require 'image_optim/image_meta'
2
+
3
+ describe ImageOptim::ImageMeta do
4
+ let(:image_path){ 'spec/images/lena.jpg' }
5
+ let(:non_image_path){ __FILE__ }
6
+ let(:broken_image_path){ 'spec/images/broken_jpeg' }
7
+
8
+ describe '.format_for_path' do
9
+ context 'for an image' do
10
+ it 'returns format' do
11
+ expect(described_class.format_for_path(image_path)).
12
+ to eq(:jpeg)
13
+ end
14
+ end
15
+
16
+ context 'for broken image' do
17
+ it 'warns and returns nil' do
18
+ expect(described_class).to receive(:warn)
19
+
20
+ expect(described_class.format_for_path(broken_image_path)).
21
+ to eq(nil)
22
+ end
23
+ end
24
+
25
+ context 'for not an image' do
26
+ it 'does not warn and returns nil' do
27
+ expect(described_class).not_to receive(:warn)
28
+
29
+ expect(described_class.format_for_path(non_image_path)).
30
+ to eq(nil)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '.format_for_data' do
36
+ context 'for image data' do
37
+ it 'returns format' do
38
+ expect(described_class.format_for_data(File.read(image_path))).
39
+ to eq(:jpeg)
40
+ end
41
+ end
42
+
43
+ context 'for broken image data' do
44
+ it 'warns and returns nil' do
45
+ expect(described_class).to receive(:warn)
46
+
47
+ expect(described_class.format_for_data(File.read(broken_image_path))).
48
+ to eq(nil)
49
+ end
50
+ end
51
+
52
+ context 'for not an image data' do
53
+ it 'does not warn and returns nil' do
54
+ expect(described_class).not_to receive(:warn)
55
+
56
+ expect(described_class.format_for_data(File.read(non_image_path))).
57
+ to eq(nil)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,58 @@
1
+ require 'image_optim/optimized_path'
2
+
3
+ describe ImageOptim::OptimizedPath do
4
+ describe '#initialize' do
5
+ context 'when second argument is original' do
6
+ subject{ described_class.new('a', 'b') }
7
+
8
+ before do
9
+ allow_any_instance_of(ImageOptim::Path).
10
+ to receive(:size).and_return(616)
11
+ end
12
+
13
+ it 'delegates to optimized path as Path' do
14
+ is_expected.to eq(ImageOptim::Path.new('a'))
15
+ end
16
+
17
+ it 'returns original path as Path for original' do
18
+ expect(subject.original).to eq(ImageOptim::Path.new('b'))
19
+ end
20
+
21
+ it 'returns original size for original_size' do
22
+ expect(subject.original_size).to eq(616)
23
+ end
24
+ end
25
+
26
+ context 'when second argument is size' do
27
+ subject{ described_class.new('a', 616) }
28
+
29
+ it 'delegates to optimized path as Path' do
30
+ is_expected.to eq(ImageOptim::Path.new('a'))
31
+ end
32
+
33
+ it 'returns optimized path as Path for original' do
34
+ expect(subject.original).to eq(ImageOptim::Path.new('a'))
35
+ end
36
+
37
+ it 'returns size for original_size' do
38
+ expect(subject.original_size).to eq(616)
39
+ end
40
+ end
41
+
42
+ context 'when no second argument' do
43
+ subject{ described_class.new('a') }
44
+
45
+ it 'delegates to optimized path as Path' do
46
+ is_expected.to eq(ImageOptim::Path.new('a'))
47
+ end
48
+
49
+ it 'returns nil for original' do
50
+ expect(subject.original).to eq(nil)
51
+ end
52
+
53
+ it 'returns nil for original_size' do
54
+ expect(subject.original_size).to eq(nil)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/option_helpers'
3
+
4
+ describe ImageOptim::OptionHelpers do
5
+ describe '.limit_with_range' do
6
+ {
7
+ 2..4 => 'inclusive',
8
+ 2...5 => 'exclusive',
9
+ }.each do |range, type|
10
+ context "for an end #{type} range" do
11
+ it 'returns number when it is in range' do
12
+ expect(described_class.limit_with_range(4, range)).to eq(4)
13
+ end
14
+
15
+ it 'returns begin when it is less than minimum' do
16
+ expect(described_class.limit_with_range(1, range)).to eq(2)
17
+ end
18
+
19
+ it 'returns end when it is more than maximum' do
20
+ expect(described_class.limit_with_range(10, range)).to eq(4)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+ require 'image_optim/path'
3
+ require 'tempfile'
4
+
5
+ describe ImageOptim::Path do
6
+ include CapabilityCheckHelpers
7
+
8
+ before do
9
+ stub_const('Path', ImageOptim::Path)
10
+ end
11
+
12
+ describe '.convert' do
13
+ it 'returns Path for string' do
14
+ path = 'a'
15
+
16
+ expect(Path.convert(path)).to be_a(Path)
17
+ expect(Path.convert(path)).to eq(Path.new(path))
18
+
19
+ expect(Path.convert(path)).not_to eq(path)
20
+ expect(Path.convert(path)).not_to be(path)
21
+ end
22
+
23
+ it 'returns Path for Pathname' do
24
+ pathname = Pathname.new('a')
25
+
26
+ expect(Path.convert(pathname)).to be_a(Path)
27
+ expect(Path.convert(pathname)).to eq(Path.new(pathname))
28
+
29
+ expect(Path.convert(pathname)).to eq(pathname)
30
+ expect(Path.convert(pathname)).not_to be(pathname)
31
+ end
32
+
33
+ it 'returns same instance for Path' do
34
+ image_path = Path.new('a')
35
+
36
+ expect(Path.convert(image_path)).to be_a(Path)
37
+ expect(Path.convert(image_path)).to eq(Path.new(image_path))
38
+
39
+ expect(Path.convert(image_path)).to eq(image_path)
40
+ expect(Path.convert(image_path)).to be(image_path)
41
+ end
42
+ end
43
+
44
+ describe '#binread' do
45
+ it 'reads binary data' do
46
+ data = (0..255).to_a.pack('c*')
47
+
48
+ path = Path.temp_file_path
49
+ path.binwrite(data)
50
+
51
+ expect(path.binread).to eq(data)
52
+ if ''.respond_to?(:encoding)
53
+ expect(path.binread.encoding).to eq(Encoding.find('ASCII-8BIT'))
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#replace' do
59
+ let(:src){ Path.temp_file_path }
60
+ let(:dst){ Path.temp_file_path }
61
+
62
+ it 'moves data to destination' do
63
+ src.write('src')
64
+
65
+ src.replace(dst)
66
+
67
+ expect(dst.read).to eq('src')
68
+ end
69
+
70
+ it 'removes original file' do
71
+ src.replace(dst)
72
+
73
+ expect(src).to_not exist
74
+ end
75
+
76
+ it 'preserves attributes of destination file' do
77
+ skip 'full file modes are not support' unless any_file_modes_allowed?
78
+ mode = 0o666
79
+
80
+ dst.chmod(mode)
81
+
82
+ src.replace(dst)
83
+
84
+ got = dst.stat.mode & 0o777
85
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
86
+ end
87
+
88
+ it 'does not preserve mtime of destination file' do
89
+ time = src.mtime
90
+
91
+ dst.utime(time - 1000, time - 1000)
92
+
93
+ src.replace(dst)
94
+
95
+ expect(dst.mtime).to be >= time
96
+ end
97
+
98
+ it 'changes inode of destination' do
99
+ skip 'inodes are not supported' unless inodes_supported?
100
+ expect do
101
+ src.replace(dst)
102
+ end.to change{ dst.stat.ino }
103
+ end
104
+ end
105
+ end
@@ -33,7 +33,7 @@ describe 'ImageOptim::Railtie' do
33
33
  Rails.application = nil
34
34
  end
35
35
 
36
- describe :initialization do
36
+ describe 'initialization' do
37
37
  it 'initializes by default' do
38
38
  expect(ImageOptim).to receive(:new)
39
39
  init_rails_app
@@ -117,9 +117,9 @@ describe 'ImageOptim::Railtie' do
117
117
  end
118
118
  end
119
119
 
120
- describe :assets do
120
+ describe 'optimizing assets' do
121
121
  before do
122
- stub_const('ImagePath', ImageOptim::ImagePath)
122
+ stub_const('Path', ImageOptim::Path)
123
123
  end
124
124
 
125
125
  %w[
@@ -132,12 +132,12 @@ describe 'ImageOptim::Railtie' do
132
132
  asset = init_rails_app.assets.find_asset(asset_name)
133
133
 
134
134
  asset_data = asset.source
135
- original = ImagePath.convert(asset.pathname)
135
+ original = Path.convert(asset.pathname)
136
136
 
137
137
  expect(asset_data).to be_smaller_than(original)
138
138
 
139
- ImagePath.temp_file_path %W[spec .#{original.format}] do |temp|
140
- temp.write(asset_data)
139
+ Path.temp_file_path %W[spec .#{original.image_format}] do |temp|
140
+ temp.binwrite(asset_data)
141
141
 
142
142
  expect(temp).to be_similar_to(original, 0)
143
143
  end
@@ -2,11 +2,7 @@ require 'spec_helper'
2
2
  require 'image_optim/runner/glob_helpers'
3
3
 
4
4
  describe ImageOptim::Runner::GlobHelpers do
5
- before do
6
- stub_const('GlobHelpers', ImageOptim::Runner::GlobHelpers)
7
- end
8
-
9
- describe :expand_braces do
5
+ describe '.expand_braces' do
10
6
  {
11
7
  'hello.world' => %w[hello.world],
12
8
  '{hello,.world}' => %w[hello .world],
@@ -18,7 +14,7 @@ describe ImageOptim::Runner::GlobHelpers do
18
14
  'test{ing,}' => %w[testing test],
19
15
  }.each do |glob, expected|
20
16
  it "expands #{glob}" do
21
- expect(GlobHelpers.expand_braces(glob)).to match_array(expected)
17
+ expect(described_class.expand_braces(glob)).to match_array(expected)
22
18
  end
23
19
  end
24
20
  end
@@ -12,7 +12,7 @@ describe ImageOptim::Runner::OptionParser do
12
12
  end
13
13
  end
14
14
 
15
- describe :parse! do
15
+ describe '.parse!' do
16
16
  it 'returns empty hash for arguments without options' do
17
17
  args = %w[foo bar]
18
18
  expect(OptionParser.parse!(args)).to eq({})
@@ -25,7 +25,7 @@ describe ImageOptim::Runner::OptionParser do
25
25
  expect(args).to eq(%w[foo bar])
26
26
  end
27
27
 
28
- it 'stops parsing optiosn after --' do
28
+ it 'stops parsing options after --' do
29
29
  args = %w[-- -r foo bar]
30
30
  OptionParser.parse!(args)
31
31
  expect(args).to eq(%w[-r foo bar])
@@ -92,7 +92,7 @@ describe ImageOptim::Runner::OptionParser do
92
92
  end
93
93
  end
94
94
 
95
- describe :help do
95
+ describe '#help' do
96
96
  it 'returns wrapped text' do
97
97
  parser = OptionParser.new({})
98
98
 
@@ -2,24 +2,22 @@ require 'spec_helper'
2
2
  require 'image_optim/space'
3
3
 
4
4
  describe ImageOptim::Space do
5
- before do
6
- stub_const('Space', ImageOptim::Space)
7
- end
8
-
9
- {
10
- 0 => ' ',
11
- 1 => ' 1B',
12
- 10 => ' 10B',
13
- 100 => ' 100B',
14
- 1_000 => ' 1000B',
15
- 10_000 => ' 9.8K',
16
- 100_000 => ' 97.7K',
17
- 1_000_000 => '976.6K',
18
- 10_000_000 => ' 9.5M',
19
- 100_000_000 => ' 95.4M',
20
- }.each do |size, space|
21
- it "converts #{size} to #{space}" do
22
- expect(Space.space(size)).to eq(space)
5
+ describe '.space' do
6
+ {
7
+ 0 => ' ',
8
+ 1 => ' 1B',
9
+ 10 => ' 10B',
10
+ 100 => ' 100B',
11
+ 1_000 => ' 1000B',
12
+ 10_000 => ' 9.8K',
13
+ 100_000 => ' 97.7K',
14
+ 1_000_000 => '976.6K',
15
+ 10_000_000 => ' 9.5M',
16
+ 100_000_000 => ' 95.4M',
17
+ }.each do |size, space|
18
+ it "converts #{size} to #{space}" do
19
+ expect(described_class.space(size)).to eq(space)
20
+ end
23
21
  end
24
22
  end
25
23
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'image_optim/worker/optipng'
3
- require 'image_optim/image_path'
3
+ require 'image_optim/path'
4
4
 
5
5
  describe ImageOptim::Worker::Optipng do
6
6
  describe 'strip option' do
@@ -8,8 +8,8 @@ describe ImageOptim::Worker::Optipng do
8
8
 
9
9
  let(:options){ {} }
10
10
  let(:optipng_version){ '0.7' }
11
- let(:src){ instance_double(ImageOptim::ImagePath, :copy => nil) }
12
- let(:dst){ instance_double(ImageOptim::ImagePath) }
11
+ let(:src){ instance_double(ImageOptim::Path, :copy => nil) }
12
+ let(:dst){ instance_double(ImageOptim::Path) }
13
13
 
14
14
  before do
15
15
  optipng_bin = instance_double(ImageOptim::BinResolver::Bin)
@@ -10,17 +10,57 @@ describe ImageOptim::Worker::Pngquant do
10
10
  end
11
11
 
12
12
  describe 'value' do
13
- let(:options){ {} }
14
- subject{ described_class.new(ImageOptim.new, options).quality }
13
+ let(:subject){ described_class.new(ImageOptim.new, options).quality }
15
14
 
16
- context 'by default' do
17
- it{ is_expected.to eq(100..100) }
15
+ context 'when lossy not allowed' do
16
+ context 'by default' do
17
+ let(:options){ {} }
18
+
19
+ it{ is_expected.to eq(100..100) }
20
+ end
21
+
22
+ context 'when value is passed through options' do
23
+ let(:options){ {:quality => 10..90} }
24
+
25
+ it 'warns and keeps default' do
26
+ expect_any_instance_of(described_class).
27
+ to receive(:warn).with(/ignored in lossless mode/)
28
+ is_expected.to eq(100..100)
29
+ end
30
+ end
18
31
  end
19
32
 
20
- context 'when lossy allowed by default' do
21
- let(:options){ {:allow_lossy => true} }
33
+ context 'when lossy allowed' do
34
+ context 'by default' do
35
+ let(:options){ {:allow_lossy => true} }
36
+
37
+ it{ is_expected.to eq(0..100) }
38
+ end
39
+
40
+ context 'when value is passed through options' do
41
+ let(:options){ {:allow_lossy => true, :quality => 10..90} }
42
+
43
+ it 'sets the value without warning' do
44
+ expect_any_instance_of(described_class).not_to receive(:warn)
45
+ is_expected.to eq(10..90)
46
+ end
47
+ end
48
+
49
+ context 'when passed range begin is less than 0' do
50
+ let(:options){ {:allow_lossy => true, :quality => -50..50} }
51
+
52
+ it 'sets begin to 0' do
53
+ is_expected.to eq(0..50)
54
+ end
55
+ end
56
+
57
+ context 'when passed range end is more than 100' do
58
+ let(:options){ {:allow_lossy => true, :quality => 50..150} }
22
59
 
23
- it{ is_expected.to eq(0..100) }
60
+ it 'sets end to 100' do
61
+ is_expected.to eq(50..100)
62
+ end
63
+ end
24
64
  end
25
65
  end
26
66
  end