discourse_image_optim 0.24.4
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.
- checksums.yaml +7 -0
- data/.appveyor.yml +46 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +110 -0
- data/.travis.yml +42 -0
- data/CHANGELOG.markdown +316 -0
- data/CONTRIBUTING.markdown +11 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +358 -0
- data/Vagrantfile +38 -0
- data/bin/image_optim +28 -0
- data/image_optim.gemspec +34 -0
- data/lib/image_optim.rb +267 -0
- data/lib/image_optim/bin_resolver.rb +142 -0
- data/lib/image_optim/bin_resolver/bin.rb +115 -0
- data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
- data/lib/image_optim/bin_resolver/error.rb +6 -0
- data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
- data/lib/image_optim/cache.rb +72 -0
- data/lib/image_optim/cache_path.rb +16 -0
- data/lib/image_optim/cmd.rb +122 -0
- data/lib/image_optim/config.rb +219 -0
- data/lib/image_optim/configuration_error.rb +3 -0
- data/lib/image_optim/handler.rb +57 -0
- data/lib/image_optim/hash_helpers.rb +45 -0
- data/lib/image_optim/image_meta.rb +20 -0
- data/lib/image_optim/non_negative_integer_range.rb +11 -0
- data/lib/image_optim/optimized_path.rb +25 -0
- data/lib/image_optim/option_definition.rb +38 -0
- data/lib/image_optim/option_helpers.rb +17 -0
- data/lib/image_optim/path.rb +70 -0
- data/lib/image_optim/runner.rb +139 -0
- data/lib/image_optim/runner/glob_helpers.rb +45 -0
- data/lib/image_optim/runner/option_parser.rb +246 -0
- data/lib/image_optim/space.rb +29 -0
- data/lib/image_optim/true_false_nil.rb +16 -0
- data/lib/image_optim/worker.rb +170 -0
- data/lib/image_optim/worker/advpng.rb +37 -0
- data/lib/image_optim/worker/class_methods.rb +107 -0
- data/lib/image_optim/worker/gifsicle.rb +65 -0
- data/lib/image_optim/worker/jhead.rb +47 -0
- data/lib/image_optim/worker/jpegoptim.rb +63 -0
- data/lib/image_optim/worker/jpegrecompress.rb +49 -0
- data/lib/image_optim/worker/jpegtran.rb +48 -0
- data/lib/image_optim/worker/optipng.rb +53 -0
- data/lib/image_optim/worker/pngcrush.rb +56 -0
- data/lib/image_optim/worker/pngout.rb +40 -0
- data/lib/image_optim/worker/pngquant.rb +61 -0
- data/lib/image_optim/worker/svgo.rb +34 -0
- data/script/template/jquery-2.1.3.min.js +4 -0
- data/script/template/sortable-0.6.0.min.js +2 -0
- data/script/template/worker_analysis.erb +254 -0
- data/script/update_worker_options_in_readme +59 -0
- data/script/worker_analysis +589 -0
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
- data/spec/image_optim/bin_resolver_spec.rb +290 -0
- data/spec/image_optim/cache_path_spec.rb +57 -0
- data/spec/image_optim/cache_spec.rb +162 -0
- data/spec/image_optim/cmd_spec.rb +93 -0
- data/spec/image_optim/config_spec.rb +254 -0
- data/spec/image_optim/handler_spec.rb +90 -0
- data/spec/image_optim/hash_helpers_spec.rb +74 -0
- data/spec/image_optim/image_meta_spec.rb +61 -0
- data/spec/image_optim/optimized_path_spec.rb +58 -0
- data/spec/image_optim/option_definition_spec.rb +138 -0
- data/spec/image_optim/option_helpers_spec.rb +25 -0
- data/spec/image_optim/path_spec.rb +103 -0
- data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
- data/spec/image_optim/runner/option_parser_spec.rb +105 -0
- data/spec/image_optim/space_spec.rb +23 -0
- data/spec/image_optim/worker/optipng_spec.rb +102 -0
- data/spec/image_optim/worker/pngquant_spec.rb +67 -0
- data/spec/image_optim/worker_spec.rb +303 -0
- data/spec/image_optim_spec.rb +259 -0
- data/spec/images/broken_jpeg +1 -0
- data/spec/images/comparison.png +0 -0
- data/spec/images/decompressed.jpeg +0 -0
- data/spec/images/icecream.gif +0 -0
- data/spec/images/image.jpg +0 -0
- data/spec/images/invisiblepixels/generate +24 -0
- data/spec/images/invisiblepixels/image.png +0 -0
- data/spec/images/lena.jpg +0 -0
- data/spec/images/orient/0.jpg +0 -0
- data/spec/images/orient/1.jpg +0 -0
- data/spec/images/orient/2.jpg +0 -0
- data/spec/images/orient/3.jpg +0 -0
- data/spec/images/orient/4.jpg +0 -0
- data/spec/images/orient/5.jpg +0 -0
- data/spec/images/orient/6.jpg +0 -0
- data/spec/images/orient/7.jpg +0 -0
- data/spec/images/orient/8.jpg +0 -0
- data/spec/images/orient/generate +23 -0
- data/spec/images/orient/original.jpg +0 -0
- data/spec/images/quant/64.png +0 -0
- data/spec/images/quant/generate +25 -0
- data/spec/images/rails.png +0 -0
- data/spec/images/test.svg +3 -0
- data/spec/images/transparency1.png +0 -0
- data/spec/images/transparency2.png +0 -0
- data/spec/images/vergroessert.jpg +0 -0
- data/spec/spec_helper.rb +93 -0
- metadata +281 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'image_optim'
|
|
4
|
+
require 'image_optim/true_false_nil'
|
|
5
|
+
require 'image_optim/non_negative_integer_range'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
|
|
8
|
+
class ImageOptim
|
|
9
|
+
class Runner
|
|
10
|
+
# Parse options from arguments to image_optim binary
|
|
11
|
+
class OptionParser < ::OptionParser
|
|
12
|
+
# Parse and remove options from args, return options Hash
|
|
13
|
+
# Calls abort in case of parse error
|
|
14
|
+
def self.parse!(args)
|
|
15
|
+
# assume -v to be a request to print version if it is the only argument
|
|
16
|
+
args = %w[--version] if args == %w[-v]
|
|
17
|
+
|
|
18
|
+
options = {}
|
|
19
|
+
parser = new(options)
|
|
20
|
+
parser.parse!(args)
|
|
21
|
+
options
|
|
22
|
+
rescue OptionParser::ParseError => e
|
|
23
|
+
abort "#{e}\n\n#{parser.help}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# After initialization passes self and options to DEFINE
|
|
27
|
+
def initialize(options)
|
|
28
|
+
super
|
|
29
|
+
DEFINE.call(self, options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Wraps and indents lines of overriden method
|
|
33
|
+
def help
|
|
34
|
+
text = super
|
|
35
|
+
|
|
36
|
+
# reserve one column
|
|
37
|
+
columns = terminal_columns - 1
|
|
38
|
+
# 1 for distance between summary and description
|
|
39
|
+
# 2 for additional indent
|
|
40
|
+
wrapped_indent = summary_indent + ' ' * (summary_width + 1 + 2)
|
|
41
|
+
wrapped_width = columns - wrapped_indent.length
|
|
42
|
+
# don't try to wrap if there is too little space for description
|
|
43
|
+
return text if wrapped_width < 20
|
|
44
|
+
|
|
45
|
+
wrapped = ''
|
|
46
|
+
text.split("\n").each do |line|
|
|
47
|
+
if line.length <= columns
|
|
48
|
+
wrapped << line << "\n"
|
|
49
|
+
else
|
|
50
|
+
indented = line =~ /^\s/
|
|
51
|
+
wrapped << line.slice!(wrap_regex(columns)) << "\n"
|
|
52
|
+
line.scan(wrap_regex(wrapped_width)) do |part|
|
|
53
|
+
wrapped << wrapped_indent if indented
|
|
54
|
+
wrapped << part << "\n"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
wrapped
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def terminal_columns
|
|
64
|
+
stty_columns = `stty size 2> /dev/null`[/^\d+ (\d+)$/, 1]
|
|
65
|
+
stty_columns ? stty_columns.to_i : `tput cols`.to_i
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def wrap_regex(width)
|
|
69
|
+
/.*?.{1,#{width}}(?:\s|\z)/
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
|
|
76
|
+
unless op.is_a?(OptionParser)
|
|
77
|
+
fail ArgumentError, "expected instance of OptionParser, got #{op.inspect}"
|
|
78
|
+
end
|
|
79
|
+
unless options.is_a?(Hash)
|
|
80
|
+
fail ArgumentError, "expected instance of Hash, got #{options.inspect}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ImageOptim::TrueFalseNil.add_to_option_parser(op)
|
|
84
|
+
ImageOptim::NonNegativeIntegerRange.add_to_option_parser(op)
|
|
85
|
+
|
|
86
|
+
op.banner = <<-TEXT.gsub(/^\s*\|/, '')
|
|
87
|
+
|#{ImageOptim.full_version}
|
|
88
|
+
|
|
|
89
|
+
|Usage:
|
|
90
|
+
| #{op.program_name} [options] image_path …
|
|
91
|
+
|
|
|
92
|
+
|Configuration will be read and prepended to options from two paths:
|
|
93
|
+
| #{ImageOptim::Config::GLOBAL_PATH}
|
|
94
|
+
| #{ImageOptim::Config::LOCAL_PATH}
|
|
95
|
+
|
|
|
96
|
+
TEXT
|
|
97
|
+
|
|
98
|
+
op.on('--config-paths PATH1,PATH2', Array, 'Config paths to use instead of '\
|
|
99
|
+
'default ones') do |paths|
|
|
100
|
+
options[:config_paths] = paths
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
op.separator nil
|
|
104
|
+
|
|
105
|
+
op.on('-r', '-R', '--recursive', 'Recursively scan directories '\
|
|
106
|
+
'for images') do |recursive|
|
|
107
|
+
options[:recursive] = recursive
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
op.on("--exclude-dir 'GLOB'", 'Glob for excluding directories '\
|
|
111
|
+
'(defaults to .*)') do |glob|
|
|
112
|
+
options[:exclude_dir_glob] = glob
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
op.on("--exclude-file 'GLOB'", 'Glob for excluding files '\
|
|
116
|
+
'(defaults to .*)') do |glob|
|
|
117
|
+
options[:exclude_file_glob] = glob
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
op.on("--exclude 'GLOB'", 'Set glob for excluding both directories and '\
|
|
121
|
+
'files') do |glob|
|
|
122
|
+
options[:exclude_file_glob] = options[:exclude_dir_glob] = glob
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
op.separator nil
|
|
126
|
+
|
|
127
|
+
op.on('--no-progress', 'Disable showing progress') do |show_progress|
|
|
128
|
+
options[:show_progress] = show_progress
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
op.on('--[no-]threads N', Integer, 'Number of threads or disable '\
|
|
132
|
+
'(defaults to number of processors)') do |threads|
|
|
133
|
+
options[:threads] = threads
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
op.on('--[no-]nice N', Integer, 'Nice level, priority of all used tools '\
|
|
137
|
+
'with higher value meaning lower priority, in range -20..19, negative '\
|
|
138
|
+
'values can be set only if run by root user (defaults to 10)') do |nice|
|
|
139
|
+
options[:nice] = nice
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
op.on('--[no-]pack', 'Require image_optim_pack or disable it, '\
|
|
143
|
+
'by default image_optim_pack will be used if available, '\
|
|
144
|
+
'will turn on skip-missing-workers unless explicitly disabled') do |pack|
|
|
145
|
+
options[:pack] = pack
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
op.separator nil
|
|
149
|
+
op.separator ' Caching:'
|
|
150
|
+
|
|
151
|
+
op.on('--cache-dir DIR', 'Cache optimized images '\
|
|
152
|
+
'into the specified directory') do |cache_dir|
|
|
153
|
+
options[:cache_dir] = cache_dir
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
op.on('--cache-worker-digests', 'Cache worker digests '\
|
|
157
|
+
'(updating workers invalidates cache)') do |cache_worker_digests|
|
|
158
|
+
options[:cache_worker_digests] = cache_worker_digests
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
op.separator nil
|
|
162
|
+
op.separator ' Disabling workers:'
|
|
163
|
+
|
|
164
|
+
op.on('--[no-]skip-missing-workers', 'Skip workers with missing or '\
|
|
165
|
+
'problematic binaries') do |skip|
|
|
166
|
+
options[:skip_missing_workers] = skip
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
ImageOptim::Worker.klasses.each do |klass|
|
|
170
|
+
bin = klass.bin_sym
|
|
171
|
+
op.on("--no-#{bin}", "disable #{bin} worker") do |enable|
|
|
172
|
+
options[bin] = enable
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
op.separator nil
|
|
177
|
+
op.separator ' Worker options:'
|
|
178
|
+
|
|
179
|
+
op.on('--allow-lossy', 'Allow lossy workers and '\
|
|
180
|
+
'optimizations') do |allow_lossy|
|
|
181
|
+
options[:allow_lossy] = allow_lossy
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
op.on('--timeout N', 'Sets a timeout for workers') do |timeout|
|
|
185
|
+
options[:timeout] = timeout.to_i
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
op.separator nil
|
|
189
|
+
|
|
190
|
+
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
|
191
|
+
next if klass.option_definitions.empty?
|
|
192
|
+
op.separator nil unless i.zero?
|
|
193
|
+
|
|
194
|
+
bin = klass.bin_sym
|
|
195
|
+
klass.option_definitions.each do |option_definition|
|
|
196
|
+
name = option_definition.name.to_s.tr('_', '-')
|
|
197
|
+
default = option_definition.default_description
|
|
198
|
+
type = option_definition.type
|
|
199
|
+
|
|
200
|
+
type, marking = case
|
|
201
|
+
when [TrueClass, FalseClass, ImageOptim::TrueFalseNil].include?(type)
|
|
202
|
+
[type, 'B']
|
|
203
|
+
when Integer >= type
|
|
204
|
+
[Integer, 'N']
|
|
205
|
+
when Array >= type
|
|
206
|
+
[Array, 'a,b,c']
|
|
207
|
+
when ImageOptim::NonNegativeIntegerRange == type
|
|
208
|
+
[type, 'M-N']
|
|
209
|
+
else
|
|
210
|
+
fail "Unknown type #{type}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
description = option_definition.description.gsub(' - ', ' - ')
|
|
214
|
+
unless description['(defaults']
|
|
215
|
+
description << " (defaults to #{default})"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
op.on("--#{bin}-#{name} #{marking}", type, description) do |value|
|
|
219
|
+
options[bin] = {} unless options[bin].is_a?(Hash)
|
|
220
|
+
options[bin][option_definition.name.to_sym] = value
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
op.separator nil
|
|
226
|
+
op.separator ' Common options:'
|
|
227
|
+
|
|
228
|
+
op.on_tail('-v', '--verbose', 'Verbose output') do
|
|
229
|
+
options[:verbose] = true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
op.on_tail('-h', '--help', 'Show help and exit') do
|
|
233
|
+
puts op.help
|
|
234
|
+
exit
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
op.on_tail('--version', 'Show version and exit') do
|
|
238
|
+
puts ImageOptim.version
|
|
239
|
+
exit
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
op.on_tail('--info', 'Show environment info and exit') do
|
|
243
|
+
options[:verbose] = true
|
|
244
|
+
options[:only_info] = true
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Present size in readable form as fixed length string
|
|
3
|
+
module Space
|
|
4
|
+
SIZE_SYMBOLS = %w[B K M G T P E].freeze
|
|
5
|
+
BASE = 1024.0
|
|
6
|
+
PRECISION = 1
|
|
7
|
+
LENGTH = 4 + PRECISION + 1
|
|
8
|
+
|
|
9
|
+
EMPTY_SPACE = ' ' * LENGTH
|
|
10
|
+
|
|
11
|
+
def self.space(size)
|
|
12
|
+
case size
|
|
13
|
+
when 0, nil
|
|
14
|
+
EMPTY_SPACE
|
|
15
|
+
else
|
|
16
|
+
log_denominator = Math.log(size.abs) / Math.log(BASE)
|
|
17
|
+
degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
|
|
18
|
+
number_string = if degree.zero?
|
|
19
|
+
size.to_s
|
|
20
|
+
else
|
|
21
|
+
denominator = BASE**degree
|
|
22
|
+
number = size / denominator
|
|
23
|
+
format("%.#{PRECISION}f", number)
|
|
24
|
+
end
|
|
25
|
+
"#{number_string}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Denote ternary value (`true`/`false`/`nil`) for worker option
|
|
3
|
+
class TrueFalseNil
|
|
4
|
+
# Add handling of ternary value in OptionParser instance, maps `nil` and
|
|
5
|
+
# `'nil'` to `nil`
|
|
6
|
+
def self.add_to_option_parser(option_parser)
|
|
7
|
+
completing = OptionParser.top.atype[TrueClass][0].merge('nil' => nil)
|
|
8
|
+
option_parser.accept(self, completing){ |_arg, val| val }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Convert everything truthy to `true`, leave `false` and `nil` as is
|
|
12
|
+
def self.convert(v)
|
|
13
|
+
v && true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'image_optim/cmd'
|
|
4
|
+
require 'image_optim/configuration_error'
|
|
5
|
+
require 'image_optim/path'
|
|
6
|
+
require 'image_optim/worker/class_methods'
|
|
7
|
+
require 'shellwords'
|
|
8
|
+
require 'English'
|
|
9
|
+
|
|
10
|
+
class ImageOptim
|
|
11
|
+
# Base class for all workers
|
|
12
|
+
class Worker
|
|
13
|
+
extend ClassMethods
|
|
14
|
+
|
|
15
|
+
class TimeoutExceeded < StandardError; end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# Default init for worker is new
|
|
19
|
+
# Check example of override in gifsicle worker
|
|
20
|
+
alias_method :init, :new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Configure (raises on extra options)
|
|
24
|
+
def initialize(image_optim, options = {})
|
|
25
|
+
unless image_optim.is_a?(ImageOptim)
|
|
26
|
+
fail ArgumentError, 'first parameter should be an ImageOptim instance'
|
|
27
|
+
end
|
|
28
|
+
@image_optim = image_optim
|
|
29
|
+
parse_options(options)
|
|
30
|
+
assert_no_unknown_options!(options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Return hash with worker options
|
|
34
|
+
def options
|
|
35
|
+
hash = {}
|
|
36
|
+
self.class.option_definitions.each do |option|
|
|
37
|
+
hash[option.name] = send(option.name)
|
|
38
|
+
end
|
|
39
|
+
hash
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Optimize image at src, output at dst, must be overriden in subclass
|
|
43
|
+
# return true on success
|
|
44
|
+
def optimize(_src, _dst)
|
|
45
|
+
fail NotImplementedError, "implement method optimize in #{self.class}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# List of formats which worker can optimize
|
|
49
|
+
def image_formats
|
|
50
|
+
format_from_name = self.class.name.downcase[/gif|jpeg|png|svg/]
|
|
51
|
+
unless format_from_name
|
|
52
|
+
fail "#{self.class}: can't guess applicable format from worker name"
|
|
53
|
+
end
|
|
54
|
+
[format_from_name.to_sym]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Ordering in list of workers, 0 by default
|
|
58
|
+
def run_order
|
|
59
|
+
0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# List of bins used by worker
|
|
63
|
+
def used_bins
|
|
64
|
+
[self.class.bin_sym]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Resolve used bins, raise exception concatenating all messages
|
|
68
|
+
def resolve_used_bins!
|
|
69
|
+
errors = BinResolver.collect_errors(used_bins) do |bin|
|
|
70
|
+
@image_optim.resolve_bin!(bin)
|
|
71
|
+
end
|
|
72
|
+
return if errors.empty?
|
|
73
|
+
fail BinResolver::Error, wrap_resolver_error_message(errors.join(', '))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if operation resulted in optimized file
|
|
77
|
+
def optimized?(src, dst)
|
|
78
|
+
dst_size = dst.size?
|
|
79
|
+
dst_size && dst_size < src.size
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Short inspect
|
|
83
|
+
def inspect
|
|
84
|
+
options_string = self.class.option_definitions.map do |option|
|
|
85
|
+
" @#{option.name}=#{send(option.name).inspect}"
|
|
86
|
+
end.join(',')
|
|
87
|
+
"#<#{self.class}#{options_string}>"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def parse_options(options)
|
|
93
|
+
self.class.option_definitions.each do |option_definition|
|
|
94
|
+
value = option_definition.value(self, options)
|
|
95
|
+
instance_variable_set("@#{option_definition.name}", value)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def assert_no_unknown_options!(options)
|
|
100
|
+
known_keys = self.class.option_definitions.map(&:name)
|
|
101
|
+
unknown_options = options.reject{ |key, _value| known_keys.include?(key) }
|
|
102
|
+
return if unknown_options.empty?
|
|
103
|
+
fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
|
|
104
|
+
"for #{self}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Forward bin resolving to image_optim
|
|
108
|
+
def resolve_bin!(bin)
|
|
109
|
+
@image_optim.resolve_bin!(bin)
|
|
110
|
+
rescue BinResolver::Error => e
|
|
111
|
+
raise e, wrap_resolver_error_message(e.message), e.backtrace
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def wrap_resolver_error_message(message)
|
|
115
|
+
name = self.class.bin_sym
|
|
116
|
+
"#{name} worker: #{message}; please provide proper binary or "\
|
|
117
|
+
"disable this worker (--no-#{name} argument or "\
|
|
118
|
+
"`:#{name} => false` through options)"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Run command setting priority and hiding output
|
|
122
|
+
def execute(bin, *arguments)
|
|
123
|
+
resolve_bin!(bin)
|
|
124
|
+
|
|
125
|
+
cmd_args = [bin, *arguments].map(&:to_s)
|
|
126
|
+
|
|
127
|
+
start = Time.now
|
|
128
|
+
|
|
129
|
+
success = run_command(cmd_args)
|
|
130
|
+
|
|
131
|
+
if @image_optim.verbose
|
|
132
|
+
seconds = Time.now - start
|
|
133
|
+
$stderr << "#{success ? '✓' : '✗'} #{seconds}s #{cmd_args.shelljoin}\n"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
success
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Run command defining environment, setting nice level, removing output and
|
|
140
|
+
# reraising signal exception
|
|
141
|
+
def run_command(cmd_args)
|
|
142
|
+
args = if RUBY_VERSION < '1.9' || defined?(JRUBY_VERSION)
|
|
143
|
+
%W[
|
|
144
|
+
env PATH=#{@image_optim.env_path.shellescape}
|
|
145
|
+
nice -n #{@image_optim.nice}
|
|
146
|
+
#{cmd_args.shelljoin} > #{Path::NULL} 2>&1
|
|
147
|
+
].join(' ')
|
|
148
|
+
else
|
|
149
|
+
[
|
|
150
|
+
{'PATH' => @image_optim.env_path},
|
|
151
|
+
%W[nice -n #{@image_optim.nice}],
|
|
152
|
+
cmd_args,
|
|
153
|
+
{:out => Path::NULL, :err => Path::NULL},
|
|
154
|
+
].flatten
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
seconds_to_timeout = timeout || @image_optim.timeout
|
|
158
|
+
|
|
159
|
+
if seconds_to_timeout > 0
|
|
160
|
+
begin
|
|
161
|
+
Cmd.run_with_timeout(seconds_to_timeout, *args)
|
|
162
|
+
rescue Cmd::TimeoutExceeded
|
|
163
|
+
raise ImageOptim::Worker::TimeoutExceeded
|
|
164
|
+
end
|
|
165
|
+
else
|
|
166
|
+
Cmd.run(*args)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|