image_optim 0.2.1 → 0.3.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.
@@ -48,14 +48,61 @@ if ARGV.empty?
48
48
  abort "specify image paths to optimize\n\n#{option_parser.help}"
49
49
  else
50
50
  io = ImageOptim.new(options)
51
- paths = ARGV
51
+ paths = ARGV.reject do |arg|
52
+ unless File.file?(arg)
53
+ $stderr << "WARN: #{arg} is not a file\n"
54
+ end
55
+ end
52
56
  paths = paths.with_progress('optimizing') if paths.length > 1
53
57
 
54
- lines = paths.map do |path|
55
- before = File.size(path)
56
- result = io.optimize_image!(path)
57
- after = File.size(path)
58
- "#{result ? '%5.2f%%' % (100.0 * after / before) : '--.--%'} #{path}"
58
+ module Space
59
+ SIZE_SYMBOLS = %w[B K M G T P E Z Y].freeze
60
+ PRECISION = 1
61
+ LENGTH = 4 + PRECISION + 1
62
+ COEF = 1 / Math.log(10)
63
+
64
+ EMPTY_SPACE = ' ' * LENGTH
65
+ NOT_COUNTED_SPACE = '!' * LENGTH
66
+
67
+ class << self
68
+ attr_writer :base10
69
+ def denominator
70
+ @denominator ||= @base10 ? 1000.0 : 1024.0
71
+ end
72
+
73
+ def space(size, options = {})
74
+ case size
75
+ when false
76
+ NOT_COUNTED_SPACE.bold.red
77
+ when 0, nil
78
+ EMPTY_SPACE
79
+ else
80
+ number, degree = size, 0
81
+ while number.abs >= 1000 && degree < SIZE_SYMBOLS.length - 1
82
+ number /= denominator
83
+ degree += 1
84
+ end
85
+
86
+ "#{degree == 0 ? number.to_s : "%.#{PRECISION}f" % number}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def size_percent(src_size, dst_size)
93
+ '%5.2f%% %s' % [100 - 100.0 * dst_size / src_size, Space.space(src_size - dst_size)]
94
+ end
95
+
96
+ results = io.optimize_images(paths) do |src, dst|
97
+ if dst
98
+ src_size, dst_size = src.size, dst.size
99
+ percent = size_percent(src_size, dst_size)
100
+ dst.replace(src)
101
+ ["#{percent} #{src}", src_size, dst_size]
102
+ else
103
+ ["------ #{Space::EMPTY_SPACE} #{src}", src.size, src.size]
104
+ end
59
105
  end
60
- puts lines
106
+ lines, src_sizes, dst_sizes = results.transpose
107
+ $stdout.puts lines, "Total: #{size_percent(src_sizes.inject(:+), dst_sizes.inject(:+))}\n"
61
108
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_optim'
5
- s.version = '0.2.1'
5
+ s.version = '0.3.0'
6
6
  s.summary = %q{Optimize images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)}
7
7
  s.homepage = "http://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
@@ -94,33 +94,23 @@ class ImageOptim
94
94
  def optimize_image!(original)
95
95
  original = ImagePath.new(original)
96
96
  if result = optimize_image(original)
97
- original.temp_path(original.dirname) do |temp|
98
- original.copy(temp)
99
- temp.write(result.read)
100
- temp.rename(original)
101
- end
97
+ result.replace(original)
102
98
  true
103
99
  end
104
100
  end
105
101
 
106
- # Optimize multiple images, returning list of results
107
- # yields path and result if block given
108
- def optimize_images(paths)
109
- apply_threading(paths).map do |path|
110
- result = optimize_image(path)
111
- yield path, result if block_given?
112
- result
113
- end
102
+ # Optimize multiple images
103
+ # if block given yields path and result for each image and returns array of yield results
104
+ # else return array of results
105
+ def optimize_images(paths, &block)
106
+ run_method_for(paths, :optimize_image, &block)
114
107
  end
115
108
 
116
- # Optimize multiple images in place, returning list of results
117
- # yields path and result if block given
118
- def optimize_images!(paths)
119
- apply_threading(paths).map do |path|
120
- result = optimize_image!(path)
121
- yield path, result if block_given?
122
- result
123
- end
109
+ # Optimize multiple images in place
110
+ # if block given yields path and result for each image and returns array of yield results
111
+ # else return array of results
112
+ def optimize_images!(paths, &block)
113
+ run_method_for(paths, :optimize_image!, &block)
124
114
  end
125
115
 
126
116
  # Optimization methods with default options
@@ -138,8 +128,21 @@ class ImageOptim
138
128
 
139
129
  private
140
130
 
131
+ def run_method_for(paths, method_name, &block)
132
+ method = method(method_name)
133
+ apply_threading(paths).map do |path|
134
+ path = ImagePath.new(path)
135
+ result = method.call(path)
136
+ if block
137
+ block.call(path, result)
138
+ else
139
+ result
140
+ end
141
+ end
142
+ end
143
+
141
144
  def apply_threading(array)
142
- if threads > 1 && array.length > 1
145
+ if threads > 1
143
146
  array.in_threads(threads)
144
147
  else
145
148
  array
@@ -14,6 +14,16 @@ class ImageOptim
14
14
  FileUtils.copy_file(self, dst, true)
15
15
  end
16
16
 
17
+ # Atomic replace src with self
18
+ def replace(src)
19
+ src = self.class.new(src)
20
+ src.temp_path(src.dirname) do |temp|
21
+ src.copy(temp)
22
+ temp.write(read)
23
+ temp.rename(src)
24
+ end
25
+ end
26
+
17
27
  # Get format using ImageSize
18
28
  def format
19
29
  open{ |f| ImageSize.new(f) }.format
@@ -8,12 +8,8 @@ image_dir = spec_dir / 'images'
8
8
  def temp_copy_path(original)
9
9
  original.class.temp_dir do |dir|
10
10
  temp_path = dir / original.basename
11
- begin
12
- original.copy(temp_path)
13
- yield temp_path
14
- ensure
15
- temp_path.unlink if temp_path.exist?
16
- end
11
+ original.copy(temp_path)
12
+ yield temp_path
17
13
  end
18
14
  end
19
15
 
@@ -117,4 +113,45 @@ describe ImageOptim do
117
113
  end
118
114
  end
119
115
  end
116
+
117
+ describe "optimize multiple" do
118
+ let(:srcs){ ('a'..'z').to_a }
119
+
120
+ before do
121
+ srcs.each do |src|
122
+ ImageOptim::ImagePath.should_receive(:new).with(src).and_return(src)
123
+ end
124
+ end
125
+
126
+ %w[optimize_images optimize_images!].each do |list_method|
127
+ single_method = list_method.sub('images', 'image')
128
+ describe "without block" do
129
+ it "should optimize images and return array of results" do
130
+ io = ImageOptim.new
131
+ dsts = []
132
+ srcs.each do |src|
133
+ dst = "#{src}_"
134
+ io.should_receive(single_method).with(src).and_return(dst)
135
+ dsts << dst
136
+ end
137
+ io.send(list_method, srcs).should == dsts
138
+ end
139
+ end
140
+
141
+ describe "given block" do
142
+ it "should optimize images, yield path and result for each and return array of yield results" do
143
+ io = ImageOptim.new
144
+ srcs.each do |src|
145
+ io.should_receive(single_method).with(src).and_return("#{src}_")
146
+ end
147
+ results = []
148
+ io.send(list_method, srcs) do |src, dst|
149
+ result = "#{src} #{dst}"
150
+ results << "#{src} #{dst}"
151
+ result
152
+ end.should == results
153
+ end
154
+ end
155
+ end
156
+ end
120
157
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_optim
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 1
10
- version: 0.2.1
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ivan Kuchin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-10 00:00:00 Z
18
+ date: 2012-01-12 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: fspath