diff --git a/lib/zero/renderer.rb b/lib/zero/renderer.rb index 40a9bed..3e9f790 100644 --- a/lib/zero/renderer.rb +++ b/lib/zero/renderer.rb @@ -1,98 +1,85 @@ module Zero - class FileNotFoundError < IOError; end - # This class helps with rendering of content. + # the base renderer for getting render containers # - # The purpose of this class is to render templates. All variables pushed into - # the renderer should be already processed, so that the raw data can be used. + # This class handles templates and render coontainers, which can be used for + # the actual rendering. # - # The workflow of this class is like the following. + # To use this renderer you have to give it a template path and optionally + # a map of shorthand type descriptions to fully types. This will then be used + # to extend the internal map of templates to possible formats in a way, that + # you will be able to answer xhtml and html requests with the same template. # - # * setup the type mapping - # * create a new instance of the class to prepare rendering - # * call #render to process the template + # When the object is initialized and you are sure, everything is loaded, call + # #read_template_path! and the template tree will be built. Without this step, + # you will probably don't get any output. # - # The call to #render will return the String representation of the template - # with all data given. + # After the setup, the renderer can be used to build render containers, which + # then can be used to actually render something. class Renderer - class << self - # set a base path for template search - # @param path [String] the path to the template base dir - def template_path=(path) - @@path = path + '/' - end - - # save a mapping hash for the type - # - # With that it is possible to map long and complex contant types to simpler - # representations. These get then used in the finding process for the best - # fitting template. - # - # @example - # Zero::Renderer.map = {'text/html' => 'html'} - # - # @param map [Hash] maps the content type to a simple representation - def type_map=(map) - @@map = map - end - - # returns the type map - # @return [Hash] the mapping for types - def type_map - @@map ||= {} - end + # initializes a new Renderer + # + # This method takes a path to the base template directory and a type map. + # This type map is used to extend the possible renderings for different + # types, which the clients sends. + # + # @example create a simple renderer + # Renderer.new('app/templates') + # + # @example create a renderer with a small map + # Renderer.new('app', { + # 'html' => ['text/html', 'application/html+xml'], + # 'json' => ['application/json', 'application/aweomse+json'] + # }) + # + # @param [String] a string to templates + def initialize(template_path, type_map = {}) + @template_path = template_path + '/' + @type_map = type_map end - # take the path and render the template within the context - # @param path [String] the relative path to the template - # @param context [Object] the object to process on - # @param accept_types - def initialize(path, context, accept_types) - accept_types ||= Request::Accept.new('text/html') - @path = find_template(path, accept_types) - @context = context - end + # returns the hash of type conversions + # @return [Hash] type conversion + attr_reader :type_map + # get the path to the templates + # @return [String] the base template path + attr_reader :template_path + # get the tree of templates + # @api private + # @return [Hash] the template tree + attr_reader :templates - # render the template within the context - # @return [String] the rendered template - def render - Tilt.new(@path).render(@context) + # load the template tree + # + # This method gets all templates in the `template_path` and builds an + # internal tree structure, where templates and types direct the request to + # the wanted template. + def read_template_path! + @templates = Hash.new do |hash, key| + subtree = {} + search_files(key).each do |file| + parts = file.split('.') + read_type(parts[2]).each do |type| + subtree[type] = file + end + end + hash[key] = subtree + end + self end private - # check if the template does exist - # @api private - # @param template_path [String] the relative path to the template - # @param types [Array] a sorted list of types to search for - # @return [String] a file name to use - def find_template(template_path, types) - types.each do |type| - Dir[@@path + template_path + '.' + transform(type) + '.*'].each do |file| - return file - end - end - raise FileNotFoundError.new("Template '#{template_path}' not found!") + def search_files(template_name) + Dir[template_path + template_name + '**/*.*'] end - # @see transform - # @api private - def transform(string) - self.class.transform(string) + def read_type(short_notation) + to_type_list(type_map[short_notation] || short_notation) end - # transform a type into a simpler representation - # @api private - # @param string [String] the original type name - # @return [String] the shorter representation or the original - def self.transform(string) - return type_map[string] if type_map.has_key?(string) - string - end - - # an alias to Renderer.map - # @api private - def map - self.class.map + def to_type_list(original_map) + return original_map if original_map.respond_to?(:each) + [original_map] end end end diff --git a/spec/unit/renderer/read_template_path_spec.rb b/spec/unit/renderer/read_template_path_spec.rb new file mode 100644 index 0000000..8522007 --- /dev/null +++ b/spec/unit/renderer/read_template_path_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Zero::Renderer, 'read_template_path!' do + subject { Zero::Renderer.new(template_path, type_map) } + let(:template_path) { 'foo' } + let(:file_list) { ['./foo/welcome/index.html.erb'] } + + before :each do + subject.stub(:search_files).and_return(file_list) + end + + shared_examples_for 'a template loader' do + it 'creates a template tree' do + subject.read_template_path! + subject.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 +end diff --git a/spec/unit/renderer/template_path.rb b/spec/unit/renderer/template_path.rb new file mode 100644 index 0000000..261faa8 --- /dev/null +++ b/spec/unit/renderer/template_path.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe Zero::Renderer, '#template_path' do + subject { Zero::Renderer.new(template_path) } + let(:template_path) { 'foo' } + + its(:type_map) { should be(template_path) } +end diff --git a/spec/unit/renderer/transform_spec.rb b/spec/unit/renderer/transform_spec.rb deleted file mode 100644 index fce6730..0000000 --- a/spec/unit/renderer/transform_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe Zero::Renderer, '.transform' do - subject { Zero::Renderer } - let(:map) {{ 'text/html' => 'html' }} - - shared_examples_for 'a transformer' do - before :each do - Zero::Renderer.type_map = map - end - - after :each do - Zero::Renderer.type_map = {} - end - - it "transforms a string" do - subject.transform(original).should eq(result) - end - end - - context "with a shortable type" do - let(:original) { 'text/html' } - let(:result) { 'html' } - it_behaves_like 'a transformer' - end - - context "with an unshortable type" do - let(:original) { 'application/json' } - let(:result) { 'application/json' } - it_behaves_like 'a transformer' - end -end diff --git a/spec/unit/renderer/type_map_spec.rb b/spec/unit/renderer/type_map_spec.rb index f9839e7..f0b86ad 100644 --- a/spec/unit/renderer/type_map_spec.rb +++ b/spec/unit/renderer/type_map_spec.rb @@ -1,10 +1,9 @@ require 'spec_helper' -describe Zero::Renderer, '.type_map' do - subject { Zero::Renderer } - let(:mapping) {{ 'test/foo' => 'foo' }} - it "saves the map" do - subject.type_map = mapping - subject.type_map.should be(mapping) - end +describe Zero::Renderer, '#type_map' do + subject { Zero::Renderer.new(template_path, type_map) } + let(:template_path) { 'foo' } + let(:type_map) { {'html' => ['text/html']} } + + its(:type_map) { should be(type_map) } end