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,60 @@
|
|
|
1
|
+
class ImageOptim
|
|
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
|
|
12
|
+
class ComparableCondition
|
|
13
|
+
# Helper class for creating conditions using ComparableCondition.is
|
|
14
|
+
class Builder
|
|
15
|
+
Comparable.instance_methods.each do |method|
|
|
16
|
+
define_method method do |*args|
|
|
17
|
+
ComparableCondition.new(method, *args)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.is
|
|
23
|
+
Builder.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :method, :args
|
|
27
|
+
def initialize(method, *args)
|
|
28
|
+
@method, @args = method.to_sym, args
|
|
29
|
+
|
|
30
|
+
case @method
|
|
31
|
+
when :between?
|
|
32
|
+
@args.length == 2 || argument_error!("`between?' expects 2 arguments")
|
|
33
|
+
when :<, :<=, :==, :>, :>=
|
|
34
|
+
@args.length == 1 || argument_error!("`#{method}' expects 1 argument")
|
|
35
|
+
else
|
|
36
|
+
argument_error! "Unknown method `#{method}'"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ===(other)
|
|
41
|
+
other.send(@method, *@args)
|
|
42
|
+
end
|
|
43
|
+
alias_method :match, :===
|
|
44
|
+
|
|
45
|
+
def to_s
|
|
46
|
+
if @method == :between?
|
|
47
|
+
@args.join('..')
|
|
48
|
+
else
|
|
49
|
+
"#{@method} #{@args.first}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def argument_error!(message)
|
|
56
|
+
fail ArgumentError, message
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
class BinResolver
|
|
3
|
+
# Allows comparision of simple versions, only numbers separated by dots are
|
|
4
|
+
# taken into account
|
|
5
|
+
class SimpleVersion
|
|
6
|
+
include Comparable
|
|
7
|
+
|
|
8
|
+
# Numbers extracted from version string
|
|
9
|
+
attr_reader :parts
|
|
10
|
+
|
|
11
|
+
# Initialize with a string or an object convertible to string
|
|
12
|
+
#
|
|
13
|
+
# SimpleVersion.new('2.0.1') <=> SimpleVersion.new(2)
|
|
14
|
+
def initialize(str)
|
|
15
|
+
@str = String(str)
|
|
16
|
+
@parts = @str.split('.').map(&:to_i).reverse.drop_while(&:zero?).reverse
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns original version string
|
|
20
|
+
def to_s
|
|
21
|
+
@str
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Compare version parts of self with other
|
|
25
|
+
def <=>(other)
|
|
26
|
+
other = self.class.new(other) unless other.is_a?(self.class)
|
|
27
|
+
parts <=> other.parts
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
require 'fspath'
|
|
3
|
+
require 'image_optim/cache_path'
|
|
4
|
+
|
|
5
|
+
class ImageOptim
|
|
6
|
+
# Handles image cache
|
|
7
|
+
class Cache
|
|
8
|
+
def initialize(image_optim, workers_by_format)
|
|
9
|
+
return unless image_optim.cache_dir
|
|
10
|
+
@cache_dir = FSPath.new(image_optim.cache_dir)
|
|
11
|
+
@cache_worker_digests = image_optim.cache_worker_digests
|
|
12
|
+
@options_by_format = Hash[workers_by_format.map do |format, workers|
|
|
13
|
+
[format, workers.map(&:inspect).sort.join(', ')]
|
|
14
|
+
end]
|
|
15
|
+
@bins_by_format = Hash[workers_by_format.map do |format, workers|
|
|
16
|
+
[format, workers.map(&:used_bins).flatten!.map! do |sym|
|
|
17
|
+
bin = image_optim.resolve_bin!(sym)
|
|
18
|
+
"#{bin.name}[#{bin.digest}]"
|
|
19
|
+
end.sort!.uniq.join(', ')]
|
|
20
|
+
end]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch(original)
|
|
24
|
+
return yield unless @cache_dir
|
|
25
|
+
|
|
26
|
+
digest = digest(original, original.image_format)
|
|
27
|
+
cached = @cache_dir / digest
|
|
28
|
+
return cached.size? && CachePath.convert(cached) if cached.file?
|
|
29
|
+
|
|
30
|
+
optimized = yield
|
|
31
|
+
|
|
32
|
+
cached.dirname.mkpath
|
|
33
|
+
|
|
34
|
+
if optimized
|
|
35
|
+
tmp = FSPath.temp_file_path(digest, @cache_dir)
|
|
36
|
+
FileUtils.mv(optimized, tmp)
|
|
37
|
+
tmp.chmod(0o666 & ~File.umask)
|
|
38
|
+
tmp.rename(cached)
|
|
39
|
+
cached_path = CachePath.convert(cached)
|
|
40
|
+
|
|
41
|
+
# mark cached image as already optimized
|
|
42
|
+
cached = @cache_dir / digest(cached, original.image_format)
|
|
43
|
+
cached.dirname.mkpath
|
|
44
|
+
FileUtils.touch(cached)
|
|
45
|
+
|
|
46
|
+
cached_path
|
|
47
|
+
else
|
|
48
|
+
# mark image as already optimized
|
|
49
|
+
FileUtils.touch(cached)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def options_by_format(format)
|
|
57
|
+
@options_by_format[format]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def bins_by_format(format)
|
|
61
|
+
@bins_by_format[format]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def digest(path, format)
|
|
65
|
+
digest = Digest::SHA1.file(path)
|
|
66
|
+
digest.update options_by_format(format)
|
|
67
|
+
digest.update bins_by_format(format) if @cache_worker_digests
|
|
68
|
+
s = digest.hexdigest
|
|
69
|
+
"#{s[0..1]}/#{s[2..-1]}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'image_optim/path'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# ImageOptiom::Path with a non self destructing #replace method
|
|
5
|
+
class CachePath < Path
|
|
6
|
+
# Atomic replace dst with self
|
|
7
|
+
def replace(dst)
|
|
8
|
+
dst = self.class.new(dst)
|
|
9
|
+
dst.temp_path(dst.dirname) do |temp|
|
|
10
|
+
copy(temp)
|
|
11
|
+
dst.copy_metadata(temp)
|
|
12
|
+
temp.rename(dst.to_s)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'English'
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
# Helper for running commands
|
|
6
|
+
module Cmd
|
|
7
|
+
class TimeoutExceeded < StandardError; end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Run using `system`
|
|
11
|
+
# Return success status
|
|
12
|
+
# Will raise SignalException if process was interrupted
|
|
13
|
+
def run(*args)
|
|
14
|
+
success = system(*args)
|
|
15
|
+
|
|
16
|
+
check_status!
|
|
17
|
+
|
|
18
|
+
success
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def support_timeout?
|
|
22
|
+
if defined?(JRUBY_VERSION)
|
|
23
|
+
JRUBY_VERSION >= '9.0.0.0'
|
|
24
|
+
else
|
|
25
|
+
RUBY_VERSION >= '1.9'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Run the specified command, and kill it off if it runs longer
|
|
30
|
+
# than `timeout` seconds.
|
|
31
|
+
#
|
|
32
|
+
# Return success status
|
|
33
|
+
# Will raise an error when command timeouts
|
|
34
|
+
def run_with_timeout(timeout, *args)
|
|
35
|
+
return run(*args) unless timeout > 0 && support_timeout?
|
|
36
|
+
|
|
37
|
+
success = false
|
|
38
|
+
init_options!(args)
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
stdin, stdout, thread = Open3.popen2(*args)
|
|
42
|
+
stdin.close
|
|
43
|
+
stdout.close
|
|
44
|
+
|
|
45
|
+
pid = thread[:pid]
|
|
46
|
+
|
|
47
|
+
if thread.join(timeout).nil?
|
|
48
|
+
cleanup_process(pid)
|
|
49
|
+
thread.kill
|
|
50
|
+
fail TimeoutExceeded
|
|
51
|
+
else
|
|
52
|
+
success = thread.value.exitstatus.zero?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
success
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Run using backtick
|
|
60
|
+
# Return captured output
|
|
61
|
+
# Will raise SignalException if process was interrupted
|
|
62
|
+
def capture(cmd)
|
|
63
|
+
output = `#{cmd}`
|
|
64
|
+
|
|
65
|
+
check_status!
|
|
66
|
+
|
|
67
|
+
output
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def check_status!
|
|
73
|
+
status = $CHILD_STATUS
|
|
74
|
+
|
|
75
|
+
return unless status.signaled?
|
|
76
|
+
|
|
77
|
+
# jruby incorrectly returns true for `signaled?` if process exits with
|
|
78
|
+
# non zero status. For following code
|
|
79
|
+
#
|
|
80
|
+
# `sh -c 'exit 66'`
|
|
81
|
+
# p [$?.signaled?, $?.exitstatus, $?.termsig]
|
|
82
|
+
#
|
|
83
|
+
# jruby outputs `[true, 66, 66]` instead of expected `[false, 66, nil]`
|
|
84
|
+
return if defined?(JRUBY_VERSION) && status.exitstatus == status.termsig
|
|
85
|
+
|
|
86
|
+
fail SignalException, status.termsig
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def init_options!(args)
|
|
90
|
+
pgroup_opt = Gem.win_platform? ? :new_pgroup : :pgroup
|
|
91
|
+
|
|
92
|
+
if args.last.is_a?(Hash)
|
|
93
|
+
args.last[pgroup_opt] = true
|
|
94
|
+
else
|
|
95
|
+
args.push(pgroup_opt => true)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cleanup_process(pid)
|
|
100
|
+
Thread.new do
|
|
101
|
+
Process.kill('-TERM', pid)
|
|
102
|
+
Process.detach(pid)
|
|
103
|
+
now = Time.now
|
|
104
|
+
|
|
105
|
+
while Time.now - now < 10
|
|
106
|
+
begin
|
|
107
|
+
Process.kill(0, pid)
|
|
108
|
+
sleep 0.001
|
|
109
|
+
next
|
|
110
|
+
rescue Errno::ESRCH
|
|
111
|
+
break
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if Process.getpgid(pid)
|
|
116
|
+
Process.kill('-KILL', pid)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
require 'image_optim/option_helpers'
|
|
2
|
+
require 'image_optim/configuration_error'
|
|
3
|
+
require 'image_optim/hash_helpers'
|
|
4
|
+
require 'image_optim/worker'
|
|
5
|
+
require 'image_optim/cmd'
|
|
6
|
+
require 'set'
|
|
7
|
+
require 'yaml'
|
|
8
|
+
|
|
9
|
+
class ImageOptim
|
|
10
|
+
# Read, merge and parse configuration
|
|
11
|
+
class Config
|
|
12
|
+
include OptionHelpers
|
|
13
|
+
|
|
14
|
+
# Global config path at `$XDG_CONFIG_HOME/image_optim.yml` (by default
|
|
15
|
+
# `~/.config/image_optim.yml`)
|
|
16
|
+
GLOBAL_PATH = begin
|
|
17
|
+
File.join(ENV['XDG_CONFIG_HOME'] || '~/.config', 'image_optim.yml')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Local config path at `./.image_optim.yml`
|
|
21
|
+
LOCAL_PATH = './.image_optim.yml'.freeze
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# Read options at path: expand path (warn on failure), return {} if file
|
|
25
|
+
# does not exist or is empty, read yaml, check if it is a Hash, deep
|
|
26
|
+
# symbolise keys
|
|
27
|
+
def read_options(path)
|
|
28
|
+
begin
|
|
29
|
+
full_path = File.expand_path(path)
|
|
30
|
+
rescue ArgumentError => e
|
|
31
|
+
warn "Can't expand path #{path}: #{e}"
|
|
32
|
+
return {}
|
|
33
|
+
end
|
|
34
|
+
return {} unless File.size?(full_path)
|
|
35
|
+
config = YAML.load_file(full_path)
|
|
36
|
+
unless config.is_a?(Hash)
|
|
37
|
+
fail "expected hash, got #{config.inspect}"
|
|
38
|
+
end
|
|
39
|
+
HashHelpers.deep_symbolise_keys(config)
|
|
40
|
+
rescue => e
|
|
41
|
+
warn "exception when reading #{full_path}: #{e}"
|
|
42
|
+
{}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Merge config from files with passed options
|
|
47
|
+
# Config files are checked at `GLOBAL_PATH` and `LOCAL_PATH` unless
|
|
48
|
+
# overriden using `:config_paths`
|
|
49
|
+
def initialize(options)
|
|
50
|
+
config_paths = options.delete(:config_paths) || [GLOBAL_PATH, LOCAL_PATH]
|
|
51
|
+
config_paths = Array(config_paths)
|
|
52
|
+
|
|
53
|
+
to_merge = config_paths.map{ |path| self.class.read_options(path) }
|
|
54
|
+
to_merge << HashHelpers.deep_symbolise_keys(options)
|
|
55
|
+
|
|
56
|
+
@options = to_merge.reduce do |memo, hash|
|
|
57
|
+
HashHelpers.deep_merge(memo, hash)
|
|
58
|
+
end
|
|
59
|
+
@used = Set.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Gets value for key converted to symbol and mark option as used
|
|
63
|
+
def get!(key)
|
|
64
|
+
key = key.to_sym
|
|
65
|
+
@used << key
|
|
66
|
+
@options[key]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if key is present
|
|
70
|
+
def key?(key)
|
|
71
|
+
key = key.to_sym
|
|
72
|
+
@options.key?(key)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Fail unless all options were marked as used (directly or indirectly
|
|
76
|
+
# accessed using `get!`)
|
|
77
|
+
def assert_no_unused_options!
|
|
78
|
+
unknown_options = @options.reject{ |key, _value| @used.include?(key) }
|
|
79
|
+
return if unknown_options.empty?
|
|
80
|
+
fail ConfigurationError, "unknown options #{unknown_options.inspect}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Nice level:
|
|
84
|
+
# * `10` by default and for `nil` or `true`
|
|
85
|
+
# * `0` for `false`
|
|
86
|
+
# * otherwise convert to integer
|
|
87
|
+
def nice
|
|
88
|
+
nice = get!(:nice)
|
|
89
|
+
|
|
90
|
+
case nice
|
|
91
|
+
when true, nil
|
|
92
|
+
10
|
|
93
|
+
when false
|
|
94
|
+
0
|
|
95
|
+
else
|
|
96
|
+
nice.to_i
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Number of parallel threads:
|
|
101
|
+
# * `processor_count` by default and for `nil` or `true`
|
|
102
|
+
# * `1` for `false`
|
|
103
|
+
# * otherwise convert to integer
|
|
104
|
+
def threads
|
|
105
|
+
threads = get!(:threads)
|
|
106
|
+
|
|
107
|
+
case threads
|
|
108
|
+
when true, nil
|
|
109
|
+
processor_count
|
|
110
|
+
when false
|
|
111
|
+
1
|
|
112
|
+
else
|
|
113
|
+
threads.to_i
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Verbose mode, converted to boolean
|
|
118
|
+
def verbose
|
|
119
|
+
!!get!(:verbose)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Using image_optim_pack:
|
|
123
|
+
# * `false` to disable
|
|
124
|
+
# * `nil` to use if available
|
|
125
|
+
# * everything else to require
|
|
126
|
+
def pack
|
|
127
|
+
pack = get!(:pack)
|
|
128
|
+
return false if pack == false
|
|
129
|
+
|
|
130
|
+
require 'image_optim/pack'
|
|
131
|
+
true
|
|
132
|
+
rescue LoadError => e
|
|
133
|
+
raise "Cannot load image_optim_pack: #{e}" if pack
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Skip missing workers, converted to boolean
|
|
138
|
+
def skip_missing_workers
|
|
139
|
+
if key?(:skip_missing_workers)
|
|
140
|
+
!!get!(:skip_missing_workers)
|
|
141
|
+
else
|
|
142
|
+
pack
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Allow lossy workers and optimizations, converted to boolean
|
|
147
|
+
def allow_lossy
|
|
148
|
+
!!get!(:allow_lossy)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def cache_dir
|
|
152
|
+
dir = get!(:cache_dir)
|
|
153
|
+
dir unless dir.nil? || dir.empty?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def cache_worker_digests
|
|
157
|
+
!!get!(:cache_worker_digests)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def timeout
|
|
161
|
+
timeout = get!(:timeout)
|
|
162
|
+
timeout ? timeout.to_i : 0
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Options for worker class by its `bin_sym`:
|
|
166
|
+
# * `Hash` passed as is
|
|
167
|
+
# * `{}` for `true` or `nil`
|
|
168
|
+
# * `false` for `false`
|
|
169
|
+
# * otherwise fail with `ConfigurationError`
|
|
170
|
+
def for_worker(klass)
|
|
171
|
+
worker_options = get!(klass.bin_sym)
|
|
172
|
+
|
|
173
|
+
case worker_options
|
|
174
|
+
when Hash
|
|
175
|
+
worker_options
|
|
176
|
+
when true, nil
|
|
177
|
+
{}
|
|
178
|
+
when false
|
|
179
|
+
{:disable => true}
|
|
180
|
+
else
|
|
181
|
+
fail ConfigurationError, "Got #{worker_options.inspect} for "\
|
|
182
|
+
"#{klass.name} options"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# yaml dump without document beginning prefix `---`
|
|
187
|
+
def to_s
|
|
188
|
+
YAML.dump(HashHelpers.deep_stringify_keys(@options)).sub(/\A---\n/, '')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
# http://stackoverflow.com/a/6420817
|
|
194
|
+
def processor_count
|
|
195
|
+
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
|
196
|
+
when /darwin9/
|
|
197
|
+
Cmd.capture 'hwprefs cpu_count'
|
|
198
|
+
when /darwin/
|
|
199
|
+
if (Cmd.capture 'which hwprefs') != ''
|
|
200
|
+
Cmd.capture 'hwprefs thread_count'
|
|
201
|
+
else
|
|
202
|
+
Cmd.capture 'sysctl -n hw.ncpu'
|
|
203
|
+
end
|
|
204
|
+
when /linux/
|
|
205
|
+
Cmd.capture 'grep -c processor /proc/cpuinfo'
|
|
206
|
+
when /freebsd/
|
|
207
|
+
Cmd.capture 'sysctl -n hw.ncpu'
|
|
208
|
+
when /mswin|mingw/
|
|
209
|
+
require 'win32ole'
|
|
210
|
+
query = 'select NumberOfLogicalProcessors from Win32_Processor'
|
|
211
|
+
result = WIN32OLE.connect('winmgmts://').ExecQuery(query)
|
|
212
|
+
result.to_enum.first.NumberOfLogicalProcessors
|
|
213
|
+
else
|
|
214
|
+
warn "Unknown architecture (#{host_os}) assuming one processor."
|
|
215
|
+
1
|
|
216
|
+
end.to_i
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|