diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 0f79f6febe..99705c4784 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -35,6 +35,44 @@ module ActionView alias :to_s :to_str 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 + (?:(?.*)/)? + (?_)? + (?.*?) + (?:\.(?#{locales}))?? + (?:\.(?#{formats}))?? + (?:\+(?#{variants}))?? + (?:\.(?#{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 class Cache #:nodoc: class SmallCache < Concurrent::Map @@ -172,11 +210,13 @@ module ActionView @pattern = DEFAULT_PATTERN end @unbound_templates = Concurrent::Map.new + @path_parser = PathParser.new super() end def clear_cache @unbound_templates.clear + @path_parser = PathParser.new super() end @@ -278,18 +318,11 @@ module ActionView # from the path, or the handler, we should return the array of formats given # to the resolver. def extract_handler_and_format_and_variant(path) - pieces = File.basename(path).split(".") - pieces.shift + details = @path_parser.parse(path) - extension = pieces.pop - - handler = Template.handler_for_extension(extension) - 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 + handler = Template.handler_for_extension(details[:handler]) + format = details[:format] || handler.try(:default_format) + variant = details[:variant] # Template::Types[format] and handler.default_format can return nil [handler, format, variant] diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index f946e4360d..0b2627c290 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -18,9 +18,13 @@ end module TemplateHandlerHelper def with_template_handler(*extensions, handler) ActionView::Template.register_template_handler(*extensions, handler) + ActionController::Base.view_paths.paths.each(&:clear_cache) + ActionView::LookupContext::DetailsKey.clear yield ensure ActionView::Template.unregister_template_handler(*extensions) + ActionController::Base.view_paths.paths.each(&:clear_cache) + ActionView::LookupContext::DetailsKey.clear end end diff --git a/actionview/test/template/resolver_shared_tests.rb b/actionview/test/template/resolver_shared_tests.rb index 4f89420ed8..f513cca0bb 100644 --- a/actionview/test/template/resolver_shared_tests.rb +++ b/actionview/test/template/resolver_shared_tests.rb @@ -204,8 +204,6 @@ module ResolverSharedTests end def test_templates_no_format_with_variant - return skip - 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]) @@ -218,8 +216,6 @@ module ResolverSharedTests end def test_templates_no_format_or_handler_with_variant - return skip - 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])