image_optim 0.26.2 → 0.27.1
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 +4 -4
- data/.appveyor.yml +9 -2
- data/.rubocop.yml +38 -10
- data/.travis.yml +19 -15
- data/CHANGELOG.markdown +21 -0
- data/Gemfile +3 -1
- data/LICENSE.txt +1 -1
- data/README.markdown +9 -3
- data/Vagrantfile +2 -0
- data/bin/image_optim +1 -0
- data/image_optim.gemspec +5 -7
- data/lib/image_optim.rb +5 -0
- data/lib/image_optim/bin_resolver.rb +5 -0
- data/lib/image_optim/bin_resolver/bin.rb +45 -19
- data/lib/image_optim/bin_resolver/comparable_condition.rb +3 -0
- data/lib/image_optim/bin_resolver/error.rb +2 -0
- data/lib/image_optim/bin_resolver/simple_version.rb +2 -0
- data/lib/image_optim/cache.rb +3 -0
- data/lib/image_optim/cache_path.rb +21 -2
- data/lib/image_optim/cmd.rb +2 -0
- data/lib/image_optim/config.rb +7 -1
- data/lib/image_optim/configuration_error.rb +2 -0
- data/lib/image_optim/handler.rb +4 -0
- data/lib/image_optim/hash_helpers.rb +2 -0
- data/lib/image_optim/image_meta.rb +2 -0
- data/lib/image_optim/non_negative_integer_range.rb +2 -0
- data/lib/image_optim/optimized_path.rb +3 -1
- data/lib/image_optim/option_definition.rb +2 -0
- data/lib/image_optim/option_helpers.rb +2 -0
- data/lib/image_optim/path.rb +30 -5
- data/lib/image_optim/runner.rb +3 -0
- data/lib/image_optim/runner/glob_helpers.rb +3 -1
- data/lib/image_optim/runner/option_parser.rb +4 -2
- data/lib/image_optim/space.rb +3 -1
- data/lib/image_optim/true_false_nil.rb +2 -0
- data/lib/image_optim/worker.rb +5 -0
- data/lib/image_optim/worker/advpng.rb +2 -0
- data/lib/image_optim/worker/class_methods.rb +5 -0
- data/lib/image_optim/worker/gifsicle.rb +2 -0
- data/lib/image_optim/worker/jhead.rb +4 -1
- data/lib/image_optim/worker/jpegoptim.rb +2 -0
- data/lib/image_optim/worker/jpegrecompress.rb +2 -0
- data/lib/image_optim/worker/jpegtran.rb +2 -0
- data/lib/image_optim/worker/optipng.rb +4 -2
- data/lib/image_optim/worker/pngcrush.rb +4 -2
- data/lib/image_optim/worker/pngout.rb +3 -0
- data/lib/image_optim/worker/pngquant.rb +2 -0
- data/lib/image_optim/worker/svgo.rb +2 -0
- data/script/update_worker_options_in_readme +3 -2
- data/script/worker_analysis +13 -3
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +2 -0
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +2 -0
- data/spec/image_optim/bin_resolver_spec.rb +4 -2
- data/spec/image_optim/cache_path_spec.rb +71 -26
- data/spec/image_optim/cache_spec.rb +4 -0
- data/spec/image_optim/cmd_spec.rb +2 -0
- data/spec/image_optim/config_spec.rb +2 -0
- data/spec/image_optim/handler_spec.rb +2 -0
- data/spec/image_optim/hash_helpers_spec.rb +2 -0
- data/spec/image_optim/image_meta_spec.rb +2 -0
- data/spec/image_optim/optimized_path_spec.rb +2 -0
- data/spec/image_optim/option_definition_spec.rb +2 -0
- data/spec/image_optim/option_helpers_spec.rb +2 -0
- data/spec/image_optim/path_spec.rb +65 -24
- data/spec/image_optim/runner/glob_helpers_spec.rb +2 -0
- data/spec/image_optim/runner/option_parser_spec.rb +2 -0
- data/spec/image_optim/space_spec.rb +13 -11
- data/spec/image_optim/worker/optipng_spec.rb +2 -0
- data/spec/image_optim/worker/pngquant_spec.rb +2 -0
- data/spec/image_optim/worker_spec.rb +2 -0
- data/spec/image_optim_spec.rb +7 -4
- data/spec/images/invisiblepixels/generate +1 -0
- data/spec/images/quant/generate +3 -2
- data/spec/spec_helper.rb +3 -0
- metadata +18 -13
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ImageOptim
|
2
4
|
class BinResolver
|
3
5
|
# Allows to externalize conditions for an instance of Comparable to use in
|
@@ -24,6 +26,7 @@ class ImageOptim
|
|
24
26
|
end
|
25
27
|
|
26
28
|
attr_reader :method, :args
|
29
|
+
|
27
30
|
def initialize(method, *args)
|
28
31
|
@method, @args = method.to_sym, args
|
29
32
|
|
data/lib/image_optim/cache.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest/sha1'
|
2
4
|
require 'fspath'
|
3
5
|
require 'image_optim/cache_path'
|
@@ -7,6 +9,7 @@ class ImageOptim
|
|
7
9
|
class Cache
|
8
10
|
def initialize(image_optim, workers_by_format)
|
9
11
|
return unless image_optim.cache_dir
|
12
|
+
|
10
13
|
@cache_dir = FSPath.new(image_optim.cache_dir)
|
11
14
|
@cache_worker_digests = image_optim.cache_worker_digests
|
12
15
|
@options_by_format = Hash[workers_by_format.map do |format, workers|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/path'
|
2
4
|
|
3
5
|
class ImageOptim
|
@@ -5,8 +7,25 @@ class ImageOptim
|
|
5
7
|
class CachePath < Path
|
6
8
|
# Atomic replace dst with self
|
7
9
|
def replace(dst)
|
8
|
-
dst = self.class.
|
9
|
-
|
10
|
+
dst = self.class.convert(dst)
|
11
|
+
tmpdir = [dirname, Path.new(Dir.tmpdir)].find do |dir|
|
12
|
+
dir.same_dev?(dst.dirname)
|
13
|
+
end
|
14
|
+
if tmpdir
|
15
|
+
begin
|
16
|
+
replace_using_tmp_file(dst, tmpdir)
|
17
|
+
rescue Errno::EXDEV
|
18
|
+
replace_using_tmp_file(dst, dst.dirname)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
replace_using_tmp_file(dst, dst.dirname)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def replace_using_tmp_file(dst, tmpdir)
|
28
|
+
dst.temp_path_with_tmp_ext(tmpdir) do |temp|
|
10
29
|
copy(temp)
|
11
30
|
dst.copy_metadata(temp)
|
12
31
|
temp.rename(dst.to_s)
|
data/lib/image_optim/cmd.rb
CHANGED
data/lib/image_optim/config.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/option_helpers'
|
2
4
|
require 'image_optim/configuration_error'
|
3
5
|
require 'image_optim/hash_helpers'
|
@@ -18,7 +20,7 @@ class ImageOptim
|
|
18
20
|
end
|
19
21
|
|
20
22
|
# Local config path at `./.image_optim.yml`
|
21
|
-
LOCAL_PATH = './.image_optim.yml'
|
23
|
+
LOCAL_PATH = './.image_optim.yml'
|
22
24
|
|
23
25
|
class << self
|
24
26
|
# Read options at path: expand path (warn on failure), return {} if file
|
@@ -32,10 +34,12 @@ class ImageOptim
|
|
32
34
|
return {}
|
33
35
|
end
|
34
36
|
return {} unless File.size?(full_path)
|
37
|
+
|
35
38
|
config = YAML.load_file(full_path)
|
36
39
|
unless config.is_a?(Hash)
|
37
40
|
fail "expected hash, got #{config.inspect}"
|
38
41
|
end
|
42
|
+
|
39
43
|
HashHelpers.deep_symbolise_keys(config)
|
40
44
|
rescue => e
|
41
45
|
warn "exception when reading #{full_path}: #{e}"
|
@@ -77,6 +81,7 @@ class ImageOptim
|
|
77
81
|
def assert_no_unused_options!
|
78
82
|
unknown_options = @options.reject{ |key, _value| @used.include?(key) }
|
79
83
|
return if unknown_options.empty?
|
84
|
+
|
80
85
|
fail ConfigurationError, "unknown options #{unknown_options.inspect}"
|
81
86
|
end
|
82
87
|
|
@@ -131,6 +136,7 @@ class ImageOptim
|
|
131
136
|
true
|
132
137
|
rescue LoadError => e
|
133
138
|
raise "Cannot load image_optim_pack: #{e}" if pack
|
139
|
+
|
134
140
|
false
|
135
141
|
end
|
136
142
|
|
data/lib/image_optim/handler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/path'
|
2
4
|
|
3
5
|
class ImageOptim
|
@@ -39,6 +41,7 @@ class ImageOptim
|
|
39
41
|
@dst ||= @original.temp_path
|
40
42
|
|
41
43
|
return unless yield @src, @dst
|
44
|
+
|
42
45
|
@result = @dst
|
43
46
|
if @src == @original
|
44
47
|
@src, @dst = @dst, nil
|
@@ -50,6 +53,7 @@ class ImageOptim
|
|
50
53
|
# Remove extra temp files
|
51
54
|
def cleanup
|
52
55
|
return unless @dst
|
56
|
+
|
53
57
|
@dst.unlink
|
54
58
|
@dst = nil
|
55
59
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/path'
|
2
4
|
|
3
5
|
class ImageOptim
|
@@ -5,7 +7,7 @@ class ImageOptim
|
|
5
7
|
class OptimizedPath < DelegateClass(Path)
|
6
8
|
def initialize(path, original_or_size = nil)
|
7
9
|
path = Path.convert(path)
|
8
|
-
|
10
|
+
super(path)
|
9
11
|
if original_or_size.is_a?(Integer)
|
10
12
|
@original = path
|
11
13
|
@original_size = original_or_size
|
data/lib/image_optim/path.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fspath'
|
2
4
|
require 'image_optim/image_meta'
|
3
5
|
|
@@ -48,11 +50,16 @@ class ImageOptim
|
|
48
50
|
|
49
51
|
# Atomic replace dst with self
|
50
52
|
def replace(dst)
|
51
|
-
dst = self.class.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
dst = self.class.convert(dst)
|
54
|
+
if same_dev?(dst.dirname)
|
55
|
+
dst.copy_metadata(self)
|
56
|
+
begin
|
57
|
+
rename(dst.to_s)
|
58
|
+
rescue Errno::EXDEV
|
59
|
+
replace_using_tmp_file(dst)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
replace_using_tmp_file(dst)
|
56
63
|
end
|
57
64
|
end
|
58
65
|
|
@@ -66,5 +73,23 @@ class ImageOptim
|
|
66
73
|
def self.convert(path)
|
67
74
|
path.is_a?(self) ? path : new(path)
|
68
75
|
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def same_dev?(other)
|
80
|
+
stat.dev == other.stat.dev
|
81
|
+
end
|
82
|
+
|
83
|
+
def replace_using_tmp_file(dst)
|
84
|
+
dst.temp_path_with_tmp_ext(dst.dirname) do |temp|
|
85
|
+
move(temp)
|
86
|
+
dst.copy_metadata(temp)
|
87
|
+
temp.rename(dst.to_s)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def temp_path_with_tmp_ext(*args, &block)
|
92
|
+
self.class.temp_file_path([basename.to_s, '.tmp'], *args, &block)
|
93
|
+
end
|
69
94
|
end
|
70
95
|
end
|
data/lib/image_optim/runner.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim'
|
2
4
|
require 'image_optim/hash_helpers'
|
3
5
|
require 'image_optim/runner/glob_helpers'
|
@@ -104,6 +106,7 @@ class ImageOptim
|
|
104
106
|
if File.file?(path)
|
105
107
|
next if exclude_file?(dir, path)
|
106
108
|
next unless @image_optim.optimizable?(path)
|
109
|
+
|
107
110
|
to_optimize << path
|
108
111
|
elsif File.directory?(path)
|
109
112
|
Find.prune if dir != path && exclude_dir?(dir, path)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ImageOptim
|
2
4
|
class Runner
|
3
5
|
# Helper methods for glob
|
@@ -21,7 +23,7 @@ class ImageOptim
|
|
21
23
|
.* # what is left
|
22
24
|
)
|
23
25
|
\z
|
24
|
-
/x
|
26
|
+
/x.freeze
|
25
27
|
|
26
28
|
# Expand curly braces in glob as fnmatch in ruby before 2.0 doesn't
|
27
29
|
# support them
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'image_optim'
|
4
5
|
require 'image_optim/true_false_nil'
|
@@ -42,7 +43,7 @@ class ImageOptim
|
|
42
43
|
# don't try to wrap if there is too little space for description
|
43
44
|
return text if wrapped_width < 20
|
44
45
|
|
45
|
-
wrapped = ''
|
46
|
+
wrapped = ''.dup
|
46
47
|
text.split("\n").each do |line|
|
47
48
|
if line.length <= columns
|
48
49
|
wrapped << line << "\n"
|
@@ -185,7 +186,8 @@ ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
|
|
185
186
|
|
186
187
|
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
187
188
|
next if klass.option_definitions.empty?
|
188
|
-
|
189
|
+
|
190
|
+
op.separator nil unless i == 0
|
189
191
|
|
190
192
|
bin = klass.bin_sym
|
191
193
|
klass.option_definitions.each do |option_definition|
|
data/lib/image_optim/space.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ImageOptim
|
2
4
|
# Present size in readable form as fixed length string
|
3
5
|
module Space
|
@@ -15,7 +17,7 @@ class ImageOptim
|
|
15
17
|
else
|
16
18
|
log_denominator = Math.log(size.abs) / Math.log(BASE)
|
17
19
|
degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
|
18
|
-
number_string = if degree
|
20
|
+
number_string = if degree == 0
|
19
21
|
size.to_s
|
20
22
|
else
|
21
23
|
denominator = BASE**degree
|
data/lib/image_optim/worker.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'image_optim/cmd'
|
4
5
|
require 'image_optim/configuration_error'
|
@@ -23,6 +24,7 @@ class ImageOptim
|
|
23
24
|
unless image_optim.is_a?(ImageOptim)
|
24
25
|
fail ArgumentError, 'first parameter should be an ImageOptim instance'
|
25
26
|
end
|
27
|
+
|
26
28
|
@image_optim = image_optim
|
27
29
|
parse_options(options)
|
28
30
|
assert_no_unknown_options!(options)
|
@@ -49,6 +51,7 @@ class ImageOptim
|
|
49
51
|
unless format_from_name
|
50
52
|
fail "#{self.class}: can't guess applicable format from worker name"
|
51
53
|
end
|
54
|
+
|
52
55
|
[format_from_name.to_sym]
|
53
56
|
end
|
54
57
|
|
@@ -68,6 +71,7 @@ class ImageOptim
|
|
68
71
|
@image_optim.resolve_bin!(bin)
|
69
72
|
end
|
70
73
|
return if errors.empty?
|
74
|
+
|
71
75
|
fail BinResolver::Error, wrap_resolver_error_message(errors.join(', '))
|
72
76
|
end
|
73
77
|
|
@@ -98,6 +102,7 @@ class ImageOptim
|
|
98
102
|
known_keys = self.class.option_definitions.map(&:name)
|
99
103
|
unknown_options = options.reject{ |key, _value| known_keys.include?(key) }
|
100
104
|
return if unknown_options.empty?
|
105
|
+
|
101
106
|
fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
|
102
107
|
"for #{self}"
|
103
108
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/bin_resolver'
|
2
4
|
require 'image_optim/option_definition'
|
3
5
|
|
@@ -16,6 +18,7 @@ class ImageOptim
|
|
16
18
|
|
17
19
|
# Remember all classes inheriting from this one
|
18
20
|
def inherited(base)
|
21
|
+
super
|
19
22
|
@klasses << base
|
20
23
|
end
|
21
24
|
|
@@ -34,6 +37,7 @@ class ImageOptim
|
|
34
37
|
|
35
38
|
def option(name, default, type, description = nil, &proc)
|
36
39
|
attr_reader name
|
40
|
+
|
37
41
|
OptionDefinition.new(name, default, type, description, &proc).
|
38
42
|
tap{ |option_definition| option_definitions << option_definition }
|
39
43
|
end
|
@@ -83,6 +87,7 @@ class ImageOptim
|
|
83
87
|
klasses.map do |klass|
|
84
88
|
options = options_proc[klass]
|
85
89
|
next if options[:disable]
|
90
|
+
|
86
91
|
if !options.key?(:allow_lossy) && klass.method_defined?(:allow_lossy)
|
87
92
|
options[:allow_lossy] = image_optim.allow_lossy
|
88
93
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'image_optim/worker'
|
2
4
|
require 'exifr/jpeg'
|
3
5
|
|
@@ -7,7 +9,7 @@ class ImageOptim
|
|
7
9
|
#
|
8
10
|
# Jhead internally uses jpegtran which should be on path
|
9
11
|
class Jhead < Worker
|
10
|
-
ORIENTED = 2..8 # not top-left
|
12
|
+
ORIENTED = (2..8).freeze # not top-left
|
11
13
|
|
12
14
|
# Works on jpegs
|
13
15
|
def image_formats
|
@@ -25,6 +27,7 @@ class ImageOptim
|
|
25
27
|
|
26
28
|
def optimize(src, dst)
|
27
29
|
return false unless oriented?(src)
|
30
|
+
|
28
31
|
src.copy(dst)
|
29
32
|
args = %W[
|
30
33
|
-autorot
|