discourse_image_optim 0.24.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|