image_optim 0.9.1 → 0.10.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/.travis.yml +0 -2
- data/README.markdown +34 -1
- data/bin/image_optim +21 -113
- data/image_optim.gemspec +2 -2
- data/lib/image_optim/bin_not_found_error.rb +3 -0
- data/lib/image_optim/bin_resolver.rb +50 -0
- data/lib/image_optim/config.rb +137 -0
- data/lib/image_optim/configuration_error.rb +3 -0
- data/lib/image_optim/handler.rb +27 -0
- data/lib/image_optim/hash_helpers.rb +35 -0
- data/lib/image_optim/image_path.rb +27 -2
- data/lib/image_optim/option_helpers.rb +1 -20
- data/lib/image_optim/railtie.rb +19 -0
- data/lib/image_optim/runner.rb +107 -0
- data/lib/image_optim/true_false_nil.rb +3 -0
- data/lib/image_optim/worker/advpng.rb +1 -0
- data/lib/image_optim/worker/jpegoptim.rb +1 -0
- data/lib/image_optim/worker/optipng.rb +2 -0
- data/lib/image_optim/worker/pngout.rb +1 -0
- data/lib/image_optim/worker.rb +25 -6
- data/lib/image_optim.rb +60 -126
- data/script/update_worker_options_in_readme +36 -0
- data/spec/image_optim/bin_resolver_spec.rb +92 -0
- data/spec/image_optim/config_spec.rb +153 -0
- data/spec/image_optim/handler_spec.rb +44 -0
- data/spec/image_optim/hash_helpers_spec.rb +74 -0
- data/spec/image_optim/image_path_spec.rb +39 -0
- data/spec/image_optim_spec.rb +26 -94
- metadata +24 -6
- data/TODO +0 -12
- data/script/options_for_readme +0 -14
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzI5M2M3MWM1OWNiMDM5MGFiMmFkNDYwYzBiZjY5NWFiNDc0NTkyNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YWI3NDcwN2U3ZmIzZGI4NjYxZmJkOTVhYmQ5OGMzNTYyNGFlODI3Ng==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjVkYjUwYTEzMzQ2ZWZjNjg3ZWU1ZDJlMGRmY2FjOTc3NzU4YThlYTVmMmE2
|
10
|
+
YWEwODAzMGVkN2QyZDc4MjUyZGQ4ZDAyMTgzMDA3ZjVjNDQ3ODc5NTBlMzI1
|
11
|
+
MDU4ZDRlZTAzNjc2M2IwNWMxZGNjZjY0OWQ0MDQ2NGNhMThlYTE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YjlhYjk1NDIyM2FlMzE4NjZlM2QxOTJjYzk0ZjJlYTI1YTYwMGUxODA3Yzdj
|
14
|
+
NzdjODY2NDBjZDIwODA2YTIzNThjZWJjMTg2ZGRlYjllYjg3NGIxNzU0Y2Fk
|
15
|
+
NWYwNDYwODkwN2FjNmI2ODU5NWNlNjdmMjY5NjdkZjk3ZTY3NDM=
|
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -13,7 +13,10 @@ Optimize (lossless compress) images (jpeg, png, gif) using external utilities:
|
|
13
13
|
|
14
14
|
Based on [ImageOptim.app](http://imageoptim.com/).
|
15
15
|
|
16
|
+
[](http://badge.fury.io/rb/image_optim)
|
16
17
|
[](https://travis-ci.org/toy/image_optim)
|
18
|
+
[](https://codeclimate.com/github/toy/image_optim)
|
19
|
+
[](https://gemnasium.com/toy/image_optim)
|
17
20
|
|
18
21
|
## Gem installation
|
19
22
|
|
@@ -123,10 +126,38 @@ Multiple images:
|
|
123
126
|
|
124
127
|
image_optim.optimize_images!(Dir['*.*'])
|
125
128
|
|
129
|
+
### From rails
|
130
|
+
|
131
|
+
`ImageOptim::Railtie` will automatically initialize processing of assets if `config.assets.compress` is true.
|
132
|
+
|
133
|
+
As image optimization can be time consuming you may prefer to optimize original asset files.
|
134
|
+
|
135
|
+
Automatic assets processing can be turned off by setting `config.assets.image_optim = false`.
|
136
|
+
|
137
|
+
## Configuration
|
138
|
+
|
139
|
+
Configuration in YAML format will be read and prepanded to options from two paths:
|
140
|
+
|
141
|
+
* `$XDG_CONFIG_HOME/image_optim.yml` (by default `~/.config/image_optim.yml`)
|
142
|
+
* `.image_optim.yml` in current working directory
|
143
|
+
|
144
|
+
Example configuration:
|
145
|
+
|
146
|
+
nice: 20
|
147
|
+
pngout: false # disable
|
148
|
+
optipng:
|
149
|
+
level: 5
|
150
|
+
|
126
151
|
## Options
|
127
152
|
|
153
|
+
* `:nice` — Nice level *(defaults to 10)*
|
154
|
+
* `:threads` — Number of threads or disable *(defaults to number of processors)*
|
155
|
+
* `:verbose` — Verbose output *(defaults to false)*
|
156
|
+
|
128
157
|
Worker can be disabled by passing false instead of options hash.
|
129
158
|
|
159
|
+
<!---<worker-options>-->
|
160
|
+
|
130
161
|
### pngcrush
|
131
162
|
* `:chunks` — List of chunks to remove or 'alla' - all except tRNS/transparency or 'allb' - all except tRNS and gAMA/gamma *(defaults to alla)*
|
132
163
|
* `:fix` — Fix otherwise fatal conditions such as bad CRCs *(defaults to false)*
|
@@ -150,11 +181,13 @@ Worker can be disabled by passing false instead of options hash.
|
|
150
181
|
### jpegtran
|
151
182
|
* `:copy_chunks` — Copy all chunks *(defaults to false)*
|
152
183
|
* `:progressive` — Create progressive JPEG file *(defaults to true)*
|
153
|
-
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to
|
184
|
+
* `:jpegrescan` — Use jpegtran through jpegrescan, ignore progressive option *(defaults to false)*
|
154
185
|
|
155
186
|
### gifsicle
|
156
187
|
* `:interlace` — Turn interlacing on *(defaults to false)*
|
157
188
|
|
189
|
+
<!---</worker-options>-->
|
190
|
+
|
158
191
|
## Copyright
|
159
192
|
|
160
193
|
Copyright (c) 2012-2013 Ivan Kuchin. See LICENSE.txt for details.
|
data/bin/image_optim
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: UTF-8
|
3
3
|
|
4
|
-
require 'image_optim'
|
5
|
-
require 'progress'
|
6
|
-
require 'optparse'
|
7
|
-
require 'find'
|
4
|
+
require 'image_optim/runner'
|
8
5
|
|
6
|
+
args = ARGV.dup
|
9
7
|
options = {}
|
10
8
|
|
11
|
-
OptionParser.accept(ImageOptim::TrueFalseNil, OptionParser.top.atype[TrueClass][0].merge('nil' => nil)){ |arg, val| val }
|
12
|
-
|
13
9
|
option_parser = OptionParser.new do |op|
|
14
|
-
op.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
op.accept(ImageOptim::TrueFalseNil, OptionParser.top.atype[TrueClass][0].merge('nil' => nil)){ |arg, val| val }
|
11
|
+
|
12
|
+
op.banner = <<-TEXT.gsub(/^\s*\|/, '')
|
13
|
+
|#{op.program_name} v#{ImageOptim.version}
|
14
|
+
|
|
15
|
+
|Usege:
|
16
|
+
| #{op.program_name} [options] image_path …
|
17
|
+
|
|
18
|
+
|Configuration will be read and prepanded to options from two paths:
|
19
|
+
| #{ImageOptim::Config::GLOBAL_CONFIG_PATH}
|
20
|
+
| #{ImageOptim::Config::LOCAL_CONFIG_PATH} (in current working directory)
|
21
|
+
|
|
20
22
|
TEXT
|
21
23
|
|
22
24
|
op.on('-r', '-R', '--recursive', 'Recurively scan directories for images') do |recursive|
|
@@ -84,7 +86,7 @@ Usege:
|
|
84
86
|
end
|
85
87
|
|
86
88
|
op.on_tail('-h', '--help', 'Show full help') do
|
87
|
-
puts
|
89
|
+
puts op.help
|
88
90
|
exit
|
89
91
|
end
|
90
92
|
|
@@ -95,108 +97,14 @@ Usege:
|
|
95
97
|
end
|
96
98
|
|
97
99
|
begin
|
98
|
-
option_parser.parse!
|
100
|
+
option_parser.parse!(args)
|
101
|
+
ImageOptim::Runner.run!(args, options) or exit 1
|
99
102
|
rescue OptionParser::ParseError => e
|
100
103
|
abort "#{e.to_s}\n\n#{option_parser.help}"
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
prefix = ' ' * level
|
106
|
-
options.each do |key, value|
|
107
|
-
if value.is_a?(Hash)
|
108
|
-
puts "#{prefix}#{key}:"
|
109
|
-
print_options(value, level + 1)
|
110
|
-
else
|
111
|
-
puts "#{prefix}#{key}: #{value.inspect}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
puts 'Options:'
|
117
|
-
print_options(options)
|
118
|
-
end
|
119
|
-
|
120
|
-
if ARGV.empty?
|
121
|
-
abort "specify image paths to optimize\n\n#{option_parser.help}"
|
122
|
-
else
|
123
|
-
recursive = options.delete(:recursive)
|
124
|
-
image_optim = begin
|
125
|
-
ImageOptim.new(options)
|
126
|
-
rescue ImageOptim::ConfigurationError => e
|
104
|
+
rescue => e
|
105
|
+
if options[:verbose]
|
106
|
+
abort "#{e.to_s}\n#{e.backtrace.join("\n")}"
|
107
|
+
else
|
127
108
|
abort e.to_s
|
128
109
|
end
|
129
|
-
|
130
|
-
paths = []
|
131
|
-
ARGV.each do |arg|
|
132
|
-
if File.file?(arg)
|
133
|
-
if image_optim.optimizable?(arg)
|
134
|
-
paths << arg
|
135
|
-
else
|
136
|
-
warn "#{arg} is not an image or there is no optimizer for it"
|
137
|
-
end
|
138
|
-
else
|
139
|
-
if recursive
|
140
|
-
Find.find(arg) do |path|
|
141
|
-
paths << path if File.file?(path) && image_optim.optimizable?(path)
|
142
|
-
end
|
143
|
-
else
|
144
|
-
warn "#{arg} is not a file"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
module Space
|
150
|
-
SIZE_SYMBOLS = %w[B K M G T P E Z Y].freeze
|
151
|
-
PRECISION = 1
|
152
|
-
LENGTH = 4 + PRECISION + 1
|
153
|
-
COEF = 1 / Math.log(10)
|
154
|
-
|
155
|
-
EMPTY_SPACE = ' ' * LENGTH
|
156
|
-
NOT_COUNTED_SPACE = '!' * LENGTH
|
157
|
-
|
158
|
-
class << self
|
159
|
-
attr_writer :base10
|
160
|
-
def denominator
|
161
|
-
@denominator ||= @base10 ? 1000.0 : 1024.0
|
162
|
-
end
|
163
|
-
|
164
|
-
def space(size, options = {})
|
165
|
-
case size
|
166
|
-
when false
|
167
|
-
NOT_COUNTED_SPACE.bold.red
|
168
|
-
when 0, nil
|
169
|
-
EMPTY_SPACE
|
170
|
-
else
|
171
|
-
number, degree = size, 0
|
172
|
-
while number.abs >= 1000 && degree < SIZE_SYMBOLS.length - 1
|
173
|
-
number /= denominator
|
174
|
-
degree += 1
|
175
|
-
end
|
176
|
-
|
177
|
-
"#{degree == 0 ? number.to_s : "%.#{PRECISION}f" % number}#{SIZE_SYMBOLS[degree]}".rjust(LENGTH)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def size_percent(src_size, dst_size)
|
184
|
-
'%5.2f%% %s' % [100 - 100.0 * dst_size / src_size, Space.space(src_size - dst_size)]
|
185
|
-
end
|
186
|
-
|
187
|
-
paths = paths.with_progress('optimizing')
|
188
|
-
results = image_optim.optimize_images(paths) do |src, dst|
|
189
|
-
if dst
|
190
|
-
src_size, dst_size = src.size, dst.size
|
191
|
-
percent = size_percent(src_size, dst_size)
|
192
|
-
dst.replace(src)
|
193
|
-
["#{percent} #{src}", src_size, dst_size]
|
194
|
-
else
|
195
|
-
["------ #{Space::EMPTY_SPACE} #{src}", src.size, src.size]
|
196
|
-
end
|
197
|
-
end
|
198
|
-
lines, src_sizes, dst_sizes = results.transpose
|
199
|
-
if lines
|
200
|
-
$stdout.puts lines, "Total: #{size_percent(src_sizes.inject(:+), dst_sizes.inject(:+))}\n"
|
201
|
-
end
|
202
110
|
end
|
data/image_optim.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_optim'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.10.0'
|
6
6
|
s.summary = %q{Optimize (lossless compress) images (jpeg, png, gif) using external utilities (advpng, gifsicle, jpegoptim, jpegtran, optipng, pngcrush, pngout)}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
16
|
s.require_paths = %w[lib]
|
17
17
|
|
18
|
-
s.add_dependency 'fspath', '~> 2.0
|
18
|
+
s.add_dependency 'fspath', '~> 2.1.0'
|
19
19
|
s.add_dependency 'image_size', '~> 1.1.2'
|
20
20
|
s.add_dependency 'exifr', '~> 1.1.3'
|
21
21
|
s.add_dependency 'progress', '~> 3.0.0'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'image_optim/bin_not_found_error'
|
2
|
+
require 'thread'
|
3
|
+
require 'fspath'
|
4
|
+
|
5
|
+
class ImageOptim
|
6
|
+
class BinResolver
|
7
|
+
attr_reader :dir
|
8
|
+
def initialize
|
9
|
+
@bins = {}
|
10
|
+
@lock = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve!(bin)
|
14
|
+
bin = bin.to_sym
|
15
|
+
unless @bins.include?(bin)
|
16
|
+
@lock.synchronize do
|
17
|
+
@bins[bin] = resolve?(bin) unless @bins.include?(bin)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@bins[bin] or raise BinNotFoundError, "`#{bin}` not found"
|
21
|
+
end
|
22
|
+
|
23
|
+
VENDOR_PATH = File.expand_path('../../../vendor', __FILE__)
|
24
|
+
|
25
|
+
def env_path
|
26
|
+
"#{dir}:#{ENV['PATH']}:#{VENDOR_PATH}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def resolve?(bin)
|
32
|
+
if path = ENV["#{bin}_bin".upcase]
|
33
|
+
unless @dir
|
34
|
+
@dir = FSPath.temp_dir
|
35
|
+
at_exit{ FileUtils.remove_entry_secure @dir }
|
36
|
+
end
|
37
|
+
symlink = @dir / bin
|
38
|
+
symlink.make_symlink(File.expand_path(path))
|
39
|
+
|
40
|
+
accessible?(symlink)
|
41
|
+
else
|
42
|
+
accessible?(bin)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def accessible?(bin)
|
47
|
+
`env PATH=#{env_path.shellescape} which #{bin.to_s.shellescape}` != ''
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'image_optim/option_helpers'
|
2
|
+
require 'image_optim/configuration_error'
|
3
|
+
require 'image_optim/hash_helpers'
|
4
|
+
require 'image_optim/worker'
|
5
|
+
require 'set'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class ImageOptim
|
9
|
+
class Config
|
10
|
+
include OptionHelpers
|
11
|
+
|
12
|
+
GLOBAL_CONFIG_PATH = File.join(File.expand_path(ENV['XDG_CONFIG_HOME'] || '~/.config'), 'image_optim.yml')
|
13
|
+
LOCAL_CONFIG_PATH = '.image_optim.yml'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def global
|
17
|
+
File.file?(GLOBAL_CONFIG_PATH) ? read(GLOBAL_CONFIG_PATH) : {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def local
|
21
|
+
File.file?(LOCAL_CONFIG_PATH) ? read(LOCAL_CONFIG_PATH) : {}
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def read(path)
|
27
|
+
config = YAML.load_file(path)
|
28
|
+
unless config.is_a?(Hash)
|
29
|
+
raise "excpected hash, got #{config.inspect}"
|
30
|
+
end
|
31
|
+
HashHelpers.deep_symbolise_keys(config)
|
32
|
+
rescue => e
|
33
|
+
warn "exception when reading #{path}: #{e}"
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(options)
|
39
|
+
@options = [
|
40
|
+
Config.global,
|
41
|
+
Config.local,
|
42
|
+
HashHelpers.deep_symbolise_keys(options),
|
43
|
+
].inject do |memo, hash|
|
44
|
+
HashHelpers.deep_merge(memo, hash)
|
45
|
+
end
|
46
|
+
@used = Set.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def get!(key)
|
50
|
+
key = key.to_sym
|
51
|
+
@used << key
|
52
|
+
@options[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_no_unused_options!
|
56
|
+
unknown_options = @options.reject{ |key, value| @used.include?(key) }
|
57
|
+
unless unknown_options.empty?
|
58
|
+
raise ConfigurationError, "unknown options #{unknown_options.inspect} for #{self}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def nice
|
63
|
+
nice = get!(:nice)
|
64
|
+
|
65
|
+
case nice
|
66
|
+
when true, nil
|
67
|
+
10
|
68
|
+
when false
|
69
|
+
0
|
70
|
+
else
|
71
|
+
nice.to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def threads
|
76
|
+
threads = get!(:threads)
|
77
|
+
|
78
|
+
threads = case threads
|
79
|
+
when true, nil
|
80
|
+
processor_count
|
81
|
+
when false
|
82
|
+
1
|
83
|
+
else
|
84
|
+
threads.to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
OptionHelpers.limit_with_range(threads, 1..16)
|
88
|
+
end
|
89
|
+
|
90
|
+
def verbose
|
91
|
+
!!get!(:verbose)
|
92
|
+
end
|
93
|
+
|
94
|
+
def for_worker(klass)
|
95
|
+
worker_options = get!(klass.bin_sym)
|
96
|
+
|
97
|
+
case worker_options
|
98
|
+
when Hash
|
99
|
+
worker_options
|
100
|
+
when true, nil
|
101
|
+
{}
|
102
|
+
when false
|
103
|
+
false
|
104
|
+
else
|
105
|
+
raise ConfigurationError, "Got #{worker_options.inspect} for #{klass.name} options"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
YAML.dump(HashHelpers.deep_stringify_keys(@options)).sub(/\A---\n/, '')
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
|
116
|
+
def processor_count
|
117
|
+
@processor_count ||= case host_os = RbConfig::CONFIG['host_os']
|
118
|
+
when /darwin9/
|
119
|
+
`hwprefs cpu_count`
|
120
|
+
when /darwin/
|
121
|
+
(`which hwprefs` != '') ? `hwprefs thread_count` : `sysctl -n hw.ncpu`
|
122
|
+
when /linux/
|
123
|
+
`grep -c processor /proc/cpuinfo`
|
124
|
+
when /freebsd/
|
125
|
+
`sysctl -n hw.ncpu`
|
126
|
+
when /mswin|mingw/
|
127
|
+
require 'win32ole'
|
128
|
+
wmi = WIN32OLE.connect('winmgmts://')
|
129
|
+
cpu = wmi.ExecQuery('select NumberOfLogicalProcessors from Win32_Processor')
|
130
|
+
cpu.to_enum.first.NumberOfLogicalProcessors
|
131
|
+
else
|
132
|
+
warn "Unknown architecture (#{host_os}) assuming one processor."
|
133
|
+
1
|
134
|
+
end.to_i
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'image_optim/image_path'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Handler
|
5
|
+
attr_reader :result
|
6
|
+
def initialize(original)
|
7
|
+
raise ArgumentError, 'original should respond to temp_path' unless original.respond_to?(:temp_path)
|
8
|
+
|
9
|
+
@original = original
|
10
|
+
@result = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
@src ||= @original
|
15
|
+
@dst ||= @original.temp_path
|
16
|
+
|
17
|
+
if yield @src, @dst
|
18
|
+
@result = @dst
|
19
|
+
if @src == @original
|
20
|
+
@src, @dst = @dst, nil
|
21
|
+
else
|
22
|
+
@src, @dst = @dst, @src
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ImageOptim
|
2
|
+
module HashHelpers
|
3
|
+
class << self
|
4
|
+
def deep_transform_keys(hash, &block)
|
5
|
+
new_hash = {}
|
6
|
+
hash.each do |k, v|
|
7
|
+
new_hash[block.call(k)] = if v.is_a?(Hash)
|
8
|
+
deep_transform_keys(v, &block)
|
9
|
+
else
|
10
|
+
v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
new_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def deep_stringify_keys(hash)
|
17
|
+
deep_transform_keys(hash, &:to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deep_symbolise_keys(hash)
|
21
|
+
deep_transform_keys(hash, &:to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
def deep_merge(a, b)
|
25
|
+
a.merge(b) do |k, v_a, v_b|
|
26
|
+
if v_a.is_a?(Hash) && v_b.is_a?(Hash)
|
27
|
+
deep_merge(v_a, v_b)
|
28
|
+
else
|
29
|
+
v_b
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,10 +1,30 @@
|
|
1
1
|
require 'fspath'
|
2
2
|
require 'image_size'
|
3
3
|
|
4
|
-
require 'image_optim'
|
5
|
-
|
6
4
|
class ImageOptim
|
7
5
|
class ImagePath < FSPath
|
6
|
+
class Optimized < DelegateClass(self)
|
7
|
+
def initialize(path, original_or_size = nil)
|
8
|
+
path = ImagePath.convert(path)
|
9
|
+
__setobj__(path)
|
10
|
+
if original_or_size
|
11
|
+
if original_or_size.is_a?(Integer)
|
12
|
+
@original = path
|
13
|
+
@original_size = original_or_size
|
14
|
+
else
|
15
|
+
@original = ImagePath.convert(original_or_size)
|
16
|
+
@original_size = @original.size
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Original path, use original_size to get its size as original can be overwritten
|
22
|
+
attr_reader :original
|
23
|
+
|
24
|
+
# Stored size of original
|
25
|
+
attr_reader :original_size
|
26
|
+
end
|
27
|
+
|
8
28
|
# Get temp path for this file with same extension
|
9
29
|
def temp_path(*args, &block)
|
10
30
|
ext = extname
|
@@ -30,5 +50,10 @@ class ImageOptim
|
|
30
50
|
def format
|
31
51
|
open{ |f| ImageSize.new(f) }.format
|
32
52
|
end
|
53
|
+
|
54
|
+
# Returns path if it is already an instance of this class otherwise new instance
|
55
|
+
def self.convert(path)
|
56
|
+
path.is_a?(self) ? path : new(path)
|
57
|
+
end
|
33
58
|
end
|
34
59
|
end
|
@@ -1,19 +1,7 @@
|
|
1
|
-
require 'image_optim'
|
1
|
+
require 'image_optim/configuration_error'
|
2
2
|
|
3
3
|
class ImageOptim
|
4
4
|
module OptionHelpers
|
5
|
-
# Remove option from hash and run through block or return default
|
6
|
-
def get_option!(options, name, default)
|
7
|
-
value = default
|
8
|
-
if options.has_key?(name)
|
9
|
-
value = options.delete(name)
|
10
|
-
end
|
11
|
-
if block_given?
|
12
|
-
value = yield(value)
|
13
|
-
end
|
14
|
-
instance_variable_set("@#{name}", value)
|
15
|
-
end
|
16
|
-
|
17
5
|
# Ensure number is in range
|
18
6
|
def self.limit_with_range(number, range)
|
19
7
|
if range.include?(number)
|
@@ -26,12 +14,5 @@ class ImageOptim
|
|
26
14
|
range.last
|
27
15
|
end
|
28
16
|
end
|
29
|
-
|
30
|
-
# Raise unless all options are deleted
|
31
|
-
def assert_options_empty!(options)
|
32
|
-
unless options.empty?
|
33
|
-
raise ConfigurationError, "unknown options #{options.inspect} for #{self}"
|
34
|
-
end
|
35
|
-
end
|
36
17
|
end
|
37
18
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'image_optim'
|
2
|
+
|
3
|
+
class ImageOptim
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'image_optim.initializer' do |app|
|
6
|
+
if app.config.assets.compress && app.config.assets.image_optim != false
|
7
|
+
image_optim = ImageOptim.new
|
8
|
+
|
9
|
+
processor = proc do |context, data|
|
10
|
+
image_optim.optimize_image_data(data) || data
|
11
|
+
end
|
12
|
+
|
13
|
+
app.assets.register_preprocessor 'image/gif', :image_optim, &processor
|
14
|
+
app.assets.register_preprocessor 'image/jpeg', :image_optim, &processor
|
15
|
+
app.assets.register_preprocessor 'image/png', :image_optim, &processor
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|