diff options
| -rw-r--r-- | lib/zero/renderer.rb | 48 | ||||
| -rw-r--r-- | lib/zero/renderer/template_finder.rb | 164 | ||||
| -rw-r--r-- | spec/unit/zero/renderer/read_template_path_bang_spec.rb | 8 | ||||
| -rw-r--r-- | spec/unit/zero/renderer/render_spec.rb | 2 | ||||
| -rw-r--r-- | spec/unit/zero/renderer/template_finder/get_templates_spec.rb | 65 | ||||
| -rw-r--r-- | spec/unit/zero/renderer/template_finder/initialize_spec.rb | 19 | 
6 files changed, 257 insertions, 49 deletions
| diff --git a/lib/zero/renderer.rb b/lib/zero/renderer.rb index b2b7c94..2a848c2 100644 --- a/lib/zero/renderer.rb +++ b/lib/zero/renderer.rb @@ -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 diff --git a/lib/zero/renderer/template_finder.rb b/lib/zero/renderer/template_finder.rb new file mode 100644 index 0000000..39fb8cb --- /dev/null +++ b/lib/zero/renderer/template_finder.rb @@ -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 diff --git a/spec/unit/zero/renderer/read_template_path_bang_spec.rb b/spec/unit/zero/renderer/read_template_path_bang_spec.rb index 8655777..948a3cc 100644 --- a/spec/unit/zero/renderer/read_template_path_bang_spec.rb +++ b/spec/unit/zero/renderer/read_template_path_bang_spec.rb @@ -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({}) diff --git a/spec/unit/zero/renderer/render_spec.rb b/spec/unit/zero/renderer/render_spec.rb index 30d2225..3270ac3 100644 --- a/spec/unit/zero/renderer/render_spec.rb +++ b/spec/unit/zero/renderer/render_spec.rb @@ -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'] diff --git a/spec/unit/zero/renderer/template_finder/get_templates_spec.rb b/spec/unit/zero/renderer/template_finder/get_templates_spec.rb new file mode 100644 index 0000000..5928e66 --- /dev/null +++ b/spec/unit/zero/renderer/template_finder/get_templates_spec.rb @@ -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 diff --git a/spec/unit/zero/renderer/template_finder/initialize_spec.rb b/spec/unit/zero/renderer/template_finder/initialize_spec.rb new file mode 100644 index 0000000..5ed9502 --- /dev/null +++ b/spec/unit/zero/renderer/template_finder/initialize_spec.rb @@ -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 | 
