middleman-thumbnailer 0.1.2
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 +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +19 -0
- data/Dockerfile +17 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +15 -0
- data/docker-compose.yml +7 -0
- data/features/namespace.feature +15 -0
- data/features/step_definitions/server_steps.rb +46 -0
- data/features/support/env.rb +8 -0
- data/features/thumbnails.feature +24 -0
- data/fixtures/namespace/config.rb +77 -0
- data/fixtures/namespace/source/images/background.png +0 -0
- data/fixtures/namespace/source/images/middleman.png +0 -0
- data/fixtures/namespace/source/images/test/background.png +0 -0
- data/fixtures/namespace/source/images/test1/background.png +0 -0
- data/fixtures/namespace/source/index.html.erb +10 -0
- data/fixtures/namespace/source/javascripts/all.js +1 -0
- data/fixtures/namespace/source/layouts/layout.erb +19 -0
- data/fixtures/namespace/source/stylesheets/all.css +55 -0
- data/fixtures/namespace/source/stylesheets/normalize.css +375 -0
- data/fixtures/thumbnails/config.rb +76 -0
- data/fixtures/thumbnails/source/images/background.png +0 -0
- data/fixtures/thumbnails/source/images/middleman.png +0 -0
- data/fixtures/thumbnails/source/index.html.erb +10 -0
- data/fixtures/thumbnails/source/javascripts/all.js +1 -0
- data/fixtures/thumbnails/source/layouts/layout.erb +19 -0
- data/fixtures/thumbnails/source/stylesheets/all.css +55 -0
- data/fixtures/thumbnails/source/stylesheets/normalize.css +375 -0
- data/lib/middleman-thumbnailer.rb +8 -0
- data/lib/middleman-thumbnailer/extension.rb +192 -0
- data/lib/middleman-thumbnailer/thumbnail-generator.rb +78 -0
- data/lib/middleman-thumbnailer/version.rb +5 -0
- data/lib/middleman_extension.rb +1 -0
- data/middleman-thumbnailer.gemspec +24 -0
- data/spec/thumbnail-generator_spec.rb +22 -0
- metadata +142 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'mime-types'
|
3
|
+
|
4
|
+
require 'middleman-thumbnailer/thumbnail-generator'
|
5
|
+
|
6
|
+
module Middleman
|
7
|
+
module Thumbnailer
|
8
|
+
class << self
|
9
|
+
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
def registered(app, options={})
|
13
|
+
|
14
|
+
options[:filetypes] ||= [:jpg, :jpeg, :png]
|
15
|
+
options[:include_data_thumbnails] = false unless options.has_key? :include_data_thumbnails
|
16
|
+
options[:namespace_directory] = ["**"] unless options.has_key? :namespace_directory
|
17
|
+
|
18
|
+
Thumbnailer.options = options
|
19
|
+
|
20
|
+
app.helpers Helpers
|
21
|
+
|
22
|
+
options[:middleman_app] = app
|
23
|
+
|
24
|
+
app.after_configuration do
|
25
|
+
|
26
|
+
options[:build_dir] = build_dir
|
27
|
+
|
28
|
+
#stash the source images dir in options for the Rack middleware
|
29
|
+
options[:images_source_dir] = File.join(source_dir, images_dir)
|
30
|
+
options[:source_dir] = source_dir
|
31
|
+
|
32
|
+
dimensions = options[:dimensions]
|
33
|
+
namespace = options[:namespace_directory]
|
34
|
+
|
35
|
+
app.before_build do
|
36
|
+
dir = File.join(source_dir, images_dir)
|
37
|
+
|
38
|
+
|
39
|
+
files = DirGlob.glob(dir, namespace, options[:filetypes])
|
40
|
+
|
41
|
+
files.each do |file|
|
42
|
+
path = file.gsub(source_dir, '')
|
43
|
+
specs = ThumbnailGenerator.specs(path, dimensions)
|
44
|
+
ThumbnailGenerator.generate(source_dir, File.join(root, build_dir), path, specs)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
sitemap.register_resource_list_manipulator(:thumbnailer, SitemapExtension.new(self), true)
|
49
|
+
|
50
|
+
app.use Rack, options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
alias :included :registered
|
54
|
+
end
|
55
|
+
|
56
|
+
module Helpers
|
57
|
+
def thumbnail_specs(image, name)
|
58
|
+
dimensions = Thumbnailer.options[:dimensions]
|
59
|
+
ThumbnailGenerator.specs(image, dimensions)
|
60
|
+
end
|
61
|
+
|
62
|
+
def thumbnail_url(image, name, options = {})
|
63
|
+
include_images_dir = options.delete :include_images_dir
|
64
|
+
|
65
|
+
url = thumbnail_specs(image, name)[name][:name]
|
66
|
+
url = File.join(images_dir, url) if include_images_dir
|
67
|
+
|
68
|
+
url
|
69
|
+
end
|
70
|
+
|
71
|
+
def thumbnail(image, name, html_options = {})
|
72
|
+
specs_for_data_attribute = thumbnail_specs(image, name).map {|name, spec| "#{name}:#{spec[:name]}"}
|
73
|
+
|
74
|
+
html_options.merge!({'data-thumbnails' => specs_for_data_attribute.join('|')}) if Thumbnailer.options[:include_data_thumbnails]
|
75
|
+
|
76
|
+
image_tag(thumbnail_url(image, name), html_options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class DirGlob
|
81
|
+
def self.glob(root, namespaces, filetypes)
|
82
|
+
filetypes_with_capitals = filetypes.reduce([]) { |memo, file| memo.concat [file, file.upcase] }
|
83
|
+
glob_str = "#{root}/{#{namespaces.join(',')}}/**/*.{#{filetypes_with_capitals.join(',')}}"
|
84
|
+
Dir[glob_str]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class SitemapExtension
|
89
|
+
def initialize(app)
|
90
|
+
@app = app
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add sitemap resource for every image in the sprockets load path
|
94
|
+
def manipulate_resource_list(resources)
|
95
|
+
|
96
|
+
images_dir_abs = File.join(@app.source_dir, @app.images_dir)
|
97
|
+
|
98
|
+
images_dir = @app.images_dir
|
99
|
+
|
100
|
+
options = Thumbnailer.options
|
101
|
+
dimensions = options[:dimensions]
|
102
|
+
namespace = options[:namespace_directory].join(',')
|
103
|
+
|
104
|
+
files = DirGlob.glob(images_dir_abs, options[:namespace_directory], options[:filetypes])
|
105
|
+
|
106
|
+
resource_list = files.map do |file|
|
107
|
+
path = file.gsub(@app.source_dir + File::SEPARATOR, '')
|
108
|
+
specs = ThumbnailGenerator.specs(path, dimensions)
|
109
|
+
specs.map do |name, spec|
|
110
|
+
resource = nil
|
111
|
+
resource = Middleman::Sitemap::Resource.new(@app.sitemap, spec[:name], File.join(options[:build_dir], spec[:name])) unless name == :original
|
112
|
+
end
|
113
|
+
end.flatten.reject {|resource| resource.nil? }
|
114
|
+
|
115
|
+
resources + resource_list
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Rack middleware to convert images on the fly
|
122
|
+
class Rack
|
123
|
+
|
124
|
+
# Init
|
125
|
+
# @param [Class] app
|
126
|
+
# @param [Hash] options
|
127
|
+
def initialize(app, options={})
|
128
|
+
@app = app
|
129
|
+
@options = options
|
130
|
+
|
131
|
+
files = DirGlob.glob(options[:images_source_dir], options[:namespace_directory], options[:filetypes])
|
132
|
+
|
133
|
+
@original_map = ThumbnailGenerator.original_map_for_files(files, options[:dimensions])
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
# Rack interface
|
138
|
+
# @param [Rack::Environmemt] env
|
139
|
+
# @return [Array]
|
140
|
+
def call(env)
|
141
|
+
status, headers, response = @app.call(env)
|
142
|
+
|
143
|
+
path = env["PATH_INFO"]
|
144
|
+
|
145
|
+
absolute_path = File.join(@options[:source_dir], path)
|
146
|
+
|
147
|
+
root_dir = @options[:middleman_app].root
|
148
|
+
|
149
|
+
tmp_dir = File.join(root_dir, 'tmp', 'middleman-thumbnail-cache')
|
150
|
+
|
151
|
+
if original_specs = @original_map[absolute_path]
|
152
|
+
thumbnail_path = Pathname.new original_specs[:spec][:name]
|
153
|
+
#the name for the generate spec filename is relative to the source dir, remove this
|
154
|
+
thumbnail_path = thumbnail_path.relative_path_from Pathname.new(@options[:source_dir])
|
155
|
+
|
156
|
+
cache_file = File.join tmp_dir, thumbnail_path.to_s
|
157
|
+
cache_dir = File.dirname cache_file
|
158
|
+
FileUtils.mkdir_p(cache_dir) unless Dir.exist?(cache_dir)
|
159
|
+
|
160
|
+
file_info = nil
|
161
|
+
file_data = nil
|
162
|
+
|
163
|
+
original_file = original_specs[:original]
|
164
|
+
original_mtime = File.mtime original_file
|
165
|
+
if File.exist?(cache_file) && (original_mtime == File.mtime(cache_file))
|
166
|
+
file_data = IO.read(cache_file)
|
167
|
+
file_info = {length: ::Rack::Utils.bytesize(file_data).to_s, mime_type: ::MIME::Types.type_for(cache_file).first.to_s}
|
168
|
+
else
|
169
|
+
image = ThumbnailGenerator.image_for_spec(original_file, original_specs[:spec])
|
170
|
+
file_data = image.to_blob
|
171
|
+
|
172
|
+
file_info = {length: ::Rack::Utils.bytesize(file_data).to_s, mime_type: image.mime_type}
|
173
|
+
|
174
|
+
File.open(cache_file, 'wb') do |file|
|
175
|
+
file.write file_data
|
176
|
+
end
|
177
|
+
|
178
|
+
File.utime(original_mtime, original_mtime, cache_file)
|
179
|
+
|
180
|
+
image.destroy!
|
181
|
+
end
|
182
|
+
|
183
|
+
status = 200
|
184
|
+
headers["Content-Length"] = file_info[:length]
|
185
|
+
headers["Content-Type"] = file_info[:mime_type]
|
186
|
+
response = [file_data]
|
187
|
+
end
|
188
|
+
[status, headers, response]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Middleman
|
5
|
+
#actually creates the thumbnail names
|
6
|
+
class ThumbnailGenerator
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def specs(origin, dimensions)
|
10
|
+
ret = {original: {name: origin}}
|
11
|
+
file_parts = origin.split('.')
|
12
|
+
basename = file_parts[0..-2].join('.')
|
13
|
+
ext = file_parts.last
|
14
|
+
|
15
|
+
dimensions.each do |name, dimension|
|
16
|
+
ret[name] = {name: "#{basename}-#{name}-#{dimension}.#{ext}", dimensions: dimension}
|
17
|
+
end
|
18
|
+
|
19
|
+
ret
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate(source_dir, output_dir, origin, specs)
|
23
|
+
origin_absolute = File.join(source_dir, origin)
|
24
|
+
yield_images(origin_absolute, specs) do |img, spec|
|
25
|
+
output_file = File.join(output_dir, spec[:name])
|
26
|
+
origin_mtime = File.mtime origin_absolute
|
27
|
+
#FIXME: this sucks & I should be shot, however in before_build, we havend created the build dir
|
28
|
+
# therefor we will have to create it here
|
29
|
+
output_file_immediate_dir = File.dirname output_file
|
30
|
+
FileUtils.mkdir_p output_file_immediate_dir unless Dir.exist? output_file_immediate_dir
|
31
|
+
if !File.exist?(output_file) || origin_mtime != File.mtime(output_file) then
|
32
|
+
#puts "writing #{output_file}"
|
33
|
+
img.write output_file
|
34
|
+
end
|
35
|
+
File.utime(origin_mtime, origin_mtime, output_file)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def yield_images(origin, specs)
|
40
|
+
image = ::Magick::Image.read(origin).first
|
41
|
+
specs.each do |name, spec|
|
42
|
+
if spec.has_key? :dimensions then
|
43
|
+
image.change_geometry(spec[:dimensions]) do |cols, rows, img|
|
44
|
+
img = img.resize(cols, rows)
|
45
|
+
img = img.sharpen(0.5, 0.5)
|
46
|
+
yield img, spec
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def image_for_spec(origin, spec)
|
53
|
+
image = ::Magick::Image.read(origin).first
|
54
|
+
|
55
|
+
if spec.has_key? :dimensions then
|
56
|
+
image.change_geometry(spec[:dimensions]) do |cols, rows, img|
|
57
|
+
img = img.resize(cols, rows)
|
58
|
+
img = img.sharpen(0.5, 0.5)
|
59
|
+
return img
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return image
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
#This returns a reverse mapping from a thumbnail's filename to the original filename, and the thumbnail's specs
|
67
|
+
def original_map_for_files(files, specs)
|
68
|
+
map = files.inject({}) do |memo, file|
|
69
|
+
generated_specs = self.specs(file, specs)
|
70
|
+
generated_specs.each do |name, spec|
|
71
|
+
memo[spec[:name]] = {:original => generated_specs[:original][:name], :spec => spec}
|
72
|
+
end
|
73
|
+
memo
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "middleman-thumbnailer"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.expand_path("./lib")
|
3
|
+
require "middleman-thumbnailer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "middleman-thumbnailer"
|
7
|
+
s.version = Middleman::Thumbnailer::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Nicholas Hemsley"]
|
10
|
+
s.email = ["nick.hems@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/nhemsley/middleman-thumbnailer"
|
12
|
+
s.licenses = ['MIT']
|
13
|
+
s.summary = %q{Generate thumbnail versions of image files}
|
14
|
+
s.description = s.summary
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_runtime_dependency("middleman", ["~> 3.4.1"])
|
21
|
+
s.add_runtime_dependency("rake", ['<= 11.0'])
|
22
|
+
s.add_runtime_dependency("rmagick", ["~> 2.16.0"])
|
23
|
+
s.add_runtime_dependency("mime-types", ["~> 2.1"])
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# require 'spec_helper'
|
2
|
+
require 'middleman-thumbnailer'
|
3
|
+
|
4
|
+
require "middleman-thumbnailer/thumbnail-generator"
|
5
|
+
|
6
|
+
describe ::Middleman::ThumbnailGenerator do
|
7
|
+
let(:klass) { Middleman::ThumbnailGenerator }
|
8
|
+
let(:filename) {'test.jpg'}
|
9
|
+
describe "#specs" do
|
10
|
+
|
11
|
+
# it "should always return the original filename in :original" do
|
12
|
+
# dimensions = {:small => '100x100'}
|
13
|
+
# klass.generate(filename, dimensions).should include(:original => filename)
|
14
|
+
# end
|
15
|
+
|
16
|
+
# it "should always return the original filename in :original" do
|
17
|
+
# dimensions = {:small => '100x100'}
|
18
|
+
# klass.generate(filename, dimensions).should include(:small => 'test-small-100x100.jpg')
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: middleman-thumbnailer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicholas Hemsley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: middleman
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.4.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.4.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "<="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '11.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "<="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '11.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rmagick
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.16.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.16.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mime-types
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.1'
|
69
|
+
description: Generate thumbnail versions of image files
|
70
|
+
email:
|
71
|
+
- nick.hems@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Dockerfile
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- docker-compose.yml
|
84
|
+
- features/namespace.feature
|
85
|
+
- features/step_definitions/server_steps.rb
|
86
|
+
- features/support/env.rb
|
87
|
+
- features/thumbnails.feature
|
88
|
+
- fixtures/namespace/config.rb
|
89
|
+
- fixtures/namespace/source/images/background.png
|
90
|
+
- fixtures/namespace/source/images/middleman.png
|
91
|
+
- fixtures/namespace/source/images/test/background.png
|
92
|
+
- fixtures/namespace/source/images/test1/background.png
|
93
|
+
- fixtures/namespace/source/index.html.erb
|
94
|
+
- fixtures/namespace/source/javascripts/all.js
|
95
|
+
- fixtures/namespace/source/layouts/layout.erb
|
96
|
+
- fixtures/namespace/source/stylesheets/all.css
|
97
|
+
- fixtures/namespace/source/stylesheets/normalize.css
|
98
|
+
- fixtures/thumbnails/config.rb
|
99
|
+
- fixtures/thumbnails/source/images/background.png
|
100
|
+
- fixtures/thumbnails/source/images/middleman.png
|
101
|
+
- fixtures/thumbnails/source/index.html.erb
|
102
|
+
- fixtures/thumbnails/source/javascripts/all.js
|
103
|
+
- fixtures/thumbnails/source/layouts/layout.erb
|
104
|
+
- fixtures/thumbnails/source/stylesheets/all.css
|
105
|
+
- fixtures/thumbnails/source/stylesheets/normalize.css
|
106
|
+
- lib/middleman-thumbnailer.rb
|
107
|
+
- lib/middleman-thumbnailer/extension.rb
|
108
|
+
- lib/middleman-thumbnailer/thumbnail-generator.rb
|
109
|
+
- lib/middleman-thumbnailer/version.rb
|
110
|
+
- lib/middleman_extension.rb
|
111
|
+
- middleman-thumbnailer.gemspec
|
112
|
+
- spec/thumbnail-generator_spec.rb
|
113
|
+
homepage: https://github.com/nhemsley/middleman-thumbnailer
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.6.8
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: Generate thumbnail versions of image files
|
137
|
+
test_files:
|
138
|
+
- features/namespace.feature
|
139
|
+
- features/step_definitions/server_steps.rb
|
140
|
+
- features/support/env.rb
|
141
|
+
- features/thumbnails.feature
|
142
|
+
- spec/thumbnail-generator_spec.rb
|