1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Introduce Resolver::PathParser

The template resolver is supposed to determine the handler, format, and
variant from the path in extract_handler_and_format_and_variant.

Previously this behaviour was close but didn't exactly match the
behaviour of finding templates, and in some cases (particularly with
handlers or formats missing) would return incorrect results.

This commit introduces Resolver::PathParser, a class which should be
able to accurately from any path inside a view directory be able to tell
us exactly the prefix, partial, variant, locale, format, variant, and
handler of that template.

This works by building a building a regexp from the known handlers and
file types. This requires that any resolvers have their cache cleared
when new handlers or types are registered (this was already somewhat the
requirement, since resolver lookups are cached, but this makes it
necessary in more situations).
This commit is contained in:
John Hawthorn 2019-09-30 22:43:59 -07:00
parent 8762d93afc
commit e53c45b80c
3 changed files with 48 additions and 15 deletions

View file

@ -35,6 +35,44 @@ module ActionView
alias :to_s :to_str alias :to_s :to_str
end end
class PathParser
def initialize
@regex = build_path_regex
end
def build_path_regex
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
locales = "[a-z]{2}(?:-[A-Z]{2})?"
variants = "[^.]*"
%r{
\A
(?:(?<prefix>.*)/)?
(?<partial>_)?
(?<action>.*?)
(?:\.(?<locale>#{locales}))??
(?:\.(?<format>#{formats}))??
(?:\+(?<variant>#{variants}))??
(?:\.(?<handler>#{handlers}))?
\z
}x
end
def parse(path)
match = @regex.match(path)
{
prefix: match[:prefix] || "",
action: match[:action],
partial: !!match[:partial],
locale: match[:locale]&.to_sym,
handler: match[:handler]&.to_sym,
format: match[:format]&.to_sym,
variant: match[:variant]
}
end
end
# Threadsafe template cache # Threadsafe template cache
class Cache #:nodoc: class Cache #:nodoc:
class SmallCache < Concurrent::Map class SmallCache < Concurrent::Map
@ -172,11 +210,13 @@ module ActionView
@pattern = DEFAULT_PATTERN @pattern = DEFAULT_PATTERN
end end
@unbound_templates = Concurrent::Map.new @unbound_templates = Concurrent::Map.new
@path_parser = PathParser.new
super() super()
end end
def clear_cache def clear_cache
@unbound_templates.clear @unbound_templates.clear
@path_parser = PathParser.new
super() super()
end end
@ -278,18 +318,11 @@ module ActionView
# from the path, or the handler, we should return the array of formats given # from the path, or the handler, we should return the array of formats given
# to the resolver. # to the resolver.
def extract_handler_and_format_and_variant(path) def extract_handler_and_format_and_variant(path)
pieces = File.basename(path).split(".") details = @path_parser.parse(path)
pieces.shift
extension = pieces.pop handler = Template.handler_for_extension(details[:handler])
format = details[:format] || handler.try(:default_format)
handler = Template.handler_for_extension(extension) variant = details[:variant]
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
format = if format
Template::Types[format]&.ref
elsif handler.respond_to?(:default_format) # default_format can return nil
handler.default_format
end
# Template::Types[format] and handler.default_format can return nil # Template::Types[format] and handler.default_format can return nil
[handler, format, variant] [handler, format, variant]

View file

@ -18,9 +18,13 @@ end
module TemplateHandlerHelper module TemplateHandlerHelper
def with_template_handler(*extensions, handler) def with_template_handler(*extensions, handler)
ActionView::Template.register_template_handler(*extensions, handler) ActionView::Template.register_template_handler(*extensions, handler)
ActionController::Base.view_paths.paths.each(&:clear_cache)
ActionView::LookupContext::DetailsKey.clear
yield yield
ensure ensure
ActionView::Template.unregister_template_handler(*extensions) ActionView::Template.unregister_template_handler(*extensions)
ActionController::Base.view_paths.paths.each(&:clear_cache)
ActionView::LookupContext::DetailsKey.clear
end end
end end

View file

@ -204,8 +204,6 @@ module ResolverSharedTests
end end
def test_templates_no_format_with_variant def test_templates_no_format_with_variant
return skip
with_file "test/hello_world+mobile.erb", "Hello HTML!" with_file "test/hello_world+mobile.erb", "Hello HTML!"
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]) templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
@ -218,8 +216,6 @@ module ResolverSharedTests
end end
def test_templates_no_format_or_handler_with_variant def test_templates_no_format_or_handler_with_variant
return skip
with_file "test/hello_world+mobile", "Hello HTML!" with_file "test/hello_world+mobile", "Hello HTML!"
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder]) templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])