From cecafc52ee0a4a53c903ddbaba95683261f88e5f Mon Sep 17 00:00:00 2001 From: Yehuda Katz + Carl Lerche Date: Thu, 23 Apr 2009 15:58:38 -0700 Subject: Refactor ActionView::Template ActionView::Template is now completely independent from template storage, which allows different back ends such as the database. ActionView::Template's only responsibility is to take in the template source (passed in from ActionView::Path), compile it, and render it. --- actionpack/lib/action_view/template/template.rb | 336 +++++++++++++----------- 1 file changed, 189 insertions(+), 147 deletions(-) (limited to 'actionpack/lib/action_view/template/template.rb') diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index e541336613..ce6268729a 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -1,188 +1,230 @@ +# encoding: utf-8 +# This is so that templates compiled in this file are UTF-8 + require 'set' require "action_view/template/path" -module ActionView #:nodoc: +module ActionView class Template extend TemplateHandlers - extend ActiveSupport::Memoizable + attr_reader :source, :identifier, :handler - module Loading - def load! - @cached = true - # freeze - end + def initialize(source, identifier, handler, details) + @source = source + @identifier = identifier + @handler = handler + @details = details end - include Loading - include Renderable - - # Templates that are exempt from layouts - @@exempt_from_layout = Set.new([/\.rjs$/]) - - # Don't render layouts for templates with the given extensions. - def self.exempt_from_layout(*extensions) - regexps = extensions.collect do |extension| - extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ - end - @@exempt_from_layout.merge(regexps) + def render(view, locals, &blk) + method_name = compile(locals, view) + view.send(method_name, locals, &blk) + end + + # TODO: Figure out how to abstract this + def variable_name + identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym end - attr_accessor :template_path, :filename, :load_path, :base_path - attr_accessor :locale, :name, :format, :extension - delegate :to_s, :to => :path + # TODO: Figure out how to abstract this + def counter_name + "#{variable_name}_counter".to_sym + end + + # TODO: kill hax + def partial? + @details[:partial] + end + + # TODO: Move out of Template + def mime_type + Mime::Type.lookup_by_extension(@details[:format]) if @details[:format] + end + + private - def initialize(template_path, load_paths = []) - template_path = template_path.dup - @load_path, @filename = find_full_path(template_path, load_paths) - @base_path, @name, @locale, @format, @extension = split(template_path) - @base_path.to_s.gsub!(/\/$/, '') # Push to split method + 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 + + source = <<-end_src + def #{method_name}(local_assigns) + old_output_buffer = output_buffer;#{locals_code};#{@handler.call(self)} + ensure + self.output_buffer = old_output_buffer + end + end_src + + begin + ActionView::Base::CompiledTemplates.module_eval(source, identifier, 0) + 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 - # Extend with partial super powers - extend RenderablePartial if @name =~ /^_/ + raise ActionView::TemplateError.new(self, {}, e) + end + end + + def build_method_name(locals) + # TODO: is locals.keys.hash reliably the same? + "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") end + end +end + +if false + module ActionView #:nodoc: + class Template + extend TemplateHandlers + extend ActiveSupport::Memoizable - def accessible_paths - paths = [] + module Loading + def load! + @cached = true + # freeze + end + end + include Loading + + include Renderable - if valid_extension?(extension) - paths << path - paths << path_without_extension - if multipart? - formats = format.split(".") - paths << "#{path_without_format_and_extension}.#{formats.first}" - paths << "#{path_without_format_and_extension}.#{formats.second}" + # Templates that are exempt from layouts + @@exempt_from_layout = Set.new([/\.rjs$/]) + + # Don't render layouts for templates with the given extensions. + def self.exempt_from_layout(*extensions) + regexps = extensions.collect do |extension| + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ end - else - # template without explicit template handler should only be reachable through its exact path - paths << template_path + @@exempt_from_layout.merge(regexps) end - paths - end + attr_accessor :template_path, :filename, :load_path, :base_path + attr_accessor :locale, :name, :format, :extension + delegate :to_s, :to => :path + + def initialize(template_path, load_paths = []) + template_path = template_path.dup + @load_path, @filename = find_full_path(template_path, load_paths) + @name = template_path.to_s.split("/").last.split(".").first + # @base_path, @name, @locale, @format, @extension = split(template_path) + @base_path.to_s.gsub!(/\/$/, '') # Push to split method + + # Extend with partial super powers + extend RenderablePartial if @name =~ /^_/ + end - def relative_path - path = File.expand_path(filename) - path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT) - path - end - memoize :relative_path + def accessible_paths + paths = [] + + if valid_extension?(extension) + paths << path + paths << path_without_extension + if multipart? + formats = format.split(".") + paths << "#{path_without_format_and_extension}.#{formats.first}" + paths << "#{path_without_format_and_extension}.#{formats.second}" + end + else + # template without explicit template handler should only be reachable through its exact path + paths << template_path + end + + paths + end - def source - File.read(filename) - end - memoize :source + def relative_path + path = File.expand_path(filename) + path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT) + path + end + memoize :relative_path - def exempt_from_layout? - @@exempt_from_layout.any? { |exempted| path =~ exempted } - end + def source + File.read(filename) + end + memoize :source - def path_without_extension - [base_path, [name, locale, format].compact.join('.')].compact.join('/') - end - memoize :path_without_extension + def exempt_from_layout? + @@exempt_from_layout.any? { |exempted| path =~ exempted } + end + + def path_without_extension + [base_path, [name, locale, format].compact.join('.')].compact.join('/') + end + memoize :path_without_extension - def path_without_format_and_extension - [base_path, [name, locale].compact.join('.')].compact.join('/') - end - memoize :path_without_format_and_extension + def path_without_format_and_extension + [base_path, [name, locale].compact.join('.')].compact.join('/') + end + memoize :path_without_format_and_extension - def path - [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/') - end - memoize :path + def path + [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/') + end + memoize :path - def mime_type - Mime::Type.lookup_by_extension(format) if format && defined?(::Mime) - end - memoize :mime_type + def mime_type + Mime::Type.lookup_by_extension(format) if format && defined?(::Mime) + end + memoize :mime_type - def multipart? - format && format.include?('.') - end + def multipart? + format && format.include?('.') + end - def content_type - format && format.gsub('.', '/') - end + def content_type + format && format.gsub('.', '/') + end - private + private - def format_and_extension - (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions - end - memoize :format_and_extension - - def mtime - File.mtime(filename) - end - memoize :mtime + def format_and_extension + (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions + end + memoize :format_and_extension - def method_segment - relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord } - end - memoize :method_segment + def mtime + File.mtime(filename) + end + memoize :mtime - def stale? - File.mtime(filename) > mtime - end + def method_segment + relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord } + end + memoize :method_segment - def recompile? - !@cached - end + def stale? + File.mtime(filename) > mtime + end - def valid_extension?(extension) - !Template.registered_template_handler(extension).nil? - end + def recompile? + !@cached + end - def valid_locale?(locale) - I18n.available_locales.include?(locale.to_sym) - end + def valid_extension?(extension) + !Template.registered_template_handler(extension).nil? + end - def find_full_path(path, load_paths) - load_paths = Array(load_paths) + [nil] - load_paths.each do |load_path| - file = load_path ? "#{load_path.to_str}/#{path}" : path - return load_path, file if File.file?(file) + def valid_locale?(locale) + I18n.available_locales.include?(locale.to_sym) end - raise MissingTemplate.new(load_paths, path) - end - # Returns file split into an array - # [base_path, name, locale, format, extension] - def split(file) - if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/) - base_path = m[1] - name = m[2] - extensions = m[3] - else - return - end - - locale = nil - format = nil - extension = nil - - if m = extensions.split(".") - if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three - locale = m[0] - format = m[1] - extension = m[2] - elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats - format = "#{m[0]}.#{m[1]}" - extension = m[2] - elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension - locale = m[0] - extension = m[1] - elsif valid_extension?(m[1]) # format and extension - format = m[0] - extension = m[1] - elsif valid_extension?(m[0]) # Just extension - extension = m[0] - else # No extension - format = m[0] + def find_full_path(path, load_paths) + load_paths = Array(load_paths) + [nil] + load_paths.each do |load_path| + file = load_path ? "#{load_path.to_str}/#{path}" : path + return load_path, file if File.file?(file) end + raise MissingTemplate.new(load_paths, path) end - - [base_path, name, locale, format, extension] end end -end +end \ No newline at end of file -- cgit v1.2.3