image_optim 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +0 -2
- data/README.markdown +34 -1
- data/bin/image_optim +21 -113
- data/image_optim.gemspec +2 -2
- data/lib/image_optim/bin_not_found_error.rb +3 -0
- data/lib/image_optim/bin_resolver.rb +50 -0
- data/lib/image_optim/config.rb +137 -0
- data/lib/image_optim/configuration_error.rb +3 -0
- data/lib/image_optim/handler.rb +27 -0
- data/lib/image_optim/hash_helpers.rb +35 -0
- data/lib/image_optim/image_path.rb +27 -2
- data/lib/image_optim/option_helpers.rb +1 -20
- data/lib/image_optim/railtie.rb +19 -0
- data/lib/image_optim/runner.rb +107 -0
- data/lib/image_optim/true_false_nil.rb +3 -0
- data/lib/image_optim/worker/advpng.rb +1 -0
- data/lib/image_optim/worker/jpegoptim.rb +1 -0
- data/lib/image_optim/worker/optipng.rb +2 -0
- data/lib/image_optim/worker/pngout.rb +1 -0
- data/lib/image_optim/worker.rb +25 -6
- data/lib/image_optim.rb +60 -126
- data/script/update_worker_options_in_readme +36 -0
- data/spec/image_optim/bin_resolver_spec.rb +92 -0
- data/spec/image_optim/config_spec.rb +153 -0
- data/spec/image_optim/handler_spec.rb +44 -0
- data/spec/image_optim/hash_helpers_spec.rb +74 -0
- data/spec/image_optim/image_path_spec.rb +39 -0
- data/spec/image_optim_spec.rb +26 -94
- metadata +24 -6
- data/TODO +0 -12
- data/script/options_for_readme +0 -14
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'image_optim'
|
4
|
+
require 'image_optim/hash_helpers'
|
5
|
+
require 'image_optim/true_false_nil'
|
6
|
+
require 'progress'
|
7
|
+
require 'optparse'
|
8
|
+
require 'find'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
class ImageOptim
|
12
|
+
class Runner
|
13
|
+
module Space
|
14
|
+
SIZE_SYMBOLS = %w[B K M G T P E].freeze
|
15
|
+
PRECISION = 1
|
16
|
+
LENGTH = 4 + PRECISION + 1
|
17
|
+
|
18
|
+
EMPTY_SPACE = ' ' * LENGTH
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_writer :base10
|
22
|
+
def denominator
|
23
|
+
@denominator ||= @base10 ? 1000.0 : 1024.0
|
24
|
+
end
|
25
|
+
|
26
|
+
def space(size)
|
27
|
+
case size
|
28
|
+
when 0, nil
|
29
|
+
EMPTY_SPACE
|
30
|
+
else
|
31
|
+
log_denominator = Math.log(size) / Math.log(denominator)
|
32
|
+
degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
|
33
|
+
number = size / (denominator ** degree)
|
34
|
+
"#{degree == 0 ? number.to_i : "%.#{PRECISION}f" % number}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(args, options)
|
41
|
+
raise 'specify paths to optimize' if args.empty?
|
42
|
+
options = HashHelpers.deep_symbolise_keys(options)
|
43
|
+
@recursive = options.delete(:recursive)
|
44
|
+
@image_optim = ImageOptim.new(options)
|
45
|
+
@files = find_files(args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run!
|
49
|
+
unless @files.empty?
|
50
|
+
lines, original_sizes, optimized_sizes =
|
51
|
+
@image_optim.optimize_images!(@files.with_progress('optimizing')) do |original, optimized|
|
52
|
+
original_size = optimized ? optimized.original_size : original.size
|
53
|
+
optimized_size = optimized ? optimized.size : original.size
|
54
|
+
["#{size_percent(original_size, optimized_size)} #{original}", original_size, optimized_size]
|
55
|
+
end.transpose
|
56
|
+
|
57
|
+
puts lines, "Total: #{size_percent(original_sizes.inject(:+), optimized_sizes.inject(:+))}"
|
58
|
+
end
|
59
|
+
|
60
|
+
!warnings?
|
61
|
+
end
|
62
|
+
|
63
|
+
def warnings?
|
64
|
+
!!@warnings
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.run!(args, options)
|
68
|
+
new(args, options).run!
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def find_files(args)
|
74
|
+
files = []
|
75
|
+
args.each do |arg|
|
76
|
+
if File.file?(arg)
|
77
|
+
if @image_optim.optimizable?(arg)
|
78
|
+
files << arg
|
79
|
+
else
|
80
|
+
warning "#{arg} is not an image or there is no optimizer for it"
|
81
|
+
end
|
82
|
+
elsif @recursive && File.directory?(arg)
|
83
|
+
Find.find(arg) do |path|
|
84
|
+
files << path if File.file?(path) && @image_optim.optimizable?(path)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
warning "#{arg} does not exist"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
files
|
91
|
+
end
|
92
|
+
|
93
|
+
def warning(message)
|
94
|
+
@warnings = true
|
95
|
+
warn message
|
96
|
+
end
|
97
|
+
|
98
|
+
def size_percent(size_a, size_b)
|
99
|
+
if size_a == size_b
|
100
|
+
"------ #{Space::EMPTY_SPACE}"
|
101
|
+
else
|
102
|
+
'%5.2f%% %s' % [100 - 100.0 * size_b / size_a, Space.space(size_a - size_b)]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
data/lib/image_optim/worker.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
require 'image_optim/option_definition'
|
4
|
+
require 'image_optim/option_helpers'
|
3
5
|
require 'shellwords'
|
4
6
|
|
5
|
-
require 'image_optim'
|
6
|
-
|
7
7
|
class ImageOptim
|
8
8
|
class Worker
|
9
9
|
class << self
|
@@ -32,15 +32,22 @@ class ImageOptim
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
include OptionHelpers
|
36
|
-
|
37
35
|
# Configure (raises on extra options)
|
38
36
|
def initialize(image_optim, options = {})
|
39
37
|
@image_optim = image_optim
|
40
38
|
self.class.option_definitions.each do |option_definition|
|
41
|
-
|
39
|
+
value = if options.has_key?(option_definition.name)
|
40
|
+
options[option_definition.name]
|
41
|
+
else
|
42
|
+
option_definition.default
|
43
|
+
end
|
44
|
+
if option_definition.proc
|
45
|
+
value = option_definition.proc[value]
|
46
|
+
end
|
47
|
+
instance_variable_set("@#{option_definition.name}", value)
|
42
48
|
end
|
43
|
-
|
49
|
+
|
50
|
+
assert_no_unknown_options!(options)
|
44
51
|
end
|
45
52
|
|
46
53
|
# List of formats which worker can optimize
|
@@ -55,6 +62,10 @@ class ImageOptim
|
|
55
62
|
0
|
56
63
|
end
|
57
64
|
|
65
|
+
def <=>(other)
|
66
|
+
run_order <=> other.run_order
|
67
|
+
end
|
68
|
+
|
58
69
|
# Check if operation resulted in optimized file
|
59
70
|
def optimized?(src, dst)
|
60
71
|
dst.size? && dst.size < src.size
|
@@ -62,6 +73,14 @@ class ImageOptim
|
|
62
73
|
|
63
74
|
private
|
64
75
|
|
76
|
+
def assert_no_unknown_options!(options)
|
77
|
+
known_keys = self.class.option_definitions.map(&:name)
|
78
|
+
unknown_options = options.reject{ |key, value| known_keys.include?(key) }
|
79
|
+
unless unknown_options.empty?
|
80
|
+
raise ConfigurationError, "unknown options #{unknown_options.inspect} for #{self}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
65
84
|
# Forward bin resolving to image_optim
|
66
85
|
def resolve_bin!(bin)
|
67
86
|
@image_optim.resolve_bin!(bin)
|
data/lib/image_optim.rb
CHANGED
@@ -1,19 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
1
|
+
require 'image_optim/bin_resolver'
|
2
|
+
require 'image_optim/config'
|
3
|
+
require 'image_optim/handler'
|
4
4
|
require 'image_optim/image_path'
|
5
|
-
require 'image_optim/option_helpers'
|
6
|
-
require 'image_optim/option_definition'
|
7
5
|
require 'image_optim/worker'
|
6
|
+
require 'in_threads'
|
7
|
+
require 'shellwords'
|
8
8
|
|
9
9
|
class ImageOptim
|
10
|
-
class ConfigurationError < StandardError; end
|
11
|
-
class BinNotFoundError < StandardError; end
|
12
|
-
|
13
|
-
class TrueFalseNil; end
|
14
|
-
|
15
|
-
include OptionHelpers
|
16
|
-
|
17
10
|
# Nice level
|
18
11
|
attr_reader :nice
|
19
12
|
|
@@ -43,88 +36,71 @@ class ImageOptim
|
|
43
36
|
#
|
44
37
|
# ImageOptim.new(:nice => 20)
|
45
38
|
def initialize(options = {})
|
46
|
-
@
|
47
|
-
@resolver_lock = Mutex.new
|
48
|
-
|
49
|
-
nice = options.delete(:nice)
|
50
|
-
@nice = case nice
|
51
|
-
when true, nil
|
52
|
-
10
|
53
|
-
when false
|
54
|
-
0
|
55
|
-
else
|
56
|
-
nice.to_i
|
57
|
-
end
|
39
|
+
@bin_resolver = BinResolver.new
|
58
40
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
when false
|
64
|
-
1
|
65
|
-
else
|
66
|
-
threads.to_i
|
67
|
-
end
|
68
|
-
@threads = OptionHelpers.limit_with_range(threads, 1..16)
|
69
|
-
|
70
|
-
@verbose = !!options.delete(:verbose)
|
41
|
+
config = Config.new(options)
|
42
|
+
@nice = config.nice
|
43
|
+
@threads = config.threads
|
44
|
+
@verbose = config.verbose
|
71
45
|
|
72
46
|
@workers_by_format = {}
|
73
47
|
Worker.klasses.each do |klass|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
raise ConfigurationError, "Got #{worker_options.inspect} for #{klass.name} options"
|
82
|
-
end
|
83
|
-
worker = klass.new(self, worker_options)
|
84
|
-
worker.image_formats.each do |format|
|
85
|
-
@workers_by_format[format] ||= []
|
86
|
-
@workers_by_format[format] << worker
|
48
|
+
if worker_options = config.for_worker(klass)
|
49
|
+
worker = klass.new(self, worker_options)
|
50
|
+
worker.image_formats.each do |format|
|
51
|
+
@workers_by_format[format] ||= []
|
52
|
+
@workers_by_format[format] << worker
|
53
|
+
end
|
87
54
|
end
|
88
55
|
end
|
89
|
-
@workers_by_format.each
|
90
|
-
workers.replace workers.sort_by(&:run_order) # There is no sort_by! in ruby 1.8
|
91
|
-
end
|
56
|
+
@workers_by_format.values.each(&:sort!)
|
92
57
|
|
93
|
-
|
58
|
+
config.assert_no_unused_options!
|
59
|
+
|
60
|
+
puts config if verbose?
|
94
61
|
end
|
95
62
|
|
96
63
|
# Get workers for image
|
97
64
|
def workers_for_image(path)
|
98
|
-
@workers_by_format[ImagePath.
|
65
|
+
@workers_by_format[ImagePath.convert(path).format]
|
99
66
|
end
|
100
67
|
|
101
|
-
# Optimize one file, return new path or nil if optimization failed
|
68
|
+
# Optimize one file, return new path as OptimizedImagePath or nil if optimization failed
|
102
69
|
def optimize_image(original)
|
103
|
-
original = ImagePath.
|
70
|
+
original = ImagePath.convert(original)
|
104
71
|
if workers = workers_for_image(original)
|
105
|
-
|
106
|
-
ts = [original, original.temp_path]
|
72
|
+
handler = Handler.new(original)
|
107
73
|
workers.each do |worker|
|
108
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
if worker.optimize(*ts.last(2))
|
112
|
-
result = ts.last
|
113
|
-
if ts.length == 3
|
114
|
-
ts[-2, 2] = ts[-1], ts[-2]
|
115
|
-
end
|
74
|
+
handler.process do |src, dst|
|
75
|
+
worker.optimize(src, dst)
|
116
76
|
end
|
117
77
|
end
|
118
|
-
result
|
78
|
+
if handler.result
|
79
|
+
ImagePath::Optimized.new(handler.result, original)
|
80
|
+
end
|
119
81
|
end
|
120
82
|
end
|
121
83
|
|
122
|
-
# Optimize one file in place, return optimization
|
84
|
+
# Optimize one file in place, return original as OptimizedImagePath or nil if optimization failed
|
123
85
|
def optimize_image!(original)
|
124
|
-
original = ImagePath.
|
86
|
+
original = ImagePath.convert(original)
|
125
87
|
if result = optimize_image(original)
|
126
88
|
result.replace(original)
|
127
|
-
|
89
|
+
ImagePath::Optimized.new(original, result.original_size)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Optimize image data, return new data or nil if optimization failed
|
94
|
+
def optimize_image_data(original_data)
|
95
|
+
format = ImageSize.new(original_data).format
|
96
|
+
ImagePath.temp_file %W[image_optim .#{format}] do |temp|
|
97
|
+
temp.binmode
|
98
|
+
temp.write(original_data)
|
99
|
+
temp.close
|
100
|
+
|
101
|
+
if result = optimize_image(temp.path)
|
102
|
+
result.read
|
103
|
+
end
|
128
104
|
end
|
129
105
|
end
|
130
106
|
|
@@ -132,19 +108,26 @@ class ImageOptim
|
|
132
108
|
# if block given yields path and result for each image and returns array of yield results
|
133
109
|
# else return array of results
|
134
110
|
def optimize_images(paths, &block)
|
135
|
-
run_method_for(paths, :optimize_image, &block)
|
111
|
+
run_method_for(paths.map{ |path| ImagePath.convert(path) }, :optimize_image, &block)
|
136
112
|
end
|
137
113
|
|
138
114
|
# Optimize multiple images in place
|
139
115
|
# if block given yields path and result for each image and returns array of yield results
|
140
116
|
# else return array of results
|
141
117
|
def optimize_images!(paths, &block)
|
142
|
-
run_method_for(paths, :optimize_image!, &block)
|
118
|
+
run_method_for(paths.map{ |path| ImagePath.convert(path) }, :optimize_image!, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Optimize multiple image datas
|
122
|
+
# if block given yields original and result for each image data and returns array of yield results
|
123
|
+
# else return array of results
|
124
|
+
def optimize_images_data(datas, &block)
|
125
|
+
run_method_for(datas, :optimize_image_data, &block)
|
143
126
|
end
|
144
127
|
|
145
128
|
# Optimization methods with default options
|
146
129
|
def self.method_missing(method, *args, &block)
|
147
|
-
if method.to_s =~ /^
|
130
|
+
if method_defined?(method) && method.to_s =~ /^optimize_image/
|
148
131
|
new.send(method, *args, &block)
|
149
132
|
else
|
150
133
|
super
|
@@ -161,37 +144,14 @@ class ImageOptim
|
|
161
144
|
!!workers_for_image(path)
|
162
145
|
end
|
163
146
|
|
164
|
-
# Temp directory for symlinks to bins with path coming from ENV
|
165
|
-
attr_reader :resolve_dir
|
166
|
-
|
167
147
|
# Check existance of binary, create symlink if ENV contains path for key XXX_BIN where XXX is upper case bin name
|
168
148
|
def resolve_bin!(bin)
|
169
|
-
bin
|
170
|
-
@resolved_bins.include?(bin) || @resolver_lock.synchronize do
|
171
|
-
@resolved_bins.include?(bin) || begin
|
172
|
-
if path = ENV["#{bin}_bin".upcase]
|
173
|
-
unless @resolve_dir
|
174
|
-
@resolve_dir = FSPath.temp_dir
|
175
|
-
at_exit{ FileUtils.remove_entry_secure @resolve_dir }
|
176
|
-
end
|
177
|
-
symlink = @resolve_dir / bin
|
178
|
-
symlink.make_symlink(File.expand_path(path))
|
179
|
-
at_exit{ symlink.unlink }
|
180
|
-
|
181
|
-
@resolved_bins[bin] = bin_accessible?(symlink)
|
182
|
-
else
|
183
|
-
@resolved_bins[bin] = bin_accessible?(bin)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
@resolved_bins[bin] or raise BinNotFoundError, "`#{bin}` not found"
|
149
|
+
@bin_resolver.resolve!(bin)
|
188
150
|
end
|
189
151
|
|
190
|
-
VENDOR_PATH = File.expand_path('../../vendor', __FILE__)
|
191
|
-
|
192
152
|
# Join resolve_dir, default path and vendor path for PATH environment variable
|
193
153
|
def env_path
|
194
|
-
|
154
|
+
@bin_resolver.env_path
|
195
155
|
end
|
196
156
|
|
197
157
|
private
|
@@ -199,7 +159,6 @@ private
|
|
199
159
|
# Run method for each path and yield each path and result if block given
|
200
160
|
def run_method_for(paths, method_name, &block)
|
201
161
|
apply_threading(paths).map do |path|
|
202
|
-
path = ImagePath.new(path)
|
203
162
|
result = send(method_name, path)
|
204
163
|
if block
|
205
164
|
block.call(path, result)
|
@@ -217,33 +176,6 @@ private
|
|
217
176
|
enum
|
218
177
|
end
|
219
178
|
end
|
220
|
-
|
221
|
-
# Check if bin can be accessed
|
222
|
-
def bin_accessible?(bin)
|
223
|
-
`env PATH=#{env_path.shellescape} which #{bin.to_s.shellescape}` != ''
|
224
|
-
end
|
225
|
-
|
226
|
-
# http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
|
227
|
-
def processor_count
|
228
|
-
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
229
|
-
when /darwin9/
|
230
|
-
`hwprefs cpu_count`
|
231
|
-
when /darwin/
|
232
|
-
(`which hwprefs` != '') ? `hwprefs thread_count` : `sysctl -n hw.ncpu`
|
233
|
-
when /linux/
|
234
|
-
`grep -c processor /proc/cpuinfo`
|
235
|
-
when /freebsd/
|
236
|
-
`sysctl -n hw.ncpu`
|
237
|
-
when /mswin|mingw/
|
238
|
-
require 'win32ole'
|
239
|
-
wmi = WIN32OLE.connect('winmgmts://')
|
240
|
-
cpu = wmi.ExecQuery('select NumberOfLogicalProcessors from Win32_Processor')
|
241
|
-
cpu.to_enum.first.NumberOfLogicalProcessors
|
242
|
-
else
|
243
|
-
warn "Unknown architecture (#{host_os}) assuming one processor."
|
244
|
-
1
|
245
|
-
end.to_i
|
246
|
-
end
|
247
179
|
end
|
248
180
|
|
249
181
|
%w[
|
@@ -253,3 +185,5 @@ end
|
|
253
185
|
].each do |worker|
|
254
186
|
require "image_optim/worker/#{worker}"
|
255
187
|
end
|
188
|
+
|
189
|
+
require 'image_optim/railtie' if defined?(Rails)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
5
|
+
|
6
|
+
require 'image_optim'
|
7
|
+
|
8
|
+
README_FILE = File.expand_path('../../README.markdown', __FILE__)
|
9
|
+
BEGIN_MARKER = '<!---<worker-options>-->'
|
10
|
+
END_MARKER = '<!---</worker-options>-->'
|
11
|
+
|
12
|
+
def worker_options
|
13
|
+
io = StringIO.new
|
14
|
+
|
15
|
+
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
16
|
+
unless klass.option_definitions.empty?
|
17
|
+
io.puts "### #{klass.bin_sym}"
|
18
|
+
klass.option_definitions.each do |option_definition|
|
19
|
+
io.puts "* `:#{option_definition.name}` — #{option_definition.description} *(defaults to #{option_definition.default})*"
|
20
|
+
end
|
21
|
+
io.puts
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
io.string
|
26
|
+
end
|
27
|
+
|
28
|
+
readme = File.read(README_FILE)
|
29
|
+
|
30
|
+
if readme.sub!(/#{Regexp.escape(BEGIN_MARKER)}.*#{Regexp.escape(END_MARKER)}/m, "#{BEGIN_MARKER}\n\n#{worker_options.strip}\n\n#{END_MARKER}")
|
31
|
+
File.open(README_FILE, 'w') do |f|
|
32
|
+
f.write readme
|
33
|
+
end
|
34
|
+
else
|
35
|
+
abort "Did not update worker options"
|
36
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
$:.unshift File.expand_path('../../../lib', __FILE__)
|
2
|
+
require 'rspec'
|
3
|
+
require 'image_optim/bin_resolver'
|
4
|
+
|
5
|
+
def with_env(key, value)
|
6
|
+
saved, ENV[key] = ENV[key], value
|
7
|
+
yield
|
8
|
+
ensure
|
9
|
+
ENV[key] = saved
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ImageOptim::BinResolver do
|
13
|
+
it "should resolve bin in path" do
|
14
|
+
with_env 'LS_BIN', nil do
|
15
|
+
resolver = ImageOptim::BinResolver.new
|
16
|
+
resolver.should_receive(:accessible?).with(:ls).once.and_return(true)
|
17
|
+
FSPath.should_not_receive(:temp_dir)
|
18
|
+
|
19
|
+
5.times do
|
20
|
+
resolver.resolve!(:ls).should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should resolve bin specified in ENV" do
|
26
|
+
path = (FSPath(__FILE__).dirname / '../bin/image_optim').relative_path_from(Dir.pwd).to_s
|
27
|
+
with_env 'IMAGE_OPTIM_BIN', path do
|
28
|
+
tmpdir = double(:tmpdir)
|
29
|
+
symlink = double(:symlink)
|
30
|
+
|
31
|
+
resolver = ImageOptim::BinResolver.new
|
32
|
+
resolver.should_receive(:accessible?).with(symlink).once.and_return(true)
|
33
|
+
FSPath.should_receive(:temp_dir).once.and_return(tmpdir)
|
34
|
+
tmpdir.should_receive(:/).with(:image_optim).once.and_return(symlink)
|
35
|
+
symlink.should_receive(:make_symlink).with(File.expand_path(path)).once
|
36
|
+
|
37
|
+
at_exit_blocks = []
|
38
|
+
resolver.should_receive(:at_exit).once do |&block|
|
39
|
+
at_exit_blocks.unshift(block)
|
40
|
+
end
|
41
|
+
|
42
|
+
5.times do
|
43
|
+
resolver.resolve!(:image_optim).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
FileUtils.should_receive(:remove_entry_secure).with(tmpdir)
|
47
|
+
at_exit_blocks.each(&:call)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should raise on failure to resolve bin" do
|
52
|
+
with_env 'SHOULD_NOT_EXIST_BIN', nil do
|
53
|
+
resolver = ImageOptim::BinResolver.new
|
54
|
+
resolver.should_receive(:accessible?).with(:should_not_exist).once.and_return(false)
|
55
|
+
FSPath.should_not_receive(:temp_dir)
|
56
|
+
|
57
|
+
5.times do
|
58
|
+
expect do
|
59
|
+
resolver.resolve!(:should_not_exist)
|
60
|
+
end.to raise_error ImageOptim::BinNotFoundError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise on failure to resolve bin specified in ENV" do
|
66
|
+
path = (FSPath(__FILE__).dirname / '../bin/should_not_exist_bin').relative_path_from(Dir.pwd).to_s
|
67
|
+
with_env 'SHOULD_NOT_EXIST_BIN', path do
|
68
|
+
tmpdir = double(:tmpdir)
|
69
|
+
symlink = double(:symlink)
|
70
|
+
|
71
|
+
resolver = ImageOptim::BinResolver.new
|
72
|
+
resolver.should_receive(:accessible?).with(symlink).once.and_return(false)
|
73
|
+
FSPath.should_receive(:temp_dir).once.and_return(tmpdir)
|
74
|
+
tmpdir.should_receive(:/).with(:should_not_exist).once.and_return(symlink)
|
75
|
+
symlink.should_receive(:make_symlink).with(File.expand_path(path)).once
|
76
|
+
|
77
|
+
at_exit_blocks = []
|
78
|
+
resolver.should_receive(:at_exit).once do |&block|
|
79
|
+
at_exit_blocks.unshift(block)
|
80
|
+
end
|
81
|
+
|
82
|
+
5.times do
|
83
|
+
expect do
|
84
|
+
resolver.resolve!(:should_not_exist)
|
85
|
+
end.to raise_error ImageOptim::BinNotFoundError
|
86
|
+
end
|
87
|
+
|
88
|
+
FileUtils.should_receive(:remove_entry_secure).with(tmpdir)
|
89
|
+
at_exit_blocks.each(&:call)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|