diff options
Diffstat (limited to 'actionview/lib/action_view/template')
-rw-r--r-- | actionview/lib/action_view/template/error.rb | 22 | ||||
-rw-r--r-- | actionview/lib/action_view/template/handlers.rb | 6 | ||||
-rw-r--r-- | actionview/lib/action_view/template/handlers/erb/erubi.rb | 6 | ||||
-rw-r--r-- | actionview/lib/action_view/template/html.rb | 19 | ||||
-rw-r--r-- | actionview/lib/action_view/template/inline.rb | 22 | ||||
-rw-r--r-- | actionview/lib/action_view/template/raw_file.rb | 28 | ||||
-rw-r--r-- | actionview/lib/action_view/template/resolver.rb | 196 | ||||
-rw-r--r-- | actionview/lib/action_view/template/sources.rb | 13 | ||||
-rw-r--r-- | actionview/lib/action_view/template/sources/file.rb | 17 | ||||
-rw-r--r-- | actionview/lib/action_view/template/text.rb | 8 |
10 files changed, 206 insertions, 131 deletions
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index 4e3c02e05e..d0ea03e228 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -109,7 +109,7 @@ module ActionView end end - def annoted_source_code + def annotated_source_code source_extract(4) end @@ -138,4 +138,24 @@ module ActionView end TemplateError = Template::Error + + class SyntaxErrorInTemplate < TemplateError #:nodoc + def initialize(template, offending_code_string) + @offending_code_string = offending_code_string + super(template) + end + + def message + <<~MESSAGE + Encountered a syntax error while rendering template: check #{@offending_code_string} + MESSAGE + end + + def annotated_source_code + @offending_code_string.split("\n").map.with_index(1) { |line, index| + indentation = " " * 4 + "#{index}:#{indentation}#{line}" + } + end + end end diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb index ddaac7a100..6450513003 100644 --- a/actionview/lib/action_view/template/handlers.rb +++ b/actionview/lib/action_view/template/handlers.rb @@ -45,12 +45,12 @@ module ActionView #:nodoc: unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2 ActiveSupport::Deprecation.warn <<~eowarn - Single arity template handlers are deprecated. Template handlers must + Single arity template handlers are deprecated. Template handlers must now accept two parameters, the view object and the source for the view object. Change: - >> #{handler.class}#call(#{params.map(&:last).join(", ")}) + >> #{handler}.call(#{params.map(&:last).join(", ")}) To: - >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source) + >> #{handler}.call(#{params.map(&:last).join(", ")}, source) eowarn handler = LegacyHandlerWrapper.new(handler) end diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb index 15ca202024..e602aa117a 100644 --- a/actionview/lib/action_view/template/handlers/erb/erubi.rb +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -25,9 +25,9 @@ module ActionView src = @src view = Class.new(ActionView::Base) { include action_view_erb_handler_context._routes.url_helpers - class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0) - }.with_context(action_view_erb_handler_context) - view.run(:_template, {}, ActionView::OutputBuffer.new) + class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0) + }.empty + view._run(:_template, nil, {}, ActionView::OutputBuffer.new) end private diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index a262c6d9ad..ecd1c31e79 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -1,15 +1,21 @@ # frozen_string_literal: true +require "active_support/deprecation" + module ActionView #:nodoc: # = Action View HTML Template class Template #:nodoc: class HTML #:nodoc: - attr_accessor :type + attr_reader :type def initialize(string, type = nil) + unless type + ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter" + type = :html + end + @string = string.to_s - @type = Types[type] || type if type - @type ||= Types[:html] + @type = type end def identifier @@ -26,9 +32,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + def format + @type end + + def formats; Array(format); end + deprecate :formats end end end diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb new file mode 100644 index 0000000000..44658487ea --- /dev/null +++ b/actionview/lib/action_view/template/inline.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + class Template #:nodoc: + class Inline < Template #:nodoc: + # This finalizer is needed (and exactly with a proc inside another proc) + # otherwise templates leak in development. + Finalizer = proc do |method_name, mod| # :nodoc: + proc do + mod.module_eval do + remove_possible_method method_name + end + end + end + + def compile(mod) + super + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end + end + end +end diff --git a/actionview/lib/action_view/template/raw_file.rb b/actionview/lib/action_view/template/raw_file.rb new file mode 100644 index 0000000000..623351305f --- /dev/null +++ b/actionview/lib/action_view/template/raw_file.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View RawFile Template + class Template #:nodoc: + class RawFile #:nodoc: + attr_accessor :type, :format + + def initialize(filename) + @filename = filename.to_s + extname = ::File.extname(filename).delete(".") + @type = Template::Types[extname] || Template::Types[:text] + @format = @type.symbol + end + + def identifier + @filename + end + + def render(*args) + ::File.read(@filename) + end + + def formats; Array(format); end + deprecate :formats + end + end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3b4594942b..ce53eb046d 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -63,26 +63,11 @@ module ActionView # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) - if Resolver.caching? - @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) - else - fresh_templates = yield - cached_templates = @data[key][name][prefix][partial][locals] - - if templates_have_changed?(cached_templates, fresh_templates) - @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) - else - cached_templates || NO_TEMPLATES - end - end + @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) end def cache_query(query) # :nodoc: - if Resolver.caching? - @query_cache[query] ||= canonical_no_templates(yield) - else - yield - end + @query_cache[query] ||= canonical_no_templates(yield) end def clear @@ -112,19 +97,6 @@ module ActionView def canonical_no_templates(templates) templates.empty? ? NO_TEMPLATES : templates end - - def templates_have_changed?(cached_templates, fresh_templates) - # if either the old or new template list is empty, we don't need to (and can't) - # compare modification times, and instead just check whether the lists are different - if cached_templates.blank? || fresh_templates.blank? - return fresh_templates.blank? != cached_templates.blank? - end - - cached_templates_max_updated_at = cached_templates.map(&:updated_at).max - - # if a template has changed, it will be now be newer than all the cached templates - fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } - end end cattr_accessor :caching, default: true @@ -143,35 +115,33 @@ module ActionView # Normalizes the arguments and passes it on to find_templates. def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = []) - cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details) - end - end + locals = locals.map(&:to_s).sort!.freeze - def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = []) cached(key, [name, prefix, partial], details, locals) do - find_templates(name, prefix, partial, details, true) + _find_all(name, prefix, partial, details, key, locals) end end + alias :find_all_anywhere :find_all + deprecate :find_all_anywhere + def find_all_with_query(query) # :nodoc: @cache.cache_query(query) { find_template_paths(File.join(@path, query)) } end private + def _find_all(name, prefix, partial, details, key, locals) + find_templates(name, prefix, partial, details, locals) + end + delegate :caching?, to: :class # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, prefix, partial, details, outside_app_allowed = false) - raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method" - end - - # Helpers that builds a path. Useful for building virtual paths. - def build_path(name, prefix, partial) - Path.build(name, prefix, partial) + def find_templates(name, prefix, partial, details, locals = []) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method" end # Handles templates caching. If a key is given and caching is on @@ -180,25 +150,13 @@ module ActionView # resolver is fresher before returning it. def cached(key, path_info, details, locals) name, prefix, partial = path_info - locals = locals.map(&:to_s).sort! if key @cache.cache(key, name, prefix, partial, locals) do - decorate(yield, path_info, details, locals) + yield end else - decorate(yield, path_info, details, locals) - end - end - - # Ensures all the resolver information is set in the template. - def decorate(templates, path_info, details, locals) - cached = nil - templates.each do |t| - t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? - t.variants = details[:variants] || [] if t.variants.empty? - t.virtual_path ||= (cached ||= build_path(*path_info)) + yield end end end @@ -209,33 +167,60 @@ module ActionView DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern = nil) - @pattern = pattern || DEFAULT_PATTERN + if pattern + ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead." + @pattern = pattern + else + @pattern = DEFAULT_PATTERN + end + @unbound_templates = Concurrent::Map.new + super() + end + + def clear_cache + @unbound_templates.clear super() end private - def find_templates(name, prefix, partial, details, outside_app_allowed = false) + def _find_all(name, prefix, partial, details, key, locals) path = Path.build(name, prefix, partial) - query(path, details, details[:formats], outside_app_allowed) + query(path, details, details[:formats], locals, cache: !!key) end - def query(path, details, formats, outside_app_allowed) + def query(path, details, formats, locals, cache:) template_paths = find_template_paths_from_details(path, details) - template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed + template_paths = reject_files_external_to_app(template_paths) template_paths.map do |template| - handler, format, variant = extract_handler_and_format_and_variant(template) - - FileTemplate.new(File.expand_path(template), handler, - virtual_path: path.virtual, - format: format, - variant: variant, - updated_at: mtime(template) - ) + unbound_template = + if cache + @unbound_templates.compute_if_absent([template, path.virtual]) do + build_unbound_template(template, path.virtual) + end + else + build_unbound_template(template, path.virtual) + end + + unbound_template.bind_locals(locals) end end + def build_unbound_template(template, virtual_path) + handler, format, variant = extract_handler_and_format_and_variant(template) + source = Template::Sources::File.new(template) + + UnboundTemplate.new( + source, + template, + handler, + virtual_path: virtual_path, + format: format, + variant: variant, + ) + end + def reject_files_external_to_app(files) files.reject { |filename| !inside_path?(@path, filename) } end @@ -284,11 +269,6 @@ module ActionView entry.gsub(/[*?{}\[\]]/, '\\\\\\&') end - # Returns the file mtime from the filesystem. - def mtime(p) - File.mtime(p) - end - # Extract handler, formats and variant from path. If a format cannot be found neither # from the path, or the handler, we should return the array of formats given # to the resolver. @@ -300,51 +280,25 @@ module ActionView handler = Template.handler_for_extension(extension) format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last - format &&= Template::Types[format] + format = if format + Template::Types[format]&.ref + else + if handler.respond_to?(:default_format) # default_format can return nil + handler.default_format + else + nil + end + end + # Template::Types[format] and handler.default_format can return nil [handler, format, variant] end end - # A resolver that loads files from the filesystem. It allows setting your own - # resolving pattern. Such pattern can be a glob string supported by some variables. - # - # ==== Examples - # - # Default pattern, loads views the same way as previous versions of rails, eg. when you're - # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt> - # - # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") - # - # This one allows you to keep files with different formats in separate subdirectories, - # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>, - # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc. - # - # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") - # - # If you don't specify a pattern then the default will be used. - # - # In order to use any of the customized resolvers above in a Rails application, you just need - # to configure ActionController::Base.view_paths in an initializer, for example: - # - # ActionController::Base.view_paths = FileSystemResolver.new( - # Rails.root.join("app/views"), - # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", - # ) - # - # ==== Pattern format and variables - # - # Pattern has to be a valid glob string, and it allows you to use the - # following variables: - # - # * <tt>:prefix</tt> - usually the controller path - # * <tt>:action</tt> - name of the action - # * <tt>:locale</tt> - possible locale versions - # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) - # * <tt>:variants</tt> - possible request variants (for example phone, tablet...) - # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...) - # + # A resolver that loads files from the filesystem. class FileSystemResolver < PathResolver + attr_reader :path + def initialize(path, pattern = nil) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) super(pattern) @@ -364,6 +318,10 @@ module ActionView # An Optimized resolver for Rails' most common case. class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: + def initialize(path) + super(path) + end + private def find_template_paths_from_details(path, details) @@ -419,12 +377,18 @@ module ActionView # The same as FileSystemResolver but does not allow templates to store # a virtual path since it is invalid for such resolvers. class FallbackFileSystemResolver < FileSystemResolver #:nodoc: + private_class_method :new + def self.instances [new(""), new("/")] end - def decorate(*) - super.each { |t| t.virtual_path = nil } + def build_unbound_template(template, _) + super(template, nil) + end + + def reject_files_external_to_app(files) + files end end end diff --git a/actionview/lib/action_view/template/sources.rb b/actionview/lib/action_view/template/sources.rb new file mode 100644 index 0000000000..8b89535741 --- /dev/null +++ b/actionview/lib/action_view/template/sources.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + extend ActiveSupport::Autoload + + eager_autoload do + autoload :File + end + end + end +end diff --git a/actionview/lib/action_view/template/sources/file.rb b/actionview/lib/action_view/template/sources/file.rb new file mode 100644 index 0000000000..2d3a7dec7f --- /dev/null +++ b/actionview/lib/action_view/template/sources/file.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + class File + def initialize(filename) + @filename = filename + end + + def to_s + ::File.binread @filename + end + end + end + end +end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index f8d6c2811f..c5fd55f1b3 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -8,7 +8,6 @@ module ActionView #:nodoc: def initialize(string) @string = string.to_s - @type = Types[:text] end def identifier @@ -25,9 +24,12 @@ module ActionView #:nodoc: to_str end - def formats - [@type.ref] + def format + :text end + + def formats; Array(format); end + deprecate :formats end end end |