require'sprockets/asset_attributes'require'sprockets/bundled_asset'require'sprockets/caching'require'sprockets/processed_asset'require'sprockets/processing'require'sprockets/server'require'sprockets/static_asset'require'sprockets/trail'require'pathname'moduleSprockets# `Base` class for `Environment` and `Index`.classBaseincludeCaching,Processing,Server,Trail# Returns a `Digest` implementation class.## Defaults to `Digest::MD5`.attr_reader:digest_class# Assign a `Digest` implementation class. This maybe any Ruby# `Digest::` implementation such as `Digest::MD5` or# `Digest::SHA1`.## environment.digest_class = Digest::SHA1#defdigest_class=(klass)expire_index!@digest_class=klassend# The `Environment#version` is a custom value used for manually# expiring all asset caches.## Sprockets is able to track most file and directory changes and# will take care of expiring the cache for you. However, its# impossible to know when any custom helpers change that you mix# into the `Context`.## It would be wise to increment this value anytime you make a# configuration change to the `Environment` object.attr_reader:version# Assign an environment version.## environment.version = '2.0'#defversion=(version)expire_index!@version=versionend# Returns a `Digest` instance for the `Environment`.## This value serves two purposes. If two `Environment`s have the# same digest value they can be treated as equal. This is more# useful for comparing environment states between processes rather# than in the same. Two equal `Environment`s can share the same# cached assets.## The value also provides a seed digest for all `Asset`# digests. Any change in the environment digest will affect all of# its assets.defdigest# Compute the initial digest using the implementation class. The# Sprockets release version and custom environment version are# mixed in. So any new releases will affect all your assets.@digest||=digest_class.new.update(VERSION).update(version.to_s)# Returned a dupped copy so the caller can safely mutate it with `.update`@digest.dupend# Get and set `Logger` instance.attr_accessor:logger# Get `Context` class.## This class maybe mutated and mixed in with custom helpers.## environment.context_class.instance_eval do# include MyHelpers# def asset_url; end# end#attr_reader:context_class# Get persistent cache storeattr_reader:cache# Set persistent cache store## The cache store must implement a pair of getters and# setters. Either `get(key)`/`set(key, value)`,# `[key]`/`[key]=value`, `read(key)`/`write(key, value)`.defcache=(cache)expire_index!@cache=cacheend# Return an `Index`. Must be implemented by the subclass.defindexraiseNotImplementedErrorendifdefined?Encoding.default_external# Define `default_external_encoding` accessor on 1.9.# Defaults to UTF-8.attr_accessor:default_external_encodingend# Works like `Dir.entries`.## Subclasses may cache this method.defentries(pathname)trail.entries(pathname)end# Works like `File.stat`.## Subclasses may cache this method.defstat(path)trail.stat(path)end# Read and compute digest of filename.## Subclasses may cache this method.deffile_digest(path)ifstat=self.stat(path)# If its a file, digest the contentsifstat.file?digest.file(path.to_s)# If its a directive, digest the list of filenameselsifstat.directory?contents=self.entries(path).join(',')digest.update(contents)endendend# Internal. Return a `AssetAttributes` for `path`.defattributes_for(path)AssetAttributes.new(self,path)end# Internal. Return content type of `path`.defcontent_type_of(path)attributes_for(path).content_typeend# Find asset by logical path or expanded path.deffind_asset(path,options={})logical_path=pathpathname=Pathname.new(path)ifpathname.absolute?returnunlessstat(pathname)logical_path=attributes_for(pathname).logical_pathelsebeginpathname=resolve(logical_path)rescueFileNotFoundreturnnilendendbuild_asset(logical_path,pathname,options)end# Preferred `find_asset` shorthand.## environment['application.js']#def[](*args)find_asset(*args)enddefeach_entry(root,&block)returnto_enum(__method__,root)unlessblock_given?root=Pathname.new(root)unlessroot.is_a?(Pathname)paths=[]entries(root).sort.eachdo|filename|path=root.join(filename)paths<<pathifstat(path).directory?each_entry(path)do|subpath|paths<<subpathendendendpaths.sort_by(&:to_s).each(&block)nilenddefeach_filereturnto_enum(__method__)unlessblock_given?paths.eachdo|root|each_entry(root)do|path|if!stat(path).directory?yieldpathendendendnilenddefeach_logical_path(*args)returnto_enum(__method__,*args)unlessblock_given?filters=args.flattenfiles={}each_filedo|filename|iflogical_path=logical_path_for_filename(filename,filters)yieldlogical_pathunlessfiles[logical_path]files[logical_path]=trueendendnilend# Pretty inspectdefinspect"#<#{self.class}:0x#{object_id.to_s(16)} "+"root=#{root.to_s.inspect}, "+"paths=#{paths.inspect}, "+"digest=#{digest.to_s.inspect}"+">"endprotected# Clear index after mutating state. Must be implemented by the subclass.defexpire_index!raiseNotImplementedErrorenddefbuild_asset(logical_path,pathname,options)pathname=Pathname.new(pathname)# If there are any processors to run on the pathname, use# `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.ifattributes_for(pathname).processors.any?ifoptions[:bundle]==falsecircular_call_protection(pathname.to_s)doProcessedAsset.new(index,logical_path,pathname)endelseBundledAsset.new(index,logical_path,pathname)endelseStaticAsset.new(index,logical_path,pathname)endenddefcache_key_for(path,options)"#{path}:#{options[:bundle]?'1':'0'}"enddefcircular_call_protection(path)reset=Thread.current[:sprockets_circular_calls].nil?calls=Thread.current[:sprockets_circular_calls]||=Set.newifcalls.include?(path)raiseCircularDependencyError,"#{path} has already been required"endcalls<<pathyieldensureThread.current[:sprockets_circular_calls]=nilifresetenddeflogical_path_for_filename(filename,filters)logical_path=attributes_for(filename).logical_path.to_sifmatches_filter(filters,logical_path)returnlogical_pathend# If filename is an index file, retest with aliasifFile.basename(logical_path)[/[^\.]+/,0]=='index'path=logical_path.sub(/\/index\./,'.')ifmatches_filter(filters,path)returnpathendendnilenddefmatches_filter(filters,filename)returntrueiffilters.empty?filters.any?do|filter|iffilter.is_a?(Regexp)filter.match(filename)elsiffilter.respond_to?(:call)filter.call(filename)elseFile.fnmatch(filter.to_s,filename)endendendendend