openstreetmap-image_optim 0.21.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rubocop.yml +65 -0
  4. data/.travis.yml +42 -0
  5. data/CHANGELOG.markdown +272 -0
  6. data/CONTRIBUTING.markdown +10 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.markdown +344 -0
  10. data/Vagrantfile +33 -0
  11. data/bin/image_optim +28 -0
  12. data/image_optim.gemspec +29 -0
  13. data/lib/image_optim.rb +228 -0
  14. data/lib/image_optim/bin_resolver.rb +144 -0
  15. data/lib/image_optim/bin_resolver/bin.rb +105 -0
  16. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  17. data/lib/image_optim/bin_resolver/error.rb +6 -0
  18. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  19. data/lib/image_optim/cmd.rb +49 -0
  20. data/lib/image_optim/config.rb +205 -0
  21. data/lib/image_optim/configuration_error.rb +3 -0
  22. data/lib/image_optim/handler.rb +57 -0
  23. data/lib/image_optim/hash_helpers.rb +45 -0
  24. data/lib/image_optim/image_meta.rb +25 -0
  25. data/lib/image_optim/image_path.rb +68 -0
  26. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  27. data/lib/image_optim/option_definition.rb +32 -0
  28. data/lib/image_optim/option_helpers.rb +17 -0
  29. data/lib/image_optim/railtie.rb +38 -0
  30. data/lib/image_optim/runner.rb +139 -0
  31. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  32. data/lib/image_optim/runner/option_parser.rb +227 -0
  33. data/lib/image_optim/space.rb +29 -0
  34. data/lib/image_optim/true_false_nil.rb +16 -0
  35. data/lib/image_optim/worker.rb +159 -0
  36. data/lib/image_optim/worker/advpng.rb +35 -0
  37. data/lib/image_optim/worker/class_methods.rb +91 -0
  38. data/lib/image_optim/worker/gifsicle.rb +63 -0
  39. data/lib/image_optim/worker/jhead.rb +43 -0
  40. data/lib/image_optim/worker/jpegoptim.rb +58 -0
  41. data/lib/image_optim/worker/jpegrecompress.rb +44 -0
  42. data/lib/image_optim/worker/jpegtran.rb +46 -0
  43. data/lib/image_optim/worker/optipng.rb +45 -0
  44. data/lib/image_optim/worker/pngcrush.rb +54 -0
  45. data/lib/image_optim/worker/pngout.rb +38 -0
  46. data/lib/image_optim/worker/pngquant.rb +51 -0
  47. data/lib/image_optim/worker/svgo.rb +32 -0
  48. data/script/template/jquery-2.1.3.min.js +4 -0
  49. data/script/template/sortable-0.6.0.min.js +2 -0
  50. data/script/template/worker_analysis.erb +254 -0
  51. data/script/update_worker_options_in_readme +60 -0
  52. data/script/worker_analysis +599 -0
  53. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  54. data/spec/image_optim/bin_resolver/simple_version_spec.rb +57 -0
  55. data/spec/image_optim/bin_resolver_spec.rb +272 -0
  56. data/spec/image_optim/cmd_spec.rb +66 -0
  57. data/spec/image_optim/config_spec.rb +217 -0
  58. data/spec/image_optim/handler_spec.rb +95 -0
  59. data/spec/image_optim/hash_helpers_spec.rb +76 -0
  60. data/spec/image_optim/image_path_spec.rb +54 -0
  61. data/spec/image_optim/railtie_spec.rb +121 -0
  62. data/spec/image_optim/runner/glob_helpers_spec.rb +25 -0
  63. data/spec/image_optim/runner/option_parser_spec.rb +99 -0
  64. data/spec/image_optim/space_spec.rb +25 -0
  65. data/spec/image_optim/worker_spec.rb +192 -0
  66. data/spec/image_optim_spec.rb +242 -0
  67. data/spec/images/comparison.png +0 -0
  68. data/spec/images/decompressed.jpeg +0 -0
  69. data/spec/images/icecream.gif +0 -0
  70. data/spec/images/image.jpg +0 -0
  71. data/spec/images/invisiblepixels/generate +24 -0
  72. data/spec/images/invisiblepixels/image.png +0 -0
  73. data/spec/images/lena.jpg +0 -0
  74. data/spec/images/orient/0.jpg +0 -0
  75. data/spec/images/orient/1.jpg +0 -0
  76. data/spec/images/orient/2.jpg +0 -0
  77. data/spec/images/orient/3.jpg +0 -0
  78. data/spec/images/orient/4.jpg +0 -0
  79. data/spec/images/orient/5.jpg +0 -0
  80. data/spec/images/orient/6.jpg +0 -0
  81. data/spec/images/orient/7.jpg +0 -0
  82. data/spec/images/orient/8.jpg +0 -0
  83. data/spec/images/orient/generate +23 -0
  84. data/spec/images/orient/original.jpg +0 -0
  85. data/spec/images/quant/64.png +0 -0
  86. data/spec/images/quant/generate +25 -0
  87. data/spec/images/rails.png +0 -0
  88. data/spec/images/test.svg +3 -0
  89. data/spec/images/transparency1.png +0 -0
  90. data/spec/images/transparency2.png +0 -0
  91. data/spec/images/vergroessert.jpg +0 -0
  92. data/spec/spec_helper.rb +64 -0
  93. data/vendor/jpegrescan +143 -0
  94. metadata +308 -0
@@ -0,0 +1,144 @@
1
+ require 'thread'
2
+ require 'fspath'
3
+ require 'image_optim/bin_resolver/error'
4
+ require 'image_optim/bin_resolver/bin'
5
+
6
+ class ImageOptim
7
+ # Handles resolving binaries and checking versions
8
+ #
9
+ # If there is an environment variable XXX_BIN when resolving xxx, then a
10
+ # symlink to binary will be created in a temporary directory which will be
11
+ # added to PATH
12
+ class BinResolver
13
+ class BinNotFound < Error; end
14
+
15
+ # Directory for symlinks to bins if XXX_BIN was used
16
+ attr_reader :dir
17
+
18
+ # Path to pack from image_optim_pack if used
19
+ attr_reader :pack_path
20
+
21
+ def initialize(image_optim)
22
+ @image_optim = image_optim
23
+ @bins = {}
24
+ @lock = Mutex.new
25
+ init_pack
26
+ end
27
+
28
+ # Binary resolving: create symlink if there is XXX_BIN environment variable,
29
+ # build Bin with full path, check binary version
30
+ # Return Bin instance
31
+ def resolve!(name)
32
+ name = name.to_sym
33
+
34
+ resolving(name) do
35
+ path = symlink_custom_bin!(name) || full_path(name)
36
+ bin = Bin.new(name, path) if path
37
+
38
+ if bin && @image_optim.verbose
39
+ $stderr << "Resolved #{bin}\n"
40
+ end
41
+
42
+ @bins[name] = bin
43
+
44
+ bin.check! if bin
45
+ end
46
+
47
+ if @bins[name]
48
+ @bins[name].check_fail!
49
+ else
50
+ fail BinNotFound, "`#{name}` not found"
51
+ end
52
+
53
+ @bins[name]
54
+ end
55
+
56
+ # Path to vendor at root of image_optim
57
+ VENDOR_PATH = File.expand_path('../../../vendor', __FILE__)
58
+
59
+ # Prepand `dir` and append `VENDOR_PATH` to `PATH` from environment
60
+ def env_path
61
+ [
62
+ dir,
63
+ pack_path,
64
+ ENV['PATH'],
65
+ VENDOR_PATH,
66
+ ].compact.join(File::PATH_SEPARATOR)
67
+ end
68
+
69
+ # Collect resolving errors when running block over items of enumerable
70
+ def self.collect_errors(enumerable)
71
+ errors = []
72
+ enumerable.each do |item|
73
+ begin
74
+ yield item
75
+ rescue Error => e
76
+ errors << e
77
+ end
78
+ end
79
+ errors
80
+ end
81
+
82
+ private
83
+
84
+ def init_pack
85
+ return unless @image_optim.pack
86
+
87
+ @pack_path = if @image_optim.verbose
88
+ Pack.path do |message|
89
+ $stderr << "#{message}\n"
90
+ end
91
+ else
92
+ Pack.path
93
+ end
94
+ return if @pack_path
95
+
96
+ warn 'No pack for this OS and/or ARCH, check verbose output'
97
+ end
98
+
99
+ # Double-checked locking
100
+ def resolving(name)
101
+ return if @bins.include?(name)
102
+ @lock.synchronize do
103
+ yield unless @bins.include?(name)
104
+ end
105
+ end
106
+
107
+ # Check path in XXX_BIN to exist, be a file and be executable and symlink to
108
+ # dir as name
109
+ def symlink_custom_bin!(name)
110
+ env_name = "#{name}_bin".upcase
111
+ path = ENV[env_name]
112
+ return unless path
113
+ path = File.expand_path(path)
114
+ desc = "`#{path}` specified in #{env_name}"
115
+ fail "#{desc} doesn\'t exist" unless File.exist?(path)
116
+ fail "#{desc} is not a file" unless File.file?(path)
117
+ fail "#{desc} is not executable" unless File.executable?(path)
118
+ if @image_optim.verbose
119
+ $stderr << "Custom path for #{name} specified in #{env_name}: #{path}\n"
120
+ end
121
+ unless @dir
122
+ @dir = FSPath.temp_dir
123
+ at_exit{ FileUtils.remove_entry_secure @dir }
124
+ end
125
+ symlink = @dir / name
126
+ symlink.make_symlink(path)
127
+ path
128
+ end
129
+
130
+ # Return full path to bin or null
131
+ # based on http://stackoverflow.com/a/5471032/96823
132
+ def full_path(name)
133
+ # PATHEXT is needed only for windows
134
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
135
+ env_path.split(File::PATH_SEPARATOR).each do |dir|
136
+ exts.each do |ext|
137
+ path = File.expand_path("#{name}#{ext}", dir)
138
+ return path if File.file?(path) && File.executable?(path)
139
+ end
140
+ end
141
+ nil
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,105 @@
1
+ require 'image_optim/bin_resolver/error'
2
+ require 'image_optim/bin_resolver/simple_version'
3
+ require 'image_optim/bin_resolver/comparable_condition'
4
+ require 'image_optim/cmd'
5
+ require 'shellwords'
6
+
7
+ class ImageOptim
8
+ class BinResolver
9
+ # Holds bin name and path, gets version
10
+ class Bin
11
+ class UnknownVersion < Error; end
12
+ class BadVersion < Error; end
13
+
14
+ attr_reader :name, :path, :version
15
+ def initialize(name, path)
16
+ @name = name.to_sym
17
+ @path = path.to_s
18
+ @version = detect_version
19
+ end
20
+
21
+ def to_s
22
+ "#{name} #{version || '?'} at #{path}"
23
+ end
24
+
25
+ is = ComparableCondition.is
26
+
27
+ FAIL_CHECKS = [
28
+ [:pngcrush, is.between?('1.7.60', '1.7.65'), 'is known to produce '\
29
+ 'broken pngs'],
30
+ [:pngcrush, is == '1.7.80', 'loses one color in indexed images'],
31
+ [:pngquant, is < '2.0', 'is not supported'],
32
+ ]
33
+
34
+ WARN_CHECKS = [
35
+ [:advpng, is < '1.17', 'does not use zopfli'],
36
+ [:gifsicle, is < '1.85', 'does not support removing extension blocks'],
37
+ [:pngcrush, is < '1.7.38', 'does not have blacken flag'],
38
+ [:pngquant, is < '2.1', 'may be lossy even with quality `100-`'],
39
+ ]
40
+
41
+ # Fail if version will not work properly
42
+ def check_fail!
43
+ fail UnknownVersion, "didn't get version of #{self}" unless version
44
+
45
+ FAIL_CHECKS.each do |bin_name, matcher, message|
46
+ next unless bin_name == name
47
+ next unless matcher.match(version)
48
+ fail BadVersion, "#{self} (#{matcher}) #{message}"
49
+ end
50
+ end
51
+
52
+ # Run check_fail!, otherwise warn if version is known to misbehave
53
+ def check!
54
+ check_fail!
55
+
56
+ WARN_CHECKS.each do |bin_name, matcher, message|
57
+ next unless bin_name == name
58
+ next unless matcher.match(version)
59
+ warn "WARN: #{self} (#{matcher}) #{message}"
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Wrap version_string with SimpleVersion
66
+ def detect_version
67
+ str = version_string
68
+ str && SimpleVersion.new(str)
69
+ end
70
+
71
+ # Getting version of bin, will fail for an unknown name
72
+ def version_string
73
+ case name
74
+ when :advpng, :gifsicle, :jpegoptim, :optipng, :pngquant
75
+ capture("#{escaped_path} --version 2> /dev/null")[/\d+(\.\d+){1,}/]
76
+ when :svgo
77
+ capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+){1,}/]
78
+ when :jhead, :'jpeg-recompress'
79
+ capture("#{escaped_path} -V 2> /dev/null")[/\d+(\.\d+){1,}/]
80
+ when :jpegtran
81
+ capture("#{escaped_path} -v - 2>&1")[/version (\d+\S*)/, 1]
82
+ when :pngcrush
83
+ capture("#{escaped_path} -version 2>&1")[/\d+(\.\d+){1,}/]
84
+ when :pngout
85
+ date_regexp = /[A-Z][a-z]{2} (?: |\d)\d \d{4}/
86
+ date_str = capture("#{escaped_path} 2>&1")[date_regexp]
87
+ Date.parse(date_str).strftime('%Y%m%d') if date_str
88
+ when :jpegrescan
89
+ # jpegrescan has no version so just check presence
90
+ path && '-'
91
+ else
92
+ fail "getting `#{name}` version is not defined"
93
+ end
94
+ end
95
+
96
+ def capture(cmd)
97
+ Cmd.capture(cmd)
98
+ end
99
+
100
+ def escaped_path
101
+ path.shellescape
102
+ end
103
+ end
104
+ end
105
+ end
@@ -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,6 @@
1
+ class ImageOptim
2
+ class BinResolver
3
+ # Base error during bin resolving
4
+ class Error < StandardError; end
5
+ end
6
+ 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,49 @@
1
+ require 'English'
2
+
3
+ class ImageOptim
4
+ # Helper for running commands
5
+ module Cmd
6
+ class << self
7
+ # Run using `system`
8
+ # Return success status
9
+ # Will raise SignalException if process was interrupted
10
+ def run(*args)
11
+ success = system(*args)
12
+
13
+ check_status!
14
+
15
+ success
16
+ end
17
+
18
+ # Run using backtick
19
+ # Return captured output
20
+ # Will raise SignalException if process was interrupted
21
+ def capture(cmd)
22
+ output = `#{cmd}`
23
+
24
+ check_status!
25
+
26
+ output
27
+ end
28
+
29
+ private
30
+
31
+ def check_status!
32
+ status = $CHILD_STATUS
33
+
34
+ return unless status.signaled?
35
+
36
+ # jruby incorrectly returns true for `signaled?` if process exits with
37
+ # non zero status. For following code
38
+ #
39
+ # `sh -c 'exit 66'`
40
+ # p [$?.signaled?, $?.exitstatus, $?.termsig]
41
+ #
42
+ # jruby outputs `[true, 66, 66]` instead of expected `[false, 66, nil]`
43
+ return if defined?(JRUBY_VERSION) && status.exitstatus == status.termsig
44
+
45
+ fail SignalException, status.termsig
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,205 @@
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'
22
+
23
+ class << self
24
+ # Read options at path: expand path (warn on failure), return {} if file
25
+ # does not exist, read yaml, check if it is a Hash, deep symbolise keys
26
+ def read_options(path)
27
+ begin
28
+ full_path = File.expand_path(path)
29
+ rescue ArgumentError => e
30
+ warn "Can't expand path #{path}: #{e}"
31
+ return {}
32
+ end
33
+ return {} unless File.file?(full_path)
34
+ config = YAML.load_file(full_path)
35
+ unless config.is_a?(Hash)
36
+ fail "expected hash, got #{config.inspect}"
37
+ end
38
+ HashHelpers.deep_symbolise_keys(config)
39
+ rescue => e
40
+ warn "exception when reading #{full_path}: #{e}"
41
+ {}
42
+ end
43
+ end
44
+
45
+ # Merge config from files with passed options
46
+ # Config files are checked at `GLOBAL_PATH` and `LOCAL_PATH` unless
47
+ # overriden using `:config_paths`
48
+ def initialize(options)
49
+ config_paths = options.delete(:config_paths) || [GLOBAL_PATH, LOCAL_PATH]
50
+ config_paths = Array(config_paths)
51
+
52
+ to_merge = config_paths.map{ |path| self.class.read_options(path) }
53
+ to_merge << HashHelpers.deep_symbolise_keys(options)
54
+
55
+ @options = to_merge.reduce do |memo, hash|
56
+ HashHelpers.deep_merge(memo, hash)
57
+ end
58
+ @used = Set.new
59
+ end
60
+
61
+ # Gets value for key converted to symbol and mark option as used
62
+ def get!(key)
63
+ key = key.to_sym
64
+ @used << key
65
+ @options[key]
66
+ end
67
+
68
+ # Check if key is present
69
+ def key?(key)
70
+ key = key.to_sym
71
+ @options.key?(key)
72
+ end
73
+
74
+ # Fail unless all options were marked as used (directly or indirectly
75
+ # accessed using `get!`)
76
+ def assert_no_unused_options!
77
+ unknown_options = @options.reject{ |key, _value| @used.include?(key) }
78
+ return if unknown_options.empty?
79
+ fail ConfigurationError, "unknown options #{unknown_options.inspect}"
80
+ end
81
+
82
+ # Nice level:
83
+ # * `10` by default and for `nil` or `true`
84
+ # * `0` for `false`
85
+ # * otherwise convert to integer
86
+ def nice
87
+ nice = get!(:nice)
88
+
89
+ case nice
90
+ when true, nil
91
+ 10
92
+ when false
93
+ 0
94
+ else
95
+ nice.to_i
96
+ end
97
+ end
98
+
99
+ # Number of parallel threads:
100
+ # * `processor_count` by default and for `nil` or `true`
101
+ # * `1` for `false`
102
+ # * otherwise convert to integer
103
+ def threads
104
+ threads = get!(:threads)
105
+
106
+ case threads
107
+ when true, nil
108
+ processor_count
109
+ when false
110
+ 1
111
+ else
112
+ threads.to_i
113
+ end
114
+ end
115
+
116
+ # Verbose mode, converted to boolean
117
+ def verbose
118
+ !!get!(:verbose)
119
+ end
120
+
121
+ # Using image_optim_pack:
122
+ # * `false` to disable
123
+ # * `nil` to use if available
124
+ # * everything else to require
125
+ def pack
126
+ pack = get!(:pack)
127
+ return false if pack == false
128
+
129
+ require 'image_optim/pack'
130
+ true
131
+ rescue LoadError => e
132
+ raise "Cannot load image_optim_pack: #{e}" if pack
133
+ false
134
+ end
135
+
136
+ # Skip missing workers, converted to boolean
137
+ def skip_missing_workers
138
+ if key?(:skip_missing_workers)
139
+ !!get!(:skip_missing_workers)
140
+ else
141
+ pack
142
+ end
143
+ end
144
+
145
+ # Allow lossy workers and optimizations, converted to boolean
146
+ def allow_lossy
147
+ !!get!(:allow_lossy)
148
+ end
149
+
150
+ # Options for worker class by its `bin_sym`:
151
+ # * `Hash` passed as is
152
+ # * `{}` for `true` or `nil`
153
+ # * `false` for `false`
154
+ # * otherwise fail with `ConfigurationError`
155
+ def for_worker(klass)
156
+ worker_options = get!(klass.bin_sym)
157
+
158
+ case worker_options
159
+ when Hash
160
+ worker_options
161
+ when true, nil
162
+ {}
163
+ when false
164
+ {:disable => true}
165
+ else
166
+ fail ConfigurationError, "Got #{worker_options.inspect} for "\
167
+ "#{klass.name} options"
168
+ end
169
+ end
170
+
171
+ # yaml dump without document beginning prefix `---`
172
+ def to_s
173
+ YAML.dump(HashHelpers.deep_stringify_keys(@options)).sub(/\A---\n/, '')
174
+ end
175
+
176
+ private
177
+
178
+ # http://stackoverflow.com/a/6420817
179
+ def processor_count
180
+ @processor_count ||= case host_os = RbConfig::CONFIG['host_os']
181
+ when /darwin9/
182
+ Cmd.capture 'hwprefs cpu_count'
183
+ when /darwin/
184
+ if (Cmd.capture 'which hwprefs') != ''
185
+ Cmd.capture 'hwprefs thread_count'
186
+ else
187
+ Cmd.capture 'sysctl -n hw.ncpu'
188
+ end
189
+ when /linux/
190
+ Cmd.capture 'grep -c processor /proc/cpuinfo'
191
+ when /freebsd/
192
+ Cmd.capture 'sysctl -n hw.ncpu'
193
+ when /mswin|mingw/
194
+ require 'win32ole'
195
+ WIN32OLE.
196
+ connect('winmgmts://').
197
+ ExecQuery('select NumberOfLogicalProcessors from Win32_Processor').
198
+ to_enum.first.NumberOfLogicalProcessors
199
+ else
200
+ warn "Unknown architecture (#{host_os}) assuming one processor."
201
+ 1
202
+ end.to_i
203
+ end
204
+ end
205
+ end