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.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.appveyor.yml +46 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +110 -0
  5. data/.travis.yml +42 -0
  6. data/CHANGELOG.markdown +316 -0
  7. data/CONTRIBUTING.markdown +11 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +358 -0
  11. data/Vagrantfile +38 -0
  12. data/bin/image_optim +28 -0
  13. data/image_optim.gemspec +34 -0
  14. data/lib/image_optim.rb +267 -0
  15. data/lib/image_optim/bin_resolver.rb +142 -0
  16. data/lib/image_optim/bin_resolver/bin.rb +115 -0
  17. data/lib/image_optim/bin_resolver/comparable_condition.rb +60 -0
  18. data/lib/image_optim/bin_resolver/error.rb +6 -0
  19. data/lib/image_optim/bin_resolver/simple_version.rb +31 -0
  20. data/lib/image_optim/cache.rb +72 -0
  21. data/lib/image_optim/cache_path.rb +16 -0
  22. data/lib/image_optim/cmd.rb +122 -0
  23. data/lib/image_optim/config.rb +219 -0
  24. data/lib/image_optim/configuration_error.rb +3 -0
  25. data/lib/image_optim/handler.rb +57 -0
  26. data/lib/image_optim/hash_helpers.rb +45 -0
  27. data/lib/image_optim/image_meta.rb +20 -0
  28. data/lib/image_optim/non_negative_integer_range.rb +11 -0
  29. data/lib/image_optim/optimized_path.rb +25 -0
  30. data/lib/image_optim/option_definition.rb +38 -0
  31. data/lib/image_optim/option_helpers.rb +17 -0
  32. data/lib/image_optim/path.rb +70 -0
  33. data/lib/image_optim/runner.rb +139 -0
  34. data/lib/image_optim/runner/glob_helpers.rb +45 -0
  35. data/lib/image_optim/runner/option_parser.rb +246 -0
  36. data/lib/image_optim/space.rb +29 -0
  37. data/lib/image_optim/true_false_nil.rb +16 -0
  38. data/lib/image_optim/worker.rb +170 -0
  39. data/lib/image_optim/worker/advpng.rb +37 -0
  40. data/lib/image_optim/worker/class_methods.rb +107 -0
  41. data/lib/image_optim/worker/gifsicle.rb +65 -0
  42. data/lib/image_optim/worker/jhead.rb +47 -0
  43. data/lib/image_optim/worker/jpegoptim.rb +63 -0
  44. data/lib/image_optim/worker/jpegrecompress.rb +49 -0
  45. data/lib/image_optim/worker/jpegtran.rb +48 -0
  46. data/lib/image_optim/worker/optipng.rb +53 -0
  47. data/lib/image_optim/worker/pngcrush.rb +56 -0
  48. data/lib/image_optim/worker/pngout.rb +40 -0
  49. data/lib/image_optim/worker/pngquant.rb +61 -0
  50. data/lib/image_optim/worker/svgo.rb +34 -0
  51. data/script/template/jquery-2.1.3.min.js +4 -0
  52. data/script/template/sortable-0.6.0.min.js +2 -0
  53. data/script/template/worker_analysis.erb +254 -0
  54. data/script/update_worker_options_in_readme +59 -0
  55. data/script/worker_analysis +589 -0
  56. data/spec/image_optim/bin_resolver/comparable_condition_spec.rb +37 -0
  57. data/spec/image_optim/bin_resolver/simple_version_spec.rb +65 -0
  58. data/spec/image_optim/bin_resolver_spec.rb +290 -0
  59. data/spec/image_optim/cache_path_spec.rb +57 -0
  60. data/spec/image_optim/cache_spec.rb +162 -0
  61. data/spec/image_optim/cmd_spec.rb +93 -0
  62. data/spec/image_optim/config_spec.rb +254 -0
  63. data/spec/image_optim/handler_spec.rb +90 -0
  64. data/spec/image_optim/hash_helpers_spec.rb +74 -0
  65. data/spec/image_optim/image_meta_spec.rb +61 -0
  66. data/spec/image_optim/optimized_path_spec.rb +58 -0
  67. data/spec/image_optim/option_definition_spec.rb +138 -0
  68. data/spec/image_optim/option_helpers_spec.rb +25 -0
  69. data/spec/image_optim/path_spec.rb +103 -0
  70. data/spec/image_optim/runner/glob_helpers_spec.rb +21 -0
  71. data/spec/image_optim/runner/option_parser_spec.rb +105 -0
  72. data/spec/image_optim/space_spec.rb +23 -0
  73. data/spec/image_optim/worker/optipng_spec.rb +102 -0
  74. data/spec/image_optim/worker/pngquant_spec.rb +67 -0
  75. data/spec/image_optim/worker_spec.rb +303 -0
  76. data/spec/image_optim_spec.rb +259 -0
  77. data/spec/images/broken_jpeg +1 -0
  78. data/spec/images/comparison.png +0 -0
  79. data/spec/images/decompressed.jpeg +0 -0
  80. data/spec/images/icecream.gif +0 -0
  81. data/spec/images/image.jpg +0 -0
  82. data/spec/images/invisiblepixels/generate +24 -0
  83. data/spec/images/invisiblepixels/image.png +0 -0
  84. data/spec/images/lena.jpg +0 -0
  85. data/spec/images/orient/0.jpg +0 -0
  86. data/spec/images/orient/1.jpg +0 -0
  87. data/spec/images/orient/2.jpg +0 -0
  88. data/spec/images/orient/3.jpg +0 -0
  89. data/spec/images/orient/4.jpg +0 -0
  90. data/spec/images/orient/5.jpg +0 -0
  91. data/spec/images/orient/6.jpg +0 -0
  92. data/spec/images/orient/7.jpg +0 -0
  93. data/spec/images/orient/8.jpg +0 -0
  94. data/spec/images/orient/generate +23 -0
  95. data/spec/images/orient/original.jpg +0 -0
  96. data/spec/images/quant/64.png +0 -0
  97. data/spec/images/quant/generate +25 -0
  98. data/spec/images/rails.png +0 -0
  99. data/spec/images/test.svg +3 -0
  100. data/spec/images/transparency1.png +0 -0
  101. data/spec/images/transparency2.png +0 -0
  102. data/spec/images/vergroessert.jpg +0 -0
  103. data/spec/spec_helper.rb +93 -0
  104. metadata +281 -0
@@ -0,0 +1,246 @@
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
+ |Usage:
90
+ | #{op.program_name} [options] image_path …
91
+ |
92
+ |Configuration will be read and prepended 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-progress', 'Disable showing progress') do |show_progress|
128
+ options[:show_progress] = show_progress
129
+ end
130
+
131
+ op.on('--[no-]threads N', Integer, 'Number of threads or disable '\
132
+ '(defaults to number of processors)') do |threads|
133
+ options[:threads] = threads
134
+ end
135
+
136
+ op.on('--[no-]nice N', Integer, 'Nice level, priority of all used tools '\
137
+ 'with higher value meaning lower priority, in range -20..19, negative '\
138
+ 'values can be set only if run by root user (defaults to 10)') do |nice|
139
+ options[:nice] = nice
140
+ end
141
+
142
+ op.on('--[no-]pack', 'Require image_optim_pack or disable it, '\
143
+ 'by default image_optim_pack will be used if available, '\
144
+ 'will turn on skip-missing-workers unless explicitly disabled') do |pack|
145
+ options[:pack] = pack
146
+ end
147
+
148
+ op.separator nil
149
+ op.separator ' Caching:'
150
+
151
+ op.on('--cache-dir DIR', 'Cache optimized images '\
152
+ 'into the specified directory') do |cache_dir|
153
+ options[:cache_dir] = cache_dir
154
+ end
155
+
156
+ op.on('--cache-worker-digests', 'Cache worker digests '\
157
+ '(updating workers invalidates cache)') do |cache_worker_digests|
158
+ options[:cache_worker_digests] = cache_worker_digests
159
+ end
160
+
161
+ op.separator nil
162
+ op.separator ' Disabling workers:'
163
+
164
+ op.on('--[no-]skip-missing-workers', 'Skip workers with missing or '\
165
+ 'problematic binaries') do |skip|
166
+ options[:skip_missing_workers] = skip
167
+ end
168
+
169
+ ImageOptim::Worker.klasses.each do |klass|
170
+ bin = klass.bin_sym
171
+ op.on("--no-#{bin}", "disable #{bin} worker") do |enable|
172
+ options[bin] = enable
173
+ end
174
+ end
175
+
176
+ op.separator nil
177
+ op.separator ' Worker options:'
178
+
179
+ op.on('--allow-lossy', 'Allow lossy workers and '\
180
+ 'optimizations') do |allow_lossy|
181
+ options[:allow_lossy] = allow_lossy
182
+ end
183
+
184
+ op.on('--timeout N', 'Sets a timeout for workers') do |timeout|
185
+ options[:timeout] = timeout.to_i
186
+ end
187
+
188
+ op.separator nil
189
+
190
+ ImageOptim::Worker.klasses.each_with_index do |klass, i|
191
+ next if klass.option_definitions.empty?
192
+ op.separator nil unless i.zero?
193
+
194
+ bin = klass.bin_sym
195
+ klass.option_definitions.each do |option_definition|
196
+ name = option_definition.name.to_s.tr('_', '-')
197
+ default = option_definition.default_description
198
+ type = option_definition.type
199
+
200
+ type, marking = case
201
+ when [TrueClass, FalseClass, ImageOptim::TrueFalseNil].include?(type)
202
+ [type, 'B']
203
+ when Integer >= type
204
+ [Integer, 'N']
205
+ when Array >= type
206
+ [Array, 'a,b,c']
207
+ when ImageOptim::NonNegativeIntegerRange == type
208
+ [type, 'M-N']
209
+ else
210
+ fail "Unknown type #{type}"
211
+ end
212
+
213
+ description = option_definition.description.gsub(' - ', ' - ')
214
+ unless description['(defaults']
215
+ description << " (defaults to #{default})"
216
+ end
217
+
218
+ op.on("--#{bin}-#{name} #{marking}", type, description) do |value|
219
+ options[bin] = {} unless options[bin].is_a?(Hash)
220
+ options[bin][option_definition.name.to_sym] = value
221
+ end
222
+ end
223
+ end
224
+
225
+ op.separator nil
226
+ op.separator ' Common options:'
227
+
228
+ op.on_tail('-v', '--verbose', 'Verbose output') do
229
+ options[:verbose] = true
230
+ end
231
+
232
+ op.on_tail('-h', '--help', 'Show help and exit') do
233
+ puts op.help
234
+ exit
235
+ end
236
+
237
+ op.on_tail('--version', 'Show version and exit') do
238
+ puts ImageOptim.version
239
+ exit
240
+ end
241
+
242
+ op.on_tail('--info', 'Show environment info and exit') do
243
+ options[:verbose] = true
244
+ options[:only_info] = true
245
+ end
246
+ end
@@ -0,0 +1,29 @@
1
+ class ImageOptim
2
+ # Present size in readable form as fixed length string
3
+ module Space
4
+ SIZE_SYMBOLS = %w[B K M G T P E].freeze
5
+ BASE = 1024.0
6
+ PRECISION = 1
7
+ LENGTH = 4 + PRECISION + 1
8
+
9
+ EMPTY_SPACE = ' ' * LENGTH
10
+
11
+ def self.space(size)
12
+ case size
13
+ when 0, nil
14
+ EMPTY_SPACE
15
+ else
16
+ log_denominator = Math.log(size.abs) / Math.log(BASE)
17
+ degree = [log_denominator.floor, SIZE_SYMBOLS.length - 1].min
18
+ number_string = if degree.zero?
19
+ size.to_s
20
+ else
21
+ denominator = BASE**degree
22
+ number = size / denominator
23
+ format("%.#{PRECISION}f", number)
24
+ end
25
+ "#{number_string}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ class ImageOptim
2
+ # Denote ternary value (`true`/`false`/`nil`) for worker option
3
+ class TrueFalseNil
4
+ # Add handling of ternary value in OptionParser instance, maps `nil` and
5
+ # `'nil'` to `nil`
6
+ def self.add_to_option_parser(option_parser)
7
+ completing = OptionParser.top.atype[TrueClass][0].merge('nil' => nil)
8
+ option_parser.accept(self, completing){ |_arg, val| val }
9
+ end
10
+
11
+ # Convert everything truthy to `true`, leave `false` and `nil` as is
12
+ def self.convert(v)
13
+ v && true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,170 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'image_optim/cmd'
4
+ require 'image_optim/configuration_error'
5
+ require 'image_optim/path'
6
+ require 'image_optim/worker/class_methods'
7
+ require 'shellwords'
8
+ require 'English'
9
+
10
+ class ImageOptim
11
+ # Base class for all workers
12
+ class Worker
13
+ extend ClassMethods
14
+
15
+ class TimeoutExceeded < StandardError; end
16
+
17
+ class << self
18
+ # Default init for worker is new
19
+ # Check example of override in gifsicle worker
20
+ alias_method :init, :new
21
+ end
22
+
23
+ # Configure (raises on extra options)
24
+ def initialize(image_optim, options = {})
25
+ unless image_optim.is_a?(ImageOptim)
26
+ fail ArgumentError, 'first parameter should be an ImageOptim instance'
27
+ end
28
+ @image_optim = image_optim
29
+ parse_options(options)
30
+ assert_no_unknown_options!(options)
31
+ end
32
+
33
+ # Return hash with worker options
34
+ def options
35
+ hash = {}
36
+ self.class.option_definitions.each do |option|
37
+ hash[option.name] = send(option.name)
38
+ end
39
+ hash
40
+ end
41
+
42
+ # Optimize image at src, output at dst, must be overriden in subclass
43
+ # return true on success
44
+ def optimize(_src, _dst)
45
+ fail NotImplementedError, "implement method optimize in #{self.class}"
46
+ end
47
+
48
+ # List of formats which worker can optimize
49
+ def image_formats
50
+ format_from_name = self.class.name.downcase[/gif|jpeg|png|svg/]
51
+ unless format_from_name
52
+ fail "#{self.class}: can't guess applicable format from worker name"
53
+ end
54
+ [format_from_name.to_sym]
55
+ end
56
+
57
+ # Ordering in list of workers, 0 by default
58
+ def run_order
59
+ 0
60
+ end
61
+
62
+ # List of bins used by worker
63
+ def used_bins
64
+ [self.class.bin_sym]
65
+ end
66
+
67
+ # Resolve used bins, raise exception concatenating all messages
68
+ def resolve_used_bins!
69
+ errors = BinResolver.collect_errors(used_bins) do |bin|
70
+ @image_optim.resolve_bin!(bin)
71
+ end
72
+ return if errors.empty?
73
+ fail BinResolver::Error, wrap_resolver_error_message(errors.join(', '))
74
+ end
75
+
76
+ # Check if operation resulted in optimized file
77
+ def optimized?(src, dst)
78
+ dst_size = dst.size?
79
+ dst_size && dst_size < src.size
80
+ end
81
+
82
+ # Short inspect
83
+ def inspect
84
+ options_string = self.class.option_definitions.map do |option|
85
+ " @#{option.name}=#{send(option.name).inspect}"
86
+ end.join(',')
87
+ "#<#{self.class}#{options_string}>"
88
+ end
89
+
90
+ private
91
+
92
+ def parse_options(options)
93
+ self.class.option_definitions.each do |option_definition|
94
+ value = option_definition.value(self, options)
95
+ instance_variable_set("@#{option_definition.name}", value)
96
+ end
97
+ end
98
+
99
+ def assert_no_unknown_options!(options)
100
+ known_keys = self.class.option_definitions.map(&:name)
101
+ unknown_options = options.reject{ |key, _value| known_keys.include?(key) }
102
+ return if unknown_options.empty?
103
+ fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
104
+ "for #{self}"
105
+ end
106
+
107
+ # Forward bin resolving to image_optim
108
+ def resolve_bin!(bin)
109
+ @image_optim.resolve_bin!(bin)
110
+ rescue BinResolver::Error => e
111
+ raise e, wrap_resolver_error_message(e.message), e.backtrace
112
+ end
113
+
114
+ def wrap_resolver_error_message(message)
115
+ name = self.class.bin_sym
116
+ "#{name} worker: #{message}; please provide proper binary or "\
117
+ "disable this worker (--no-#{name} argument or "\
118
+ "`:#{name} => false` through options)"
119
+ end
120
+
121
+ # Run command setting priority and hiding output
122
+ def execute(bin, *arguments)
123
+ resolve_bin!(bin)
124
+
125
+ cmd_args = [bin, *arguments].map(&:to_s)
126
+
127
+ start = Time.now
128
+
129
+ success = run_command(cmd_args)
130
+
131
+ if @image_optim.verbose
132
+ seconds = Time.now - start
133
+ $stderr << "#{success ? '✓' : '✗'} #{seconds}s #{cmd_args.shelljoin}\n"
134
+ end
135
+
136
+ success
137
+ end
138
+
139
+ # Run command defining environment, setting nice level, removing output and
140
+ # reraising signal exception
141
+ def run_command(cmd_args)
142
+ args = if RUBY_VERSION < '1.9' || defined?(JRUBY_VERSION)
143
+ %W[
144
+ env PATH=#{@image_optim.env_path.shellescape}
145
+ nice -n #{@image_optim.nice}
146
+ #{cmd_args.shelljoin} > #{Path::NULL} 2>&1
147
+ ].join(' ')
148
+ else
149
+ [
150
+ {'PATH' => @image_optim.env_path},
151
+ %W[nice -n #{@image_optim.nice}],
152
+ cmd_args,
153
+ {:out => Path::NULL, :err => Path::NULL},
154
+ ].flatten
155
+ end
156
+
157
+ seconds_to_timeout = timeout || @image_optim.timeout
158
+
159
+ if seconds_to_timeout > 0
160
+ begin
161
+ Cmd.run_with_timeout(seconds_to_timeout, *args)
162
+ rescue Cmd::TimeoutExceeded
163
+ raise ImageOptim::Worker::TimeoutExceeded
164
+ end
165
+ else
166
+ Cmd.run(*args)
167
+ end
168
+ end
169
+ end
170
+ end