require'pathname'require'shellwords'require'tilt'require'yaml'moduleSprockets# The `DirectiveProcessor` is responsible for parsing and evaluating# directive comments in a source file.## A directive comment starts with a comment prefix, followed by an "=",# then the directive name, then any arguments.## // JavaScript# //= require "foo"## # CoffeeScript# #= require "bar"## /* CSS# *= require "baz"# */## The Processor is implemented as a `Tilt::Template` and is loosely# coupled to Sprockets. This makes it possible to disable or modify# the processor to do whatever you'd like. You could add your own# custom directives or invent your own directive syntax.## `Environment#processors` includes `DirectiveProcessor` by default.## To remove the processor entirely:## env.unregister_processor('text/css', Sprockets::DirectiveProcessor)# env.unregister_processor('application/javascript', Sprockets::DirectiveProcessor)## Then inject your own preprocessor:## env.register_processor('text/css', MyProcessor)#classDirectiveProcessor<Tilt::Template# Directives will only be picked up if they are in the header# of the source file. C style (/* */), JavaScript (//), and# Ruby (#) comments are supported.## Directives in comments after the first non-whitespace line# of code will not be processed.#HEADER_PATTERN=/
\A (
(?m:\s*) (
(\/\* (?m:.*?) \*\/) |
(\#\#\# (?m:.*?) \#\#\#) |
(\/\/ .* \n?)+ |
(\# .* \n?)+
)
)+
/x# Directives are denoted by a `=` followed by the name, then# argument list.## A few different styles are allowed:## // =require foo# //= require foo# //= require "foo"#DIRECTIVE_PATTERN=/
^ [\W]* = \s* (\w+.*?) (\*\/)? $
/xattr_reader:pathnameattr_reader:header,:bodydefprepare@pathname=Pathname.new(file)@header=data[HEADER_PATTERN,0]||""@body=$'||data# Ensure body ends in a new line@body+="\n"if@body!=""&&@body!~/\n\Z/m@included_pathnames=[]@compat=falseend# Implemented for Tilt#render.## `context` is a `Context` instance with methods that allow you to# access the environment and append to the bundle. See `Context`# for the complete API.defevaluate(context,locals,&block)@context=context@result=""@has_written_body=falseprocess_directivesprocess_source@resultend# Returns the header String with any directives stripped.defprocessed_headerlineno=0@processed_header||=header.lines.map{|line|lineno+=1# Replace directive line with a clean breakdirectives.assoc(lineno)?"\n":line}.join.chompend# Returns the source String with any directives stripped.defprocessed_source@processed_source||=processed_header+bodyend# Returns an Array of directive structures. Each structure# is an Array with the line number as the first element, the# directive name as the second element, followed by any# arguments.## [[1, "require", "foo"], [2, "require", "bar"]]#defdirectives@directives||=header.lines.each_with_index.map{|line,index|ifdirective=line[DIRECTIVE_PATTERN,1]name,*args=Shellwords.shellwords(directive)ifrespond_to?("process_#{name}_directive")[index+1,name,*args]endend}.compactendprotectedattr_reader:included_pathnamesattr_reader:context# Gathers comment directives in the source and processes them.# Any directive method matching `process_*_directive` will# automatically be available. This makes it easy to extend the# processor.## To implement a custom directive called `require_glob`, subclass# `Sprockets::DirectiveProcessor`, then add a method called# `process_require_glob_directive`.## class DirectiveProcessor < Sprockets::DirectiveProcessor# def process_require_glob_directive# Dir["#{pathname.dirname}/#{glob}"].sort.each do |filename|# require(filename)# end# end# end## Replace the current processor on the environment with your own:## env.unregister_processor('text/css', Sprockets::DirectiveProcessor)# env.register_processor('text/css', DirectiveProcessor)#defprocess_directivesdirectives.eachdo|line_number,name,*args|context.__LINE__=line_numbersend("process_#{name}_directive",*args)context.__LINE__=nilendenddefprocess_sourceunless@has_written_body||processed_header.empty?@result<<processed_header<<"\n"endincluded_pathnames.eachdo|pathname|@result<<context.evaluate(pathname)endunless@has_written_body@result<<bodyendifcompat?&&constants.any?@result.gsub!(/<%=(.*?)%>/){constants[$1.strip]}endend# The `require` directive functions similar to Ruby's own `require`.# It provides a way to declare a dependency on a file in your path# and ensures its only loaded once before the source file.## `require` works with files in the environment path:## //= require "foo.js"## Extensions are optional. If your source file is ".js", it# assumes you are requiring another ".js".## //= require "foo"## Relative paths work too. Use a leading `./` to denote a relative# path:## //= require "./bar"#defprocess_require_directive(path)if@compatifpath=~/<([^>]+)>/path=$1elsepath="./#{path}"unlessrelative?(path)endendcontext.require_asset(path)end# `require_self` causes the body of the current file to be# inserted before any subsequent `require` or `include`# directives. Useful in CSS files, where it's common for the# index file to contain global styles that need to be defined# before other dependencies are loaded.## /*= require "reset"# *= require_self# *= require_tree .# */#defprocess_require_self_directiveif@has_written_bodyraiseArgumentError,"require_self can only be called once per source file"endcontext.require_asset(pathname)process_sourceincluded_pathnames.clear@has_written_body=trueend# The `include` directive works similar to `require` but# inserts the contents of the dependency even if it already# has been required.## //= include "header"#defprocess_include_directive(path)pathname=context.resolve(path)context.depend_on_asset(pathname)included_pathnames<<pathnameend# `require_directory` requires all the files inside a single# directory. It's similar to `path/*` since it does not follow# nested directories.## //= require_directory "./javascripts"#defprocess_require_directory_directive(path=".")ifrelative?(path)root=pathname.dirname.join(path).expand_pathunless(stats=stat(root))&&stats.directory?raiseArgumentError,"require_directory argument must be a directory"endcontext.depend_on(root)entries(root).eachdo|pathname|pathname=root.join(pathname)ifpathname.to_s==self.filenextelsifcontext.asset_requirable?(pathname)context.require_asset(pathname)endendelse# The path must be relative and start with a `./`.raiseArgumentError,"require_directory argument must be a relative path"endend# `require_tree` requires all the nested files in a directory.# Its glob equivalent is `path/**/*`.## //= require_tree "./public"#defprocess_require_tree_directive(path=".")ifrelative?(path)root=pathname.dirname.join(path).expand_pathunless(stats=stat(root))&&stats.directory?raiseArgumentError,"require_tree argument must be a directory"endcontext.depend_on(root)each_entry(root)do|pathname|ifpathname.to_s==self.filenextelsifstat(pathname).directory?context.depend_on(pathname)elsifcontext.asset_requirable?(pathname)context.require_asset(pathname)endendelse# The path must be relative and start with a `./`.raiseArgumentError,"require_tree argument must be a relative path"endend# Allows you to state a dependency on a file without# including it.## This is used for caching purposes. Any changes made to# the dependency file will invalidate the cache of the# source file.## This is useful if you are using ERB and File.read to pull# in contents from another file.## //= depend_on "foo.png"#defprocess_depend_on_directive(path)context.depend_on(path)end# Allows you to state a dependency on an asset without including# it.## This is used for caching purposes. Any changes that would# invalid the asset dependency will invalidate the cache our the# source file.## Unlike `depend_on`, the path must be a requirable asset.## //= depend_on_asset "bar.js"#defprocess_depend_on_asset_directive(path)context.depend_on_asset(path)end# Allows dependency to be excluded from the asset bundle.## The `path` must be a valid asset and may or may not already# be part of the bundle. Once stubbed, it is blacklisted and# can't be brought back by any other `require`.## //= stub "jquery"#defprocess_stub_directive(path)context.stub_asset(path)end# Enable Sprockets 1.x compat mode.## Makes it possible to use the same JavaScript source# file in both Sprockets 1 and 2.## //= compat#defprocess_compat_directive@compat=trueend# Checks if Sprockets 1.x compat mode enableddefcompat?@compatend# Sprockets 1.x allowed for constant interpolation if a# constants.yml was present. This is only available if# compat mode is on.defconstantsifcompat?pathname=Pathname.new(context.root_path).join("constants.yml")stat(pathname)?YAML.load_file(pathname):{}else{}endend# `provide` is stubbed out for Sprockets 1.x compat.# Mutating the path when an asset is being built is# not permitted.defprocess_provide_directive(path)endprivatedefrelative?(path)path=~/^\.($|\.?\/)/enddefstat(path)context.environment.stat(path)enddefentries(path)context.environment.entries(path)enddefeach_entry(root,&block)context.environment.each_entry(root,&block)endendend