image_optim 0.26.3 → 0.28.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.appveyor.yml +9 -2
  3. data/.rubocop.yml +34 -12
  4. data/.travis.yml +18 -15
  5. data/CHANGELOG.markdown +23 -0
  6. data/CONTRIBUTING.markdown +4 -1
  7. data/Gemfile +3 -1
  8. data/LICENSE.txt +1 -1
  9. data/README.markdown +10 -3
  10. data/Vagrantfile +2 -0
  11. data/bin/image_optim +1 -0
  12. data/image_optim.gemspec +5 -7
  13. data/lib/image_optim.rb +2 -0
  14. data/lib/image_optim/bin_resolver.rb +2 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +4 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +3 -0
  17. data/lib/image_optim/bin_resolver/error.rb +2 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
  19. data/lib/image_optim/cache.rb +2 -0
  20. data/lib/image_optim/cache_path.rb +21 -2
  21. data/lib/image_optim/cmd.rb +2 -0
  22. data/lib/image_optim/config.rb +6 -4
  23. data/lib/image_optim/configuration_error.rb +2 -0
  24. data/lib/image_optim/handler.rb +2 -0
  25. data/lib/image_optim/hash_helpers.rb +2 -0
  26. data/lib/image_optim/image_meta.rb +2 -0
  27. data/lib/image_optim/non_negative_integer_range.rb +2 -0
  28. data/lib/image_optim/optimized_path.rb +3 -1
  29. data/lib/image_optim/option_definition.rb +2 -0
  30. data/lib/image_optim/option_helpers.rb +2 -0
  31. data/lib/image_optim/path.rb +30 -5
  32. data/lib/image_optim/runner.rb +2 -0
  33. data/lib/image_optim/runner/glob_helpers.rb +3 -1
  34. data/lib/image_optim/runner/option_parser.rb +5 -2
  35. data/lib/image_optim/space.rb +3 -1
  36. data/lib/image_optim/true_false_nil.rb +2 -0
  37. data/lib/image_optim/worker.rb +1 -0
  38. data/lib/image_optim/worker/advpng.rb +2 -0
  39. data/lib/image_optim/worker/class_methods.rb +4 -0
  40. data/lib/image_optim/worker/gifsicle.rb +2 -0
  41. data/lib/image_optim/worker/jhead.rb +3 -1
  42. data/lib/image_optim/worker/jpegoptim.rb +8 -4
  43. data/lib/image_optim/worker/jpegrecompress.rb +17 -0
  44. data/lib/image_optim/worker/jpegtran.rb +2 -0
  45. data/lib/image_optim/worker/optipng.rb +4 -2
  46. data/lib/image_optim/worker/pngcrush.rb +4 -2
  47. data/lib/image_optim/worker/pngout.rb +2 -0
  48. data/lib/image_optim/worker/pngquant.rb +3 -0
  49. data/lib/image_optim/worker/svgo.rb +2 -0
  50. data/script/update_worker_options_in_readme +4 -3
  51. data/script/worker_analysis +7 -3
  52. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +2 -0
  53. data/spec/image_optim/bin_resolver/simple_version_spec.rb +2 -0
  54. data/spec/image_optim/bin_resolver_spec.rb +2 -0
  55. data/spec/image_optim/cache_path_spec.rb +71 -26
  56. data/spec/image_optim/cache_spec.rb +5 -1
  57. data/spec/image_optim/cmd_spec.rb +2 -0
  58. data/spec/image_optim/config_spec.rb +2 -0
  59. data/spec/image_optim/handler_spec.rb +2 -0
  60. data/spec/image_optim/hash_helpers_spec.rb +2 -0
  61. data/spec/image_optim/image_meta_spec.rb +2 -0
  62. data/spec/image_optim/optimized_path_spec.rb +2 -0
  63. data/spec/image_optim/option_definition_spec.rb +2 -0
  64. data/spec/image_optim/option_helpers_spec.rb +2 -0
  65. data/spec/image_optim/path_spec.rb +65 -24
  66. data/spec/image_optim/runner/glob_helpers_spec.rb +2 -0
  67. data/spec/image_optim/runner/option_parser_spec.rb +2 -0
  68. data/spec/image_optim/space_spec.rb +13 -11
  69. data/spec/image_optim/worker/jpegrecompress_spec.rb +32 -0
  70. data/spec/image_optim/worker/optipng_spec.rb +2 -0
  71. data/spec/image_optim/worker/pngquant_spec.rb +2 -0
  72. data/spec/image_optim/worker_spec.rb +2 -0
  73. data/spec/image_optim_spec.rb +7 -4
  74. data/spec/images/invisiblepixels/generate +1 -0
  75. data/spec/images/quant/generate +3 -2
  76. data/spec/spec_helper.rb +2 -0
  77. metadata +14 -13
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/bin_resolver/comparable_condition'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/bin_resolver/simple_version'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/bin_resolver'
3
5
  require 'image_optim/cmd'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/cache_path'
3
5
  require 'tempfile'
@@ -6,52 +8,95 @@ describe ImageOptim::CachePath do
6
8
  include CapabilityCheckHelpers
7
9
 
8
10
  before do
11
+ stub_const('Path', ImageOptim::Path)
9
12
  stub_const('CachePath', ImageOptim::CachePath)
10
13
  end
11
14
 
12
15
  describe '#replace' do
13
- let(:src){ CachePath.temp_file_path }
14
- let(:dst){ CachePath.temp_file_path }
16
+ let(:src_dir){ Path.temp_dir }
17
+ let(:src){ CachePath.temp_file_path(nil, src_dir) }
18
+ let(:dst){ Path.temp_file_path }
15
19
 
16
- it 'moves data to destination' do
17
- src.write('src')
20
+ shared_examples 'replaces file' do
21
+ it 'moves data to destination' do
22
+ src.write('src')
18
23
 
19
- src.replace(dst)
24
+ src.replace(dst)
20
25
 
21
- expect(dst.read).to eq('src')
22
- end
26
+ expect(dst.read).to eq('src')
27
+ end
23
28
 
24
- it 'does not remove original file' do
25
- src.replace(dst)
29
+ it 'does not remove original file' do
30
+ src.replace(dst)
26
31
 
27
- expect(src).to exist
28
- end
32
+ expect(src).to exist
33
+ end
34
+
35
+ it 'preserves attributes of destination file' do
36
+ skip 'full file modes are not support' unless any_file_modes_allowed?
37
+ mode = 0o666
38
+
39
+ dst.chmod(mode)
40
+
41
+ src.replace(dst)
29
42
 
30
- it 'preserves attributes of destination file' do
31
- skip 'full file modes are not support' unless any_file_modes_allowed?
32
- mode = 0o666
43
+ got = dst.stat.mode & 0o777
44
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
45
+ end
33
46
 
34
- dst.chmod(mode)
47
+ it 'does not preserve mtime of destination file' do
48
+ time = src.mtime
35
49
 
36
- src.replace(dst)
50
+ dst.utime(time - 1000, time - 1000)
37
51
 
38
- got = dst.stat.mode & 0o777
39
- expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
52
+ src.replace(dst)
53
+
54
+ expect(dst.mtime).to be >= time
55
+ end
56
+
57
+ it 'changes inode of destination' do
58
+ skip 'inodes are not supported' unless inodes_supported?
59
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
60
+ end
61
+
62
+ it 'is using temporary file with .tmp extension' do
63
+ expect(src).to receive(:copy).with(having_attributes(:extname => '.tmp')).at_least(:once)
64
+
65
+ src.replace(dst)
66
+ end
40
67
  end
41
68
 
42
- it 'does not preserve mtime of destination file' do
43
- time = src.mtime
69
+ context 'when src and dst are on same device' do
70
+ before do
71
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
72
+ end
44
73
 
45
- dst.utime(time - 1000, time - 1000)
74
+ include_examples 'replaces file'
75
+ end
46
76
 
47
- src.replace(dst)
77
+ context 'when src and dst are on different devices' do
78
+ before do
79
+ allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
80
+ end
48
81
 
49
- expect(dst.mtime).to be >= time
82
+ include_examples 'replaces file'
50
83
  end
51
84
 
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 }
85
+ context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
86
+ before do
87
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
88
+ allow(described_class).to receive(:temp_file_path).and_call_original
89
+ expect(described_class).to receive(:temp_file_path).
90
+ with([dst.basename.to_s, '.tmp'], src.dirname).
91
+ and_wrap_original do |m, *args, &block|
92
+ m.call(*args) do |tmp|
93
+ expect(tmp).to receive(:rename).with(dst.to_s).and_raise(Errno::EXDEV)
94
+ block.call(tmp)
95
+ end
96
+ end
97
+ end
98
+
99
+ include_examples 'replaces file'
55
100
  end
56
101
  end
57
102
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'fspath'
3
5
  require 'image_optim/cache'
@@ -62,6 +64,7 @@ describe ImageOptim::Cache do
62
64
  end
63
65
  end
64
66
 
67
+ # rubocop:disable Style/RedundantFetchBlock
65
68
  shared_examples 'an enabled cache' do
66
69
  context 'when cached file does not exist' do
67
70
  describe :fetch do
@@ -99,7 +102,7 @@ describe ImageOptim::Cache do
99
102
  expect(FileUtils).not_to receive(:mv)
100
103
  expect(File).not_to receive(:rename)
101
104
 
102
- expect(cache.fetch(original){}).to eq(cached)
105
+ expect(cache.fetch(original){ nil }).to eq(cached)
103
106
  end
104
107
 
105
108
  it 'returns nil when file is already optimized' do
@@ -114,6 +117,7 @@ describe ImageOptim::Cache do
114
117
  end
115
118
  end
116
119
  end
120
+ # rubocop:enable Style/RedundantFetchBlock
117
121
 
118
122
  context 'when cache is enabled (without worker digests)' do
119
123
  let(:image_optim) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/cmd'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/config'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/handler'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/hash_helpers'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/image_meta'
2
4
 
3
5
  describe ImageOptim::ImageMeta do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'image_optim/optimized_path'
2
4
 
3
5
  describe ImageOptim::OptimizedPath do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/option_definition'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/option_helpers'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/path'
3
5
  require 'tempfile'
@@ -59,45 +61,84 @@ describe ImageOptim::Path do
59
61
  let(:src){ Path.temp_file_path }
60
62
  let(:dst){ Path.temp_file_path }
61
63
 
62
- it 'moves data to destination' do
63
- src.write('src')
64
+ shared_examples 'replaces file' do
65
+ it 'moves data to destination' do
66
+ src.write('src')
64
67
 
65
- src.replace(dst)
68
+ src.replace(dst)
66
69
 
67
- expect(dst.read).to eq('src')
68
- end
70
+ expect(dst.read).to eq('src')
71
+ end
69
72
 
70
- it 'removes original file' do
71
- src.replace(dst)
73
+ it 'removes original file' do
74
+ src.replace(dst)
72
75
 
73
- expect(src).to_not exist
74
- end
76
+ expect(src).to_not exist
77
+ end
78
+
79
+ it 'preserves attributes of destination file' do
80
+ skip 'full file modes are not support' unless any_file_modes_allowed?
81
+ mode = 0o666
82
+
83
+ dst.chmod(mode)
84
+
85
+ src.replace(dst)
86
+
87
+ got = dst.stat.mode & 0o777
88
+ expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
89
+ end
90
+
91
+ it 'does not preserve mtime of destination file' do
92
+ time = src.mtime
93
+
94
+ dst.utime(time - 1000, time - 1000)
75
95
 
76
- it 'preserves attributes of destination file' do
77
- skip 'full file modes are not support' unless any_file_modes_allowed?
78
- mode = 0o666
96
+ src.replace(dst)
79
97
 
80
- dst.chmod(mode)
98
+ expect(dst.mtime).to be >= time
99
+ end
100
+
101
+ it 'changes inode of destination' do
102
+ skip 'inodes are not supported' unless inodes_supported?
103
+ expect{ src.replace(dst) }.to change{ dst.stat.ino }
104
+ end
105
+ end
81
106
 
82
- src.replace(dst)
107
+ context 'when src and dst are on same device' do
108
+ before do
109
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
110
+ end
83
111
 
84
- got = dst.stat.mode & 0o777
85
- expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
112
+ include_examples 'replaces file'
86
113
  end
87
114
 
88
- it 'does not preserve mtime of destination file' do
89
- time = src.mtime
115
+ context 'when src and dst are on different devices' do
116
+ before do
117
+ allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
118
+ end
90
119
 
91
- dst.utime(time - 1000, time - 1000)
120
+ include_examples 'replaces file'
92
121
 
93
- src.replace(dst)
122
+ it 'is using temporary file with .tmp extension' do
123
+ expect(src).to receive(:move).with(having_attributes(:extname => '.tmp'))
94
124
 
95
- expect(dst.mtime).to be >= time
125
+ src.replace(dst)
126
+ end
96
127
  end
97
128
 
98
- it 'changes inode of destination' do
99
- skip 'inodes are not supported' unless inodes_supported?
100
- expect{ src.replace(dst) }.to change{ dst.stat.ino }
129
+ context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
130
+ before do
131
+ allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
132
+ expect(src).to receive(:rename).with(dst.to_s).once.and_raise(Errno::EXDEV)
133
+ end
134
+
135
+ include_examples 'replaces file'
136
+
137
+ it 'is using temporary file with .tmp extension' do
138
+ expect(src).to receive(:move).with(having_attributes(:extname => '.tmp'))
139
+
140
+ src.replace(dst)
141
+ end
101
142
  end
102
143
  end
103
144
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/runner/glob_helpers'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/runner/option_parser'
3
5
 
@@ -1,20 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/space'
3
5
 
4
6
  describe ImageOptim::Space do
5
7
  describe '.space' do
6
8
  {
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|
9
+ ' ' => 0,
10
+ ' 1B' => 1,
11
+ ' 10B' => 10,
12
+ ' 100B' => 100,
13
+ ' 1000B' => 1_000,
14
+ ' 9.8K' => 10_000,
15
+ ' 97.7K' => 100_000,
16
+ '976.6K' => 1_000_000,
17
+ ' 9.5M' => 10_000_000,
18
+ ' 95.4M' => 100_000_000,
19
+ }.each do |space, size|
18
20
  it "converts #{size} to #{space}" do
19
21
  expect(described_class.space(size)).to eq(space)
20
22
  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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/worker/optipng'
3
5
  require 'image_optim/path'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/worker/pngquant'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'image_optim/worker'
3
5
  require 'image_optim/bin_resolver'