image_optim 0.17.1 → 0.18.0
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 +8 -8
- data/.gitignore +1 -0
- data/.travis.yml +5 -18
- data/CHANGELOG.markdown +10 -0
- data/README.markdown +31 -1
- data/bin/image_optim +3 -137
- data/image_optim.gemspec +6 -3
- data/lib/image_optim.rb +20 -3
- data/lib/image_optim/bin_resolver.rb +28 -1
- data/lib/image_optim/bin_resolver/bin.rb +17 -7
- data/lib/image_optim/cmd.rb +49 -0
- data/lib/image_optim/config.rb +64 -4
- data/lib/image_optim/image_path.rb +5 -0
- data/lib/image_optim/option_definition.rb +5 -3
- data/lib/image_optim/runner.rb +1 -2
- data/lib/image_optim/runner/option_parser.rb +216 -0
- data/lib/image_optim/worker.rb +32 -17
- data/lib/image_optim/worker/advpng.rb +7 -1
- data/lib/image_optim/worker/gifsicle.rb +16 -3
- data/lib/image_optim/worker/jhead.rb +15 -8
- data/lib/image_optim/worker/jpegoptim.rb +6 -2
- data/lib/image_optim/worker/jpegtran.rb +10 -3
- data/lib/image_optim/worker/optipng.rb +6 -1
- data/lib/image_optim/worker/pngcrush.rb +8 -1
- data/lib/image_optim/worker/pngout.rb +8 -1
- data/lib/image_optim/worker/svgo.rb +4 -1
- data/script/worker_analysis +523 -0
- data/script/worker_analysis.haml +153 -0
- data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +4 -5
- data/spec/image_optim/bin_resolver/simple_version_spec.rb +44 -21
- data/spec/image_optim/bin_resolver_spec.rb +63 -29
- data/spec/image_optim/cmd_spec.rb +66 -0
- data/spec/image_optim/config_spec.rb +38 -38
- data/spec/image_optim/handler_spec.rb +15 -12
- data/spec/image_optim/hash_helpers_spec.rb +14 -13
- data/spec/image_optim/image_path_spec.rb +22 -7
- data/spec/image_optim/runner/glob_helpers_spec.rb +6 -5
- data/spec/image_optim/runner/option_parser_spec.rb +99 -0
- data/spec/image_optim/space_spec.rb +5 -4
- data/spec/image_optim/worker_spec.rb +6 -5
- data/spec/image_optim_spec.rb +209 -237
- data/spec/spec_helper.rb +3 -0
- metadata +43 -11
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'image_optim/bin_resolver/error'
|
2
2
|
require 'image_optim/bin_resolver/simple_version'
|
3
3
|
require 'image_optim/bin_resolver/comparable_condition'
|
4
|
+
require 'image_optim/cmd'
|
5
|
+
require 'shellwords'
|
4
6
|
|
5
7
|
class ImageOptim
|
6
8
|
class BinResolver
|
@@ -11,7 +13,7 @@ class ImageOptim
|
|
11
13
|
attr_reader :name, :path, :version
|
12
14
|
def initialize(name, path)
|
13
15
|
@name = name.to_sym
|
14
|
-
@path = path
|
16
|
+
@path = path.to_s
|
15
17
|
@version = detect_version
|
16
18
|
end
|
17
19
|
|
@@ -59,18 +61,18 @@ class ImageOptim
|
|
59
61
|
def version_string
|
60
62
|
case name
|
61
63
|
when :advpng, :gifsicle, :jpegoptim, :optipng, :pngquant
|
62
|
-
|
64
|
+
capture("#{escaped_path} --version 2> /dev/null")[/\d+(\.\d+){1,}/]
|
63
65
|
when :svgo
|
64
|
-
|
66
|
+
capture("#{escaped_path} --version 2>&1")[/\d+(\.\d+){1,}/]
|
65
67
|
when :jhead
|
66
|
-
|
68
|
+
capture("#{escaped_path} -V 2> /dev/null")[/\d+(\.\d+){1,}/]
|
67
69
|
when :jpegtran
|
68
|
-
|
70
|
+
capture("#{escaped_path} -v - 2>&1")[/version (\d+\S*)/, 1]
|
69
71
|
when :pngcrush
|
70
|
-
|
72
|
+
capture("#{escaped_path} -version 2>&1")[/\d+(\.\d+){1,}/]
|
71
73
|
when :pngout
|
72
74
|
date_regexp = /[A-Z][a-z]{2} (?: |\d)\d \d{4}/
|
73
|
-
date_str =
|
75
|
+
date_str = capture("#{escaped_path} 2>&1")[date_regexp]
|
74
76
|
Date.parse(date_str).strftime('%Y%m%d') if date_str
|
75
77
|
when :jpegrescan
|
76
78
|
# jpegrescan has no version so just check presence
|
@@ -79,6 +81,14 @@ class ImageOptim
|
|
79
81
|
fail "getting `#{name}` version is not defined"
|
80
82
|
end
|
81
83
|
end
|
84
|
+
|
85
|
+
def capture(cmd)
|
86
|
+
Cmd.capture(cmd)
|
87
|
+
end
|
88
|
+
|
89
|
+
def escaped_path
|
90
|
+
path.shellescape
|
91
|
+
end
|
82
92
|
end
|
83
93
|
end
|
84
94
|
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
|
data/lib/image_optim/config.rb
CHANGED
@@ -2,6 +2,7 @@ require 'image_optim/option_helpers'
|
|
2
2
|
require 'image_optim/configuration_error'
|
3
3
|
require 'image_optim/hash_helpers'
|
4
4
|
require 'image_optim/worker'
|
5
|
+
require 'image_optim/cmd'
|
5
6
|
require 'set'
|
6
7
|
require 'yaml'
|
7
8
|
|
@@ -10,9 +11,13 @@ class ImageOptim
|
|
10
11
|
class Config
|
11
12
|
include OptionHelpers
|
12
13
|
|
14
|
+
# Global config path at `$XDG_CONFIG_HOME/image_optim.yml` (by default
|
15
|
+
# `~/.config/image_optim.yml`)
|
13
16
|
GLOBAL_PATH = begin
|
14
17
|
File.join(ENV['XDG_CONFIG_HOME'] || '~/.config', 'image_optim.yml')
|
15
18
|
end
|
19
|
+
|
20
|
+
# Local config path at `./.image_optim.yml`
|
16
21
|
LOCAL_PATH = './.image_optim.yml'
|
17
22
|
|
18
23
|
class << self
|
@@ -37,6 +42,9 @@ class ImageOptim
|
|
37
42
|
end
|
38
43
|
end
|
39
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`
|
40
48
|
def initialize(options)
|
41
49
|
config_paths = options.delete(:config_paths) || [GLOBAL_PATH, LOCAL_PATH]
|
42
50
|
config_paths = Array(config_paths)
|
@@ -50,18 +58,31 @@ class ImageOptim
|
|
50
58
|
@used = Set.new
|
51
59
|
end
|
52
60
|
|
61
|
+
# Gets value for key converted to symbol and mark option as used
|
53
62
|
def get!(key)
|
54
63
|
key = key.to_sym
|
55
64
|
@used << key
|
56
65
|
@options[key]
|
57
66
|
end
|
58
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!`)
|
59
76
|
def assert_no_unused_options!
|
60
77
|
unknown_options = @options.reject{ |key, _value| @used.include?(key) }
|
61
78
|
return if unknown_options.empty?
|
62
79
|
fail ConfigurationError, "unknown options #{unknown_options.inspect}"
|
63
80
|
end
|
64
81
|
|
82
|
+
# Nice level:
|
83
|
+
# * `10` by default and for `nil` or `true`
|
84
|
+
# * `0` for `false`
|
85
|
+
# * otherwise convert to integer
|
65
86
|
def nice
|
66
87
|
nice = get!(:nice)
|
67
88
|
|
@@ -75,6 +96,10 @@ class ImageOptim
|
|
75
96
|
end
|
76
97
|
end
|
77
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
|
78
103
|
def threads
|
79
104
|
threads = get!(:threads)
|
80
105
|
|
@@ -88,10 +113,40 @@ class ImageOptim
|
|
88
113
|
end
|
89
114
|
end
|
90
115
|
|
116
|
+
# Verbose mode, converted to boolean
|
91
117
|
def verbose
|
92
118
|
!!get!(:verbose)
|
93
119
|
end
|
94
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
|
+
# Options for worker class by its `bin_sym`:
|
146
|
+
# * `Hash` passed as is
|
147
|
+
# * `{}` for `true` or `nil`
|
148
|
+
# * `false` for `false`
|
149
|
+
# * otherwise fail with `ConfigurationError`
|
95
150
|
def for_worker(klass)
|
96
151
|
worker_options = get!(klass.bin_sym)
|
97
152
|
|
@@ -108,6 +163,7 @@ class ImageOptim
|
|
108
163
|
end
|
109
164
|
end
|
110
165
|
|
166
|
+
# yaml dump without document beginning prefix `---`
|
111
167
|
def to_s
|
112
168
|
YAML.dump(HashHelpers.deep_stringify_keys(@options)).sub(/\A---\n/, '')
|
113
169
|
end
|
@@ -118,13 +174,17 @@ class ImageOptim
|
|
118
174
|
def processor_count
|
119
175
|
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
120
176
|
when /darwin9/
|
121
|
-
|
177
|
+
Cmd.capture 'hwprefs cpu_count'
|
122
178
|
when /darwin/
|
123
|
-
(
|
179
|
+
if (Cmd.capture 'which hwprefs') != ''
|
180
|
+
Cmd.capture 'hwprefs thread_count'
|
181
|
+
else
|
182
|
+
Cmd.capture 'sysctl -n hw.ncpu'
|
183
|
+
end
|
124
184
|
when /linux/
|
125
|
-
|
185
|
+
Cmd.capture 'grep -c processor /proc/cpuinfo'
|
126
186
|
when /freebsd/
|
127
|
-
|
187
|
+
Cmd.capture 'sysctl -n hw.ncpu'
|
128
188
|
when /mswin|mingw/
|
129
189
|
require 'win32ole'
|
130
190
|
WIN32OLE.
|
@@ -3,9 +3,11 @@ class ImageOptim
|
|
3
3
|
class OptionDefinition
|
4
4
|
attr_reader :name, :default, :type, :description, :proc
|
5
5
|
|
6
|
-
def initialize(name, default,
|
7
|
-
if
|
8
|
-
type
|
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
|
9
11
|
end
|
10
12
|
|
11
13
|
@name = name.to_sym
|
data/lib/image_optim/runner.rb
CHANGED
@@ -3,7 +3,6 @@ require 'image_optim/hash_helpers'
|
|
3
3
|
require 'image_optim/runner/glob_helpers'
|
4
4
|
require 'image_optim/space'
|
5
5
|
require 'progress'
|
6
|
-
require 'optparse'
|
7
6
|
require 'find'
|
8
7
|
require 'yaml'
|
9
8
|
|
@@ -127,7 +126,7 @@ class ImageOptim
|
|
127
126
|
basename = File.basename(path)
|
128
127
|
globs.any? do |glob|
|
129
128
|
File.fnmatch(glob, relative_path, File::FNM_PATHNAME) ||
|
130
|
-
|
129
|
+
File.fnmatch(glob, basename, File::FNM_PATHNAME)
|
131
130
|
end
|
132
131
|
end
|
133
132
|
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'image_optim'
|
4
|
+
require 'image_optim/true_false_nil'
|
5
|
+
require 'image_optim/non_negative_integer_range'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
class ImageOptim
|
9
|
+
class Runner
|
10
|
+
# Parse options from arguments to image_optim binary
|
11
|
+
class OptionParser < ::OptionParser
|
12
|
+
# Parse and remove options from args, return options Hash
|
13
|
+
# Calls abort in case of parse error
|
14
|
+
def self.parse!(args)
|
15
|
+
# assume -v to be a request to print version if it is the only argument
|
16
|
+
args = %w[--version] if args == %w[-v]
|
17
|
+
|
18
|
+
options = {}
|
19
|
+
parser = new(options)
|
20
|
+
parser.parse!(args)
|
21
|
+
options
|
22
|
+
rescue OptionParser::ParseError => e
|
23
|
+
abort "#{e}\n\n#{parser.help}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# After initialization passes self and options to DEFINE
|
27
|
+
def initialize(options)
|
28
|
+
super
|
29
|
+
DEFINE.call(self, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Wraps and indents lines of overriden method
|
33
|
+
def help
|
34
|
+
text = super
|
35
|
+
|
36
|
+
# reserve one column
|
37
|
+
columns = terminal_columns - 1
|
38
|
+
# 1 for distance between summary and description
|
39
|
+
# 2 for additional indent
|
40
|
+
wrapped_indent = summary_indent + ' ' * (summary_width + 1 + 2)
|
41
|
+
wrapped_width = columns - wrapped_indent.length
|
42
|
+
# don't try to wrap if there is too little space for description
|
43
|
+
return text if wrapped_width < 20
|
44
|
+
|
45
|
+
wrapped = ''
|
46
|
+
text.split("\n").each do |line|
|
47
|
+
if line.length <= columns
|
48
|
+
wrapped << line << "\n"
|
49
|
+
else
|
50
|
+
indented = line =~ /^\s/
|
51
|
+
wrapped << line.slice!(wrap_regex(columns)) << "\n"
|
52
|
+
line.scan(wrap_regex(wrapped_width)) do |part|
|
53
|
+
wrapped << wrapped_indent if indented
|
54
|
+
wrapped << part << "\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
wrapped
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def terminal_columns
|
64
|
+
stty_columns = `stty size 2> /dev/null`[/^\d+ (\d+)$/, 1]
|
65
|
+
stty_columns ? stty_columns.to_i : `tput cols`.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
def wrap_regex(width)
|
69
|
+
/.*?.{1,#{width}}(?:\s|\z)/
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
ImageOptim::Runner::OptionParser::DEFINE = proc do |op, options|
|
76
|
+
unless op.is_a?(OptionParser)
|
77
|
+
fail ArgumentError, "expected instance of OptionParser, got #{op.inspect}"
|
78
|
+
end
|
79
|
+
unless options.is_a?(Hash)
|
80
|
+
fail ArgumentError, "expected instance of Hash, got #{options.inspect}"
|
81
|
+
end
|
82
|
+
|
83
|
+
ImageOptim::TrueFalseNil.add_to_option_parser(op)
|
84
|
+
ImageOptim::NonNegativeIntegerRange.add_to_option_parser(op)
|
85
|
+
|
86
|
+
op.banner = <<-TEXT.gsub(/^\s*\|/, '')
|
87
|
+
|#{ImageOptim.full_version}
|
88
|
+
|
|
89
|
+
|Usege:
|
90
|
+
| #{op.program_name} [options] image_path …
|
91
|
+
|
|
92
|
+
|Configuration will be read and prepanded to options from two paths:
|
93
|
+
| #{ImageOptim::Config::GLOBAL_PATH}
|
94
|
+
| #{ImageOptim::Config::LOCAL_PATH}
|
95
|
+
|
|
96
|
+
TEXT
|
97
|
+
|
98
|
+
op.on('--config-paths PATH1,PATH2', Array, 'Config paths to use instead of '\
|
99
|
+
'default ones') do |paths|
|
100
|
+
options[:config_paths] = paths
|
101
|
+
end
|
102
|
+
|
103
|
+
op.separator nil
|
104
|
+
|
105
|
+
op.on('-r', '-R', '--recursive', 'Recursively scan directories '\
|
106
|
+
'for images') do |recursive|
|
107
|
+
options[:recursive] = recursive
|
108
|
+
end
|
109
|
+
|
110
|
+
op.on("--exclude-dir 'GLOB'", 'Glob for excluding directories '\
|
111
|
+
'(defaults to .*)') do |glob|
|
112
|
+
options[:exclude_dir_glob] = glob
|
113
|
+
end
|
114
|
+
|
115
|
+
op.on("--exclude-file 'GLOB'", 'Glob for excluding files '\
|
116
|
+
'(defaults to .*)') do |glob|
|
117
|
+
options[:exclude_file_glob] = glob
|
118
|
+
end
|
119
|
+
|
120
|
+
op.on("--exclude 'GLOB'", 'Set glob for excluding both directories and '\
|
121
|
+
'files') do |glob|
|
122
|
+
options[:exclude_file_glob] = options[:exclude_dir_glob] = glob
|
123
|
+
end
|
124
|
+
|
125
|
+
op.separator nil
|
126
|
+
|
127
|
+
op.on('--[no-]threads N', Integer, 'Number of threads or disable '\
|
128
|
+
'(defaults to number of processors)') do |threads|
|
129
|
+
options[:threads] = threads
|
130
|
+
end
|
131
|
+
|
132
|
+
op.on('--[no-]nice N', Integer, 'Nice level (defaults to 10)') do |nice|
|
133
|
+
options[:nice] = nice
|
134
|
+
end
|
135
|
+
|
136
|
+
op.on('--[no-]pack', 'Require image_optim_pack or disable it, '\
|
137
|
+
'by default image_optim_pack will be used if available, '\
|
138
|
+
'will turn on skip-missing-workers unless explicitly disabled') do |pack|
|
139
|
+
options[:pack] = pack
|
140
|
+
end
|
141
|
+
|
142
|
+
op.separator nil
|
143
|
+
op.separator ' Disabling workers:'
|
144
|
+
|
145
|
+
op.on('--[no-]skip-missing-workers', 'Skip workers with missing or '\
|
146
|
+
'problematic binaries') do |skip|
|
147
|
+
options[:skip_missing_workers] = skip
|
148
|
+
end
|
149
|
+
|
150
|
+
ImageOptim::Worker.klasses.each do |klass|
|
151
|
+
bin = klass.bin_sym
|
152
|
+
op.on("--no-#{bin}", "disable #{bin} worker") do |enable|
|
153
|
+
options[bin] = enable
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
op.separator nil
|
158
|
+
op.separator ' Worker options:'
|
159
|
+
|
160
|
+
ImageOptim::Worker.klasses.each_with_index do |klass, i|
|
161
|
+
next if klass.option_definitions.empty?
|
162
|
+
op.separator nil unless i.zero?
|
163
|
+
|
164
|
+
bin = klass.bin_sym
|
165
|
+
klass.option_definitions.each do |option_definition|
|
166
|
+
name = option_definition.name.to_s.gsub('_', '-')
|
167
|
+
default = option_definition.default
|
168
|
+
type = option_definition.type
|
169
|
+
|
170
|
+
type, marking = case
|
171
|
+
when [TrueClass, FalseClass, ImageOptim::TrueFalseNil].include?(type)
|
172
|
+
[type, 'B']
|
173
|
+
when Integer >= type
|
174
|
+
[Integer, 'N']
|
175
|
+
when Array >= type
|
176
|
+
[Array, 'a,b,c']
|
177
|
+
when ImageOptim::NonNegativeIntegerRange == type
|
178
|
+
[type, 'M-N']
|
179
|
+
else
|
180
|
+
fail "Unknown type #{type}"
|
181
|
+
end
|
182
|
+
|
183
|
+
description_lines = %W[
|
184
|
+
#{option_definition.description.gsub(' - ', ' - ')}
|
185
|
+
(defaults to #{default})
|
186
|
+
].join(' ')
|
187
|
+
|
188
|
+
op.on("--#{bin}-#{name} #{marking}", type, *description_lines) do |value|
|
189
|
+
options[bin] = {} unless options[bin].is_a?(Hash)
|
190
|
+
options[bin][option_definition.name.to_sym] = value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
op.separator nil
|
196
|
+
op.separator ' Common options:'
|
197
|
+
|
198
|
+
op.on_tail('-v', '--verbose', 'Verbose output') do
|
199
|
+
options[:verbose] = true
|
200
|
+
end
|
201
|
+
|
202
|
+
op.on_tail('-h', '--help', 'Show help and exit') do
|
203
|
+
puts op.help
|
204
|
+
exit
|
205
|
+
end
|
206
|
+
|
207
|
+
op.on_tail('--version', 'Show version and exit') do
|
208
|
+
puts ImageOptim.version
|
209
|
+
exit
|
210
|
+
end
|
211
|
+
|
212
|
+
op.on_tail('--info', 'Show environment info and exit') do
|
213
|
+
options[:verbose] = true
|
214
|
+
options[:only_info] = true
|
215
|
+
end
|
216
|
+
end
|