2012-12-17 15:21:16 -05:00
|
|
|
require 'thread_safe'
|
|
|
|
|
2012-12-16 17:17:53 -05:00
|
|
|
module ActionView
|
2013-12-15 21:51:35 -05:00
|
|
|
class DependencyTracker # :nodoc:
|
2012-12-17 15:21:16 -05:00
|
|
|
@trackers = ThreadSafe::Cache.new
|
2012-12-16 17:38:46 -05:00
|
|
|
|
2012-12-16 17:17:53 -05:00
|
|
|
def self.find_dependencies(name, template)
|
2012-12-17 15:26:55 -05:00
|
|
|
tracker = @trackers[template.handler]
|
|
|
|
|
|
|
|
if tracker.present?
|
|
|
|
tracker.call(name, template)
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
2012-12-16 17:38:46 -05:00
|
|
|
end
|
|
|
|
|
2013-02-20 10:42:30 -05:00
|
|
|
def self.register_tracker(extension, tracker)
|
|
|
|
handler = Template.handler_for_extension(extension)
|
2012-12-16 17:38:46 -05:00
|
|
|
@trackers[handler] = tracker
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.remove_tracker(handler)
|
|
|
|
@trackers.delete(handler)
|
2012-12-16 17:17:53 -05:00
|
|
|
end
|
|
|
|
|
2013-12-15 21:51:35 -05:00
|
|
|
class ERBTracker # :nodoc:
|
2012-12-16 17:17:53 -05:00
|
|
|
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
|
|
|
|
|
2013-12-15 21:51:35 -05:00
|
|
|
# A valid ruby identifier - suitable for class, method and specially variable names
|
|
|
|
IDENTIFIER = /
|
|
|
|
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
|
|
|
|
[[:word:]]* # followed by optional letters, numbers or underscores
|
|
|
|
/x
|
|
|
|
|
|
|
|
# Any kind of variable name. e.g. @instance, @@class, $global or local.
|
|
|
|
# Possibly following a method call chain
|
|
|
|
VARIABLE_OR_METHOD_CHAIN = /
|
|
|
|
(?:\$|@{1,2})? # optional global, instance or class variable indicator
|
|
|
|
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
|
|
|
|
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
|
|
|
|
/x
|
|
|
|
|
|
|
|
# A simple string literal. e.g. "School's out!"
|
|
|
|
STRING = /
|
|
|
|
(?<quote>['"]) # an opening quote
|
|
|
|
(?<static>.*?) # with anything inside, captured as STATIC
|
|
|
|
\k<quote> # and a matching closing quote
|
|
|
|
/x
|
|
|
|
|
|
|
|
# Part of any hash containing the :partial key
|
|
|
|
PARTIAL_HASH_KEY = /
|
|
|
|
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
|
|
|
|
\s* # followed by optional spaces
|
|
|
|
/x
|
|
|
|
|
2012-12-16 17:17:53 -05:00
|
|
|
# Matches:
|
2013-12-21 19:14:07 -05:00
|
|
|
# partial: "comments/comment", collection: @all_comments => "comments/comment"
|
|
|
|
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
|
2012-12-16 17:17:53 -05:00
|
|
|
#
|
2013-12-21 19:14:07 -05:00
|
|
|
# "comments/comments"
|
|
|
|
# 'comments/comments'
|
|
|
|
# ('comments/comments')
|
|
|
|
#
|
|
|
|
# (@topic) => "topics/topic"
|
|
|
|
# topics => "topics/topic"
|
|
|
|
# (message.topics) => "topics/topic"
|
|
|
|
RENDER_ARGUMENTS = /\A
|
|
|
|
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
|
|
|
(?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration
|
|
|
|
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
2013-12-15 21:51:35 -05:00
|
|
|
/xm
|
2012-12-16 17:17:53 -05:00
|
|
|
|
|
|
|
def self.call(name, template)
|
2013-02-20 10:42:30 -05:00
|
|
|
new(name, template).dependencies
|
2012-12-16 17:17:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(name, template)
|
|
|
|
@name, @template = name, template
|
|
|
|
end
|
|
|
|
|
2013-02-20 10:42:30 -05:00
|
|
|
def dependencies
|
2012-12-16 17:17:53 -05:00
|
|
|
render_dependencies + explicit_dependencies
|
|
|
|
end
|
|
|
|
|
2013-02-26 18:56:56 -05:00
|
|
|
attr_reader :name, :template
|
|
|
|
private :name, :template
|
|
|
|
|
2012-12-16 17:17:53 -05:00
|
|
|
private
|
|
|
|
|
2013-02-20 10:42:30 -05:00
|
|
|
def source
|
|
|
|
template.source
|
|
|
|
end
|
|
|
|
|
2012-12-16 17:17:53 -05:00
|
|
|
def directory
|
|
|
|
name.split("/")[0..-2].join("/")
|
|
|
|
end
|
|
|
|
|
|
|
|
def render_dependencies
|
2013-12-15 21:51:35 -05:00
|
|
|
render_dependencies = []
|
2013-12-21 19:14:07 -05:00
|
|
|
render_calls = source.split(/\brender\b/).drop(1)
|
2012-12-16 17:17:53 -05:00
|
|
|
|
2013-12-21 19:14:07 -05:00
|
|
|
render_calls.each do |arguments|
|
|
|
|
arguments.scan(RENDER_ARGUMENTS) do
|
|
|
|
add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
|
|
|
|
add_static_dependency(render_dependencies, Regexp.last_match[:static])
|
|
|
|
end
|
2013-12-15 21:51:35 -05:00
|
|
|
end
|
2012-12-16 17:17:53 -05:00
|
|
|
|
2013-12-15 21:51:35 -05:00
|
|
|
render_dependencies.uniq
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_dynamic_dependency(dependencies, dependency)
|
|
|
|
if dependency
|
|
|
|
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
|
|
|
|
end
|
|
|
|
end
|
2012-12-16 17:17:53 -05:00
|
|
|
|
2013-12-15 21:51:35 -05:00
|
|
|
def add_static_dependency(dependencies, dependency)
|
|
|
|
if dependency
|
|
|
|
if dependency.include?('/')
|
|
|
|
dependencies << dependency
|
|
|
|
else
|
|
|
|
dependencies << "#{directory}/#{dependency}"
|
|
|
|
end
|
|
|
|
end
|
2012-12-16 17:17:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def explicit_dependencies
|
2013-02-20 10:42:30 -05:00
|
|
|
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
2012-12-16 17:17:53 -05:00
|
|
|
end
|
|
|
|
end
|
2012-12-16 17:38:46 -05:00
|
|
|
|
2013-02-20 10:42:30 -05:00
|
|
|
register_tracker :erb, ERBTracker
|
2012-12-16 17:17:53 -05:00
|
|
|
end
|
|
|
|
end
|