image_optim 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.markdown +43 -19
- data/TODO +6 -1
- data/bin/image_optim +17 -11
- data/image_optim.gemspec +1 -1
- data/lib/image_optim.rb +86 -26
- data/lib/image_optim/image_path.rb +2 -0
- data/lib/image_optim/option_helpers.rb +5 -1
- data/lib/image_optim/worker.rb +36 -49
- data/lib/image_optim/worker/advpng.rb +22 -0
- data/lib/image_optim/worker/gifsicle.rb +22 -0
- data/lib/image_optim/worker/jpegoptim.rb +41 -0
- data/lib/image_optim/worker/jpegtran.rb +27 -0
- data/lib/image_optim/worker/optipng.rb +27 -0
- data/lib/image_optim/worker/pngcrush.rb +39 -0
- data/lib/image_optim/worker/pngout.rb +30 -0
- data/spec/image_optim_spec.rb +213 -92
- metadata +11 -12
- data/lib/image_optim/util.rb +0 -27
- data/lib/image_optim/workers/advpng.rb +0 -19
- data/lib/image_optim/workers/gifsicle.rb +0 -20
- data/lib/image_optim/workers/jpegoptim.rb +0 -50
- data/lib/image_optim/workers/jpegtran.rb +0 -25
- data/lib/image_optim/workers/optipng.rb +0 -27
- data/lib/image_optim/workers/pngcrush.rb +0 -37
- data/lib/image_optim/workers/pngout.rb +0 -27
data/lib/image_optim/worker.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'shellwords'
|
4
|
+
|
4
5
|
require 'image_optim'
|
5
6
|
|
6
7
|
class ImageOptim
|
@@ -16,12 +17,6 @@ class ImageOptim
|
|
16
17
|
klasses << base
|
17
18
|
end
|
18
19
|
|
19
|
-
# List of formats which worker can optimize
|
20
|
-
def image_formats
|
21
|
-
format_from_name = name.downcase[/gif|jpeg|png/]
|
22
|
-
format_from_name ? [format_from_name.to_sym] : []
|
23
|
-
end
|
24
|
-
|
25
20
|
# Undercored class name
|
26
21
|
def underscored_name
|
27
22
|
@underscored_name ||= name.split('::').last.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
@@ -30,59 +25,51 @@ class ImageOptim
|
|
30
25
|
|
31
26
|
include OptionHelpers
|
32
27
|
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
# Binary name or path
|
37
|
-
attr_reader :nice
|
38
|
-
|
39
|
-
# Be verbose
|
40
|
-
attr_reader :verbose
|
41
|
-
|
42
|
-
# Configure (raises on extra options), find binary (raises if not found)
|
43
|
-
def initialize(options = {})
|
44
|
-
get_option!(options, :bin, default_bin)
|
45
|
-
get_option!(options, :nice, 10){ |v| v.to_i }
|
46
|
-
get_option!(options, :verbose, false)
|
28
|
+
# Configure (raises on extra options)
|
29
|
+
def initialize(image_optim, options = {})
|
30
|
+
@image_optim = image_optim
|
47
31
|
parse_options(options)
|
48
|
-
raise BinaryNotFoundError, "`#{bin}` not found" if `which #{bin.to_s.shellescape}`.empty?
|
49
32
|
assert_options_empty!(options)
|
50
33
|
end
|
51
34
|
|
52
|
-
#
|
53
|
-
def
|
35
|
+
# List of formats which worker can optimize
|
36
|
+
def image_formats
|
37
|
+
format_from_name = self.class.name.downcase[/gif|jpeg|png/]
|
38
|
+
format_from_name ? [format_from_name.to_sym] : []
|
54
39
|
end
|
55
40
|
|
56
|
-
#
|
57
|
-
def
|
58
|
-
|
41
|
+
# Ordering in list of workers
|
42
|
+
def run_order
|
43
|
+
0
|
59
44
|
end
|
60
45
|
|
61
|
-
#
|
62
|
-
def
|
63
|
-
|
64
|
-
start = Time.now
|
65
|
-
pid = fork do
|
66
|
-
$stdout.reopen('/dev/null', 'w')
|
67
|
-
$stderr.reopen('/dev/null', 'w')
|
68
|
-
Process.setpriority(Process::PRIO_PROCESS, 0, nice)
|
69
|
-
exec command
|
70
|
-
end
|
71
|
-
Process.wait pid
|
72
|
-
duration = Time.now - start
|
73
|
-
if $?.signaled?
|
74
|
-
raise SignalException.new($?.termsig)
|
75
|
-
end
|
76
|
-
success = $?.success? && dst.size? && dst.size < src.size
|
77
|
-
if verbose
|
78
|
-
print "#{success ? '✓' : '✗'} #{duration}s #{command}\n"
|
79
|
-
end
|
80
|
-
success
|
46
|
+
# Check if operation resulted in optimized file
|
47
|
+
def optimized?(src, dst)
|
48
|
+
dst.size? && dst.size < src.size
|
81
49
|
end
|
82
50
|
|
83
|
-
|
84
|
-
|
85
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
# Forward bin resolving to image_optim
|
54
|
+
def resolve_bin!(bin)
|
55
|
+
@image_optim.resolve_bin!(bin)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Run command setting priority and hiding output
|
59
|
+
def execute(bin, *arguments)
|
60
|
+
resolve_bin!(bin)
|
61
|
+
|
62
|
+
command = [bin, *arguments].map(&:to_s).shelljoin
|
63
|
+
env_path = "#{@image_optim.resolve_dir}:#{ENV['PATH']}"
|
64
|
+
start = Time.now
|
65
|
+
|
66
|
+
system "env PATH=#{env_path.shellescape} nice -n #{@image_optim.nice} #{command} >& /dev/null"
|
67
|
+
|
68
|
+
raise SignalException.new($?.termsig) if $?.signaled?
|
69
|
+
|
70
|
+
$stderr << "#{$?.success? ? '✓' : '✗'} #{Time.now - start}s #{command}\n" if @image_optim.verbose?
|
71
|
+
|
72
|
+
$?.success?
|
86
73
|
end
|
87
74
|
end
|
88
75
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Advpng < Worker
|
6
|
+
# Compression level: 0 - don't compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme (defaults to 4)
|
7
|
+
attr_reader :level
|
8
|
+
|
9
|
+
def optimize(src, dst)
|
10
|
+
src.copy(dst)
|
11
|
+
args = %W[-#{level} -z -q -- #{dst}]
|
12
|
+
execute(:advpng, *args) && optimized?(src, dst)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_options(options)
|
18
|
+
get_option!(options, :level, 4){ |v| limit_with_range(v.to_i, 0..4) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Gifsicle < Worker
|
6
|
+
# Turn on interlacing (defaults to false)
|
7
|
+
attr_reader :interlace
|
8
|
+
|
9
|
+
def optimize(src, dst)
|
10
|
+
args = %W[-o #{dst} -O3 --no-comments --no-names --same-delay --same-loopcount --no-warnings -- #{src}]
|
11
|
+
args.unshift('-i') if interlace
|
12
|
+
execute(:gifsicle, *args) && optimized?(src, dst)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_options(options)
|
18
|
+
get_option!(options, :interlace, false){ |v| !!v }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Jpegoptim < Worker
|
6
|
+
# List of extra markers to strip: comments, exif, iptc, icc (defaults to 'all')
|
7
|
+
attr_reader :strip
|
8
|
+
|
9
|
+
# Maximum image quality factor (defaults to 100)
|
10
|
+
attr_reader :max_quality
|
11
|
+
|
12
|
+
# Run first if max_quality < 100
|
13
|
+
def run_order
|
14
|
+
max_quality < 100 ? -1 : 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def optimize(src, dst)
|
18
|
+
src.copy(dst)
|
19
|
+
args = %W[-q -- #{dst}]
|
20
|
+
strip.each do |strip_marker|
|
21
|
+
args.unshift "--strip-#{strip_marker}"
|
22
|
+
end
|
23
|
+
args.unshift "-m#{max_quality}" if max_quality < 100
|
24
|
+
execute(:jpegoptim, *args) && optimized?(src, dst)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_options(options)
|
30
|
+
get_option!(options, :strip, :all) do |v|
|
31
|
+
markers = Array(v).map(&:to_s)
|
32
|
+
possible_markers = %w[all comments exif iptc icc]
|
33
|
+
unknown_markers = markers - possible_markers
|
34
|
+
warn "Unknown markers for jpegoptim: #{unknown_markers.join(', ')}" unless unknown_markers.empty?
|
35
|
+
markers & possible_markers
|
36
|
+
end
|
37
|
+
get_option!(options, :max_quality, 100){ |v| v.to_i }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Jpegtran < Worker
|
6
|
+
# Copy all chunks or none (defaults to false)
|
7
|
+
attr_reader :copy_chunks
|
8
|
+
|
9
|
+
# Create progressive JPEG file (defaults to true)
|
10
|
+
attr_reader :progressive
|
11
|
+
|
12
|
+
def optimize(src, dst)
|
13
|
+
args = %W[-optimize -outfile #{dst} #{src}]
|
14
|
+
args.unshift '-copy', copy_chunks ? 'all' : 'none'
|
15
|
+
args.unshift '-progressive' if progressive
|
16
|
+
execute(:jpegtran, *args) && optimized?(src, dst)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def parse_options(options)
|
22
|
+
get_option!(options, :copy_chunks, false){ |v| !!v }
|
23
|
+
get_option!(options, :progressive, true){ |v| !!v }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Optipng < Worker
|
6
|
+
# Optimization level preset 0..7 (0 is least, 7 is best, defaults to 6)
|
7
|
+
attr_reader :level
|
8
|
+
|
9
|
+
# Interlace, true - interlace on, false - interlace off, nil - as is in original image (defaults to false)
|
10
|
+
attr_reader :interlace
|
11
|
+
|
12
|
+
def optimize(src, dst)
|
13
|
+
src.copy(dst)
|
14
|
+
args = %W[-o#{level} -quiet -- #{dst}]
|
15
|
+
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
|
16
|
+
execute(:optipng, *args) && optimized?(src, dst)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def parse_options(options)
|
22
|
+
get_option!(options, :level, 6){ |v| limit_with_range(v.to_i, 0..7) }
|
23
|
+
get_option!(options, :interlace, false){ |v| v && true }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Pngcrush < Worker
|
6
|
+
# List of chunks to remove or 'alla' - all except tRNS/transparency or 'allb' - all except tRNS and gAMA/gamma (defaults to 'alla')
|
7
|
+
attr_reader :chunks
|
8
|
+
|
9
|
+
# Fix otherwise fatal conditions such as bad CRCs (defaults to false)
|
10
|
+
attr_reader :fix
|
11
|
+
|
12
|
+
# Brute force try all methods, very time-consuming and generally not worthwhile (defaults to false)
|
13
|
+
attr_reader :brute
|
14
|
+
|
15
|
+
# Always run first
|
16
|
+
def run_order
|
17
|
+
-1
|
18
|
+
end
|
19
|
+
|
20
|
+
def optimize(src, dst)
|
21
|
+
args = %W[-reduce -cc -q -- #{src} #{dst}]
|
22
|
+
chunks.each do |chunk|
|
23
|
+
args.unshift '-rem', chunk
|
24
|
+
end
|
25
|
+
args.unshift '-fix' if fix
|
26
|
+
args.unshift '-brute' if brute
|
27
|
+
execute(:pngcrush, *args) && optimized?(src, dst)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parse_options(options)
|
33
|
+
get_option!(options, :chunks, :alla){ |v| Array(v).map(&:to_s) }
|
34
|
+
get_option!(options, :fix, false){ |v| !!v }
|
35
|
+
get_option!(options, :brute, false){ |v| !!v }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'image_optim/worker'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Worker
|
5
|
+
class Pngout < Worker
|
6
|
+
# Copy optional chunks (defaults to false)
|
7
|
+
attr_reader :copy_chunks
|
8
|
+
|
9
|
+
# Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed (defaults to 0)
|
10
|
+
attr_reader :strategy
|
11
|
+
|
12
|
+
# Always run first
|
13
|
+
def run_order
|
14
|
+
-1
|
15
|
+
end
|
16
|
+
|
17
|
+
def optimize(src, dst)
|
18
|
+
args = %W[-k#{copy_chunks ? 1 : 0} -s#{strategy} -q -y #{src} #{dst}]
|
19
|
+
execute(:pngout, *args) && optimized?(src, dst)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parse_options(options)
|
25
|
+
get_option!(options, :copy_chunks, false){ |v| !!v }
|
26
|
+
get_option!(options, :strategy, 0){ |v| limit_with_range(v.to_i, 0..4) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/image_optim_spec.rb
CHANGED
@@ -1,116 +1,153 @@
|
|
1
1
|
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
2
2
|
require 'rspec'
|
3
3
|
require 'image_optim'
|
4
|
+
require 'tempfile'
|
4
5
|
|
5
|
-
|
6
|
-
image_dir = spec_dir / 'images'
|
6
|
+
TEST_IMAGES = (ImageOptim::ImagePath.new(__FILE__).dirname.relative_path_from(Dir.pwd) / 'images').glob('*')
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
original.copy(temp_path)
|
12
|
-
yield temp_path
|
8
|
+
Fixnum.class_eval do
|
9
|
+
def in_range?(range)
|
10
|
+
range.include?(self)
|
13
11
|
end
|
14
12
|
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def initialize(*args, &block)
|
20
|
-
self.class.initialize_called
|
21
|
-
initialize_orig(*args, &block)
|
14
|
+
Tempfile.class_eval do
|
15
|
+
def self.init_count
|
16
|
+
class_variable_get(:@@init_count)
|
22
17
|
end
|
23
18
|
|
24
|
-
def self.
|
25
|
-
|
26
|
-
@@call_count += 1
|
19
|
+
def self.init_count=(value)
|
20
|
+
class_variable_set(:@@init_count, value)
|
27
21
|
end
|
28
22
|
|
29
|
-
def self.
|
30
|
-
|
23
|
+
def self.reset_init_count
|
24
|
+
self.init_count = 0
|
31
25
|
end
|
32
26
|
|
33
|
-
|
34
|
-
|
27
|
+
reset_init_count
|
28
|
+
|
29
|
+
alias_method :initialize_orig, :initialize
|
30
|
+
def initialize(*args, &block)
|
31
|
+
self.class.init_count += 1
|
32
|
+
initialize_orig(*args, &block)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
|
-
|
39
|
-
def
|
40
|
-
|
36
|
+
ImageOptim::ImagePath.class_eval do
|
37
|
+
def temp_copy
|
38
|
+
temp_path.tap{ |path| copy(path) }
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
42
|
+
def with_env(key, value)
|
43
|
+
saved, ENV[key] = ENV[key], value
|
44
|
+
yield
|
45
|
+
ensure
|
46
|
+
ENV[key] = saved
|
47
|
+
end
|
48
|
+
|
44
49
|
describe ImageOptim do
|
45
|
-
|
46
|
-
describe "
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
describe "isolated" do
|
51
|
+
describe "optimize" do
|
52
|
+
TEST_IMAGES.each do |original|
|
53
|
+
it "should optimize #{original}" do
|
54
|
+
copy = original.temp_copy
|
55
|
+
|
56
|
+
Tempfile.reset_init_count
|
57
|
+
image_optim = ImageOptim.new
|
58
|
+
optimized_image = image_optim.optimize_image(copy)
|
59
|
+
optimized_image.should be_a(ImageOptim::ImagePath)
|
60
|
+
optimized_image.size.should be_in_range(1...original.size)
|
61
|
+
optimized_image.read.should_not == original.read
|
62
|
+
copy.read.should == original.read
|
63
|
+
|
64
|
+
if image_optim.workers_for_image(original).length > 1
|
65
|
+
Tempfile.init_count.should be_in_range(1..2)
|
59
66
|
else
|
60
|
-
Tempfile.
|
67
|
+
Tempfile.init_count.should === 1
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "optimize in place" do
|
74
|
+
TEST_IMAGES.each do |original|
|
75
|
+
it "should optimize #{original}" do
|
76
|
+
copy = original.temp_copy
|
64
77
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if io.workers_for_image(path).length > 1
|
74
|
-
Tempfile.call_count.should be_in_range(2..3)
|
78
|
+
Tempfile.reset_init_count
|
79
|
+
image_optim = ImageOptim.new
|
80
|
+
image_optim.optimize_image!(copy).should be_true
|
81
|
+
copy.size.should be_in_range(1...original.size)
|
82
|
+
copy.read.should_not == original.read
|
83
|
+
|
84
|
+
if image_optim.workers_for_image(original).length > 1
|
85
|
+
Tempfile.init_count.should be_in_range(2..3)
|
75
86
|
else
|
76
|
-
Tempfile.
|
87
|
+
Tempfile.init_count.should === 2
|
77
88
|
end
|
78
89
|
end
|
79
90
|
end
|
91
|
+
end
|
80
92
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
93
|
+
describe "stop optimizing" do
|
94
|
+
TEST_IMAGES.each do |original|
|
95
|
+
it "should stop optimizing #{original}" do
|
96
|
+
copy = original.temp_copy
|
97
|
+
|
98
|
+
tries = 0
|
99
|
+
10.times do
|
100
|
+
tries += 1
|
101
|
+
break unless ImageOptim.optimize_image!(copy)
|
86
102
|
end
|
87
|
-
|
88
|
-
count.should < 10
|
103
|
+
tries.should be_in_range(2...3)
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
92
107
|
end
|
93
108
|
|
94
|
-
describe "
|
109
|
+
describe "bunch" do
|
110
|
+
it "should optimize" do
|
111
|
+
copies = TEST_IMAGES.map(&:temp_copy)
|
112
|
+
optimized_images = ImageOptim.optimize_images(copies)
|
113
|
+
TEST_IMAGES.zip(copies, optimized_images).each do |original, copy, optimized_image|
|
114
|
+
optimized_image.should be_a(ImageOptim::ImagePath)
|
115
|
+
optimized_image.size.should be_in_range(1...original.size)
|
116
|
+
optimized_image.read.should_not == original.read
|
117
|
+
copy.read.should == original.read
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should optimize in place" do
|
122
|
+
copies = TEST_IMAGES.map(&:temp_copy)
|
123
|
+
ImageOptim.optimize_images!(copies)
|
124
|
+
TEST_IMAGES.zip(copies).each do |original, copy|
|
125
|
+
copy.size.should be_in_range(1...original.size)
|
126
|
+
copy.read.should_not == original.read
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "unsupported" do
|
95
132
|
let(:original){ ImageOptim::ImagePath.new(__FILE__) }
|
96
133
|
|
97
134
|
it "should ignore" do
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
135
|
+
copy = original.temp_copy
|
136
|
+
|
137
|
+
Tempfile.reset_init_count
|
138
|
+
optimized_image = ImageOptim.optimize_image(copy)
|
139
|
+
Tempfile.init_count.should == 0
|
140
|
+
optimized_image.should be_nil
|
141
|
+
copy.read.should == original.read
|
105
142
|
end
|
106
143
|
|
107
144
|
it "should ignore in place" do
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
145
|
+
copy = original.temp_copy
|
146
|
+
|
147
|
+
Tempfile.reset_init_count
|
148
|
+
ImageOptim.optimize_image!(copy).should_not be_true
|
149
|
+
Tempfile.init_count.should == 0
|
150
|
+
copy.read.should == original.read
|
114
151
|
end
|
115
152
|
end
|
116
153
|
|
@@ -124,34 +161,118 @@ describe ImageOptim do
|
|
124
161
|
end
|
125
162
|
|
126
163
|
%w[optimize_images optimize_images!].each do |list_method|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
164
|
+
describe list_method do
|
165
|
+
single_method = list_method.sub('images', 'image')
|
166
|
+
describe "without block" do
|
167
|
+
it "should optimize images and return array of results" do
|
168
|
+
image_optim = ImageOptim.new
|
169
|
+
dsts = srcs.map do |src|
|
170
|
+
dst = "#{src}_"
|
171
|
+
image_optim.should_receive(single_method).with(src).and_return(dst)
|
172
|
+
dst
|
173
|
+
end
|
174
|
+
image_optim.send(list_method, srcs).should == dsts
|
136
175
|
end
|
137
|
-
io.send(list_method, srcs).should == dsts
|
138
176
|
end
|
139
|
-
end
|
140
177
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
178
|
+
describe "given block" do
|
179
|
+
it "should optimize images, yield path and result for each and return array of yield results" do
|
180
|
+
image_optim = ImageOptim.new
|
181
|
+
results = srcs.map do |src|
|
182
|
+
dst = "#{src}_"
|
183
|
+
image_optim.should_receive(single_method).with(src).and_return(dst)
|
184
|
+
"#{src} #{dst}"
|
185
|
+
end
|
186
|
+
image_optim.send(list_method, srcs) do |src, dst|
|
187
|
+
"#{src} #{dst}"
|
188
|
+
end.should == results
|
149
189
|
end
|
150
|
-
io.send(list_method, srcs) do |src, dst|
|
151
|
-
"#{src} #{dst}"
|
152
|
-
end.should == results
|
153
190
|
end
|
154
191
|
end
|
155
192
|
end
|
156
193
|
end
|
194
|
+
|
195
|
+
describe "resolve bin" do
|
196
|
+
it "should resolve bin in path" do
|
197
|
+
with_env 'LS_BIN', nil do
|
198
|
+
image_optim = ImageOptim.new
|
199
|
+
image_optim.should_receive(:bin_accessible?).with(:ls).once.and_return(true)
|
200
|
+
FSPath.should_not_receive(:temp_dir)
|
201
|
+
|
202
|
+
5.times do
|
203
|
+
image_optim.resolve_bin!(:ls).should be_true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should resolve bin specified in ENV" do
|
209
|
+
path = (FSPath(__FILE__).dirname / '../bin/image_optim').relative_path_from(Dir.pwd).to_s
|
210
|
+
with_env 'IMAGE_OPTIM_BIN', path do
|
211
|
+
tmpdir = stub(:tmpdir)
|
212
|
+
symlink = stub(:symlink)
|
213
|
+
|
214
|
+
image_optim = ImageOptim.new
|
215
|
+
image_optim.should_receive(:bin_accessible?).with(symlink).once.and_return(true)
|
216
|
+
FSPath.should_receive(:temp_dir).once.and_return(tmpdir)
|
217
|
+
tmpdir.should_receive(:/).with(:image_optim).once.and_return(symlink)
|
218
|
+
symlink.should_receive(:make_symlink).with(File.expand_path(path)).once
|
219
|
+
|
220
|
+
at_exit_blocks = []
|
221
|
+
image_optim.should_receive(:at_exit).twice do |&block|
|
222
|
+
at_exit_blocks.unshift(block)
|
223
|
+
end
|
224
|
+
|
225
|
+
5.times do
|
226
|
+
image_optim.resolve_bin!(:image_optim).should be_true
|
227
|
+
end
|
228
|
+
|
229
|
+
FileUtils.should_receive(:remove_entry_secure).with(tmpdir)
|
230
|
+
symlink.should_receive(:unlink)
|
231
|
+
at_exit_blocks.each(&:call)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should raise on failure to resolve bin" do
|
236
|
+
with_env 'SHOULD_NOT_EXIST_BIN', nil do
|
237
|
+
image_optim = ImageOptim.new
|
238
|
+
image_optim.should_receive(:bin_accessible?).with(:should_not_exist).once.and_return(false)
|
239
|
+
FSPath.should_not_receive(:temp_dir)
|
240
|
+
|
241
|
+
5.times do
|
242
|
+
expect do
|
243
|
+
image_optim.resolve_bin!(:should_not_exist)
|
244
|
+
end.to raise_error ImageOptim::BinNotFoundError
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should raise on failure to resolve bin specified in ENV" do
|
250
|
+
path = (FSPath(__FILE__).dirname / '../bin/should_not_exist_bin').relative_path_from(Dir.pwd).to_s
|
251
|
+
with_env 'SHOULD_NOT_EXIST_BIN', path do
|
252
|
+
tmpdir = stub(:tmpdir)
|
253
|
+
symlink = stub(:symlink)
|
254
|
+
|
255
|
+
image_optim = ImageOptim.new
|
256
|
+
image_optim.should_receive(:bin_accessible?).with(symlink).once.and_return(false)
|
257
|
+
FSPath.should_receive(:temp_dir).once.and_return(tmpdir)
|
258
|
+
tmpdir.should_receive(:/).with(:should_not_exist).once.and_return(symlink)
|
259
|
+
symlink.should_receive(:make_symlink).with(File.expand_path(path)).once
|
260
|
+
|
261
|
+
at_exit_blocks = []
|
262
|
+
image_optim.should_receive(:at_exit).twice do |&block|
|
263
|
+
at_exit_blocks.unshift(block)
|
264
|
+
end
|
265
|
+
|
266
|
+
5.times do
|
267
|
+
expect do
|
268
|
+
image_optim.resolve_bin!(:should_not_exist)
|
269
|
+
end.to raise_error ImageOptim::BinNotFoundError
|
270
|
+
end
|
271
|
+
|
272
|
+
FileUtils.should_receive(:remove_entry_secure).with(tmpdir)
|
273
|
+
symlink.should_receive(:unlink)
|
274
|
+
at_exit_blocks.each(&:call)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
157
278
|
end
|