image_optim 0.18.0 → 0.19.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.
- checksums.yaml +8 -8
- data/.travis.yml +14 -17
- data/CHANGELOG.markdown +10 -0
- data/README.markdown +13 -3
- data/image_optim.gemspec +3 -3
- data/lib/image_optim.rb +16 -31
- data/lib/image_optim/bin_resolver.rb +3 -1
- data/lib/image_optim/bin_resolver/bin.rb +33 -24
- data/lib/image_optim/config.rb +5 -0
- data/lib/image_optim/runner/option_parser.rb +12 -5
- data/lib/image_optim/worker.rb +10 -62
- data/lib/image_optim/worker/class_methods.rb +89 -0
- data/lib/image_optim/worker/gifsicle.rb +25 -2
- data/lib/image_optim/worker/jpegrecompress.rb +44 -0
- data/lib/image_optim/worker/optipng.rb +1 -1
- data/script/update_worker_options_in_readme +6 -7
- data/script/worker_analysis +15 -5
- data/script/worker_analysis.haml +31 -10
- data/spec/image_optim/bin_resolver_spec.rb +52 -20
- data/spec/image_optim/worker_spec.rb +139 -0
- data/spec/image_optim_spec.rb +35 -53
- metadata +9 -7
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'image_optim/bin_resolver'
|
2
|
+
require 'image_optim/option_definition'
|
3
|
+
|
4
|
+
class ImageOptim
|
5
|
+
class Worker
|
6
|
+
# Class methods of ImageOptim::Worker
|
7
|
+
module ClassMethods
|
8
|
+
def self.extended(klass)
|
9
|
+
klass.instance_variable_set(:@klasses, [])
|
10
|
+
end
|
11
|
+
|
12
|
+
# List of available workers
|
13
|
+
def klasses
|
14
|
+
@klasses.to_enum
|
15
|
+
end
|
16
|
+
|
17
|
+
# Remember all classes inheriting from this one
|
18
|
+
def inherited(base)
|
19
|
+
@klasses << base
|
20
|
+
end
|
21
|
+
|
22
|
+
# Underscored class name symbol
|
23
|
+
def bin_sym
|
24
|
+
@underscored_name ||= name.
|
25
|
+
split('::').last. # get last part
|
26
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').downcase. # convert AbcDef to abc_def
|
27
|
+
to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
def option_definitions
|
31
|
+
@option_definitions ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def option(name, default, type, description = nil, &proc)
|
35
|
+
attr_reader name
|
36
|
+
option_definitions <<
|
37
|
+
OptionDefinition.new(name, default, type, description, &proc)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create hash with format mapped to list of workers sorted by run order
|
41
|
+
def create_all_by_format(image_optim, &options_proc)
|
42
|
+
by_format = {}
|
43
|
+
create_all(image_optim, &options_proc).each do |worker|
|
44
|
+
worker.image_formats.each do |format|
|
45
|
+
by_format[format] ||= []
|
46
|
+
by_format[format] << worker
|
47
|
+
end
|
48
|
+
end
|
49
|
+
by_format
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create list of workers sorted by run order
|
53
|
+
# Workers are initialized with options provided through options_proc
|
54
|
+
# Resolve all bins of all workers, if there are errors and
|
55
|
+
# skip_missing_workers of image_optim is true - show warnings, otherwise
|
56
|
+
# fail with one joint exception
|
57
|
+
def create_all(image_optim, &options_proc)
|
58
|
+
workers = init_all(image_optim, &options_proc)
|
59
|
+
|
60
|
+
resolved = []
|
61
|
+
errors = BinResolver.collect_errors(workers) do |worker|
|
62
|
+
worker.resolve_used_bins!
|
63
|
+
resolved << worker
|
64
|
+
end
|
65
|
+
|
66
|
+
unless errors.empty?
|
67
|
+
if image_optim.skip_missing_workers
|
68
|
+
errors.each{ |error| warn error }
|
69
|
+
else
|
70
|
+
message = ['Bin resolving errors:', *errors].join("\n")
|
71
|
+
fail BinResolver::Error, message
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
resolved.sort_by.with_index{ |worker, i| [worker.run_order, i] }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def init_all(image_optim, &options_proc)
|
81
|
+
klasses.map do |klass|
|
82
|
+
next unless (options = options_proc[klass])
|
83
|
+
options = options.merge(:allow_lossy => image_optim.allow_lossy)
|
84
|
+
klass.init(image_optim, options)
|
85
|
+
end.compact.flatten
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -4,8 +4,25 @@ class ImageOptim
|
|
4
4
|
class Worker
|
5
5
|
# http://www.lcdf.org/gifsicle/
|
6
6
|
class Gifsicle < Worker
|
7
|
+
# If interlace specified initialize one instance
|
8
|
+
# Otherwise initialize two, one with interlace off and one with on
|
9
|
+
def self.init(image_optim, options = {})
|
10
|
+
return super if options.key?(:interlace)
|
11
|
+
|
12
|
+
[false, true].map do |interlace|
|
13
|
+
new(image_optim, options.merge(:interlace => interlace))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
7
17
|
INTERLACE_OPTION =
|
8
|
-
option(:interlace, false, '
|
18
|
+
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
19
|
+
'`true` - interlace on, '\
|
20
|
+
'`false` - interlace off, '\
|
21
|
+
'`nil` - as is in original image '\
|
22
|
+
'(defaults to running two instances, one with interlace off and '\
|
23
|
+
'one with on)') do |v|
|
24
|
+
TrueFalseNil.convert(v)
|
25
|
+
end
|
9
26
|
|
10
27
|
LEVEL_OPTION =
|
11
28
|
option(:level, 3, 'Compression level: '\
|
@@ -30,7 +47,13 @@ class ImageOptim
|
|
30
47
|
#{src}
|
31
48
|
]
|
32
49
|
|
33
|
-
|
50
|
+
if resolve_bin!(:gifsicle).version >= '1.85'
|
51
|
+
args.unshift('--no-extensions', '--no-app-extensions')
|
52
|
+
end
|
53
|
+
|
54
|
+
unless interlace.nil?
|
55
|
+
args.unshift(interlace ? '--interlace' : '--no-interlace')
|
56
|
+
end
|
34
57
|
args.unshift('--careful') if careful
|
35
58
|
args.unshift("--optimize=#{level}") if level
|
36
59
|
execute(:gifsicle, *args) && optimized?(src, dst)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
require 'image_optim/option_helpers'
|
3
|
+
|
4
|
+
class ImageOptim
|
5
|
+
class Worker
|
6
|
+
# https://github.com/danielgtaylor/jpeg-archive#jpeg-recompress
|
7
|
+
class Jpegrecompress < Worker
|
8
|
+
# Initialize only if allow_lossy
|
9
|
+
def self.init(image_optim, options = {})
|
10
|
+
super if options[:allow_lossy]
|
11
|
+
end
|
12
|
+
|
13
|
+
QUALITY_NAMES = [:low, :medium, :high, :veryhigh]
|
14
|
+
|
15
|
+
quality_names_desc = QUALITY_NAMES.each_with_index.map do |name, i|
|
16
|
+
"`#{i}` - #{name}"
|
17
|
+
end.join(', ')
|
18
|
+
|
19
|
+
QUALITY_OPTION =
|
20
|
+
option(:quality, 3, "JPEG quality preset: #{quality_names_desc}") do |v|
|
21
|
+
OptionHelpers.limit_with_range(v.to_i, 0...QUALITY_NAMES.length)
|
22
|
+
end
|
23
|
+
|
24
|
+
def used_bins
|
25
|
+
[:'jpeg-recompress']
|
26
|
+
end
|
27
|
+
|
28
|
+
# Run first [-1]
|
29
|
+
def run_order
|
30
|
+
-5
|
31
|
+
end
|
32
|
+
|
33
|
+
def optimize(src, dst)
|
34
|
+
args = %W[
|
35
|
+
--quality #{QUALITY_NAMES[quality]}
|
36
|
+
--no-copy
|
37
|
+
#{src}
|
38
|
+
#{dst}
|
39
|
+
]
|
40
|
+
execute(:'jpeg-recompress', *args) && optimized?(src, dst)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -14,7 +14,7 @@ class ImageOptim
|
|
14
14
|
end
|
15
15
|
|
16
16
|
INTERLACE_OPTION =
|
17
|
-
option(:interlace, false, TrueFalseNil, 'Interlace
|
17
|
+
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
18
18
|
'`true` - interlace on, '\
|
19
19
|
'`false` - interlace off, '\
|
20
20
|
'`nil` - as is in original image') do |v|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
|
-
|
4
|
+
require 'bundler/setup'
|
5
5
|
|
6
6
|
require 'image_optim'
|
7
7
|
|
@@ -17,12 +17,11 @@ def write_worker_options(io, klass)
|
|
17
17
|
io.puts 'Worker has no options'
|
18
18
|
else
|
19
19
|
klass.option_definitions.each do |option_definition|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
].join(' ')
|
20
|
+
line = "* `:#{option_definition.name}` — #{option_definition.description}"
|
21
|
+
unless line['(defaults']
|
22
|
+
line << " *(defaults to `#{option_definition.default.inspect}`)*"
|
23
|
+
end
|
24
|
+
io.puts line
|
26
25
|
end
|
27
26
|
end
|
28
27
|
io.puts
|
data/script/worker_analysis
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
|
-
|
4
|
+
require 'bundler/setup'
|
5
5
|
|
6
6
|
require 'image_optim'
|
7
7
|
require 'image_optim/cmd'
|
@@ -141,12 +141,16 @@ class Analyser
|
|
141
141
|
|
142
142
|
# Delegate to worker with short id
|
143
143
|
class WorkerVariant < DelegateClass(ImageOptim::Worker)
|
144
|
-
attr_reader :
|
144
|
+
attr_reader :cons_id, :id
|
145
145
|
def initialize(klass, image_optim, options)
|
146
|
-
|
146
|
+
allow_consecutive_on = Array(options.delete(:allow_consecutive_on))
|
147
147
|
@image_optim = image_optim
|
148
|
-
@id =
|
148
|
+
@id = klass.bin_sym.to_s
|
149
|
+
unless options.empty?
|
150
|
+
@id << "(#{options.map{ |k, v| "#{k}:#{v.inspect}" }.join(', ')})"
|
151
|
+
end
|
149
152
|
__setobj__(klass.new(image_optim, options))
|
153
|
+
@cons_id = [klass, allow_consecutive_on.map{ |key| [key, send(key)] }]
|
150
154
|
end
|
151
155
|
|
152
156
|
def cache_etag
|
@@ -270,7 +274,7 @@ class Analyser
|
|
270
274
|
|
271
275
|
block.call(chain_result)
|
272
276
|
|
273
|
-
workers_left = workers.reject{ |w| w.
|
277
|
+
workers_left = workers.reject{ |w| w.cons_id == worker.cons_id }
|
274
278
|
run_workers(result_image, workers_left, chain_result, &block)
|
275
279
|
end
|
276
280
|
end
|
@@ -427,6 +431,7 @@ class Analyser
|
|
427
431
|
@workers_by_format = Hash.new{ |h, k| h[k] = [] }
|
428
432
|
ImageOptim::Worker.klasses.each do |klass|
|
429
433
|
worker_options_config = option_variants.delete(klass.bin_sym) || {}
|
434
|
+
allow_consecutive_on = worker_options_config.delete(:allow_consecutive_on)
|
430
435
|
worker_option_variants = case worker_options_config
|
431
436
|
when Array
|
432
437
|
worker_options_config
|
@@ -437,6 +442,7 @@ class Analyser
|
|
437
442
|
end
|
438
443
|
worker_option_variants.each do |options|
|
439
444
|
options = HashHelpers.deep_symbolise_keys(options)
|
445
|
+
options[:allow_consecutive_on] = allow_consecutive_on
|
440
446
|
worker = WorkerVariant.new(klass, image_optim, options)
|
441
447
|
puts worker.id
|
442
448
|
worker.image_formats.each do |format|
|
@@ -517,6 +523,10 @@ Example of `.analysis_variants.yml`:
|
|
517
523
|
optipng: # 6 worker variants by combining options
|
518
524
|
level: [6, 7]
|
519
525
|
interlace: [true, false, nil]
|
526
|
+
gifsicle: # allow variants with different interlace to run consecutively
|
527
|
+
allow_consecutive_on: interlace
|
528
|
+
interlace: [true, false]
|
529
|
+
careful: [true, false]
|
520
530
|
# other workers will be used with default options
|
521
531
|
HELP
|
522
532
|
end
|
data/script/worker_analysis.haml
CHANGED
@@ -55,14 +55,12 @@
|
|
55
55
|
table[data-sortable] tbody tr:hover {
|
56
56
|
background: #ddf;
|
57
57
|
}
|
58
|
-
tr.unused {
|
58
|
+
body:not(.show-unused) tr.unused, tr.filtered-out {
|
59
59
|
display: none;
|
60
60
|
}
|
61
61
|
|
62
|
-
input {
|
63
|
-
|
64
|
-
display: table-row;
|
65
|
-
}
|
62
|
+
input { display: block; width: 30%; }
|
63
|
+
input[type=checkbox] { display: inline; width: auto; }
|
66
64
|
|
67
65
|
.number {
|
68
66
|
text-align: right;
|
@@ -102,6 +100,24 @@
|
|
102
100
|
:javascript
|
103
101
|
/*! sortable.js 0.6.0 */
|
104
102
|
(function(){var a,b,c,d,e,f,g;a="table[data-sortable]",d=/^-?[£$¤]?[\d,.]+%?$/,g=/^\s+|\s+$/g,f="ontouchstart"in document.documentElement,c=f?"touchstart":"click",b=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},e={init:function(b){var c,d,f,g,h;for(null==b&&(b={}),null==b.selector&&(b.selector=a),d=document.querySelectorAll(b.selector),h=[],f=0,g=d.length;g>f;f++)c=d[f],h.push(e.initTable(c));return h},initTable:function(a){var b,c,d,f,g,h;if(1===(null!=(h=a.tHead)?h.rows.length:void 0)&&"true"!==a.getAttribute("data-sortable-initialized")){for(a.setAttribute("data-sortable-initialized","true"),d=a.querySelectorAll("th"),b=f=0,g=d.length;g>f;b=++f)c=d[b],"false"!==c.getAttribute("data-sortable")&&e.setupClickableTH(a,c,b);return a}},setupClickableTH:function(a,d,f){var g;return g=e.getColumnType(a,f),"click"===c&&b(d,"mousedown",function(){return event.preventDefault?event.preventDefault():event.returnValue=!1}),b(d,c,function(){var b,c,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v;for("true"===this.getAttribute("data-sorted")?(l=this.getAttribute("data-sorted-direction"),b="ascending"===l?"descending":"ascending"):b=this.getAttribute("data-default-direction")||g.defaultSortDirection,n=this.parentNode.querySelectorAll("th"),o=0,r=n.length;r>o;o++)d=n[o],d.setAttribute("data-sorted","false"),d.removeAttribute("data-sorted-direction");for(this.setAttribute("data-sorted","true"),this.setAttribute("data-sorted-direction",b),m=a.tBodies[0],i=[],u=m.rows,c=p=0,s=u.length;s>p;c=++p)h=u[c],i.push([e.getNodeValue(h.cells[f]),h,c]);for(k="descending"===b?-1:1,i.sort(function(a,b){var c;return c=g.compare(a,b),0!==c?c*k:a[2]-b[2]}),v=[],q=0,t=i.length;t>q;q++)j=i[q],v.push(m.appendChild(j[1]));return v})},getColumnType:function(a,b){var c,f,g,h,i;for(i=a.tBodies[0].rows,g=0,h=i.length;h>g;g++)if(c=i[g],f=e.getNodeValue(c.cells[b]),""!==f){if(f.match(d))return e.types.numeric;if(!isNaN(Date.parse(f)))return e.types.date}return e.types.alpha},getNodeValue:function(a){return a?null!==a.getAttribute("data-value")?a.getAttribute("data-value"):"undefined"!=typeof a.innerText?a.innerText.replace(g,""):a.textContent.replace(g,""):""},types:{numeric:{defaultSortDirection:"descending",compare:function(a,b){var c,d;return c=parseFloat(a[0].replace(/[^0-9.-]/g,""),10),d=parseFloat(b[0].replace(/[^0-9.-]/g,""),10),isNaN(c)&&(c=0),isNaN(d)&&(d=0),c-d}},alpha:{defaultSortDirection:"ascending",compare:function(a,b){return a[0].localeCompare(b[0])}},date:{defaultSortDirection:"ascending",compare:function(a,b){var c,d;return c=Date.parse(a[0]),d=Date.parse(b[0]),isNaN(c)&&(c=0),isNaN(d)&&(d=0),c-d}}}},setTimeout(e.init,0),window.Sortable=e}).call(this);
|
103
|
+
:javascript
|
104
|
+
function toggleShowUnused(){
|
105
|
+
var checked = document.getElementById('toggle-show-unused').checked
|
106
|
+
document.body.classList[checked ? 'add' : 'remove']('show-unused')
|
107
|
+
}
|
108
|
+
function filterTable(){
|
109
|
+
var filter = new RegExp(document.getElementById('filter').value)
|
110
|
+
var rows = document.querySelectorAll('tbody.filterable tr')
|
111
|
+
for (var i = 0, _i = rows.length; i < _i; i++) {
|
112
|
+
var row = rows[i]
|
113
|
+
if (!row.hasAttribute('data-filter-text')) {
|
114
|
+
var text = row.textContent.replace(/\s+/g, ' ')
|
115
|
+
row.setAttribute('data-filter-text', text)
|
116
|
+
}
|
117
|
+
var show = filter.test(row.getAttribute('data-filter-text'))
|
118
|
+
row.classList[show ? 'remove' : 'add']('filtered-out')
|
119
|
+
}
|
120
|
+
}
|
105
121
|
%body
|
106
122
|
.formats
|
107
123
|
- format_links.each do |format, link|
|
@@ -114,9 +130,14 @@
|
|
114
130
|
%td.warn-low Max difference >= 0.001
|
115
131
|
%td.warn-medium Max difference >= 0.01
|
116
132
|
%td.warn-high Max difference >= 0.1
|
117
|
-
%
|
118
|
-
%label
|
133
|
+
%p
|
134
|
+
%label
|
135
|
+
%input#toggle-show-unused(type="checkbox" onchange="toggleShowUnused()")
|
119
136
|
Show chains with workers failed for every image
|
137
|
+
%p
|
138
|
+
%label(for="filter")
|
139
|
+
Filter chains by regexp
|
140
|
+
%input#filter(type="text" onkeyup="filterTable()")
|
120
141
|
%p
|
121
142
|
Images: #{stats.each_chain.first.entry_count},
|
122
143
|
size: #{stats.each_chain.first.original_size}
|
@@ -133,7 +154,7 @@
|
|
133
154
|
%th(data-default-direction="ascending") Avg difference
|
134
155
|
%th(data-default-direction="ascending") Max difference
|
135
156
|
%th Speed (B/s)
|
136
|
-
%tbody
|
157
|
+
%tbody.filterable
|
137
158
|
- stats.each_chain do |chain|
|
138
159
|
%tr{class: chain.unused_workers && 'unused'}
|
139
160
|
%td
|
@@ -144,8 +165,8 @@
|
|
144
165
|
.worker-success-count(title="successfull count")
|
145
166
|
= worker_stat.success_count
|
146
167
|
%td.number= chain.optimized_size
|
147
|
-
%td.number= format '%.
|
148
|
-
%td.number= format '%.
|
168
|
+
%td.number= format '%.5f', chain.ratio
|
169
|
+
%td.number= format '%.5f', chain.avg_ratio
|
149
170
|
%td.number= format '%.2f', chain.time
|
150
171
|
%td.number= format '%.5f', chain.avg_difference
|
151
172
|
%td.number{class: chain.warn_level && "warn-#{chain.warn_level}"}
|
@@ -102,7 +102,8 @@ describe ImageOptim::BinResolver do
|
|
102
102
|
expect(resolver).to receive(:full_path).with(:ls).and_return('/bin/ls')
|
103
103
|
bin = double
|
104
104
|
expect(Bin).to receive(:new).with(:ls, '/bin/ls').and_return(bin)
|
105
|
-
expect(bin).to receive(:check!).
|
105
|
+
expect(bin).to receive(:check!).once
|
106
|
+
expect(bin).to receive(:check_fail!).exactly(5).times
|
106
107
|
|
107
108
|
5.times do
|
108
109
|
expect(resolver.resolve!(:ls)).to eq(bin)
|
@@ -149,7 +150,8 @@ describe ImageOptim::BinResolver do
|
|
149
150
|
bin = double
|
150
151
|
expect(Bin).to receive(:new).
|
151
152
|
with(:image_optim, File.expand_path(path)).and_return(bin)
|
152
|
-
expect(bin).to receive(:check!).
|
153
|
+
expect(bin).to receive(:check!).once
|
154
|
+
expect(bin).to receive(:check_fail!).exactly(5).times
|
153
155
|
|
154
156
|
at_exit_blocks = []
|
155
157
|
expect(resolver).to receive(:at_exit).once do |&block|
|
@@ -202,9 +204,10 @@ describe ImageOptim::BinResolver do
|
|
202
204
|
bin = double
|
203
205
|
expect(Bin).to receive(:new).once.with(:ls, '/bin/ls').and_return(bin)
|
204
206
|
|
205
|
-
|
207
|
+
count = 0
|
206
208
|
mutex = Mutex.new
|
207
|
-
allow(bin).to receive(:check!)
|
209
|
+
allow(bin).to receive(:check!).once
|
210
|
+
allow(bin).to receive(:check_fail!){ mutex.synchronize{ count += 1 } }
|
208
211
|
|
209
212
|
10.times.map do
|
210
213
|
Thread.new do
|
@@ -212,29 +215,58 @@ describe ImageOptim::BinResolver do
|
|
212
215
|
end
|
213
216
|
end.each(&:join)
|
214
217
|
|
215
|
-
expect(
|
218
|
+
expect(count).to eq(10)
|
216
219
|
end
|
217
220
|
end
|
218
221
|
|
219
|
-
|
220
|
-
|
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)
|
222
233
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
234
|
+
5.times do
|
235
|
+
expect do
|
236
|
+
resolver.resolve!(:pngcrush)
|
237
|
+
end.to raise_error Bin::UnknownVersion
|
238
|
+
end
|
239
|
+
end
|
227
240
|
end
|
228
|
-
end
|
229
241
|
|
230
|
-
|
231
|
-
|
232
|
-
|
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'))
|
233
248
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
238
270
|
end
|
239
271
|
end
|
240
272
|
end
|