aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template.rb
blob: 210ad508f52c72e660f1c93502d8d0212bb7af2e (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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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 ActiveSupport::Autoload
    
    autoload :Error
    autoload :Handler
    autoload :Handlers
    autoload :Text
    
    extend Template::Handlers
    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)
      ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do
        method_name = compile(locals, view)
        view.send(method_name, locals, &block)
      end
    rescue Exception => e
      if e.is_a?(Template::Error)
        e.sub_template_of(self)
        raise e
      else
        raise Template::Error.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

    def inspect
      if defined?(Rails.root)
        identifier.sub("#{Rails.root}/", '')
      else
        identifier
      end
    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::Template::Error.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