1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
require 'thread_safe'
module ActionView
class DependencyTracker # :nodoc:
@trackers = ThreadSafe::Cache.new
def self.find_dependencies(name, template)
tracker = @trackers[template.handler]
if tracker.present?
tracker.call(name, template)
else
[]
end
end
def self.register_tracker(extension, tracker)
handler = Template.handler_for_extension(extension)
@trackers[handler] = tracker
end
def self.remove_tracker(handler)
@trackers.delete(handler)
end
class ERBTracker # :nodoc:
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
# 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
# Part of any hash containing the :layout key
LAYOUT_HASH_KEY = /
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
\s* # followed by optional spaces
/x
# Matches:
# partial: "comments/comment", collection: @all_comments => "comments/comment"
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
#
# "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}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
def self.call(name, template)
new(name, template).dependencies
end
def initialize(name, template)
@name, @template = name, template
end
def dependencies
render_dependencies + explicit_dependencies
end
attr_reader :name, :template
private :name, :template
private
def source
template.source
end
def directory
name.split("/")[0..-2].join("/")
end
def render_dependencies
render_dependencies = []
render_calls = source.split(/\brender\b/).drop(1)
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
end
render_dependencies.uniq
end
def add_dynamic_dependency(dependencies, dependency)
if dependency
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
end
end
def add_static_dependency(dependencies, dependency)
if dependency
if dependency.include?('/')
dependencies << dependency
else
dependencies << "#{directory}/#{dependency}"
end
end
end
def explicit_dependencies
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
end
end
register_tracker :erb, ERBTracker
end
end
|