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,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
|