require'concurrent/map'require'action_view/dependency_tracker'require'monitor'moduleActionViewclassDigestor@@digest_mutex=Mutex.newmodulePerExecutionDigestCacheExpirydefself.before(target)ActionView::LookupContext::DetailsKey.clearendendclass<<self# Supported options:## * <tt>name</tt> - Template name# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt># * <tt>dependencies</tt> - An array of dependent views# * <tt>partial</tt> - Specifies whether the template is a partialdefdigest(name:,finder:,dependencies: [])dependencies||=[]cache_key=[name,finder.rendered_format,dependencies].flatten.compact.join('.')# this is a correctly done double-checked locking idiom# (Concurrent::Map's lookups have volatile semantics)finder.digest_cache[cache_key]||@@digest_mutex.synchronizedofinder.digest_cache.fetch(cache_key)do# re-check under lockpartial=name.include?("/_")root=tree(name,finder,partial)dependencies.eachdo|injected_dep|root.children<<Injected.new(injected_dep,nil,nil)endfinder.digest_cache[cache_key]=root.digest(finder)endendenddefloggerActionView::Base.logger||NullLoggerend# Create a dependency tree for template named +name+.deftree(name,finder,partial=false,seen={})logical_name=name.gsub(%r|/_|,"/")options={}options[:formats]=[finder.rendered_format]iffinder.rendered_formatiftemplate=finder.disable_cache{finder.find_all(logical_name,[],partial,[],options).first}finder.rendered_format||=template.formats.firstifnode=seen[template.identifier]# handle cycles in the treenodeelsenode=seen[template.identifier]=Node.create(name,logical_name,template,partial)deps=DependencyTracker.find_dependencies(name,template,finder.view_paths)deps.uniq{|n|n.gsub(%r|/_|,"/")}.eachdo|dep_file|node.children<<tree(dep_file,finder,true,seen)endnodeendelselogger.error" '#{name}' file doesn't exist, so no dependencies"logger.error" Couldn't find template for digesting: #{name}"seen[name]||=Missing.new(name,logical_name,nil)endendendclassNodeattr_reader:name,:logical_name,:template,:childrendefself.create(name,logical_name,template,partial)klass=partial?Partial:Nodeklass.new(name,logical_name,template,[])enddefinitialize(name,logical_name,template,children=[])@name=name@logical_name=logical_name@template=template@children=childrenenddefdigest(finder,stack=[])Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder,stack)}")enddefdependency_digest(finder,stack)children.mapdo|node|ifstack.include?(node)falseelsefinder.digest_cache[node.name]||=beginstack.pushnodenode.digest(finder,stack).tap{stack.pop}endendend.join("-")enddefto_dep_mapchildren.any??{name=>children.map(&:to_dep_map)}:nameendendclassPartial<Node;endclassMissing<Nodedefdigest(finder,_=[])''endendclassInjected<Nodedefdigest(finder,_=[])nameendendclassNullLoggerdefself.debug(_);enddefself.error(_);endendendend