images_gallery 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +674 -0
  4. data/README.md +84 -0
  5. data/Rakefile +53 -0
  6. data/bin/images_gallery +6 -0
  7. data/config.ru +8 -0
  8. data/lib/images_gallery/cli.rb +27 -0
  9. data/lib/images_gallery/collection.rb +15 -0
  10. data/lib/images_gallery/errors.rb +6 -0
  11. data/lib/images_gallery/generator.rb +66 -0
  12. data/lib/images_gallery/image.rb +19 -0
  13. data/lib/images_gallery/source.rb +104 -0
  14. data/lib/images_gallery/templates/_navigation.html.erb +5 -0
  15. data/lib/images_gallery/templates/_thumbnails.html.erb +13 -0
  16. data/lib/images_gallery/templates/layout.html.erb +61 -0
  17. data/lib/images_gallery/test_application.rb +14 -0
  18. data/lib/images_gallery/version.rb +3 -0
  19. data/lib/images_gallery/view.rb +55 -0
  20. data/lib/images_gallery/views/index.rb +26 -0
  21. data/lib/images_gallery/views/make.rb +28 -0
  22. data/lib/images_gallery/views/model.rb +29 -0
  23. data/lib/images_gallery.rb +10 -0
  24. data/spec/features/index_page_spec.rb +36 -0
  25. data/spec/features/makes_pages/canon_page_spec.rb +30 -0
  26. data/spec/features/makes_pages/leica_page_spec.rb +25 -0
  27. data/spec/features/models_pages/canon_eos_20d_page_spec.rb +14 -0
  28. data/spec/features/models_pages/lux_d_3_page_spec.rb +14 -0
  29. data/spec/fixtures/output-template.html +22 -0
  30. data/spec/fixtures/works.xml +596 -0
  31. data/spec/fixtures/works_large.xml +52780 -0
  32. data/spec/lib/images_gallery/cli_spec.rb +64 -0
  33. data/spec/lib/images_gallery/collection_spec.rb +14 -0
  34. data/spec/lib/images_gallery/generator_spec.rb +11 -0
  35. data/spec/lib/images_gallery/image_spec.rb +11 -0
  36. data/spec/lib/images_gallery/source_spec.rb +43 -0
  37. data/spec/lib/images_gallery/view_spec.rb +13 -0
  38. data/spec/lib/images_gallery/views/index_spec.rb +16 -0
  39. data/spec/lib/images_gallery/views/make_spec.rb +16 -0
  40. data/spec/lib/images_gallery/views/model_spec.rb +16 -0
  41. data/spec/spec_helper.rb +32 -0
  42. data/spec/support/capybara.rb +12 -0
  43. data/spec/support/helpers.rb +18 -0
  44. data/spec/support/spec_for_collection_interface.rb +7 -0
  45. data/spec/support/spec_for_generator_interface.rb +29 -0
  46. data/spec/support/spec_for_image_interface.rb +18 -0
  47. data/spec/support/spec_for_images_gallery.rb +28 -0
  48. data/spec/support/spec_for_make_page.rb +9 -0
  49. data/spec/support/spec_for_model_page.rb +13 -0
  50. data/spec/support/spec_for_parser_interface.rb +6 -0
  51. data/spec/support/spec_for_view_inerface.rb +19 -0
  52. data/spec/tmp/canon/canon_eos_20d.html +76 -0
  53. data/spec/tmp/canon/canon_eos_400d_digital.html +76 -0
  54. data/spec/tmp/canon.html +93 -0
  55. data/spec/tmp/fuji_photo_film_co_ltd/slp1000se.html +76 -0
  56. data/spec/tmp/fuji_photo_film_co_ltd.html +81 -0
  57. data/spec/tmp/fujifilm/finepix_s6500fd.html +76 -0
  58. data/spec/tmp/fujifilm.html +81 -0
  59. data/spec/tmp/index.html +181 -0
  60. data/spec/tmp/leica/d_lux_3.html +96 -0
  61. data/spec/tmp/leica.html +121 -0
  62. data/spec/tmp/nikon_corporation/nikon_d80.html +76 -0
  63. data/spec/tmp/nikon_corporation.html +81 -0
  64. data/spec/tmp/panasonic/dmc_fz30.html +81 -0
  65. data/spec/tmp/panasonic.html +91 -0
  66. data/spec/tmp/unknown_make/unknown_model.html +81 -0
  67. data/spec/tmp/unknown_make.html +91 -0
  68. 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]
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.push File.join(File.dirname(__FILE__), "/../lib" )
3
+
4
+ require 'images_gallery/cli'
5
+
6
+ ImagesGallery::CLI.start(ARGV)
data/config.ru ADDED
@@ -0,0 +1,8 @@
1
+ require 'rack'
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+
5
+ require 'images_gallery/test_application'
6
+
7
+ use Rack::Static, urls: [""], root: ImagesGallery::TestApplication::DEFAULT_TARGET, index: 'index.html'
8
+ run ImagesGallery::TestApplication
@@ -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,15 @@
1
+ require 'images_gallery/image'
2
+
3
+ module ImagesGallery
4
+ class Collection < Array
5
+
6
+ def makes
7
+ map { |image| image.make }.uniq.sort
8
+ end
9
+
10
+ def models
11
+ map { |image| image.model }.uniq.sort
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ module ImagesGallery
2
+
3
+ SourceFileNotFoundError = Class.new(LoadError)
4
+ TargetDirectoryNotFoundError = Class.new(LoadError)
5
+ SourceFileInvalidError = Class.new(RuntimeError)
6
+ 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,19 @@
1
+ module ImagesGallery
2
+ class Image
3
+
4
+ attr_accessor :id, :src, :make, :model
5
+
6
+ def make
7
+ @make || 'Unknown Make'
8
+ end
9
+
10
+ def model
11
+ @model || 'Unknown Model'
12
+ end
13
+
14
+ def description
15
+ "Image #{@id}"
16
+ end
17
+
18
+ end
19
+ 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,5 @@
1
+ <nav>
2
+ <% links.each do |link| %>
3
+ <a class="nav-link" href="<%= link[:href] %>"><%= link[:name] %></a>
4
+ <% end %>
5
+ </nav>
@@ -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,14 @@
1
+ require 'rack'
2
+
3
+ require 'images_gallery/generator'
4
+
5
+ module ImagesGallery
6
+ class TestApplication
7
+
8
+ DEFAULT_TARGET = 'spec/tmp'
9
+
10
+ def self.call(env)
11
+ run Rack::Directory.new(DEFAULT_TARGET).call(env)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module ImagesGallery
2
+ VERSION = '1.0.0.rc'
3
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'images_gallery/generator'
2
+ require 'images_gallery/version'
3
+
4
+ class String
5
+
6
+ # Sanitize a name so it can be used as part of an URL
7
+ def to_filename
8
+ downcase.gsub(/\W/, '_').gsub(/_+/, '_').gsub(/_\Z/, '')
9
+ end
10
+ end