image_optim 0.6.0 → 0.7.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.
- 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
|