image_optim 0.13.3 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.rubocop.yml +56 -0
- data/.travis.yml +3 -1
- data/README.markdown +23 -10
- data/bin/image_optim +25 -15
- data/image_optim.gemspec +5 -2
- data/lib/image_optim.rb +47 -37
- data/lib/image_optim/bin_resolver.rb +17 -12
- data/lib/image_optim/bin_resolver/comparable_condition.rb +23 -7
- data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
- data/lib/image_optim/config.rb +21 -13
- data/lib/image_optim/handler.rb +18 -12
- data/lib/image_optim/hash_helpers.rb +23 -13
- data/lib/image_optim/image_meta.rb +1 -0
- data/lib/image_optim/image_path.rb +14 -13
- data/lib/image_optim/option_definition.rb +11 -9
- data/lib/image_optim/option_helpers.rb +1 -2
- data/lib/image_optim/railtie.rb +18 -15
- data/lib/image_optim/runner.rb +67 -61
- data/lib/image_optim/space.rb +29 -0
- data/lib/image_optim/true_false_nil.rb +9 -1
- data/lib/image_optim/worker.rb +40 -16
- data/lib/image_optim/worker/advpng.rb +8 -1
- data/lib/image_optim/worker/gifsicle.rb +13 -1
- data/lib/image_optim/worker/jhead.rb +5 -0
- data/lib/image_optim/worker/jpegoptim.rb +17 -4
- data/lib/image_optim/worker/jpegtran.rb +9 -1
- data/lib/image_optim/worker/optipng.rb +13 -2
- data/lib/image_optim/worker/pngcrush.rb +14 -5
- data/lib/image_optim/worker/pngout.rb +10 -2
- data/lib/image_optim/worker/svgo.rb +1 -0
- data/script/update_worker_options_in_readme +42 -27
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +13 -13
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +4 -4
- data/spec/image_optim/bin_resolver_spec.rb +65 -37
- data/spec/image_optim/config_spec.rb +121 -110
- data/spec/image_optim/handler_spec.rb +29 -18
- data/spec/image_optim/hash_helpers_spec.rb +29 -27
- data/spec/image_optim/image_path_spec.rb +17 -17
- data/spec/image_optim/space_spec.rb +24 -0
- data/spec/image_optim/worker_spec.rb +18 -0
- data/spec/image_optim_spec.rb +134 -74
- metadata +27 -7
- data/script/update_instructions_in_readme +0 -44
@@ -1,6 +1,16 @@
|
|
1
1
|
class ImageOptim
|
2
2
|
class BinResolver
|
3
|
+
# Allows to externalize conditions for an instance of Comparable to use in
|
4
|
+
# case statemens
|
5
|
+
#
|
6
|
+
# is = ComparableCondition.is
|
7
|
+
# case rand(100)
|
8
|
+
# when is < 10 then # ...
|
9
|
+
# when is.between?(13, 23) then # ...
|
10
|
+
# when is >= 90 then # ...
|
11
|
+
# end
|
3
12
|
class ComparableCondition
|
13
|
+
# Helper class for creating conditions using ComparableCondition.is
|
4
14
|
class Builder
|
5
15
|
Comparable.instance_methods.each do |method|
|
6
16
|
define_method method do |*args|
|
@@ -15,22 +25,22 @@ class ImageOptim
|
|
15
25
|
|
16
26
|
attr_reader :method, :args
|
17
27
|
def initialize(method, *args)
|
18
|
-
@method = method.to_sym
|
19
|
-
@args = args
|
28
|
+
@method, @args = method.to_sym, args
|
20
29
|
|
21
30
|
case @method
|
22
31
|
when :between?
|
23
|
-
|
32
|
+
@args.length == 2 || argument_error!("`between?' expects 2 arguments")
|
24
33
|
when :<, :<=, :==, :>, :>=
|
25
|
-
|
34
|
+
@args.length == 1 || argument_error!("`#{method}' expects 1 argument")
|
26
35
|
else
|
27
|
-
|
36
|
+
argument_error! "Unknown method `#{method}'"
|
28
37
|
end
|
29
38
|
end
|
30
39
|
|
31
|
-
def ===(
|
32
|
-
|
40
|
+
def ===(other)
|
41
|
+
other.send(@method, *@args)
|
33
42
|
end
|
43
|
+
alias_method :match, :===
|
34
44
|
|
35
45
|
def to_s
|
36
46
|
if @method == :between?
|
@@ -39,6 +49,12 @@ class ImageOptim
|
|
39
49
|
"#{@method} #{@args.first}"
|
40
50
|
end
|
41
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def argument_error!(message)
|
56
|
+
fail ArgumentError, message
|
57
|
+
end
|
42
58
|
end
|
43
59
|
end
|
44
60
|
end
|
data/lib/image_optim/config.rb
CHANGED
@@ -6,17 +6,23 @@ require 'set'
|
|
6
6
|
require 'yaml'
|
7
7
|
|
8
8
|
class ImageOptim
|
9
|
+
# Read, merge and parse configuration
|
9
10
|
class Config
|
10
11
|
include OptionHelpers
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
CONFIG_HOME = File.expand_path(ENV['XDG_CONFIG_HOME'] || '~/.config')
|
14
|
+
GLOBAL_CONFIG_PATH = File.join(CONFIG_HOME, 'image_optim.yml')
|
15
|
+
LOCAL_CONFIG_PATH = './.image_optim.yml'
|
14
16
|
|
15
17
|
class << self
|
18
|
+
# Read config at GLOBAL_CONFIG_PATH if it exists, warn if anything is
|
19
|
+
# wrong
|
16
20
|
def global
|
17
21
|
File.file?(GLOBAL_CONFIG_PATH) ? read(GLOBAL_CONFIG_PATH) : {}
|
18
22
|
end
|
19
23
|
|
24
|
+
# Read config at LOCAL_CONFIG_PATH if it exists, warn if anything is
|
25
|
+
# wrong
|
20
26
|
def local
|
21
27
|
File.file?(LOCAL_CONFIG_PATH) ? read(LOCAL_CONFIG_PATH) : {}
|
22
28
|
end
|
@@ -26,7 +32,7 @@ class ImageOptim
|
|
26
32
|
def read(path)
|
27
33
|
config = YAML.load_file(path)
|
28
34
|
unless config.is_a?(Hash)
|
29
|
-
|
35
|
+
fail "excpected hash, got #{config.inspect}"
|
30
36
|
end
|
31
37
|
HashHelpers.deep_symbolise_keys(config)
|
32
38
|
rescue => e
|
@@ -40,7 +46,7 @@ class ImageOptim
|
|
40
46
|
Config.global,
|
41
47
|
Config.local,
|
42
48
|
HashHelpers.deep_symbolise_keys(options),
|
43
|
-
].
|
49
|
+
].reduce do |memo, hash|
|
44
50
|
HashHelpers.deep_merge(memo, hash)
|
45
51
|
end
|
46
52
|
@used = Set.new
|
@@ -53,10 +59,10 @@ class ImageOptim
|
|
53
59
|
end
|
54
60
|
|
55
61
|
def assert_no_unused_options!
|
56
|
-
unknown_options = @options.reject{ |key,
|
57
|
-
|
58
|
-
|
59
|
-
|
62
|
+
unknown_options = @options.reject{ |key, _value| @used.include?(key) }
|
63
|
+
return if unknown_options.empty?
|
64
|
+
fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
|
65
|
+
"for #{self}"
|
60
66
|
end
|
61
67
|
|
62
68
|
def nice
|
@@ -100,7 +106,8 @@ class ImageOptim
|
|
100
106
|
when false
|
101
107
|
false
|
102
108
|
else
|
103
|
-
|
109
|
+
fail ConfigurationError, "Got #{worker_options.inspect} for "\
|
110
|
+
"#{klass.name} options"
|
104
111
|
end
|
105
112
|
end
|
106
113
|
|
@@ -110,7 +117,7 @@ class ImageOptim
|
|
110
117
|
|
111
118
|
private
|
112
119
|
|
113
|
-
# http://stackoverflow.com/
|
120
|
+
# http://stackoverflow.com/a/6420817
|
114
121
|
def processor_count
|
115
122
|
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
116
123
|
when /darwin9/
|
@@ -123,9 +130,10 @@ class ImageOptim
|
|
123
130
|
`sysctl -n hw.ncpu`
|
124
131
|
when /mswin|mingw/
|
125
132
|
require 'win32ole'
|
126
|
-
|
127
|
-
|
128
|
-
|
133
|
+
WIN32OLE.
|
134
|
+
connect('winmgmts://').
|
135
|
+
ExecQuery('select NumberOfLogicalProcessors from Win32_Processor').
|
136
|
+
to_enum.first.NumberOfLogicalProcessors
|
129
137
|
else
|
130
138
|
warn "Unknown architecture (#{host_os}) assuming one processor."
|
131
139
|
1
|
data/lib/image_optim/handler.rb
CHANGED
@@ -1,35 +1,41 @@
|
|
1
1
|
require 'image_optim/image_path'
|
2
2
|
|
3
3
|
class ImageOptim
|
4
|
+
# Handles processing of original to result using upto two temp files
|
4
5
|
class Handler
|
6
|
+
# Holds latest successful result
|
5
7
|
attr_reader :result
|
8
|
+
|
9
|
+
# original must respond to temp_path
|
6
10
|
def initialize(original)
|
7
|
-
|
11
|
+
unless original.respond_to?(:temp_path)
|
12
|
+
fail ArgumentError, 'original should respond to temp_path'
|
13
|
+
end
|
8
14
|
|
9
15
|
@original = original
|
10
16
|
@result = nil
|
11
17
|
end
|
12
18
|
|
19
|
+
# Yields two paths, one to latest successful result or original, second to
|
20
|
+
# temp path
|
13
21
|
def process
|
14
22
|
@src ||= @original
|
15
23
|
@dst ||= @original.temp_path
|
16
24
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
25
|
+
return unless yield @src, @dst
|
26
|
+
@result = @dst
|
27
|
+
if @src == @original
|
28
|
+
@src, @dst = @dst, nil
|
29
|
+
else
|
30
|
+
@src, @dst = @dst, @src
|
24
31
|
end
|
25
32
|
end
|
26
33
|
|
27
34
|
# Remove extra temp files
|
28
35
|
def cleanup
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
36
|
+
return unless @dst
|
37
|
+
@dst.unlink
|
38
|
+
@dst = nil
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
@@ -1,28 +1,22 @@
|
|
1
1
|
class ImageOptim
|
2
|
+
# Helper methods to manipulate Hash, mainly used in config
|
2
3
|
module HashHelpers
|
3
4
|
class << self
|
4
|
-
|
5
|
-
|
6
|
-
hash.each do |k, v|
|
7
|
-
new_hash[block.call(k)] = if v.is_a?(Hash)
|
8
|
-
deep_transform_keys(v, &block)
|
9
|
-
else
|
10
|
-
v
|
11
|
-
end
|
12
|
-
end
|
13
|
-
new_hash
|
14
|
-
end
|
15
|
-
|
5
|
+
# Returns a new hash with all keys of root and nested hashes converted to
|
6
|
+
# strings
|
16
7
|
def deep_stringify_keys(hash)
|
17
8
|
deep_transform_keys(hash, &:to_s)
|
18
9
|
end
|
19
10
|
|
11
|
+
# Returns a new hash with all keys of root and nested hashes converted to
|
12
|
+
# symbols
|
20
13
|
def deep_symbolise_keys(hash)
|
21
14
|
deep_transform_keys(hash, &:to_sym)
|
22
15
|
end
|
23
16
|
|
17
|
+
# Returns a new hash with recursive merge of all keys
|
24
18
|
def deep_merge(a, b)
|
25
|
-
a.merge(b) do |
|
19
|
+
a.merge(b) do |_k, v_a, v_b|
|
26
20
|
if v_a.is_a?(Hash) && v_b.is_a?(Hash)
|
27
21
|
deep_merge(v_a, v_b)
|
28
22
|
else
|
@@ -30,6 +24,22 @@ class ImageOptim
|
|
30
24
|
end
|
31
25
|
end
|
32
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Returns a new hash with all keys of root and nested hashes converted by
|
31
|
+
# provided block
|
32
|
+
def deep_transform_keys(hash, &block)
|
33
|
+
new_hash = {}
|
34
|
+
hash.each do |k, v|
|
35
|
+
new_hash[block.call(k)] = if v.is_a?(Hash)
|
36
|
+
deep_transform_keys(v, &block)
|
37
|
+
else
|
38
|
+
v
|
39
|
+
end
|
40
|
+
end
|
41
|
+
new_hash
|
42
|
+
end
|
33
43
|
end
|
34
44
|
end
|
35
45
|
end
|
@@ -2,23 +2,24 @@ require 'fspath'
|
|
2
2
|
require 'image_optim/image_meta'
|
3
3
|
|
4
4
|
class ImageOptim
|
5
|
+
# FSPath with additional helpful methods
|
5
6
|
class ImagePath < FSPath
|
7
|
+
# Holds optimized image with reference to original and its size
|
6
8
|
class Optimized < DelegateClass(self)
|
7
9
|
def initialize(path, original_or_size = nil)
|
8
10
|
path = ImagePath.convert(path)
|
9
11
|
__setobj__(path)
|
10
|
-
if original_or_size
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@original_size = @original.size
|
17
|
-
end
|
12
|
+
if original_or_size.is_a?(Integer)
|
13
|
+
@original = path
|
14
|
+
@original_size = original_or_size
|
15
|
+
elsif original_or_size
|
16
|
+
@original = ImagePath.convert(original_or_size)
|
17
|
+
@original_size = @original.size
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
# Original path, use original_size to get its size as original can be
|
21
|
+
# Original path, use original_size to get its size as original can be
|
22
|
+
# overwritten
|
22
23
|
attr_reader :original
|
23
24
|
|
24
25
|
# Stored size of original
|
@@ -49,12 +50,12 @@ class ImageOptim
|
|
49
50
|
|
50
51
|
# Get format using ImageSize
|
51
52
|
def format
|
52
|
-
|
53
|
-
|
54
|
-
end
|
53
|
+
image_meta = ImageMeta.for_path(self)
|
54
|
+
image_meta && image_meta.format
|
55
55
|
end
|
56
56
|
|
57
|
-
# Returns path if it is already an instance of this class otherwise new
|
57
|
+
# Returns path if it is already an instance of this class otherwise new
|
58
|
+
# instance
|
58
59
|
def self.convert(path)
|
59
60
|
path.is_a?(self) ? path : new(path)
|
60
61
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
-
class
|
1
|
+
class ImageOptim
|
2
|
+
# Hold information about an option
|
3
|
+
class OptionDefinition
|
4
|
+
attr_reader :name, :default, :type, :description, :proc
|
2
5
|
|
3
|
-
|
6
|
+
def initialize(name, default, type, description, &proc)
|
7
|
+
if type.is_a?(String)
|
8
|
+
type, description = default.class, type
|
9
|
+
end
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
type,
|
11
|
+
@name = name.to_sym
|
12
|
+
@description = description.to_s
|
13
|
+
@default, @type, @proc = default, type, proc
|
8
14
|
end
|
9
|
-
|
10
|
-
@name = name.to_sym
|
11
|
-
@description = description.to_s
|
12
|
-
@default, @type, @proc = default, type, proc
|
13
15
|
end
|
14
16
|
end
|
data/lib/image_optim/railtie.rb
CHANGED
@@ -1,28 +1,31 @@
|
|
1
1
|
require 'image_optim'
|
2
2
|
|
3
3
|
class ImageOptim
|
4
|
+
# Adds image_optim as preprocessor for gif, jpeg, png and svg images
|
4
5
|
class Railtie < Rails::Railtie
|
5
6
|
initializer 'image_optim.initializer' do |app|
|
6
|
-
if app.config.assets.compress != false && app.config.assets.image_optim != false && app.assets
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
app.config.assets.image_optim || {}
|
12
|
-
end
|
8
|
+
break if app.config.assets.compress == false
|
9
|
+
break if app.config.assets.image_optim == false
|
10
|
+
break unless app.assets
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
options = if app.config.assets.image_optim == true
|
13
|
+
{}
|
14
|
+
else
|
15
|
+
app.config.assets.image_optim || {}
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
|
22
|
-
app.assets.register_preprocessor 'image/png', :image_optim, &processor
|
23
|
-
app.assets.register_preprocessor 'image/svg+xml', :image_optim, &processor
|
18
|
+
image_optim = ImageOptim.new(options)
|
24
19
|
|
20
|
+
processor = proc do |_context, data|
|
21
|
+
image_optim.optimize_image_data(data) || data
|
25
22
|
end
|
23
|
+
|
24
|
+
app.assets.register_preprocessor 'image/gif', :image_optim, &processor
|
25
|
+
app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
|
26
|
+
app.assets.register_preprocessor 'image/png', :image_optim, &processor
|
27
|
+
app.assets.register_preprocessor 'image/svg+xml', :image_optim, &processor
|
28
|
+
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
data/lib/image_optim/runner.rb
CHANGED
@@ -1,67 +1,69 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
require 'image_optim'
|
4
2
|
require 'image_optim/hash_helpers'
|
5
3
|
require 'image_optim/true_false_nil'
|
4
|
+
require 'image_optim/space'
|
6
5
|
require 'progress'
|
7
6
|
require 'optparse'
|
8
7
|
require 'find'
|
9
8
|
require 'yaml'
|
10
9
|
|
11
10
|
class ImageOptim
|
11
|
+
# Handling optimization using image_optim binary
|
12
12
|
class Runner
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Collect and output results of optimization
|
14
|
+
class Results
|
15
|
+
def initialize
|
16
|
+
@lines = []
|
17
|
+
@original_size_sum = 0
|
18
|
+
@optimized_size_sum = 0
|
19
|
+
end
|
17
20
|
|
18
|
-
|
21
|
+
def add(original, optimized)
|
22
|
+
original_size = optimized ? optimized.original_size : original.size
|
23
|
+
optimized_size = optimized ? optimized.size : original.size
|
24
|
+
@lines << "#{size_percent(original_size, optimized_size)} #{original}"
|
25
|
+
@original_size_sum += original_size
|
26
|
+
@optimized_size_sum += optimized_size
|
27
|
+
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
29
|
+
def print
|
30
|
+
puts @lines
|
31
|
+
puts "Total: #{size_percent(@original_size_sum, @optimized_size_sum)}"
|
32
|
+
end
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
34
|
+
private
|
35
|
+
|
36
|
+
def size_percent(size_a, size_b)
|
37
|
+
if size_a == size_b
|
38
|
+
"------ #{Space::EMPTY_SPACE}"
|
39
|
+
else
|
40
|
+
percent = 100 - 100.0 * size_b / size_a
|
41
|
+
space = Space.space(size_a - size_b)
|
42
|
+
format('%5.2f%% %s', percent, space)
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
40
47
|
def initialize(args, options)
|
41
|
-
|
48
|
+
fail 'specify paths to optimize' if args.empty?
|
42
49
|
options = HashHelpers.deep_symbolise_keys(options)
|
43
50
|
@recursive = options.delete(:recursive)
|
44
51
|
@image_optim = ImageOptim.new(options)
|
45
|
-
@
|
52
|
+
@to_optimize = find_to_optimize(args)
|
46
53
|
end
|
47
54
|
|
48
55
|
def run!
|
49
|
-
unless @
|
50
|
-
|
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
|
56
|
+
unless @to_optimize.empty?
|
57
|
+
results = Results.new
|
59
58
|
|
60
|
-
|
61
|
-
|
59
|
+
optimize_images! do |original, optimized|
|
60
|
+
results.add(original, optimized)
|
61
|
+
end
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
results.print
|
64
|
+
end
|
65
|
+
|
66
|
+
!@warnings
|
65
67
|
end
|
66
68
|
|
67
69
|
def self.run!(args, options)
|
@@ -70,42 +72,46 @@ class ImageOptim
|
|
70
72
|
|
71
73
|
private
|
72
74
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
def optimize_images!(&block)
|
76
|
+
@image_optim.
|
77
|
+
optimize_images!(@to_optimize.with_progress('optimizing'), &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_to_optimize(paths)
|
81
|
+
to_optimize = []
|
82
|
+
paths.each do |path|
|
83
|
+
if File.file?(path)
|
84
|
+
if @image_optim.optimizable?(path)
|
85
|
+
to_optimize << path
|
79
86
|
else
|
80
|
-
warning "#{
|
87
|
+
warning "#{path} is not an image or there is no optimizer for it"
|
81
88
|
end
|
82
89
|
elsif @recursive
|
83
|
-
if File.directory?(
|
84
|
-
|
85
|
-
files << path if File.file?(path) && @image_optim.optimizable?(path)
|
86
|
-
end
|
90
|
+
if File.directory?(path)
|
91
|
+
to_optimize += find_to_optimize_recursive(path)
|
87
92
|
else
|
88
|
-
warning "#{
|
93
|
+
warning "#{path} is not a file or a directory or does not exist"
|
89
94
|
end
|
90
95
|
else
|
91
|
-
warning "#{
|
96
|
+
warning "#{path} is not a file or does not exist"
|
92
97
|
end
|
93
98
|
end
|
94
|
-
|
99
|
+
to_optimize
|
100
|
+
end
|
101
|
+
|
102
|
+
def find_to_optimize_recursive(dir)
|
103
|
+
to_optimize = []
|
104
|
+
Find.find(dir) do |path|
|
105
|
+
next unless File.file?(path)
|
106
|
+
next unless @image_optim.optimizable?(path)
|
107
|
+
to_optimize << path
|
108
|
+
end
|
109
|
+
to_optimize
|
95
110
|
end
|
96
111
|
|
97
112
|
def warning(message)
|
98
113
|
@warnings = true
|
99
114
|
warn message
|
100
115
|
end
|
101
|
-
|
102
|
-
def size_percent(size_a, size_b)
|
103
|
-
if size_a == size_b
|
104
|
-
"------ #{Space::EMPTY_SPACE}"
|
105
|
-
else
|
106
|
-
'%5.2f%% %s' % [100 - 100.0 * size_b / size_a, Space.space(size_a - size_b)]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
116
|
end
|
111
117
|
end
|