extracted the building of template tree
This step is a preparation to extend the functionality of the renderer. To make the main class easier, the search for templates and building of the tree is extracted into its own class.
This commit is contained in:
parent
8ff98ff9dd
commit
f18ab69a91
|
@ -1,3 +1,5 @@
|
|||
require 'zero/renderer/template_finder'
|
||||
|
||||
module Zero
|
||||
# the base renderer for getting render containers
|
||||
#
|
||||
|
@ -34,7 +36,7 @@ module Zero
|
|||
# @param template_path [String] a string to templates
|
||||
# @param type_map [Hash] a map of simple types to complex ones
|
||||
def initialize(template_path, type_map = {})
|
||||
@template_path = template_path + '/'
|
||||
@template_path = template_path
|
||||
@type_map = type_map
|
||||
end
|
||||
|
||||
|
@ -56,23 +58,7 @@ module Zero
|
|||
# the wanted template.
|
||||
# @return [Self] returns the object
|
||||
def read_template_path!
|
||||
# TODO clean up later
|
||||
@templates = {}
|
||||
search_files.each do |file|
|
||||
parts = file.gsub(/#{template_path}/, '').split('.')
|
||||
@templates[parts[0]] ||= {}
|
||||
|
||||
# Set default value
|
||||
types = 'default'
|
||||
# Overwrite default value, if it's set in template path
|
||||
if parts.count > 2 then
|
||||
types = parts[1]
|
||||
end
|
||||
|
||||
read_type(types).each do |type|
|
||||
@templates[parts[0]][type] = file
|
||||
end
|
||||
end
|
||||
@templates = TemplateFinder.new(template_path, @type_map).get_templates
|
||||
end
|
||||
|
||||
# render a template
|
||||
|
@ -89,32 +75,6 @@ module Zero
|
|||
|
||||
private
|
||||
|
||||
# search in `template_path` for templates beginning with `template_name`
|
||||
# @api private
|
||||
# @param template_name [String] the name of the template
|
||||
# @return [#each] a list of all templates found
|
||||
def search_files
|
||||
Dir[template_path + '**/*.*']
|
||||
end
|
||||
|
||||
# gets the type information from a file and converts it to an array of
|
||||
# possible matching types
|
||||
# @api private
|
||||
# @param short_notation [String] a short notation of a type, like `html`
|
||||
# @return [Array] a list of matching types, like `text/html`
|
||||
def read_type(short_notation)
|
||||
to_type_list(type_map[short_notation] || short_notation)
|
||||
end
|
||||
|
||||
# convert a map to an array if it is not one
|
||||
# @api private
|
||||
# @param original_map [Object] the type(s) to convert
|
||||
# @return [Array] a list of objects
|
||||
def to_type_list(original_map)
|
||||
return original_map if original_map.respond_to?(:each)
|
||||
[original_map]
|
||||
end
|
||||
|
||||
# get the prepared template for the name and type
|
||||
# @api private
|
||||
# @param name [String] the name of the template
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
module Zero
|
||||
class Renderer
|
||||
# finds templates in a path and builds a map for the renderer to use
|
||||
#
|
||||
# When this class is feeded with a path and a type map it will generate
|
||||
# a map of templates and types for the renderer to use.
|
||||
# For that to work, it first needs a path ending on '/' and a map of type
|
||||
# names to mime types. The short type name is used in template names to find
|
||||
# out, for which mime types they are built, so that they can be rendered
|
||||
# for the correct request.
|
||||
#
|
||||
# The template files can be named in two different formats
|
||||
# * filename.extension
|
||||
# * filename.type.extension
|
||||
# The type is used to access the `type_map`. It will be used to find all
|
||||
# mime types this template can be used to answer. If no type is given in the
|
||||
# filename, the type will be set to `default`.
|
||||
# So `default` can be used in the `type_map` to map these files too.
|
||||
#
|
||||
# @example building a TemplateFinder
|
||||
# As an example, lets assume we have the following files in our path
|
||||
# * `index.erb`
|
||||
# * `index.json.erb`
|
||||
#
|
||||
# We want these to render for either html requests or json requests. To make
|
||||
# this work, we need to build a TemplateFinder like following
|
||||
#
|
||||
# TemplateFinder.new('path/', {
|
||||
# 'default' => ['text/html', '*/*'],
|
||||
# 'json' => ['application/json']
|
||||
# })
|
||||
#
|
||||
# This will build a structure, so that requests with 'text/html' will render
|
||||
# `index.erb`.
|
||||
class TemplateFinder
|
||||
# the search mask to search for files
|
||||
# @example foo/bar/**/*.*
|
||||
MARK_ALL_FILES = '**/*.*'
|
||||
# for finding the last slash
|
||||
SLASH_END = '/'
|
||||
# empty string to replace the path in the filename
|
||||
EMPTY_STRING = ''
|
||||
# split filename at this character
|
||||
SPLIT_CHAR = '.'
|
||||
# default type
|
||||
DEFAULT_TYPE = 'default'
|
||||
|
||||
# the path to all templates
|
||||
# @api private
|
||||
# @returns [String] the path given at initialization
|
||||
attr_reader :path
|
||||
|
||||
# a map of simple type names to a list of mime types
|
||||
# @api private
|
||||
# @example 'html' => ['text/html', 'text/xml', 'text/html+xml']
|
||||
# @returns [Hash] a hash with types to mime types
|
||||
attr_reader :type_map
|
||||
|
||||
# this returns the regex for the specified path
|
||||
# @api private
|
||||
# @returns [Regex] the regex built from the path
|
||||
attr_reader :path_regex
|
||||
|
||||
# initialize a new template finder
|
||||
#
|
||||
# @example
|
||||
# TemplateFinder.new('foo/bar/', {
|
||||
# 'default' => ['text/html', 'text/xml'],
|
||||
# 'json' => ['application/json']
|
||||
# })
|
||||
# @param path [String] the path to all templates ending on '/'
|
||||
# @param type_map [Hash] a map of short type names to mime types
|
||||
def initialize(path, type_map)
|
||||
raise ArgumentError.new("Has to end on '/'!") if path[-1] != SLASH_END
|
||||
@path = path
|
||||
@type_map = sanity_map(type_map)
|
||||
@path_regex = /#{path}/
|
||||
end
|
||||
|
||||
# traverses the template path to gather all templates
|
||||
#
|
||||
# This function traverses the template path, collects and sorts all
|
||||
# templates into the target types given at initialization.
|
||||
# @return [Hash] the map of type to template
|
||||
def get_templates
|
||||
result = Hash.new {|hash, key| hash[key] = {} }
|
||||
|
||||
search_files.each do |file|
|
||||
key, value = add_template(file)
|
||||
result[key] = result[key].merge(value)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# returns a list of files found at @path
|
||||
#
|
||||
# This method returns all files found in @path, which look like a template.
|
||||
# Look for `MARK_ALL_FILES` for the eact schema.
|
||||
# @api private
|
||||
# @return [Array] a list of all files found
|
||||
def search_files
|
||||
Dir[@path + MARK_ALL_FILES]
|
||||
end
|
||||
|
||||
# splits the path into a filename and its type
|
||||
#
|
||||
# This function takes a filepath and extracts the filename and short
|
||||
# notation for the type.
|
||||
# The filename is later used at rendering time to find the template.
|
||||
# @api private
|
||||
# @param filepath [String] the filename to split
|
||||
# @return [Array] an Array of the following example `[filename, type]`
|
||||
def get_fields(filepath)
|
||||
filename, *options = filepath.gsub(@path_regex, EMPTY_STRING).split(SPLIT_CHAR)
|
||||
[filename, (options.length == 1 ? DEFAULT_TYPE : options[0])]
|
||||
end
|
||||
|
||||
# add a template with its type variants
|
||||
#
|
||||
# This method adds a template with all type variants to the map of all
|
||||
# types and templates.
|
||||
# @api private
|
||||
# @param filename [String] the short name of the template
|
||||
# @param type [String] the short type of the template
|
||||
# @param path [String] the actual path to the template
|
||||
# @return [Array] a hashable array for the end result:
|
||||
def add_template(path)
|
||||
filename, type = get_fields(path)
|
||||
result = [filename, {}]
|
||||
get_types(type).each do |mime_type|
|
||||
result[1][mime_type] = path
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# get the types for the shorthand type
|
||||
#
|
||||
# This method returns all types associated with the short notation
|
||||
# of this type in the type_map.
|
||||
# @api private
|
||||
# @param short_type [String] the short notation of a type
|
||||
# @return [Array] a list of all types found in the type_map
|
||||
def get_types(short_type)
|
||||
return [short_type] unless @type_map.has_key?(short_type)
|
||||
@type_map[short_type]
|
||||
end
|
||||
|
||||
# make a cleanup of the map
|
||||
#
|
||||
# This function converts all map values to arrays, to make the processing
|
||||
# easier.
|
||||
# @api private
|
||||
# @param map [Hash] a type map
|
||||
# @return [Hash] the cleaned up map
|
||||
def sanity_map(map)
|
||||
map.each do |key, value|
|
||||
map[key] = [value] unless value.respond_to?(:each)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Zero::Renderer, 'read_template_path!' do
|
||||
describe Zero::Renderer, '#read_template_path!' do
|
||||
subject { Zero::Renderer.new(template_path, type_map) }
|
||||
let(:template_path) { 'foo' }
|
||||
let(:template_path) { 'foo/' }
|
||||
let(:file_list) { ['foo/welcome/index.html.erb'] }
|
||||
|
||||
before :each do
|
||||
Dir.stub(:[]) do |arg|
|
||||
if arg == 'foo/**/*.*'
|
||||
file_list
|
||||
file_list
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -58,7 +58,7 @@ describe Zero::Renderer, 'read_template_path!' do
|
|||
end
|
||||
|
||||
it 'creates an empty templates list without templates in path' do
|
||||
subject = Zero::Renderer.new("bar", {})
|
||||
subject = Zero::Renderer.new("bar/", {})
|
||||
subject.read_template_path!
|
||||
|
||||
subject.templates.should eq({})
|
||||
|
|
|
@ -2,7 +2,7 @@ require 'spec_helper'
|
|||
|
||||
describe Zero::Renderer, '#render' do
|
||||
subject { Zero::Renderer.new(template_path, type_map) }
|
||||
let(:template_path) { 'spec/fixtures/templates' }
|
||||
let(:template_path) { 'spec/fixtures/templates/' }
|
||||
let(:type_map) {{
|
||||
'html' => ['text/html', 'text/xml', '*/*'],
|
||||
'json' => ['application/json', 'plain/text']
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Zero::Renderer::TemplateFinder, '#initialize' do
|
||||
subject { described_class.new(template_path, type_map) }
|
||||
let(:template_path) { 'foo/' }
|
||||
let(:file_list) { ['foo/welcome/index.html.erb'] }
|
||||
|
||||
before :each do
|
||||
Dir.stub(:[]) do |arg|
|
||||
if arg == 'foo/**/*.*'
|
||||
file_list
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'a template loader' do
|
||||
it 'creates a template tree' do
|
||||
subject.get_templates['welcome/index'].should eq(result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without mapping' do
|
||||
let(:type_map) { {} }
|
||||
let(:result) { { 'html' => 'foo/welcome/index.html.erb' } }
|
||||
|
||||
it_behaves_like 'a template loader'
|
||||
end
|
||||
|
||||
context 'with a single mapping' do
|
||||
let(:type_map) { {'html' => 'text/html' } }
|
||||
let(:result) { { 'text/html' => 'foo/welcome/index.html.erb' } }
|
||||
|
||||
it_behaves_like 'a template loader'
|
||||
end
|
||||
|
||||
context 'with multiple mappings' do
|
||||
let(:type_map) { {'html' => ['text/html', 'text/xml'] } }
|
||||
let(:result) { {
|
||||
'text/html' => 'foo/welcome/index.html.erb',
|
||||
'text/xml' => 'foo/welcome/index.html.erb'
|
||||
} }
|
||||
|
||||
it_behaves_like 'a template loader'
|
||||
end
|
||||
|
||||
context 'with default template' do
|
||||
let(:file_list) {['foo/welcome/index.erb']}
|
||||
let(:type_map) { {'default' => ['text/html', 'text/xml'] } }
|
||||
let(:result) { {
|
||||
'text/html' => 'foo/welcome/index.erb',
|
||||
'text/xml' => 'foo/welcome/index.erb'
|
||||
} }
|
||||
|
||||
it_behaves_like 'a template loader'
|
||||
end
|
||||
|
||||
it 'creates an empty templates list without templates in path' do
|
||||
subject = Zero::Renderer.new("bar/", {})
|
||||
subject.read_template_path!
|
||||
|
||||
subject.templates.should eq({})
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Zero::Renderer::TemplateFinder, '#initialize' do
|
||||
subject { described_class.new(template_path, type_map) }
|
||||
let(:template_path) { 'foo/' }
|
||||
let(:type_map) { {'html' => ['text/html']} }
|
||||
|
||||
its(:path) { should be(template_path) }
|
||||
its(:path_regex) { should eq(/#{template_path}/) }
|
||||
its(:type_map) { should be(type_map) }
|
||||
|
||||
context 'with broken path' do
|
||||
let(:template_path) { 'foo' }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error(ArgumentError, "Has to end on '/'!")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue