middleman-automatic-clowncar 0.0.1 → 4.0.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 +5 -5
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/README.md +61 -1
- data/Rakefile +13 -0
- data/features/clowncar_tag_build.feature +136 -30
- data/features/clowncar_tag_preview.feature +31 -16
- data/features/support/env.rb +12 -0
- data/features/support/step_definitions.rb +14 -0
- data/features/thumbnail_generation.feature +41 -7
- data/fixtures/automatic-clowncar-app/profile/build.html +23779 -0
- data/fixtures/automatic-clowncar-app/source/{images/photos → photos}/test-image.jpg +0 -0
- data/fixtures/automatic-clowncar-app/source/tag.html.erb +1 -1
- data/fixtures/automatic-clowncar-app/source/thumbnail.html.erb +1 -0
- data/lib/middleman-automatic-clowncar/extension.rb +59 -131
- data/lib/middleman-automatic-clowncar/sitemap-extension.rb +45 -0
- data/lib/middleman-automatic-clowncar/thumbnail-generator.rb +9 -26
- data/lib/middleman-automatic-clowncar/thumbnail-resource.rb +54 -0
- data/lib/middleman-automatic-clowncar/timestamp-resource.rb +42 -0
- data/lib/middleman-automatic-clowncar/utils.rb +38 -0
- data/lib/middleman-automatic-clowncar/version.rb +1 -1
- data/middleman-automatic-clowncar.gemspec +14 -5
- metadata +84 -19
File without changes
|
@@ -1 +1 @@
|
|
1
|
-
<%= automatic_clowncar_tag "photos/test-image.jpg", :host => "http://localhost:4567/" %>
|
1
|
+
<%= automatic_clowncar_tag "photos/test-image.jpg", :host => "http://localhost:4567/", :include_original => true, :prevent_upscaling => true %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= thumbnail_url "photos/test-image.jpg", :medium %>
|
@@ -1,19 +1,23 @@
|
|
1
1
|
require 'middleman-core'
|
2
2
|
|
3
3
|
require 'middleman-automatic-clowncar/thumbnail-generator'
|
4
|
+
require 'middleman-automatic-clowncar/timestamp-resource'
|
5
|
+
require 'middleman-automatic-clowncar/thumbnail-resource'
|
6
|
+
require 'middleman-automatic-clowncar/sitemap-extension'
|
7
|
+
require 'middleman-automatic-clowncar/utils'
|
4
8
|
|
5
9
|
require 'fastimage'
|
6
10
|
|
7
11
|
module Middleman
|
8
12
|
module AutomaticClowncar
|
9
13
|
class Extension < Middleman::Extension
|
10
|
-
|
14
|
+
|
11
15
|
SVG_TEMPLATE = "<svg viewBox='0 0 ::width:: ::height::' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'><style>svg{background-size:100% 100%;background-repeat:no-repeat;}::media_queries::</style></svg>"
|
12
|
-
|
16
|
+
|
13
17
|
option :sizes, {}, "The sizes of thumbnails to generate"
|
14
|
-
option :namespace_directory, ["**"], "The directories
|
18
|
+
option :namespace_directory, ["**"], "The directories that should be clowncared. (Outside of the sprockets images dir.)"
|
15
19
|
option :filetypes, [:jpg, :jpeg, :png], "The types of files to use for automatic clowncaring."
|
16
|
-
|
20
|
+
option :include_originals, false, "Always include original images. (Or not.)"
|
17
21
|
cattr_accessor :options_hash
|
18
22
|
|
19
23
|
def initialize(app, options_hash={}, &block)
|
@@ -29,31 +33,21 @@ module Middleman
|
|
29
33
|
app.after_configuration do
|
30
34
|
|
31
35
|
#stash the source images dir in options for the Rack middleware
|
32
|
-
Extension.options_hash[:
|
33
|
-
Extension.options_hash[:source_dir] = source_dir
|
36
|
+
Extension.options_hash[:source_dir] = app.source_dir
|
34
37
|
|
35
38
|
sizes = Extension.options_hash[:sizes]
|
36
39
|
namespace = Extension.options_hash[:namespace_directory].join(',')
|
37
40
|
|
38
|
-
dir = Pathname.new(
|
39
|
-
glob = "#{dir}
|
41
|
+
dir = Pathname.new(app.source_dir)
|
42
|
+
glob = "#{dir}/{#{namespace}}/*.{#{Extension.options_hash[:filetypes].join(',')}}"
|
40
43
|
files = Dir[glob]
|
41
|
-
|
42
|
-
# don't build the files until after build
|
43
|
-
after_build do |builder|
|
44
|
-
files.each do |file|
|
45
|
-
path = file.gsub(source_dir, '')
|
46
|
-
specs = ThumbnailGenerator.specs(path, sizes)
|
47
|
-
ThumbnailGenerator.generate(source_dir, File.join(root, build_dir), path, specs)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
sitemap.register_resource_list_manipulator(:thumbnailer, SitemapExtension.new(self), true)
|
52
|
-
|
53
|
-
app.use Rack, Extension.options_hash
|
54
44
|
end
|
55
45
|
end
|
56
|
-
|
46
|
+
|
47
|
+
def manipulate_resource_list(resources)
|
48
|
+
SitemapExtension.new(self).manipulate_resource_list(resources)
|
49
|
+
end
|
50
|
+
|
57
51
|
def after_configuration
|
58
52
|
@ready = true
|
59
53
|
end
|
@@ -70,7 +64,7 @@ module Middleman
|
|
70
64
|
end
|
71
65
|
|
72
66
|
def get_image_path(name, path, is_relative, fallback_host)
|
73
|
-
puts "@@@@@@@ calling get_image_path for
|
67
|
+
#puts "@@@@@@@ calling get_image_path for name:#{name} path:#{path}, is_relative:#{is_relative}, fallback_host:#{fallback_host}"
|
74
68
|
begin
|
75
69
|
uri = URI(path)
|
76
70
|
rescue URI::InvalidURIError
|
@@ -81,13 +75,14 @@ module Middleman
|
|
81
75
|
if uri.host
|
82
76
|
path
|
83
77
|
else
|
84
|
-
|
78
|
+
|
85
79
|
svg_path = File.join(File.dirname(name),File.basename(name,".*"), path)
|
86
80
|
|
87
81
|
if is_relative
|
88
82
|
url = app.asset_path(:images, svg_path)
|
89
|
-
|
90
|
-
|
83
|
+
images_dir = app.config[:images_dir] || 'images'
|
84
|
+
url = url.sub("/#{images_dir}/",'/')
|
85
|
+
if fallback_host && is_relative_url?(url)
|
91
86
|
File.join(fallback_host, url)
|
92
87
|
else
|
93
88
|
url
|
@@ -128,11 +123,15 @@ module Middleman
|
|
128
123
|
output.join("")
|
129
124
|
end
|
130
125
|
|
126
|
+
def get_physical_image_size(name)
|
127
|
+
main_abs_path = File.join(app.source_dir,name)
|
128
|
+
FastImage.size(main_abs_path, :raise_on_failure => true)
|
129
|
+
end
|
130
|
+
|
131
131
|
def get_image_sizes(name, options)
|
132
|
-
puts "getting images sizes for #{name}"
|
132
|
+
#puts "getting images sizes for #{name}"
|
133
133
|
|
134
|
-
|
135
|
-
main_abs_path = File.join(app.source_dir,main_path)
|
134
|
+
main_abs_path = File.join(app.source_dir,name)
|
136
135
|
|
137
136
|
extname = File.extname(name)
|
138
137
|
basename = File.basename(name, ".*")
|
@@ -141,29 +140,36 @@ module Middleman
|
|
141
140
|
|
142
141
|
width, height = ::FastImage.size(main_abs_path, :raise_on_failure => true)
|
143
142
|
|
144
|
-
|
143
|
+
|
145
144
|
sizes = {}
|
146
|
-
Extension.options_hash[:sizes].each_pair do |sname,
|
147
|
-
|
145
|
+
Extension.options_hash[:sizes].each_pair do |sname,swidth|
|
146
|
+
next if swidth > width
|
147
|
+
sizes[swidth] = "#{basename}-#{sname}#{extname}"
|
148
|
+
end
|
149
|
+
|
150
|
+
if options[:include_original] || Extension.options_hash[:include_originals]
|
151
|
+
sizes[width] = "../#{basename}#{extname}"
|
148
152
|
end
|
149
153
|
|
150
|
-
puts "-"*30
|
151
|
-
puts [sizes, width, height]
|
154
|
+
#puts "-"*30
|
155
|
+
#puts [sizes, width, height]
|
152
156
|
[sizes, width, height]
|
153
157
|
end
|
154
158
|
|
155
159
|
|
156
160
|
def generate_svg(name, is_relative, options)
|
157
|
-
puts "name for generate_svg = #{name}"
|
158
|
-
puts "options for generate_svg = #{options}"
|
161
|
+
#puts "name for generate_svg = #{name}"
|
162
|
+
#puts "options for generate_svg = #{options}"
|
159
163
|
sizes, width, height = get_image_sizes(name, options)
|
160
|
-
|
164
|
+
|
161
165
|
fallback_host = false
|
162
|
-
if is_relative
|
166
|
+
if is_relative
|
163
167
|
test_path = app.asset_path(:images, "#{name}.svg")
|
164
168
|
if is_relative_url?(test_path)
|
165
169
|
if options.has_key?(:host)
|
166
170
|
fallback_host = options[:host]
|
171
|
+
elsif app.config[:asset_host]
|
172
|
+
fallback_host = app.config[:asset_host]
|
167
173
|
else
|
168
174
|
warn "WARNING: Inline clowncar images require absolute paths. Please set a :host value"
|
169
175
|
end
|
@@ -183,131 +189,53 @@ module Middleman
|
|
183
189
|
Extension.svg_files_to_generate << [name, options]
|
184
190
|
end
|
185
191
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
192
|
|
190
|
-
|
191
193
|
helpers do
|
192
194
|
def automatic_clowncar_tag(name, options={})
|
193
195
|
internal = ""
|
194
196
|
|
195
197
|
if options[:fallback]
|
196
|
-
|
197
198
|
fallback = File.basename thumbnail_url(name,:small)
|
198
199
|
fallback_path = extensions[:automatic_clowncar].get_image_path(name, fallback, true, false)
|
199
200
|
internal = %{<!--[if lte IE 8]><img src="#{fallback_path}"><![endif]-->}
|
200
201
|
end
|
201
202
|
|
203
|
+
width, height = extensions[:automatic_clowncar].get_physical_image_size(name)
|
204
|
+
object_style = ""
|
205
|
+
if options.has_key?(:prevent_upscaling) && options[:prevent_upscaling]
|
206
|
+
#if options.has_key?(:include_original) && options[:include_original]
|
207
|
+
#else
|
208
|
+
# width = extensions[:automatic_clowncar].options.sizes.map{|k,v| v }.sort.last
|
209
|
+
#end
|
210
|
+
|
211
|
+
object_style = "max-width:#{width}px;"
|
212
|
+
end
|
213
|
+
|
202
214
|
if options.has_key?(:inline) && (options[:inline] === false)
|
203
215
|
url = asset_path(:images, "#{name}.svg")
|
204
|
-
%Q{<object type="image/svg+xml" data="#{url}">#{internal}</object>}
|
216
|
+
%Q{<object type="image/svg+xml" style="#{object_style}" data-aspect-ratio="#{width.to_f/height.to_f}" data="#{url}">#{internal}</object>}
|
205
217
|
else
|
206
218
|
data = extensions[:automatic_clowncar].generate_svg(name, true, options)
|
207
|
-
%Q{<object type="image/svg+xml" data="data:image/svg+xml,#{::URI.escape(data)}">#{internal}</object>}
|
219
|
+
%Q{<object type="image/svg+xml" style="#{object_style}" data-aspect-ratio="#{width.to_f/height.to_f}" data="data:image/svg+xml,#{::URI.escape(data)}">#{internal}</object>}
|
208
220
|
end
|
209
221
|
end
|
210
222
|
|
211
223
|
def thumbnail_specs(image, name)
|
212
224
|
sizes = Extension.options_hash[:sizes]
|
213
|
-
ThumbnailGenerator.specs(image, sizes)
|
225
|
+
ThumbnailGenerator.specs(image, sizes, app.source_dir)
|
214
226
|
end
|
215
227
|
|
216
228
|
def thumbnail_url(image, name, options = {})
|
217
229
|
include_images_dir = options.delete :include_images_dir
|
218
230
|
|
219
231
|
url = thumbnail_specs(image, name)[name][:name]
|
220
|
-
url = File.join(
|
232
|
+
url = File.join(url) if include_images_dir
|
221
233
|
|
222
234
|
url
|
223
235
|
end
|
224
236
|
|
225
237
|
end # helpers
|
226
238
|
|
227
|
-
class SitemapExtension
|
228
|
-
def initialize(app)
|
229
|
-
@app = app
|
230
|
-
end
|
231
|
-
|
232
|
-
# Add sitemap resource for every image in the sprockets load path
|
233
|
-
def manipulate_resource_list(resources)
|
234
|
-
|
235
|
-
images_dir_abs = File.join(@app.source_dir, @app.images_dir)
|
236
|
-
|
237
|
-
images_dir = @app.images_dir
|
238
|
-
|
239
|
-
options = Extension.options_hash
|
240
|
-
sizes = options[:sizes]
|
241
|
-
namespace = options[:namespace_directory].join(',')
|
242
|
-
|
243
|
-
files = Dir["#{images_dir_abs}/#{namespace}/*.{#{Extension.options_hash[:filetypes].join(',')}}"]
|
244
|
-
|
245
|
-
resource_list = files.map do |file|
|
246
|
-
path = file.gsub(@app.source_dir + File::SEPARATOR, '')
|
247
|
-
specs = ::Middleman::AutomaticClowncar::ThumbnailGenerator.specs(path, sizes)
|
248
|
-
specs.map do |name, spec|
|
249
|
-
resource = nil
|
250
|
-
# puts "#{path}: #{spec[:name]}: #{file}"
|
251
|
-
resource = Middleman::Sitemap::Resource.new(@app.sitemap, spec[:name], file) unless name == :original
|
252
|
-
end
|
253
|
-
end.flatten.reject {|resource| resource.nil? }
|
254
|
-
|
255
|
-
resources + resource_list
|
256
|
-
end
|
257
|
-
end # SitemapExtension
|
258
|
-
|
259
|
-
|
260
|
-
# Rack middleware to convert images on the fly
|
261
|
-
class Rack
|
262
|
-
|
263
|
-
# Init
|
264
|
-
# @param [Class] app
|
265
|
-
# @param [Hash] options
|
266
|
-
def initialize(app, options={})
|
267
|
-
@app = app
|
268
|
-
@options = options
|
269
|
-
|
270
|
-
files = Dir["#{options[:images_source_dir]}/**/*.{#{options[:filetypes].join(',')}}"]
|
271
|
-
|
272
|
-
@original_map = ThumbnailGenerator.original_map_for_files(files, options[:sizes])
|
273
|
-
|
274
|
-
end
|
275
|
-
|
276
|
-
# Rack interface
|
277
|
-
# @param [Rack::Environmemt] env
|
278
|
-
# @return [Array]
|
279
|
-
def call(env)
|
280
|
-
status, headers, response = @app.call(env)
|
281
|
-
|
282
|
-
path = env["PATH_INFO"]
|
283
|
-
|
284
|
-
path_on_disk = File.join(@options[:source_dir], path)
|
285
|
-
|
286
|
-
#TODO: caching
|
287
|
-
if original_specs = @original_map[path_on_disk]
|
288
|
-
original_file = original_specs[:original]
|
289
|
-
spec = original_specs[:spec]
|
290
|
-
if spec.has_key? :sizes
|
291
|
-
image = ::Magick::Image.read(original_file).first
|
292
|
-
blob = nil
|
293
|
-
image.change_geometry(spec[:sizes]) do |cols, rows, img|
|
294
|
-
img = img.resize(cols, rows)
|
295
|
-
img = img.sharpen(0.5, 0.5)
|
296
|
-
blob = img.to_blob
|
297
|
-
end
|
298
|
-
|
299
|
-
unless blob.nil?
|
300
|
-
status = 200
|
301
|
-
headers["Content-Length"] = ::Rack::Utils.bytesize(blob).to_s
|
302
|
-
headers["Content-Type"] = image.mime_type
|
303
|
-
response = [blob]
|
304
|
-
end
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
[status, headers, response]
|
309
|
-
end
|
310
|
-
end
|
311
239
|
|
312
240
|
|
313
241
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Middleman
|
2
|
+
module AutomaticClowncar
|
3
|
+
class SitemapExtension
|
4
|
+
|
5
|
+
def initialize(extension)
|
6
|
+
@extension = extension
|
7
|
+
@app = extension.app
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add sitemap resource for every image in the sprockets load path
|
11
|
+
def manipulate_resource_list(resources)
|
12
|
+
puts "resources = #{resources.class}"
|
13
|
+
options = Extension.options_hash
|
14
|
+
sizes = options[:sizes]
|
15
|
+
namespace = options[:namespace_directory].join(',')
|
16
|
+
glob = "#{@app.source_dir}/{#{namespace}}/*.{#{Extension.options_hash[:filetypes].join(',')}}"
|
17
|
+
files = Dir[glob]
|
18
|
+
resource_list = []
|
19
|
+
files.each do |file|
|
20
|
+
path = Utils.naked_origin(@app.source_dir,file)
|
21
|
+
specs = ::Middleman::AutomaticClowncar::ThumbnailGenerator.specs(path, sizes,@app.source_dir)
|
22
|
+
specs.each_pair do |name, spec|
|
23
|
+
# Remove the default resource and use a ThumbnailResource that will avoid repeat imgoptim
|
24
|
+
if name == :original
|
25
|
+
default_resource = resources.select{|r| r.destination_path == spec[:name]}
|
26
|
+
resources = resources - default_resource
|
27
|
+
end
|
28
|
+
dest_path = File.join(@app.root_path,'build', spec[:name])
|
29
|
+
source = File.exists?(dest_path) ? dest_path : file
|
30
|
+
resource_list << Middleman::AutomaticClowncar::ThumbnailResource.new(@app.sitemap,spec[:name],spec[:dimensions],file,@app.root_path,'build',@app.source_dir)
|
31
|
+
#resource_list << Middleman::Sitemap::Resource.new(@app.sitemap, spec[:name], source) unless name == :original
|
32
|
+
end
|
33
|
+
fname = specs.first[1][:name]
|
34
|
+
timestampDir = File.join(File.dirname(fname),File.basename(fname,'.*'))
|
35
|
+
timestampPath = File.join(timestampDir,'timestamp.txt')
|
36
|
+
resource_list << Middleman::AutomaticClowncar::TimestampResource.new(@app.sitemap, timestampPath, path, @app.source_dir)
|
37
|
+
end
|
38
|
+
resource_list.flatten.reject {|resource| resource.nil? }
|
39
|
+
|
40
|
+
resources + resource_list
|
41
|
+
end
|
42
|
+
|
43
|
+
end # SitemapExtension
|
44
|
+
end
|
45
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'RMagick'
|
2
1
|
|
3
2
|
module Middleman
|
4
3
|
module AutomaticClowncar
|
@@ -6,7 +5,12 @@ module Middleman
|
|
6
5
|
class ThumbnailGenerator
|
7
6
|
class << self
|
8
7
|
|
9
|
-
def specs(origin, dimensions)
|
8
|
+
def specs(origin, dimensions, source_dir)
|
9
|
+
#puts "origin = #{origin}"
|
10
|
+
#puts "source_dir = #{source_dir}"
|
11
|
+
origin = Utils.naked_origin(source_dir,origin) # just in case
|
12
|
+
width, height = FastImage.size(File.join(source_dir,origin))
|
13
|
+
|
10
14
|
dir = File.dirname(origin)
|
11
15
|
filename = File.basename(origin,'.*')
|
12
16
|
ext = File.extname(origin)
|
@@ -16,35 +20,14 @@ module Middleman
|
|
16
20
|
ret = {original: {name: origin}}
|
17
21
|
|
18
22
|
dimensions.each do |name, dimension|
|
23
|
+
next if dimension > width
|
19
24
|
location = File.join(dir,"#{filename}-#{name}#{ext}")
|
20
25
|
ret[name] = {name:location , dimensions: dimension}
|
21
26
|
end
|
22
|
-
|
23
27
|
ret
|
24
28
|
end
|
25
|
-
|
26
|
-
|
27
|
-
image = ::Magick::Image.read(File.join(source_dir, origin)).first
|
28
|
-
specs.each do |name, spec|
|
29
|
-
if spec.has_key? :dimensions then
|
30
|
-
image.change_geometry(spec[:dimensions]) do |cols, rows, img|
|
31
|
-
img = img.resize(cols, rows)
|
32
|
-
img = img.sharpen(0.5, 0.5)
|
33
|
-
img.write File.join(output_dir, spec[:name])
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def original_map_for_files(files, specs)
|
40
|
-
map = files.inject({}) do |memo, file|
|
41
|
-
generated_specs = self.specs(file, specs)
|
42
|
-
generated_specs.each do |name, spec|
|
43
|
-
memo[spec[:name]] = {:original => generated_specs[:original][:name], :spec => spec}
|
44
|
-
end
|
45
|
-
memo
|
46
|
-
end
|
47
|
-
end
|
29
|
+
|
30
|
+
|
48
31
|
end
|
49
32
|
end
|
50
33
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Middleman
|
2
|
+
module AutomaticClowncar
|
3
|
+
class ThumbnailResource < ::Middleman::Sitemap::Resource
|
4
|
+
|
5
|
+
require 'mini_magick'
|
6
|
+
|
7
|
+
attr_accessor :output
|
8
|
+
|
9
|
+
def initialize(store, path, dimensions, origin, root_path, build_dir, source_dir)
|
10
|
+
@dimensions = dimensions
|
11
|
+
@origin = origin
|
12
|
+
@root_path = root_path
|
13
|
+
@build_dir = build_dir
|
14
|
+
@source_dir = source_dir
|
15
|
+
super(store, path, source_dir.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def template?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def source_file
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(*args, &block)
|
27
|
+
output_dir = File.join(@root_path,@build_dir)
|
28
|
+
dest_path = File.join(output_dir,@path)
|
29
|
+
img = nil
|
30
|
+
if Utils.timestamp_current?(@source_dir,@build_dir,@origin) && File.exist?(dest_path)
|
31
|
+
img = MiniMagick::Image.open(dest_path)
|
32
|
+
else
|
33
|
+
img = MiniMagick::Image.open(@origin)
|
34
|
+
img.resize(@dimensions) unless @dimensions.blank?
|
35
|
+
end
|
36
|
+
img.to_blob
|
37
|
+
end
|
38
|
+
|
39
|
+
def binary?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def raw_data
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
|
47
|
+
def ignored?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|