aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template/template.rb
blob: 7d6964e3e35e23c8d813f5e9068c2b97935a00b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# encoding: utf-8
# This is so that templates compiled in this file are UTF-8

require 'set'
require "action_view/template/resolver"

module ActionView
  class Template
    extend TemplateHandlers
    attr_reader :source, :identifier, :handler, :mime_type, :formats, :details
    
    def initialize(source, identifier, handler, details)
      @source     = source
      @identifier = identifier
      @handler    = handler
      @details    = details
      @method_names = {}

      format = details.delete(:format) || begin
        # TODO: Clean this up
        handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html"
      end
      @mime_type = Mime::Type.lookup_by_extension(format.to_s)
      @formats = [format.to_sym]
      @formats << :html if format == :js
      @details[:formats] = Array.wrap(format.to_sym)
    end
    
    def render(view, locals, &block)
      method_name = compile(locals, view)
      view.send(method_name, locals, &block)
    rescue Exception => e
      if e.is_a?(TemplateError)
        e.sub_template_of(self)
        raise e
      else
        raise TemplateError.new(self, view.assigns, e)
      end
    end
    
    # TODO: Figure out how to abstract this
    def variable_name
      @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
    end

    # TODO: Figure out how to abstract this
    def counter_name
      @counter_name ||= "#{variable_name}_counter".to_sym
    end
    
    # TODO: kill hax
    def partial?
      @details[:partial]
    end

  private

    def compile(locals, view)
      method_name = build_method_name(locals)
      
      return method_name if view.respond_to?(method_name)
      
      locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join

      code = @handler.call(self)
      if code.sub!(/\A(#.*coding.*)\n/, '')
        encoding_comment = $1
      elsif defined?(Encoding) && Encoding.respond_to?(:default_external)
        encoding_comment = "#coding:#{Encoding.default_external}"
      end

      source = <<-end_src
        def #{method_name}(local_assigns)
          old_output_buffer = output_buffer;#{locals_code};#{code}
        ensure
          self.output_buffer = old_output_buffer
        end
      end_src

      if encoding_comment
        source = "#{encoding_comment}\n#{source}"
        line = -1
      else
        line = 0
      end

      begin
        ActionView::CompiledTemplates.module_eval(source, identifier, line)
        method_name
      rescue Exception => e # errors from template code
        if logger = (view && view.logger)
          logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
          logger.debug "Function body: #{source}"
          logger.debug "Backtrace: #{e.backtrace.join("\n")}"
        end

        raise ActionView::TemplateError.new(self, {}, e)
      end
    end

    class LocalsKey
      @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }

      def self.get(*locals)
        @hash_keys[*locals] ||= new(klass, format, locale)
      end

      attr_accessor :hash
      def initialize(klass, format, locale)
        @hash = locals.hash
      end

      alias_method :eql?, :equal?
    end

    def build_method_name(locals)
      # TODO: is locals.keys.hash reliably the same?
      @method_names[locals.keys.hash] ||=
        "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
    end
  end
end