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,57 @@
|
|
|
1
|
+
require 'image_optim/path'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# Handles processing of original to result using upto two temp files
|
|
5
|
+
class Handler
|
|
6
|
+
# Holds latest successful result
|
|
7
|
+
attr_reader :result
|
|
8
|
+
|
|
9
|
+
# original must respond to temp_path
|
|
10
|
+
def initialize(original)
|
|
11
|
+
unless original.respond_to?(:temp_path)
|
|
12
|
+
fail ArgumentError, 'original should respond to temp_path'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@original = original
|
|
16
|
+
@result = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# with no associated block, works as new. Otherwise creates instance and
|
|
20
|
+
# passes it to block, runs cleanup and returns result of handler
|
|
21
|
+
def self.for(original)
|
|
22
|
+
handler = new(original)
|
|
23
|
+
if block_given?
|
|
24
|
+
begin
|
|
25
|
+
yield handler
|
|
26
|
+
handler.result
|
|
27
|
+
ensure
|
|
28
|
+
handler.cleanup
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
handler
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Yields two paths, one to latest successful result or original, second to
|
|
36
|
+
# temp path
|
|
37
|
+
def process
|
|
38
|
+
@src ||= @original
|
|
39
|
+
@dst ||= @original.temp_path
|
|
40
|
+
|
|
41
|
+
return unless yield @src, @dst
|
|
42
|
+
@result = @dst
|
|
43
|
+
if @src == @original
|
|
44
|
+
@src, @dst = @dst, nil
|
|
45
|
+
else
|
|
46
|
+
@src, @dst = @dst, @src
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Remove extra temp files
|
|
51
|
+
def cleanup
|
|
52
|
+
return unless @dst
|
|
53
|
+
@dst.unlink
|
|
54
|
+
@dst = nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Helper methods to manipulate Hash, mainly used in config
|
|
3
|
+
module HashHelpers
|
|
4
|
+
class << self
|
|
5
|
+
# Returns a new hash with all keys of root and nested hashes converted to
|
|
6
|
+
# strings
|
|
7
|
+
def deep_stringify_keys(hash)
|
|
8
|
+
deep_transform_keys(hash, &:to_s)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns a new hash with all keys of root and nested hashes converted to
|
|
12
|
+
# symbols
|
|
13
|
+
def deep_symbolise_keys(hash)
|
|
14
|
+
deep_transform_keys(hash, &:to_sym)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns a new hash with recursive merge of all keys
|
|
18
|
+
def deep_merge(a, b)
|
|
19
|
+
a.merge(b) do |_key, value_a, value_b|
|
|
20
|
+
if value_a.is_a?(Hash) && value_b.is_a?(Hash)
|
|
21
|
+
deep_merge(value_a, value_b)
|
|
22
|
+
else
|
|
23
|
+
value_b
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Returns a new hash with all keys of root and nested hashes converted by
|
|
31
|
+
# provided block
|
|
32
|
+
def deep_transform_keys(hash, &block)
|
|
33
|
+
new_hash = {}
|
|
34
|
+
hash.each do |key, value|
|
|
35
|
+
new_hash[yield key] = if value.is_a?(Hash)
|
|
36
|
+
deep_transform_keys(value, &block)
|
|
37
|
+
else
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
new_hash
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'image_size'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# Getting format of image at path or as data
|
|
5
|
+
module ImageMeta
|
|
6
|
+
def self.format_for_path(path)
|
|
7
|
+
is = ImageSize.path(path)
|
|
8
|
+
is.format if is
|
|
9
|
+
rescue ImageSize::FormatError => e
|
|
10
|
+
warn "#{e} (detecting format of image at #{path})"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.format_for_data(data)
|
|
14
|
+
is = ImageSize.new(data)
|
|
15
|
+
is.format if is
|
|
16
|
+
rescue ImageSize::FormatError => e
|
|
17
|
+
warn "#{e} (detecting format of image data)"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Denote range of non negative integers for worker option
|
|
3
|
+
class NonNegativeIntegerRange
|
|
4
|
+
# Add handling of range of non negative integers in OptionParser instance
|
|
5
|
+
def self.add_to_option_parser(option_parser)
|
|
6
|
+
option_parser.accept(self, /(\d+)(?:-|\.\.)(\d+)/) do |_, m, n|
|
|
7
|
+
m.to_i..n.to_i
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'image_optim/path'
|
|
2
|
+
|
|
3
|
+
class ImageOptim
|
|
4
|
+
# Holds optimized image with reference to original and its size
|
|
5
|
+
class OptimizedPath < DelegateClass(Path)
|
|
6
|
+
def initialize(path, original_or_size = nil)
|
|
7
|
+
path = Path.convert(path)
|
|
8
|
+
__setobj__(path)
|
|
9
|
+
if original_or_size.is_a?(Integer)
|
|
10
|
+
@original = path
|
|
11
|
+
@original_size = original_or_size
|
|
12
|
+
elsif original_or_size
|
|
13
|
+
@original = Path.convert(original_or_size)
|
|
14
|
+
@original_size = @original.size
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Original path, use original_size to get its size as original can be
|
|
19
|
+
# overwritten
|
|
20
|
+
attr_reader :original
|
|
21
|
+
|
|
22
|
+
# Stored size of original
|
|
23
|
+
attr_reader :original_size
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Hold information about an option
|
|
3
|
+
class OptionDefinition
|
|
4
|
+
attr_reader :name, :default, :type, :description, :proc
|
|
5
|
+
|
|
6
|
+
def initialize(name, default, type_or_description, description = nil, &proc)
|
|
7
|
+
if type_or_description.is_a?(Class)
|
|
8
|
+
type = type_or_description
|
|
9
|
+
else
|
|
10
|
+
type, description = default.class, type_or_description
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@name = name.to_sym
|
|
14
|
+
@description = description.to_s
|
|
15
|
+
@default, @type, @proc = default, type, proc
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get value for worker from options
|
|
19
|
+
def value(worker, options)
|
|
20
|
+
value = options.key?(name) ? options[name] : default
|
|
21
|
+
if proc
|
|
22
|
+
if proc.arity == 2
|
|
23
|
+
worker.instance_exec(value, self, &proc)
|
|
24
|
+
else
|
|
25
|
+
worker.instance_exec(value, &proc)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Describe default value, returns string as is otherwise surrounds
|
|
33
|
+
# inspected value with backticks
|
|
34
|
+
def default_description
|
|
35
|
+
default.is_a?(String) ? default : "`#{default.inspect}`"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
# Helper methods for options
|
|
3
|
+
module OptionHelpers
|
|
4
|
+
# Ensure number is in range
|
|
5
|
+
def self.limit_with_range(number, range)
|
|
6
|
+
if range.include?(number)
|
|
7
|
+
number
|
|
8
|
+
elsif number < range.first
|
|
9
|
+
range.first
|
|
10
|
+
elsif range.exclude_end?
|
|
11
|
+
range.last - 1
|
|
12
|
+
else
|
|
13
|
+
range.last
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'fspath'
|
|
2
|
+
require 'image_optim/image_meta'
|
|
3
|
+
|
|
4
|
+
class ImageOptim
|
|
5
|
+
# FSPath with additional helpful methods
|
|
6
|
+
class Path < FSPath
|
|
7
|
+
NULL = if defined?(IO::NULL)
|
|
8
|
+
IO::NULL
|
|
9
|
+
else
|
|
10
|
+
%w[/dev/null NUL: NUL nul NIL: NL:].find{ |dev| File.exist?(dev) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Get temp path for this file with same extension
|
|
14
|
+
def temp_path(*args, &block)
|
|
15
|
+
ext = extname
|
|
16
|
+
self.class.temp_file_path([basename(ext).to_s, ext], *args, &block)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Copy file to dst, optionally preserving attributes
|
|
20
|
+
#
|
|
21
|
+
# See FileUtils.copy_file
|
|
22
|
+
def copy(dst, preserve = false)
|
|
23
|
+
FileUtils.copy_file(self, dst, preserve)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Move file to dst: rename on same device, copy and unlink original
|
|
27
|
+
# otherwise
|
|
28
|
+
#
|
|
29
|
+
# See FileUtils.mv
|
|
30
|
+
def move(dst)
|
|
31
|
+
FileUtils.move(self, dst)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Copy metadata: uid, gid, mode, optionally atime and mtime
|
|
35
|
+
#
|
|
36
|
+
# Adapted from FileUtils::Entry_#copy_metadata by Minero Aoki
|
|
37
|
+
def copy_metadata(dst, time = false)
|
|
38
|
+
stat = lstat
|
|
39
|
+
dst.utime(stat.atime, stat.mtime) if time
|
|
40
|
+
begin
|
|
41
|
+
dst.chown(stat.uid, stat.gid)
|
|
42
|
+
rescue Errno::EPERM
|
|
43
|
+
dst.chmod(stat.mode & 0o1777)
|
|
44
|
+
else
|
|
45
|
+
dst.chmod(stat.mode)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Atomic replace dst with self
|
|
50
|
+
def replace(dst)
|
|
51
|
+
dst = self.class.new(dst)
|
|
52
|
+
dst.temp_path(dst.dirname) do |temp|
|
|
53
|
+
move(temp)
|
|
54
|
+
dst.copy_metadata(temp)
|
|
55
|
+
temp.rename(dst.to_s)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get format using ImageSize
|
|
60
|
+
def image_format
|
|
61
|
+
ImageMeta.format_for_path(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns path if it is already an instance of this class otherwise new
|
|
65
|
+
# instance
|
|
66
|
+
def self.convert(path)
|
|
67
|
+
path.is_a?(self) ? path : new(path)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'image_optim'
|
|
2
|
+
require 'image_optim/hash_helpers'
|
|
3
|
+
require 'image_optim/runner/glob_helpers'
|
|
4
|
+
require 'image_optim/space'
|
|
5
|
+
require 'progress'
|
|
6
|
+
require 'find'
|
|
7
|
+
require 'yaml'
|
|
8
|
+
|
|
9
|
+
class ImageOptim
|
|
10
|
+
# Handling optimization using image_optim binary
|
|
11
|
+
class Runner
|
|
12
|
+
# Collect and output results of optimization
|
|
13
|
+
class Results
|
|
14
|
+
def initialize
|
|
15
|
+
@lines = []
|
|
16
|
+
@original_size_sum = 0
|
|
17
|
+
@optimized_size_sum = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add(original, optimized)
|
|
21
|
+
original_size = optimized ? optimized.original_size : original.size
|
|
22
|
+
optimized_size = optimized ? optimized.size : original.size
|
|
23
|
+
@lines << "#{size_percent(original_size, optimized_size)} #{original}"
|
|
24
|
+
@original_size_sum += original_size
|
|
25
|
+
@optimized_size_sum += optimized_size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def print
|
|
29
|
+
puts @lines
|
|
30
|
+
puts "Total: #{size_percent(@original_size_sum, @optimized_size_sum)}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def size_percent(size_a, size_b)
|
|
36
|
+
if size_a == size_b
|
|
37
|
+
"------ #{Space::EMPTY_SPACE}"
|
|
38
|
+
else
|
|
39
|
+
percent = 100 - 100.0 * size_b / size_a
|
|
40
|
+
space = Space.space(size_a - size_b)
|
|
41
|
+
format('%5.2f%% %s', percent, space)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(options)
|
|
47
|
+
options = HashHelpers.deep_symbolise_keys(options)
|
|
48
|
+
@recursive = options.delete(:recursive)
|
|
49
|
+
@progress = options.delete(:show_progress) != false
|
|
50
|
+
@exclude_dir_globs, @exclude_file_globs = %w[dir file].map do |type|
|
|
51
|
+
glob = options.delete(:"exclude_#{type}_glob") || '.*'
|
|
52
|
+
GlobHelpers.expand_braces(glob)
|
|
53
|
+
end
|
|
54
|
+
@image_optim = ImageOptim.new(options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run!(args)
|
|
58
|
+
to_optimize = find_to_optimize(args)
|
|
59
|
+
unless to_optimize.empty?
|
|
60
|
+
results = Results.new
|
|
61
|
+
|
|
62
|
+
optimize_images!(to_optimize).each do |original, optimized|
|
|
63
|
+
results.add(original, optimized)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
results.print
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
!@warnings
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def optimize_images!(to_optimize, &block)
|
|
75
|
+
to_optimize = to_optimize.with_progress('optimizing') if @progress
|
|
76
|
+
@image_optim.optimize_images!(to_optimize, &block)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_to_optimize(paths)
|
|
80
|
+
to_optimize = []
|
|
81
|
+
paths.each do |path|
|
|
82
|
+
if File.file?(path)
|
|
83
|
+
if @image_optim.optimizable?(path)
|
|
84
|
+
to_optimize << path
|
|
85
|
+
else
|
|
86
|
+
warning "#{path} is not an image or there is no optimizer for it"
|
|
87
|
+
end
|
|
88
|
+
elsif File.directory?(path)
|
|
89
|
+
if @recursive
|
|
90
|
+
to_optimize += find_to_optimize_recursive(path)
|
|
91
|
+
else
|
|
92
|
+
warning "#{path} is a directory, use --recursive option"
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
warning "#{path} is not a file or a directory or does not exist"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
to_optimize
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def find_to_optimize_recursive(dir)
|
|
102
|
+
to_optimize = []
|
|
103
|
+
Find.find(dir) do |path|
|
|
104
|
+
if File.file?(path)
|
|
105
|
+
next if exclude_file?(dir, path)
|
|
106
|
+
next unless @image_optim.optimizable?(path)
|
|
107
|
+
to_optimize << path
|
|
108
|
+
elsif File.directory?(path)
|
|
109
|
+
Find.prune if dir != path && exclude_dir?(dir, path)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
to_optimize
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def exclude_dir?(dir, path)
|
|
116
|
+
exclude?(dir, path, @exclude_dir_globs)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def exclude_file?(dir, path)
|
|
120
|
+
exclude?(dir, path, @exclude_file_globs)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check if any of globs matches either part of path relative from dir or
|
|
124
|
+
# just basename
|
|
125
|
+
def exclude?(dir, path, globs)
|
|
126
|
+
relative_path = Pathname(path).relative_path_from(Pathname(dir)).to_s
|
|
127
|
+
basename = File.basename(path)
|
|
128
|
+
globs.any? do |glob|
|
|
129
|
+
File.fnmatch(glob, relative_path, File::FNM_PATHNAME) ||
|
|
130
|
+
File.fnmatch(glob, basename, File::FNM_PATHNAME)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def warning(message)
|
|
135
|
+
@warnings = true
|
|
136
|
+
warn message
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class ImageOptim
|
|
2
|
+
class Runner
|
|
3
|
+
# Helper methods for glob
|
|
4
|
+
module GlobHelpers
|
|
5
|
+
class << self
|
|
6
|
+
# Match inner curly braces in glob
|
|
7
|
+
# Negative lookbehind is not used as is not supported by ruby before 1.9
|
|
8
|
+
BRACE_REGEXP = /
|
|
9
|
+
\A
|
|
10
|
+
(
|
|
11
|
+
(?:.*[^\\]|) # anything ending not with slash or nothing
|
|
12
|
+
(?:\\\\)* # any number of self escaped slashes
|
|
13
|
+
)
|
|
14
|
+
\{ # open brace
|
|
15
|
+
(
|
|
16
|
+
(?:|.*?[^\\]) # nothing or non greedy anything ending not with slash
|
|
17
|
+
(?:\\\\)* # any number of self escaped slashes
|
|
18
|
+
)
|
|
19
|
+
\} # close brace
|
|
20
|
+
(
|
|
21
|
+
.* # what is left
|
|
22
|
+
)
|
|
23
|
+
\z
|
|
24
|
+
/x
|
|
25
|
+
|
|
26
|
+
# Expand curly braces in glob as fnmatch in ruby before 2.0 doesn't
|
|
27
|
+
# support them
|
|
28
|
+
def expand_braces(original_glob)
|
|
29
|
+
expanded = []
|
|
30
|
+
unexpanded = [original_glob]
|
|
31
|
+
while (glob = unexpanded.shift)
|
|
32
|
+
if (m = BRACE_REGEXP.match(glob))
|
|
33
|
+
m[2].split(',', -1).each do |variant|
|
|
34
|
+
unexpanded << "#{m[1]}#{variant}#{m[3]}"
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
expanded << glob
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
expanded.uniq
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|