images_gallery 1.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +674 -0
- data/README.md +84 -0
- data/Rakefile +53 -0
- data/bin/images_gallery +6 -0
- data/config.ru +8 -0
- data/lib/images_gallery/cli.rb +27 -0
- data/lib/images_gallery/collection.rb +15 -0
- data/lib/images_gallery/errors.rb +6 -0
- data/lib/images_gallery/generator.rb +66 -0
- data/lib/images_gallery/image.rb +19 -0
- data/lib/images_gallery/source.rb +104 -0
- data/lib/images_gallery/templates/_navigation.html.erb +5 -0
- data/lib/images_gallery/templates/_thumbnails.html.erb +13 -0
- data/lib/images_gallery/templates/layout.html.erb +61 -0
- data/lib/images_gallery/test_application.rb +14 -0
- data/lib/images_gallery/version.rb +3 -0
- data/lib/images_gallery/view.rb +55 -0
- data/lib/images_gallery/views/index.rb +26 -0
- data/lib/images_gallery/views/make.rb +28 -0
- data/lib/images_gallery/views/model.rb +29 -0
- data/lib/images_gallery.rb +10 -0
- data/spec/features/index_page_spec.rb +36 -0
- data/spec/features/makes_pages/canon_page_spec.rb +30 -0
- data/spec/features/makes_pages/leica_page_spec.rb +25 -0
- data/spec/features/models_pages/canon_eos_20d_page_spec.rb +14 -0
- data/spec/features/models_pages/lux_d_3_page_spec.rb +14 -0
- data/spec/fixtures/output-template.html +22 -0
- data/spec/fixtures/works.xml +596 -0
- data/spec/fixtures/works_large.xml +52780 -0
- data/spec/lib/images_gallery/cli_spec.rb +64 -0
- data/spec/lib/images_gallery/collection_spec.rb +14 -0
- data/spec/lib/images_gallery/generator_spec.rb +11 -0
- data/spec/lib/images_gallery/image_spec.rb +11 -0
- data/spec/lib/images_gallery/source_spec.rb +43 -0
- data/spec/lib/images_gallery/view_spec.rb +13 -0
- data/spec/lib/images_gallery/views/index_spec.rb +16 -0
- data/spec/lib/images_gallery/views/make_spec.rb +16 -0
- data/spec/lib/images_gallery/views/model_spec.rb +16 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/capybara.rb +12 -0
- data/spec/support/helpers.rb +18 -0
- data/spec/support/spec_for_collection_interface.rb +7 -0
- data/spec/support/spec_for_generator_interface.rb +29 -0
- data/spec/support/spec_for_image_interface.rb +18 -0
- data/spec/support/spec_for_images_gallery.rb +28 -0
- data/spec/support/spec_for_make_page.rb +9 -0
- data/spec/support/spec_for_model_page.rb +13 -0
- data/spec/support/spec_for_parser_interface.rb +6 -0
- data/spec/support/spec_for_view_inerface.rb +19 -0
- data/spec/tmp/canon/canon_eos_20d.html +76 -0
- data/spec/tmp/canon/canon_eos_400d_digital.html +76 -0
- data/spec/tmp/canon.html +93 -0
- data/spec/tmp/fuji_photo_film_co_ltd/slp1000se.html +76 -0
- data/spec/tmp/fuji_photo_film_co_ltd.html +81 -0
- data/spec/tmp/fujifilm/finepix_s6500fd.html +76 -0
- data/spec/tmp/fujifilm.html +81 -0
- data/spec/tmp/index.html +181 -0
- data/spec/tmp/leica/d_lux_3.html +96 -0
- data/spec/tmp/leica.html +121 -0
- data/spec/tmp/nikon_corporation/nikon_d80.html +76 -0
- data/spec/tmp/nikon_corporation.html +81 -0
- data/spec/tmp/panasonic/dmc_fz30.html +81 -0
- data/spec/tmp/panasonic.html +91 -0
- data/spec/tmp/unknown_make/unknown_model.html +81 -0
- data/spec/tmp/unknown_make.html +91 -0
- metadata +283 -0
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
Images Gallery Generator Kata
|
2
|
+
=============================
|
3
|
+
|
4
|
+
Generate a set of static HTML files from an XML representation of EXIF data so users can browse large collections of images.
|
5
|
+
|
6
|
+
<img alt="" src="doc/illustration.png"/>
|
7
|
+
|
8
|
+
Usage
|
9
|
+
-----
|
10
|
+
|
11
|
+
```bash
|
12
|
+
# Generate a gallery inside spec/tmp from the example source file
|
13
|
+
./bin/images_gallery generate spec/fixtures/works.xml spec/tmp
|
14
|
+
|
15
|
+
# Read usage intructions:
|
16
|
+
./bin/images_gallery help
|
17
|
+
|
18
|
+
# Open the images gallery automatically after it was generated
|
19
|
+
firefox $(./bin/images_gallery generate spec/fixtures/works.xml spec/tmp/)
|
20
|
+
```
|
21
|
+
|
22
|
+
Development
|
23
|
+
-----------
|
24
|
+
|
25
|
+
### Test suite
|
26
|
+
|
27
|
+
```bash
|
28
|
+
# Run the test suite
|
29
|
+
rake
|
30
|
+
|
31
|
+
# Run the acceptance test suite only (can be seen as a demo)
|
32
|
+
rake features
|
33
|
+
# Visit spec/tmp/index.html to browse the sample images
|
34
|
+
```
|
35
|
+
|
36
|
+
### Parser
|
37
|
+
|
38
|
+
The `ImageGallery::Source` relies on the **LibXML** SAX parser to extract the images described in the source XML file (e.g. [`works.xml`][example-source]). That parser is [known to be fast][libxml-benchmarks] and [memory-thrifty][sax-versus-dom].
|
39
|
+
|
40
|
+
[example-source]: spec/fixtures/works.xml
|
41
|
+
[libxml-benchmarks]: https://github.com/xml4r/libxml-ruby#performance
|
42
|
+
[sax-versus-dom]: http://www.saxproject.org/event.html
|
43
|
+
|
44
|
+
About
|
45
|
+
-----
|
46
|
+
|
47
|
+
This kata aims at writing a command-line tool to process **large** XML files which contain images metadata. Part of that metadata is relevant, and the images gallery should allow to preview a collection of thumbnails classified by camera **make** and **model**.
|
48
|
+
|
49
|
+
Because the images collections can be really large, [care has been taken][parser] to avoid loading the XML document in memory while parsing it.
|
50
|
+
|
51
|
+
[parser]: https://github.com/gonzalo-bulnes/kata-images_gallery_generator/tree/add-acceptance-test-suite#parser
|
52
|
+
|
53
|
+
Yet huges collections to review do also mean you probably don't want to review them without involving your team. That's to say the images gallery deployment is a key aspect of the task at hand, and keeping the files tree as simple as possible is a way to make the deployments as straightforward as possible. No external CSS, nor font, nor javascipts then.
|
54
|
+
|
55
|
+
The views/templates pattern makes the design extensible, this galleries generator is no <abbr title="Content Management System">CMS</abbr> however! Priority has been given to simplify as much as possible the rendering engine task. Plain ERb should keep the HTML generation fast, while a basic partials system ensures that adding front-end features remains [a _pomodoro_-sized task][pomodoro].
|
56
|
+
|
57
|
+
[pomodoro]: https://github.com/gonzalo-bulnes/kata-images_gallery_generator/commit/a90590e63f65d0b166c93e709a17a267c9ec119f
|
58
|
+
|
59
|
+
The main user interface is the CLI, which was built with [Thor][thor] as a way to make it both user-friendly and extensible. The CLI output was kept minimal, to make easy to insert the **images_gallery** program into any processing pipeline (since we are talking about large amount of data here, then we're probably also talking about processing pipelines).
|
60
|
+
|
61
|
+
That's pretty much all... a last word about those [particularly numerous shared specs][shared]. I like to define the "public" API of the main classes this way in order to ensure I keep always at sight which methods I can depend on and which others could lead to tighter undesired coupling. These interfaces are kinds of [consumer-driven contracts][cdc] and keeping them apart helps to remember that.
|
62
|
+
|
63
|
+
[thor]: http://whatisthor.com
|
64
|
+
[shared]: https://github.com/gonzalo-bulnes/kata-images_gallery_generator/tree/add-acceptance-test-suite/spec/support
|
65
|
+
[cdc]: http://martinfowler.com/articles/consumerDrivenContracts.html
|
66
|
+
|
67
|
+
License
|
68
|
+
-------
|
69
|
+
|
70
|
+
Images Gallery Generator
|
71
|
+
Copyright (C) 2015 Gonzalo Bulnes Guilpain
|
72
|
+
|
73
|
+
This program is free software: you can redistribute it and/or modify
|
74
|
+
it under the terms of the GNU General Public License as published by
|
75
|
+
the Free Software Foundation, either version 3 of the License, or
|
76
|
+
(at your option) any later version.
|
77
|
+
|
78
|
+
This program is distributed in the hope that it will be useful,
|
79
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
80
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
81
|
+
GNU General Public License for more details.
|
82
|
+
|
83
|
+
You should have received a copy of the GNU General Public License
|
84
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:features) do |config|
|
7
|
+
config.pattern = 'spec/features/**/*_spec.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Report code quality'
|
11
|
+
task :rubycritic do
|
12
|
+
|
13
|
+
# check if the rubycritic code quality reporter is available
|
14
|
+
`which rubycritic`
|
15
|
+
if $?.exitstatus != 0
|
16
|
+
abort <<-eos.gsub /^( |\t)+/, ""
|
17
|
+
|
18
|
+
The #{Rainbow('rubycritic').red} code quality reporter is not available.
|
19
|
+
You may want to install it in order to report the code quality.
|
20
|
+
|
21
|
+
See #https://github.com/whitesmith/rubycritic for intallation instructions.
|
22
|
+
|
23
|
+
eos
|
24
|
+
end
|
25
|
+
|
26
|
+
command = "rubycritic"
|
27
|
+
puts <<-eos.gsub /^( |\t)+/, ""
|
28
|
+
|
29
|
+
#{Rainbow('Report the code quality.').blue}
|
30
|
+
#{command}
|
31
|
+
|
32
|
+
eos
|
33
|
+
success = system('rubycritic')
|
34
|
+
exit_status = $?.exitstatus
|
35
|
+
|
36
|
+
abort unless exit_status == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'inch/rake'
|
41
|
+
|
42
|
+
Inch::Rake::Suggest.new(:inch) do |suggest|
|
43
|
+
suggest.args << "--private"
|
44
|
+
suggest.args << "--pedantic"
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
desc 'Inch rake task not available'
|
48
|
+
task :inch do
|
49
|
+
abort 'Inch rake task is not available. Be sure to install inch as a gem or plugin'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
task default: [:spec, :inch, :rubycritic]
|
data/bin/images_gallery
ADDED
data/config.ru
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require 'images_gallery/generator'
|
4
|
+
|
5
|
+
module ImagesGallery
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
desc 'generate SOURCE TARGET', 'Generate a static HTML images gallery in the TARGET directory from the SOURCE file contents.'
|
9
|
+
def generate(source, target=nil, error=STDERR, out=STDOUT)
|
10
|
+
begin
|
11
|
+
out.puts generator.run(source, target)
|
12
|
+
rescue ImagesGallery::SourceFileNotFoundError
|
13
|
+
error.puts 'Please make sure the specified source file exists.'
|
14
|
+
rescue ImagesGallery::TargetDirectoryNotFoundError
|
15
|
+
error.puts 'Please make sure the specified target directory exists.'
|
16
|
+
rescue ImagesGallery::SourceFileInvalidError
|
17
|
+
error.puts 'The source file is invalid. Please check it is well-formed XML.'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def generator
|
24
|
+
@generator ||= Generator.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'images_gallery/errors'
|
2
|
+
require 'images_gallery/source'
|
3
|
+
require 'images_gallery/views/index'
|
4
|
+
require 'images_gallery/views/make'
|
5
|
+
require 'images_gallery/views/model'
|
6
|
+
|
7
|
+
module ImagesGallery
|
8
|
+
class Generator
|
9
|
+
|
10
|
+
attr_reader :target
|
11
|
+
private :target
|
12
|
+
|
13
|
+
def run(source, target)
|
14
|
+
|
15
|
+
raise SourceFileNotFoundError unless File.file? source
|
16
|
+
raise TargetDirectoryNotFoundError unless File.directory? target
|
17
|
+
|
18
|
+
@source = Source.new(source)
|
19
|
+
@target = target
|
20
|
+
|
21
|
+
@source.parse
|
22
|
+
files = render_views(@source.images)
|
23
|
+
generate(target, files)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def render_views(images)
|
29
|
+
files = {}
|
30
|
+
|
31
|
+
files['index'] = Views::Index.new(images).render
|
32
|
+
|
33
|
+
images.makes.each do |make|
|
34
|
+
images_by_make = Collection.new
|
35
|
+
images.select{ |image| image.make == make }.each do |image|
|
36
|
+
images_by_make << image
|
37
|
+
view = Views::Make.new(images_by_make)
|
38
|
+
files[view.file_identifier(image.make)] = view.render
|
39
|
+
|
40
|
+
images_by_make.models.each do |model|
|
41
|
+
images_by_model = Collection.new
|
42
|
+
images_by_make.select{ |image| image.model == model }.each do |image|
|
43
|
+
images_by_model << image
|
44
|
+
view = Views::Model.new(images_by_model)
|
45
|
+
files[view.file_identifier(image.make, image.model)] = view.render
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
files
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate(target, files)
|
54
|
+
files.each do |name, content|
|
55
|
+
dir_path = "#{target}/#{name}".gsub('//', '/')
|
56
|
+
file_path = dir_path + '.html'
|
57
|
+
@index_path = file_path if name == 'index'
|
58
|
+
FileUtils.mkdir_p(dir_path) unless File.exists?(dir_path) || (name == 'index')
|
59
|
+
File.open(file_path, 'w') do |file|
|
60
|
+
file.write content
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@index_path
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
|
3
|
+
require 'images_gallery/collection'
|
4
|
+
|
5
|
+
module ImagesGallery
|
6
|
+
class Source
|
7
|
+
|
8
|
+
include LibXML
|
9
|
+
include XML::SaxParser::Callbacks
|
10
|
+
|
11
|
+
attr_reader :images
|
12
|
+
|
13
|
+
# Provides a collection of images to presenters
|
14
|
+
#
|
15
|
+
# Extracts some images attributes from an XML collection of works
|
16
|
+
# and collects the images as plain Ruby objects so other classes
|
17
|
+
# can use them with presentational purpose.
|
18
|
+
#
|
19
|
+
# This class should be able to parse efficiently large XML files.
|
20
|
+
#
|
21
|
+
# file - Path to an XML representation of the images (metadata, EXIF)
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# source = ImagesGallery::Source.new('path/to/source.xml')
|
26
|
+
# source.parse # creates a Collection of Images
|
27
|
+
# source.images # is available after parsing
|
28
|
+
#
|
29
|
+
# Returns a Source object, ready to parse the corresponding XML file.
|
30
|
+
def initialize(file)
|
31
|
+
@file = XML::SaxParser.file(file)
|
32
|
+
@file.callbacks = self
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse
|
36
|
+
@images = Collection.new
|
37
|
+
begin
|
38
|
+
@file.parse
|
39
|
+
rescue LibXML::XML::Error
|
40
|
+
raise SourceFileInvalidError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Callbacks can't be private, yet they are not part of the public API.
|
45
|
+
|
46
|
+
def on_start_element(element, attributes)
|
47
|
+
@current_element = element
|
48
|
+
|
49
|
+
if element == 'work'
|
50
|
+
@current_image = Image.new
|
51
|
+
end
|
52
|
+
|
53
|
+
if element == 'url' && attributes['type'] == 'small'
|
54
|
+
@current_element = 'thumbnail URL'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_end_element(element)
|
59
|
+
if element == 'work'
|
60
|
+
@images << @current_image
|
61
|
+
end
|
62
|
+
|
63
|
+
if element == 'id'
|
64
|
+
@current_image.id = @current_id
|
65
|
+
end
|
66
|
+
|
67
|
+
if element == 'make'
|
68
|
+
@current_image.make = @current_make
|
69
|
+
end
|
70
|
+
|
71
|
+
if element == 'model'
|
72
|
+
@current_image.model = @current_model
|
73
|
+
end
|
74
|
+
|
75
|
+
if @current_element == 'thumbnail URL'
|
76
|
+
@current_image.src = @current_src
|
77
|
+
end
|
78
|
+
|
79
|
+
@current_element = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_characters(char)
|
83
|
+
if @current_element == 'id'
|
84
|
+
@current_id = char
|
85
|
+
end
|
86
|
+
|
87
|
+
if @current_element == 'make'
|
88
|
+
@current_make = char
|
89
|
+
end
|
90
|
+
|
91
|
+
if @current_element == 'model'
|
92
|
+
@current_model = char
|
93
|
+
end
|
94
|
+
|
95
|
+
if @current_element == 'thumbnail URL'
|
96
|
+
@current_src = char
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_end_document
|
101
|
+
@current_element = nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="grid">
|
2
|
+
<% images.each do |image| %>
|
3
|
+
<div class="grid-item">
|
4
|
+
<img alt="<%= image.description %>" src="<%= image.src %>"/>
|
5
|
+
<% unless depth == 1 %>
|
6
|
+
<a class="grid-item-details" href="<%= link_to(depth, image.make, image.model) %>" title="Browse all the <%= image.model %> images.">
|
7
|
+
<div><%= image.make %></div>
|
8
|
+
<div><%= image.model %></div>
|
9
|
+
</a>
|
10
|
+
<% end %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= title %></title>
|
5
|
+
<style type="text/css">
|
6
|
+
html, body {
|
7
|
+
font-family: "adelle", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
8
|
+
font-size: 1rem;
|
9
|
+
}
|
10
|
+
nav {
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-width: 1px 0;
|
13
|
+
margin: 2rem 0;
|
14
|
+
}
|
15
|
+
.nav-link {
|
16
|
+
display: inline-block;
|
17
|
+
line-height: 3rem;
|
18
|
+
padding: 0 1rem;
|
19
|
+
text-decoration: none;
|
20
|
+
text-transform: uppercase;
|
21
|
+
}
|
22
|
+
.grid {
|
23
|
+
max-width: 960px;
|
24
|
+
}
|
25
|
+
.grid-item {
|
26
|
+
border: 1px solid #ccc;
|
27
|
+
display: inline-block;
|
28
|
+
padding: 10px 10px 5px;
|
29
|
+
position: relative;
|
30
|
+
margin: 5px;
|
31
|
+
}
|
32
|
+
.grid-item-details {
|
33
|
+
background-color: rgba(255, 255, 255, 0.9);
|
34
|
+
color: #333;
|
35
|
+
font-size: .8rem;
|
36
|
+
font-variant: small-caps;
|
37
|
+
height: 3rem;
|
38
|
+
left: 0;
|
39
|
+
line-height: 1.2rem;
|
40
|
+
opacity: 0;
|
41
|
+
padding: .2rem 0 10px;
|
42
|
+
position: absolute;
|
43
|
+
width: 100%;
|
44
|
+
bottom: 0;
|
45
|
+
text-align: center;
|
46
|
+
text-decoration: none;
|
47
|
+
}
|
48
|
+
.grid-item:hover .grid-item-details {
|
49
|
+
opacity: 1;
|
50
|
+
}
|
51
|
+
</style>
|
52
|
+
</head>
|
53
|
+
<body>
|
54
|
+
<header>
|
55
|
+
<h1><%= title %></h1>
|
56
|
+
<%= navigation(links) %>
|
57
|
+
</header>
|
58
|
+
|
59
|
+
<%= thumbnails(sample_images) %>
|
60
|
+
</body>
|
61
|
+
</html>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module ImagesGallery
|
4
|
+
class View
|
5
|
+
|
6
|
+
# Provides context and helpers to the corresponding template
|
7
|
+
#
|
8
|
+
# Any variable defined in the context of this class will be accessible
|
9
|
+
# from the corresponding ERb template.
|
10
|
+
# See http://ruby-doc.org/stdlib-2.2.2/libdoc/erb/rdoc/ERB.html#method-i-result
|
11
|
+
#
|
12
|
+
# Usage:
|
13
|
+
#
|
14
|
+
# Do not instanciate this class, define subclasses instead an define their
|
15
|
+
# template methods as demonstrated in Views::Index.
|
16
|
+
#
|
17
|
+
# Conventionally, templates could be stored in images_gallery/templates, but
|
18
|
+
# there is no obligation to follow the convention.
|
19
|
+
#
|
20
|
+
# This class not meant to be instanciated, subclasses do return View instances (kind of).
|
21
|
+
def initialize
|
22
|
+
@template = File.new(template)
|
23
|
+
end
|
24
|
+
|
25
|
+
def template
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def render
|
30
|
+
ERB.new(File.new(template).read).result(binding)
|
31
|
+
end
|
32
|
+
|
33
|
+
def thumbnails(images)
|
34
|
+
template = 'lib/images_gallery/templates/_thumbnails.html.erb'
|
35
|
+
ERB.new(File.new(template).read).result(binding)
|
36
|
+
end
|
37
|
+
|
38
|
+
def navigation(links)
|
39
|
+
template = 'lib/images_gallery/templates/_navigation.html.erb'
|
40
|
+
ERB.new(File.new(template).read).result(binding)
|
41
|
+
end
|
42
|
+
|
43
|
+
def link_to(depth, make, model=nil)
|
44
|
+
('../' * depth) + file_identifier(make, model) + '.html'
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_identifier(make, model=nil)
|
48
|
+
if model.nil?
|
49
|
+
"#{make.to_filename}"
|
50
|
+
else
|
51
|
+
"#{make.to_filename}/#{model.to_filename}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'images_gallery/view'
|
4
|
+
|
5
|
+
module ImagesGallery
|
6
|
+
module Views
|
7
|
+
class Index < ImagesGallery::View
|
8
|
+
|
9
|
+
attr_reader :depth, :links, :sample_images, :title
|
10
|
+
private :depth, :links, :sample_images, :title
|
11
|
+
|
12
|
+
def initialize(images)
|
13
|
+
super()
|
14
|
+
|
15
|
+
@depth = 0
|
16
|
+
@links = images.makes.map{ |make| { name: make, href: link_to(depth, make) } }
|
17
|
+
@sample_images = images.first(10)
|
18
|
+
@title = 'Index'
|
19
|
+
end
|
20
|
+
|
21
|
+
def template
|
22
|
+
'lib/images_gallery/templates/layout.html.erb'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'images_gallery/view'
|
4
|
+
|
5
|
+
module ImagesGallery
|
6
|
+
module Views
|
7
|
+
class Make < ImagesGallery::View
|
8
|
+
|
9
|
+
attr_reader :depth, :links, :make, :sample_images, :title
|
10
|
+
private :depth, :links, :make, :sample_images, :title
|
11
|
+
|
12
|
+
def initialize(images)
|
13
|
+
super()
|
14
|
+
|
15
|
+
@depth = 0
|
16
|
+
@make = images.first.make
|
17
|
+
@links = [{ name: "Browse all the images", href: link_to(depth, 'index') }]
|
18
|
+
@links += images.models.map{ |model| { name: model, href: link_to(depth, make, model) } }
|
19
|
+
@sample_images = images.first(10)
|
20
|
+
@title = "Images by #{make}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def template
|
24
|
+
'lib/images_gallery/templates/layout.html.erb'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'images_gallery/view'
|
4
|
+
|
5
|
+
module ImagesGallery
|
6
|
+
module Views
|
7
|
+
class Model < ImagesGallery::View
|
8
|
+
|
9
|
+
attr_reader :depth, :links, :make, :model, :sample_images, :title
|
10
|
+
private :depth, :links, :make, :model, :sample_images, :title
|
11
|
+
|
12
|
+
def initialize(images)
|
13
|
+
super()
|
14
|
+
|
15
|
+
@depth = 1
|
16
|
+
@make = images.first.make
|
17
|
+
@model = images.first.model
|
18
|
+
@links = [{ name: "Browse all the images", href: link_to(depth, 'index') }]
|
19
|
+
@links << { name: "Browse all the #{make} images", href: link_to(depth, make) }
|
20
|
+
@sample_images = images
|
21
|
+
@title = "Images by (#{make}) #{model}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def template
|
25
|
+
'lib/images_gallery/templates/layout.html.erb'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|