image_optim 0.26.2 → 0.27.1
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.
- checksums.yaml +4 -4
- data/.appveyor.yml +9 -2
- data/.rubocop.yml +38 -10
- data/.travis.yml +19 -15
- data/CHANGELOG.markdown +21 -0
- data/Gemfile +3 -1
- data/LICENSE.txt +1 -1
- data/README.markdown +9 -3
- data/Vagrantfile +2 -0
- data/bin/image_optim +1 -0
- data/image_optim.gemspec +5 -7
- data/lib/image_optim.rb +5 -0
- data/lib/image_optim/bin_resolver.rb +5 -0
- data/lib/image_optim/bin_resolver/bin.rb +45 -19
- data/lib/image_optim/bin_resolver/comparable_condition.rb +3 -0
- data/lib/image_optim/bin_resolver/error.rb +2 -0
- data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
- data/lib/image_optim/cache.rb +3 -0
- data/lib/image_optim/cache_path.rb +21 -2
- data/lib/image_optim/cmd.rb +2 -0
- data/lib/image_optim/config.rb +7 -1
- data/lib/image_optim/configuration_error.rb +2 -0
- data/lib/image_optim/handler.rb +4 -0
- data/lib/image_optim/hash_helpers.rb +2 -0
- data/lib/image_optim/image_meta.rb +2 -0
- data/lib/image_optim/non_negative_integer_range.rb +2 -0
- data/lib/image_optim/optimized_path.rb +3 -1
- data/lib/image_optim/option_definition.rb +2 -0
- data/lib/image_optim/option_helpers.rb +2 -0
- data/lib/image_optim/path.rb +30 -5
- data/lib/image_optim/runner.rb +3 -0
- data/lib/image_optim/runner/glob_helpers.rb +3 -1
- data/lib/image_optim/runner/option_parser.rb +4 -2
- data/lib/image_optim/space.rb +3 -1
- data/lib/image_optim/true_false_nil.rb +2 -0
- data/lib/image_optim/worker.rb +5 -0
- data/lib/image_optim/worker/advpng.rb +2 -0
- data/lib/image_optim/worker/class_methods.rb +5 -0
- data/lib/image_optim/worker/gifsicle.rb +2 -0
- data/lib/image_optim/worker/jhead.rb +4 -1
- data/lib/image_optim/worker/jpegoptim.rb +2 -0
- data/lib/image_optim/worker/jpegrecompress.rb +2 -0
- data/lib/image_optim/worker/jpegtran.rb +2 -0
- data/lib/image_optim/worker/optipng.rb +4 -2
- data/lib/image_optim/worker/pngcrush.rb +4 -2
- data/lib/image_optim/worker/pngout.rb +3 -0
- data/lib/image_optim/worker/pngquant.rb +2 -0
- data/lib/image_optim/worker/svgo.rb +2 -0
- data/script/update_worker_options_in_readme +3 -2
- data/script/worker_analysis +13 -3
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +2 -0
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +2 -0
- data/spec/image_optim/bin_resolver_spec.rb +4 -2
- data/spec/image_optim/cache_path_spec.rb +71 -26
- data/spec/image_optim/cache_spec.rb +4 -0
- data/spec/image_optim/cmd_spec.rb +2 -0
- data/spec/image_optim/config_spec.rb +2 -0
- data/spec/image_optim/handler_spec.rb +2 -0
- data/spec/image_optim/hash_helpers_spec.rb +2 -0
- data/spec/image_optim/image_meta_spec.rb +2 -0
- data/spec/image_optim/optimized_path_spec.rb +2 -0
- data/spec/image_optim/option_definition_spec.rb +2 -0
- data/spec/image_optim/option_helpers_spec.rb +2 -0
- data/spec/image_optim/path_spec.rb +65 -24
- data/spec/image_optim/runner/glob_helpers_spec.rb +2 -0
- data/spec/image_optim/runner/option_parser_spec.rb +2 -0
- data/spec/image_optim/space_spec.rb +13 -11
- data/spec/image_optim/worker/optipng_spec.rb +2 -0
- data/spec/image_optim/worker/pngquant_spec.rb +2 -0
- data/spec/image_optim/worker_spec.rb +2 -0
- data/spec/image_optim_spec.rb +7 -4
- data/spec/images/invisiblepixels/generate +1 -0
- data/spec/images/quant/generate +3 -2
- data/spec/spec_helper.rb +3 -0
- metadata +18 -13
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/worker'
|
2
4
|
require 'image_optim/option_helpers'
|
3
5
|
require 'image_optim/true_false_nil'
|
@@ -37,8 +39,8 @@ class ImageOptim
|
|
37
39
|
#{dst}
|
38
40
|
]
|
39
41
|
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
|
40
|
-
if resolve_bin!(:optipng).version >= '0.7'
|
41
|
-
args.unshift '-strip', 'all'
|
42
|
+
if strip && resolve_bin!(:optipng).version >= '0.7'
|
43
|
+
args.unshift '-strip', 'all'
|
42
44
|
end
|
43
45
|
execute(:optipng, *args) && optimized?(src, dst)
|
44
46
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/worker'
|
2
4
|
|
3
5
|
class ImageOptim
|
@@ -37,8 +39,8 @@ class ImageOptim
|
|
37
39
|
end
|
38
40
|
flags.push '-fix' if fix
|
39
41
|
flags.push '-brute' if brute
|
40
|
-
if resolve_bin!(:pngcrush).version >= '1.7.38'
|
41
|
-
flags.push '-blacken'
|
42
|
+
if blacken && resolve_bin!(:pngcrush).version >= '1.7.38'
|
43
|
+
flags.push '-blacken'
|
42
44
|
end
|
43
45
|
|
44
46
|
args = flags + %W[
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/worker'
|
2
4
|
require 'image_optim/option_helpers'
|
3
5
|
|
@@ -35,6 +37,7 @@ class ImageOptim
|
|
35
37
|
rescue SignalException => e
|
36
38
|
raise unless Signal.list.key(e.signo) == 'SEGV'
|
37
39
|
raise unless resolve_bin!(:pngout).version <= '20150920'
|
40
|
+
|
38
41
|
warn "pngout caused Segmentation fault for #{src}"
|
39
42
|
end
|
40
43
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
3
4
|
|
4
5
|
require 'bundler/setup'
|
5
6
|
|
6
7
|
require 'image_optim'
|
7
8
|
|
8
9
|
README_FILE = File.expand_path('../../README.markdown', __FILE__)
|
9
|
-
BEGIN_MARKER = '<!---<worker-options>-->'
|
10
|
-
END_MARKER = '<!---</worker-options>-->'
|
10
|
+
BEGIN_MARKER = '<!---<worker-options>-->'
|
11
|
+
END_MARKER = '<!---</worker-options>-->'
|
11
12
|
|
12
13
|
def write_worker_options(io, klass)
|
13
14
|
io.puts "### #{klass.bin_sym}:"
|
data/script/worker_analysis
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
3
4
|
|
4
5
|
require 'bundler/setup'
|
5
6
|
|
@@ -12,7 +13,7 @@ require 'digest'
|
|
12
13
|
require 'erb'
|
13
14
|
require 'ostruct'
|
14
15
|
|
15
|
-
DIR = 'tmp'
|
16
|
+
DIR = 'tmp'
|
16
17
|
Pathname(DIR).mkpath
|
17
18
|
|
18
19
|
Array.class_eval do
|
@@ -118,8 +119,10 @@ class Analyser
|
|
118
119
|
def get!(key, etag)
|
119
120
|
raw = DB[Marshal.dump(key)]
|
120
121
|
return unless raw
|
122
|
+
|
121
123
|
entry = Marshal.load(raw)
|
122
124
|
return unless entry[1] == etag
|
125
|
+
|
123
126
|
entry[0]
|
124
127
|
end
|
125
128
|
|
@@ -134,13 +137,14 @@ class Analyser
|
|
134
137
|
# Delegate to worker with short id
|
135
138
|
class WorkerVariant < DelegateClass(ImageOptim::Worker)
|
136
139
|
attr_reader :name, :id, :cons_id, :required
|
140
|
+
|
137
141
|
def initialize(klass, image_optim, options)
|
138
142
|
@required = options.delete(:required)
|
139
143
|
@run_order = options.delete(:run_order)
|
140
144
|
allow_consecutive_on = Array(options.delete(:allow_consecutive_on))
|
141
145
|
@image_optim = image_optim
|
142
146
|
@name = klass.bin_sym.to_s + options_string(options)
|
143
|
-
|
147
|
+
super(klass.new(image_optim, options))
|
144
148
|
@id = klass.bin_sym.to_s + options_string(self.options)
|
145
149
|
@cons_id = [klass, allow_consecutive_on.map{ |key| [key, send(key)] }]
|
146
150
|
end
|
@@ -175,6 +179,7 @@ class Analyser
|
|
175
179
|
|
176
180
|
def options_string(options)
|
177
181
|
return '' if options.empty?
|
182
|
+
|
178
183
|
"(#{options.sort.map{ |k, v| "#{k}:#{v.inspect}" }.join(', ')})"
|
179
184
|
end
|
180
185
|
end
|
@@ -284,6 +289,7 @@ class Analyser
|
|
284
289
|
required_workers = workers.select(&:required)
|
285
290
|
with_progress(workers, last_result) do |worker|
|
286
291
|
next if required_workers.any?{ |w| w.run_order < worker.run_order }
|
292
|
+
|
287
293
|
worker_result, result_image = run_worker(src, worker)
|
288
294
|
|
289
295
|
steps = (last_result ? last_result.steps : []) + [worker_result]
|
@@ -329,6 +335,7 @@ class Analyser
|
|
329
335
|
unless $CHILD_STATUS.success?
|
330
336
|
fail "failed comparison of #{@path} with #{other}"
|
331
337
|
end
|
338
|
+
|
332
339
|
nrmse
|
333
340
|
end
|
334
341
|
end
|
@@ -446,6 +453,7 @@ class Analyser
|
|
446
453
|
attr_reader :name
|
447
454
|
attr_reader :success_count
|
448
455
|
attr_reader :time, :avg_time
|
456
|
+
|
449
457
|
def initialize(name, steps)
|
450
458
|
@name = name
|
451
459
|
@success_count = steps.count(&:success)
|
@@ -454,11 +462,12 @@ class Analyser
|
|
454
462
|
end
|
455
463
|
|
456
464
|
def unused?
|
457
|
-
success_count
|
465
|
+
success_count == 0
|
458
466
|
end
|
459
467
|
end
|
460
468
|
|
461
469
|
attr_reader :name, :results, :ids2names
|
470
|
+
|
462
471
|
def initialize(name, results, ids2names)
|
463
472
|
@name = name.to_s
|
464
473
|
@results = results
|
@@ -482,6 +491,7 @@ class Analyser
|
|
482
491
|
ImageOptim::Worker.klasses.each do |klass|
|
483
492
|
worker_config = config.delete(klass.bin_sym)
|
484
493
|
next if worker_config == false
|
494
|
+
|
485
495
|
worker_config ||= {}
|
486
496
|
|
487
497
|
option_variants = worker_config.delete(:variants) || [{}]
|
@@ -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'
|
@@ -278,9 +280,9 @@ describe ImageOptim::BinResolver do
|
|
278
280
|
bin = Bin.new(:advpng, '/bin/advpng')
|
279
281
|
|
280
282
|
expect(Bin).to receive(:new).and_return(bin)
|
281
|
-
allow(bin).to receive(:version).and_return(SimpleVersion.new('
|
283
|
+
allow(bin).to receive(:version).and_return(SimpleVersion.new('none'))
|
282
284
|
|
283
|
-
expect(bin).to receive(:warn).once.with(match(/
|
285
|
+
expect(bin).to receive(:warn).once.with(match(/is of unknown version/))
|
284
286
|
|
285
287
|
5.times do
|
286
288
|
resolver.resolve!(:pngcrush)
|
@@ -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(:
|
14
|
-
let(:
|
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
|
-
|
17
|
-
|
20
|
+
shared_examples 'replaces file' do
|
21
|
+
it 'moves data to destination' do
|
22
|
+
src.write('src')
|
18
23
|
|
19
|
-
|
24
|
+
src.replace(dst)
|
20
25
|
|
21
|
-
|
22
|
-
|
26
|
+
expect(dst.read).to eq('src')
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
29
|
+
it 'does not remove original file' do
|
30
|
+
src.replace(dst)
|
26
31
|
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
43
|
+
got = dst.stat.mode & 0o777
|
44
|
+
expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
|
45
|
+
end
|
33
46
|
|
34
|
-
|
47
|
+
it 'does not preserve mtime of destination file' do
|
48
|
+
time = src.mtime
|
35
49
|
|
36
|
-
|
50
|
+
dst.utime(time - 1000, time - 1000)
|
37
51
|
|
38
|
-
|
39
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
74
|
+
include_examples 'replaces file'
|
75
|
+
end
|
46
76
|
|
47
|
-
|
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
|
-
|
82
|
+
include_examples 'replaces file'
|
50
83
|
end
|
51
84
|
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
@@ -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/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
|
-
|
63
|
-
|
64
|
+
shared_examples 'replaces file' do
|
65
|
+
it 'moves data to destination' do
|
66
|
+
src.write('src')
|
64
67
|
|
65
|
-
|
68
|
+
src.replace(dst)
|
66
69
|
|
67
|
-
|
68
|
-
|
70
|
+
expect(dst.read).to eq('src')
|
71
|
+
end
|
69
72
|
|
70
|
-
|
71
|
-
|
73
|
+
it 'removes original file' do
|
74
|
+
src.replace(dst)
|
72
75
|
|
73
|
-
|
74
|
-
|
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
|
-
|
77
|
-
skip 'full file modes are not support' unless any_file_modes_allowed?
|
78
|
-
mode = 0o666
|
96
|
+
src.replace(dst)
|
79
97
|
|
80
|
-
|
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
|
-
|
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
|
-
|
85
|
-
expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
|
112
|
+
include_examples 'replaces file'
|
86
113
|
end
|
87
114
|
|
88
|
-
|
89
|
-
|
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
|
-
|
120
|
+
include_examples 'replaces file'
|
92
121
|
|
93
|
-
|
122
|
+
it 'is using temporary file with .tmp extension' do
|
123
|
+
expect(src).to receive(:move).with(having_attributes(:extname => '.tmp'))
|
94
124
|
|
95
|
-
|
125
|
+
src.replace(dst)
|
126
|
+
end
|
96
127
|
end
|
97
128
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|