forked from toy/image_optim
/
bin.rb
143 lines (118 loc) · 3.9 KB
/
bin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true
require 'image_optim/bin_resolver/error'
require 'image_optim/bin_resolver/simple_version'
require 'image_optim/bin_resolver/comparable_condition'
require 'image_optim/cmd'
require 'image_optim/path'
require 'shellwords'
require 'digest/sha1'
require 'date'
class ImageOptim
class BinResolver
# Holds bin name and path, gets version
class Bin
class UnknownVersion < Error; end
class BadVersion < Error; end
attr_reader :name, :path, :version
def initialize(name, path)
@name = name.to_sym
@path = path.to_s
@version = detect_version
end
def digest
return @digest if defined?(@digest)
@digest = File.exist?(@path) && Digest::SHA1.file(@path).hexdigest
end
def to_s
"#{name} #{version || '?'} at #{path}"
end
is = ComparableCondition.is
FAIL_CHECKS = {
pngcrush: [
[is.between?('1.7.60', '1.7.65'), 'is known to produce broken pngs'],
[is == '1.7.80', 'loses one color in indexed images'],
],
pngquant: [
[is < '2.0', 'is not supported'],
],
}.freeze
WARN_CHECKS = {
advpng: [
[is == 'none', 'is of unknown version'],
[is < '1.17', 'does not use zopfli'],
],
gifsicle: [
[is < '1.85', 'does not support removing extension blocks'],
],
pngcrush: [
[is < '1.7.38', 'does not have blacken flag'],
],
pngquant: [
[is < '2.1', 'may be lossy even with quality `100-`'],
],
optipng: [
[is < '0.7', 'does not support -strip option'],
],
}.freeze
# Fail if version will not work properly
def check_fail!
unless version
fail UnknownVersion, "could not get version of #{name} at #{path}"
end
return unless FAIL_CHECKS[name]
FAIL_CHECKS[name].each do |matcher, message|
next unless matcher.match(version)
fail BadVersion, "#{self} (#{matcher}) #{message}"
end
end
# Run check_fail!, otherwise warn if version is known to misbehave
def check!
check_fail!
return unless WARN_CHECKS[name]
WARN_CHECKS[name].each do |matcher, message|
next unless matcher.match(version)
warn "WARN: #{self} (#{matcher}) #{message}"
break
end
end
private
# Wrap version_string with SimpleVersion
def detect_version
str = version_string
str && SimpleVersion.new(str)
end
# Getting version of bin, will fail for an unknown name
def version_string
case name
when :advpng
capture("#{escaped_path} --version 2> #{Path::NULL}")[/\bv(\d+(\.\d+)+|none)/, 1]
when :gifsicle, :jpegoptim, :optipng, :oxipng
capture("#{escaped_path} --version 2> #{Path::NULL}")[/\d+(\.\d+)+/]
when :svgo, :pngquant
capture("#{escaped_path} --version 2>&1")[/\A\d+(\.\d+)+/]
when :jhead, :'jpeg-recompress'
capture("#{escaped_path} -V 2> #{Path::NULL}")[/\d+(\.\d+)+/]
when :jpegtran
capture("#{escaped_path} -v - 2>&1")[/version (\d+\S*)/, 1]
when :pngcrush
capture("#{escaped_path} -version 2>&1")[/pngcrush (\d+(\.\d+)+)/, 1]
when :pngout
date_regexp = /[A-Z][a-z]{2} (?: |\d)\d \d{4}/
date_str = capture("#{escaped_path} 2>&1")[date_regexp]
Date.parse(date_str).strftime('%Y%m%d') if date_str
when :jpegrescan
# jpegrescan has no version so use first 8 characters of sha1 hex
Digest::SHA1.file(path).hexdigest[0, 8] if path
else
fail "getting `#{name}` version is not defined"
end
end
def capture(cmd)
Cmd.capture(cmd)
end
def escaped_path
path.shellescape
end
end
end
end