sprockets-3.7.0/0000755000004100000410000000000012750322502013564 5ustar www-datawww-datasprockets-3.7.0/bin/0000755000004100000410000000000012750322502014334 5ustar www-datawww-datasprockets-3.7.0/bin/sprockets0000755000004100000410000000411212750322502016275 0ustar www-datawww-data#!/usr/bin/env ruby require 'sprockets' require 'optparse' require 'shellwords' unless ARGV.delete("--noenv") if File.exist?(path = "./.sprocketsrc") rcflags = Shellwords.split(File.read(path)) ARGV.unshift(*rcflags) end end filenames = [] environment = Sprockets::Environment.new(Dir.pwd) manifest = nil (ENV['SPROCKETS_PATH'] || "").split(File::PATH_SEPARATOR).each do |path| environment.append_path path end OptionParser.new do |opts| opts.summary_width = 28 opts.banner = "Usage: sprockets [options] filename [filename ...]" def opts.show_usage puts self exit 1 end opts.on("-r", "--require LIBRARY", "Require the LIBRARY before doing anything") do |lib| require lib end opts.on("-I DIRECTORY", "--include=DIRECTORY", "Adds the directory to the Sprockets load path") do |directory| environment.append_path directory end opts.on("-o DIRECTORY", "--output=DIRECTORY", "Copy provided assets into DIRECTORY") do |directory| manifest = Sprockets::Manifest.new(environment, directory) end opts.on("--css-compressor=COMPRESSOR", "Use CSS compressor") do |compressor| environment.css_compressor = compressor.to_sym end opts.on("--js-compressor=COMPRESSOR", "Use JavaScript compressor") do |compressor| environment.js_compressor = compressor.to_sym end opts.on("--noenv", "Disables .sprocketsrc file") do end opts.on_tail("-h", "--help", "Shows this help message") do opts.show_usage end opts.on_tail("-v", "--version", "Shows version") do puts Sprockets::VERSION exit end opts.show_usage if ARGV.empty? begin opts.order(ARGV) do |filename| filenames << File.expand_path(filename) end rescue OptionParser::ParseError => e opts.warn e.message opts.show_usage end end if environment.paths.empty? warn "No load paths given" warn "Usage: sprockets -Ijavascripts/ filename" exit 1 end if manifest manifest.compile(filenames) elsif filenames.length == 1 puts environment.find_asset(filenames.first).to_s else warn "Only one file can be compiled to stdout at a time" exit 1 end sprockets-3.7.0/lib/0000755000004100000410000000000012750322502014332 5ustar www-datawww-datasprockets-3.7.0/lib/rake/0000755000004100000410000000000012750322502015254 5ustar www-datawww-datasprockets-3.7.0/lib/rake/sprocketstask.rb0000644000004100000410000000731112750322502020503 0ustar www-datawww-datarequire 'rake' require 'rake/tasklib' require 'sprockets' require 'logger' module Rake # Simple Sprockets compilation Rake task macro. # # Rake::SprocketsTask.new do |t| # t.environment = Sprockets::Environment.new # t.output = "./public/assets" # t.assets = %w( application.js application.css ) # end # class SprocketsTask < Rake::TaskLib # Name of the task. Defaults to "assets". # # The name will also be used to suffix the clean and clobber # tasks, "clean_assets" and "clobber_assets". attr_accessor :name # `Environment` instance used for finding assets. # # You'll most likely want to reassign `environment` to your own. # # Rake::SprocketsTask.new do |t| # t.environment = Foo::Assets # end # def environment if !@environment.is_a?(Sprockets::Base) && @environment.respond_to?(:call) @environment = @environment.call else @environment end end attr_writer :environment # Returns cached cached environment def cached @cached ||= environment.cached if environment end alias_method :index, :cached # `Manifest` instance used for already compiled assets. # # Will be created by default if an environment and output # directory are given def manifest if !@manifest.is_a?(Sprockets::Manifest) && @manifest.respond_to?(:call) @manifest = @manifest.call else @manifest end end attr_writer :manifest # Directory to write compiled assets too. As well as the manifest file. # # t.output = "./public/assets" # attr_accessor :output # Array of asset logical paths to compile. # # t.assets = %w( application.js jquery.js application.css ) # attr_accessor :assets # Number of old assets to keep. attr_accessor :keep # Logger to use during rake tasks. Defaults to using stderr. # # t.logger = Logger.new($stdout) # attr_accessor :logger # Returns logger level Integer. def log_level @logger.level end # Set logger level with constant or symbol. # # t.log_level = Logger::INFO # t.log_level = :debug # def log_level=(level) if level.is_a?(Integer) @logger.level = level else @logger.level = Logger.const_get(level.to_s.upcase) end end def initialize(name = :assets) @name = name @environment = lambda { Sprockets::Environment.new(Dir.pwd) } @manifest = lambda { Sprockets::Manifest.new(cached, output) } @logger = Logger.new($stderr) @logger.level = Logger::INFO @keep = 2 yield self if block_given? define end # Define tasks def define desc name == :assets ? "Compile assets" : "Compile #{name} assets" task name do with_logger do manifest.compile(assets) end end desc name == :assets ? "Remove all assets" : "Remove all #{name} assets" task "clobber_#{name}" do with_logger do manifest.clobber end end task :clobber => ["clobber_#{name}"] desc name == :assets ? "Clean old assets" : "Clean old #{name} assets" task "clean_#{name}" do with_logger do manifest.clean(keep) end end task :clean => ["clean_#{name}"] end private # Sub out environment logger with our rake task logger that # writes to stderr. def with_logger if env = manifest.environment old_logger = env.logger env.logger = @logger end yield ensure env.logger = old_logger if env end end end sprockets-3.7.0/lib/sprockets/0000755000004100000410000000000012750322502016347 5ustar www-datawww-datasprockets-3.7.0/lib/sprockets/path_dependency_utils.rb0000644000004100000410000000534312750322502023253 0ustar www-datawww-datarequire 'set' require 'sprockets/path_utils' require 'sprockets/uri_utils' module Sprockets # Internal: Related PathUtils helpers that also track all the file system # calls they make for caching purposes. All functions return a standard # return value and a Set of cache dependency URIs that can be used in the # future to see if the returned value should be invalidated from cache. # # entries_with_dependencies("app/assets/javascripts") # # => [ # # ["application.js", "projects.js", "users.js", ...] # # # # # ] # # The returned dependency set can be passed to resolve_dependencies(deps) # to check if the returned result is still fresh. In this case, entry always # returns a single path, but multiple calls should accumulate dependencies # into a single set thats saved off and checked later. # # resolve_dependencies(deps) # # => "\x01\x02\x03" # # Later, resolving the same set again will produce a different hash if # something on the file system has changed. # # resolve_dependencies(deps) # # => "\x03\x04\x05" # module PathDependencyUtils include PathUtils include URIUtils # Internal: List directory entries and return a set of dependencies that # would invalid the cached return result. # # See PathUtils#entries # # path - String directory path # # Returns an Array of entry names and a Set of dependency URIs. def entries_with_dependencies(path) return entries(path), file_digest_dependency_set(path) end # Internal: List directory filenames and associated Stats under a # directory. # # See PathUtils#stat_directory # # dir - A String directory # # Returns an Array of filenames and a Set of dependency URIs. def stat_directory_with_dependencies(dir) return stat_directory(dir).to_a, file_digest_dependency_set(dir) end # Internal: Returns a set of dependencies for a particular path. # # path - String directory path # # Returns a Set of dependency URIs. def file_digest_dependency_set(path) Set.new([build_file_digest_uri(path)]) end # Internal: List directory filenames and associated Stats under an entire # directory tree. # # See PathUtils#stat_sorted_tree # # dir - A String directory # # Returns an Array of filenames and a Set of dependency URIs. def stat_sorted_tree_with_dependencies(dir) deps = Set.new([build_file_digest_uri(dir)]) results = stat_sorted_tree(dir).map do |path, stat| deps << build_file_digest_uri(path) if stat.directory? [path, stat] end return results, deps end end end sprockets-3.7.0/lib/sprockets/cache.rb0000644000004100000410000001515012750322502017741 0ustar www-datawww-datarequire 'logger' require 'sprockets/digest_utils' module Sprockets # Public: Wrapper interface to backend cache stores. Ensures a consistent API # even when the backend uses get/set or read/write. # # Public cache interface # # Always assign the backend store instance to Environment#cache=. # # environment.cache = Sprockets::Cache::MemoryStore.new(1000) # # Environment#cache will always return a wrapped Cache interface. See the # methods marked public on this class. # # # Backend cache interface # # The Backend cache store must implement two methods. # # get(key) # # key - An opaque String with a length less than 250 characters. # # Returns an JSON serializable object. # # set(key, value) # # Will only be called once per key. Setting a key "foo" with value "bar", # then later key "foo" with value "baz" is an undefined behavior. # # key - An opaque String with a length less than 250 characters. # value - A JSON serializable object. # # Returns argument value. # class Cache # Builtin cache stores. autoload :FileStore, 'sprockets/cache/file_store' autoload :MemoryStore, 'sprockets/cache/memory_store' autoload :NullStore, 'sprockets/cache/null_store' # Internal: Cache key version for this class. Rarely should have to change # unless the cache format radically changes. Will be bump on major version # releases though. VERSION = '3.0' def self.default_logger logger = Logger.new($stderr) logger.level = Logger::FATAL logger end # Internal: Wrap a backend cache store. # # Always assign a backend cache store instance to Environment#cache= and # use Environment#cache to retreive a wrapped interface. # # cache - A compatible backend cache store instance. def initialize(cache = nil, logger = self.class.default_logger) @cache_wrapper = get_cache_wrapper(cache) @fetch_cache = Cache::MemoryStore.new(1024) @logger = logger end # Public: Prefer API to retrieve and set values in the cache store. # # key - JSON serializable key # block - # Must return a consistent JSON serializable object for the given key. # # Examples # # cache.fetch("foo") { "bar" } # # Returns a JSON serializable object. def fetch(key) start = Time.now.to_f expanded_key = expand_key(key) value = @fetch_cache.get(expanded_key) if value.nil? value = @cache_wrapper.get(expanded_key) if value.nil? value = yield @cache_wrapper.set(expanded_key, value) @logger.debug do ms = "(#{((Time.now.to_f - start) * 1000).to_i}ms)" "Sprockets Cache miss #{peek_key(key)} #{ms}" end end @fetch_cache.set(expanded_key, value) end value end # Public: Low level API to retrieve item directly from the backend cache # store. # # This API may be used publicly, but may have undefined behavior # depending on the backend store being used. Prefer the # Cache#fetch API over using this. # # key - JSON serializable key # local - Check local cache first (default: false) # # Returns a JSON serializable object or nil if there was a cache miss. def get(key, local = false) expanded_key = expand_key(key) if local && value = @fetch_cache.get(expanded_key) return value end value = @cache_wrapper.get(expanded_key) @fetch_cache.set(expanded_key, value) if local value end # Public: Low level API to set item directly to the backend cache store. # # This API may be used publicly, but may have undefined behavior # depending on the backend store being used. Prefer the # Cache#fetch API over using this. # # key - JSON serializable key # value - A consistent JSON serializable object for the given key. Setting # a different value for the given key has undefined behavior. # local - Set on local cache (default: false) # # Returns the value argument. def set(key, value, local = false) expanded_key = expand_key(key) @fetch_cache.set(expanded_key, value) if local @cache_wrapper.set(expanded_key, value) end # Public: Pretty inspect # # Returns String. def inspect "#<#{self.class} local=#{@fetch_cache.inspect} store=#{@cache_wrapper.cache.inspect}>" end private # Internal: Expand object cache key into a short String key. # # The String should be under 250 characters so its compatible with # Memcache. # # key - JSON serializable key # # Returns a String with a length less than 250 characters. def expand_key(key) digest_key = DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key)) namespace = digest_key[0, 2] "sprockets/v#{VERSION}/#{namespace}/#{digest_key}" end PEEK_SIZE = 100 # Internal: Show first 100 characters of cache key for logging purposes. # # Returns a String with a length less than 100 characters. def peek_key(key) case key when Integer key.to_s when String key[0, PEEK_SIZE].inspect when Array str = [] key.each { |k| str << peek_key(k) } str.join(':')[0, PEEK_SIZE] else peek_key(DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key))) end end def get_cache_wrapper(cache) if cache.is_a?(Cache) cache # `Cache#get(key)` for Memcache elsif cache.respond_to?(:get) GetWrapper.new(cache) # `Cache#[key]` so `Hash` can be used elsif cache.respond_to?(:[]) HashWrapper.new(cache) # `Cache#read(key)` for `ActiveSupport::Cache` support elsif cache.respond_to?(:read) ReadWriteWrapper.new(cache) else cache = Sprockets::Cache::NullStore.new GetWrapper.new(cache) end end class Wrapper < Struct.new(:cache) end class GetWrapper < Wrapper def get(key) cache.get(key) end def set(key, value) cache.set(key, value) end end class HashWrapper < Wrapper def get(key) cache[key] end def set(key, value) cache[key] = value end end class ReadWriteWrapper < Wrapper def get(key) cache.read(key) end def set(key, value) cache.write(key, value) end end end end sprockets-3.7.0/lib/sprockets/dependencies.rb0000644000004100000410000000357312750322502021332 0ustar www-datawww-datarequire 'sprockets/digest_utils' require 'sprockets/path_digest_utils' require 'sprockets/uri_utils' module Sprockets # `Dependencies` is an internal mixin whose public methods are exposed on the # `Environment` and `CachedEnvironment` classes. module Dependencies include DigestUtils, PathDigestUtils, URIUtils # Public: Mapping dependency schemes to resolver functions. # # key - String scheme # value - Proc.call(Environment, String) # # Returns Hash. def dependency_resolvers config[:dependency_resolvers] end # Public: Default set of dependency URIs for assets. # # Returns Set of String URIs. def dependencies config[:dependencies] end # Public: Register new dependency URI resolver. # # scheme - String scheme # block - # environment - Environment # uri - String dependency URI # # Returns nothing. def register_dependency_resolver(scheme, &block) self.config = hash_reassoc(config, :dependency_resolvers) do |hash| hash.merge(scheme => block) end end # Public: Add environmental dependency inheirted by all assets. # # uri - String dependency URI # # Returns nothing. def add_dependency(uri) self.config = hash_reassoc(config, :dependencies) do |set| set + Set.new([uri]) end end alias_method :depend_on, :add_dependency # Internal: Resolve dependency URIs. # # Returns resolved Object. def resolve_dependency(str) # Optimize for the most common scheme to # save 22k allocations on an average Spree app. scheme = if str.start_with?('file-digest:'.freeze) 'file-digest'.freeze else str[/([^:]+)/, 1] end if resolver = config[:dependency_resolvers][scheme] resolver.call(self, str) else nil end end end end sprockets-3.7.0/lib/sprockets/directive_processor.rb0000644000004100000410000003140612750322502022755 0ustar www-datawww-datarequire 'set' require 'shellwords' module Sprockets # 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" # */ # # 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) # class DirectiveProcessor VERSION = '1' # 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+.*?) (\*\/)? $ /x def self.instance @instance ||= new( # Deprecated: Default to C and Ruby comment styles comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]] ) end def self.call(input) instance.call(input) end def initialize(options = {}) @header_pattern = compile_header_pattern(Array(options[:comments])) end def call(input) dup._call(input) end def _call(input) @environment = input[:environment] @uri = input[:uri] @filename = input[:filename] @dirname = File.dirname(@filename) @content_type = input[:content_type] @required = Set.new(input[:metadata][:required]) @stubbed = Set.new(input[:metadata][:stubbed]) @links = Set.new(input[:metadata][:links]) @dependencies = Set.new(input[:metadata][:dependencies]) data, directives = process_source(input[:data]) process_directives(directives) { data: data, required: @required, stubbed: @stubbed, links: @links, dependencies: @dependencies } end protected # 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. def compile_header_pattern(comments) re = comments.map { |c| case c when String "(?:#{Regexp.escape(c)}.*\\n?)+" when Array "(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})" else raise TypeError, "unknown comment type: #{c.class}" end }.join("|") Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+") end def process_source(source) header = source[@header_pattern, 0] || "" body = $' || source header, directives = extract_directives(header) data = "" data.force_encoding(body.encoding) data << header << "\n" unless header.empty? data << body # Ensure body ends in a new line data << "\n" if data.length > 0 && data[-1] != "\n" return data, directives end # 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"]] # def extract_directives(header) processed_header = "" directives = [] header.lines.each_with_index do |line, index| if directive = line[DIRECTIVE_PATTERN, 1] name, *args = Shellwords.shellwords(directive) if respond_to?("process_#{name}_directive", true) directives << [index + 1, name, *args] # Replace directive line with a clean break line = "\n" end end processed_header << line end return processed_header.chomp, directives end # 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["#{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) # def process_directives(directives) directives.each do |line_number, name, *args| begin send("process_#{name}_directive", *args) rescue Exception => e e.set_backtrace(["#{@filename}:#{line_number}"] + e.backtrace) raise e end end end # 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" # def process_require_directive(path) @required << resolve(path, accept: @content_type, pipeline: :self) end # `require_self` causes the body of the current file to be inserted # before any subsequent `require` 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 . # */ # def process_require_self_directive if @required.include?(@uri) raise ArgumentError, "require_self can only be called once per source file" end @required << @uri end # `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" # def process_require_directory_directive(path = ".") path = expand_relative_dirname(:require_directory, path) require_paths(*@environment.stat_directory_with_dependencies(path)) end # `require_tree` requires all the nested files in a directory. # Its glob equivalent is `path/**/*`. # # //= require_tree "./public" # def process_require_tree_directive(path = ".") path = expand_relative_dirname(:require_tree, path) require_paths(*@environment.stat_sorted_tree_with_dependencies(path)) end # 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" # def process_depend_on_directive(path) resolve(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" # def process_depend_on_asset_directive(path) load(resolve(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" # def process_stub_directive(path) @stubbed << resolve(path, accept: @content_type, pipeline: :self) end # Declares a linked dependency on the target asset. # # The `path` must be a valid asset and should not already be part of the # bundle. Any linked assets will automatically be compiled along with the # current. # # /*= link "logo.png" */ # def process_link_directive(path) @links << load(resolve(path)).uri end # `link_directory` links all the files inside a single # directory. It's similar to `path/*` since it does not follow # nested directories. # # //= link_directory "./fonts" # # Use caution when linking against JS or CSS assets. Include an explicit # extension or content type in these cases # # //= link_directory "./scripts" .js # def process_link_directory_directive(path = ".", accept = nil) path = expand_relative_dirname(:link_directory, path) accept = expand_accept_shorthand(accept) link_paths(*@environment.stat_directory_with_dependencies(path), accept) end # `link_tree` links all the nested files in a directory. # Its glob equivalent is `path/**/*`. # # //= link_tree "./images" # # Use caution when linking against JS or CSS assets. Include an explicit # extension or content type in these cases # # //= link_tree "./styles" .css # def process_link_tree_directive(path = ".", accept = nil) path = expand_relative_dirname(:link_tree, path) accept = expand_accept_shorthand(accept) link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept) end private def expand_accept_shorthand(accept) if accept.nil? nil elsif accept.include?("/") accept elsif accept.start_with?(".") @environment.mime_exts[accept] else @environment.mime_exts[".#{accept}"] end end def require_paths(paths, deps) resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri| @required << uri end end def link_paths(paths, deps, accept) resolve_paths(paths, deps, accept: accept) do |uri| @links << load(uri).uri end end def resolve_paths(paths, deps, options = {}) @dependencies.merge(deps) paths.each do |subpath, stat| next if subpath == @filename || stat.directory? uri, deps = @environment.resolve(subpath, options.merge(compat: false)) @dependencies.merge(deps) yield uri if uri end end def expand_relative_dirname(directive, path) if @environment.relative_path?(path) path = File.expand_path(path, @dirname) stat = @environment.stat(path) if stat && stat.directory? path else raise ArgumentError, "#{directive} argument must be a directory" end else # The path must be relative and start with a `./`. raise ArgumentError, "#{directive} argument must be a relative path" end end def load(uri) asset = @environment.load(uri) @dependencies.merge(asset.metadata[:dependencies]) asset end def resolve(path, options = {}) # Prevent absolute paths in directives if @environment.absolute_path?(path) raise FileOutsidePaths, "can't require absolute file: #{path}" end uri, deps = @environment.resolve!(path, options.merge(base_path: @dirname)) @dependencies.merge(deps) uri end end end sprockets-3.7.0/lib/sprockets/transformers.rb0000644000004100000410000001055212750322502021424 0ustar www-datawww-datarequire 'sprockets/http_utils' require 'sprockets/processor_utils' require 'sprockets/utils' module Sprockets module Transformers include HTTPUtils, ProcessorUtils, Utils # Public: Two level mapping of a source mime type to a target mime type. # # environment.transformers # # => { 'text/coffeescript' => { # 'application/javascript' => ConvertCoffeeScriptToJavaScript # } # } # def transformers config[:transformers] end # Public: Register a transformer from and to a mime type. # # from - String mime type # to - String mime type # proc - Callable block that accepts an input Hash. # # Examples # # register_transformer 'text/coffeescript', 'application/javascript', # ConvertCoffeeScriptToJavaScript # # register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng # # Returns nothing. def register_transformer(from, to, proc) self.config = hash_reassoc(config, :registered_transformers, from) do |transformers| transformers.merge(to => proc) end compute_transformers! end # Internal: Resolve target mime type that the source type should be # transformed to. # # type - String from mime type # accept - String accept type list (default: '*/*') # # Examples # # resolve_transform_type('text/plain', 'text/plain') # # => 'text/plain' # # resolve_transform_type('image/svg+xml', 'image/png, image/*') # # => 'image/png' # # resolve_transform_type('text/css', 'image/png') # # => nil # # Returns String mime type or nil is no type satisfied the accept value. def resolve_transform_type(type, accept) find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys) end # Internal: Expand accept type list to include possible transformed types. # # parsed_accepts - Array of accept q values # # Examples # # expand_transform_accepts([['application/javascript', 1.0]]) # # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]] # # Returns an expanded Array of q values. def expand_transform_accepts(parsed_accepts) accepts = [] parsed_accepts.each do |(type, q)| accepts.push([type, q]) config[:inverted_transformers][type].each do |subtype| accepts.push([subtype, q * 0.8]) end end accepts end # Internal: Compose multiple transformer steps into a single processor # function. # # transformers - Two level Hash of a source mime type to a target mime type # types - Array of mime type steps # # Returns Processor. def compose_transformers(transformers, types) if types.length < 2 raise ArgumentError, "too few transform types: #{types.inspect}" end i = 0 processors = [] loop do src = types[i] dst = types[i+1] break unless src && dst unless processor = transformers[src][dst] raise ArgumentError, "missing transformer for type: #{src} to #{dst}" end processors.concat config[:postprocessors][src] processors << processor processors.concat config[:preprocessors][dst] i += 1 end if processors.size > 1 compose_processors(*processors.reverse) elsif processors.size == 1 processors.first end end private def compute_transformers! registered_transformers = self.config[:registered_transformers] transformers = Hash.new { {} } inverted_transformers = Hash.new { Set.new } registered_transformers.keys.flat_map do |key| dfs_paths([key]) { |k| registered_transformers[k].keys } end.each do |types| src, dst = types.first, types.last processor = compose_transformers(registered_transformers, types) transformers[src] = {} unless transformers.key?(src) transformers[src][dst] = processor inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst) inverted_transformers[dst] << src end self.config = hash_reassoc(config, :transformers) { transformers } self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers } end end end sprockets-3.7.0/lib/sprockets/utils.rb0000644000004100000410000001360712750322502020043 0ustar www-datawww-datarequire 'set' module Sprockets # Internal: Utils, we didn't know where else to put it! Functions may # eventually be shuffled into more specific drawers. module Utils extend self # Internal: Check if object can safely be .dup'd. # # Similar to ActiveSupport #duplicable? check. # # obj - Any Object # # Returns false if .dup would raise a TypeError, otherwise true. def duplicable?(obj) case obj when NilClass, FalseClass, TrueClass, Symbol, Numeric false else true end end # Internal: Duplicate and store key/value on new frozen hash. # # Seperated for recursive calls, always use hash_reassoc(hash, *keys). # # hash - Hash # key - Object key # # Returns Hash. def hash_reassoc1(hash, key) hash = hash.dup if hash.frozen? old_value = hash[key] old_value = old_value.dup if duplicable?(old_value) new_value = yield old_value new_value.freeze if duplicable?(new_value) hash.store(key, new_value) hash.freeze end # Internal: Duplicate and store key/value on new frozen hash. # # Similar to Hash#store for nested frozen hashes. # # hash - Hash # key - Object keys. Use multiple keys for nested hashes. # block - Receives current value at key. # # Examples # # config = {paths: ["/bin", "/sbin"]}.freeze # new_config = hash_reassoc(config, :paths) do |paths| # paths << "/usr/local/bin" # end # # Returns duplicated frozen Hash. def hash_reassoc(hash, *keys, &block) if keys.size == 1 hash_reassoc1(hash, keys[0], &block) else hash_reassoc1(hash, keys[0]) do |value| hash_reassoc(value, *keys[1..-1], &block) end end end # Internal: Check if string has a trailing semicolon. # # str - String # # Returns true or false. def string_end_with_semicolon?(str) i = str.size - 1 while i >= 0 c = str[i].ord i -= 1 # Need to compare against the ordinals because the string can be UTF_8 or UTF_32LE encoded # 0x0A == "\n" # 0x20 == " " # 0x09 == "\t" # 0x3B == ";" unless c == 0x0A || c == 0x20 || c == 0x09 return c === 0x3B end end true end # Internal: Accumulate asset source to buffer and append a trailing # semicolon if necessary. # # buf - String buffer to append to # source - String source to append # # Returns buf String. def concat_javascript_sources(buf, source) if source.bytesize > 0 buf << source # If the source contains non-ASCII characters, indexing on it becomes O(N). # This will lead to O(N^2) performance in string_end_with_semicolon?, so we should use 32 bit encoding to make sure indexing stays O(1) source = source.encode(Encoding::UTF_32LE) unless source.ascii_only? if !string_end_with_semicolon?(source) buf << ";\n" elsif source[source.size - 1].ord != 0x0A buf << "\n" end end buf end # Internal: Prepends a leading "." to an extension if its missing. # # normalize_extension("js") # # => ".js" # # normalize_extension(".css") # # => ".css" # def normalize_extension(extension) extension = extension.to_s if extension[/^\./] extension else ".#{extension}" end end # Internal: Feature detect if UnboundMethods can #bind to any Object or # just Objects that share the same super class. # Basically if RUBY_VERSION >= 2. UNBOUND_METHODS_BIND_TO_ANY_OBJECT = begin foo = Module.new { def bar; end } foo.instance_method(:bar).bind(Object.new) true rescue TypeError false end # Internal: Inject into target module for the duration of the block. # # mod - Module # # Returns result of block. def module_include(base, mod) old_methods = {} mod.instance_methods.each do |sym| old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym) end unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT base.send(:include, mod) unless base < mod end mod.instance_methods.each do |sym| method = mod.instance_method(sym) base.send(:define_method, sym, method) end yield ensure mod.instance_methods.each do |sym| base.send(:undef_method, sym) if base.method_defined?(sym) end old_methods.each do |sym, method| base.send(:define_method, sym, method) end end # Internal: Post-order Depth-First search algorithm. # # Used for resolving asset dependencies. # # initial - Initial Array of nodes to traverse. # block - # node - Current node to get children of # # Returns a Set of nodes. def dfs(initial) nodes, seen = Set.new, Set.new stack = Array(initial).reverse while node = stack.pop if seen.include?(node) nodes.add(node) else seen.add(node) stack.push(node) stack.concat(Array(yield node).reverse) end end nodes end # Internal: Post-order Depth-First search algorithm that gathers all paths # along the way. # # TODO: Rename function. # # path - Initial Array node path # block - # node - Current node to get children of # # Returns an Array of node Arrays. def dfs_paths(path) paths = [] stack, seen = [path], Set.new while path = stack.pop if !seen.include?(path.last) seen.add(path.last) paths << path if path.size > 1 Array(yield path.last).reverse_each do |node| stack.push(path + [node]) end end end paths end end end sprockets-3.7.0/lib/sprockets/autoload/0000755000004100000410000000000012750322502020157 5ustar www-datawww-datasprockets-3.7.0/lib/sprockets/autoload/eco.rb0000644000004100000410000000011412750322502021246 0ustar www-datawww-datarequire 'eco' module Sprockets module Autoload Eco = ::Eco end end sprockets-3.7.0/lib/sprockets/autoload/ejs.rb0000644000004100000410000000011412750322502021261 0ustar www-datawww-datarequire 'ejs' module Sprockets module Autoload EJS = ::EJS end end sprockets-3.7.0/lib/sprockets/autoload/sass.rb0000644000004100000410000000011712750322502021454 0ustar www-datawww-datarequire 'sass' module Sprockets module Autoload Sass = ::Sass end end sprockets-3.7.0/lib/sprockets/autoload/uglifier.rb0000644000004100000410000000013312750322502022307 0ustar www-datawww-datarequire 'uglifier' module Sprockets module Autoload Uglifier = ::Uglifier end end sprockets-3.7.0/lib/sprockets/autoload/yui.rb0000644000004100000410000000012712750322502021312 0ustar www-datawww-datarequire 'yui/compressor' module Sprockets module Autoload YUI = ::YUI end end sprockets-3.7.0/lib/sprockets/autoload/closure.rb0000644000004100000410000000014112750322502022154 0ustar www-datawww-datarequire 'closure-compiler' module Sprockets module Autoload Closure = ::Closure end end sprockets-3.7.0/lib/sprockets/autoload/coffee_script.rb0000644000004100000410000000015012750322502023313 0ustar www-datawww-datarequire 'coffee_script' module Sprockets module Autoload CoffeeScript = ::CoffeeScript end end sprockets-3.7.0/lib/sprockets/sass_compressor.rb0000644000004100000410000000222412750322502022121 0ustar www-datawww-datarequire 'sprockets/autoload' require 'sprockets/digest_utils' module Sprockets # Public: Sass CSS minifier. # # To accept the default options # # environment.register_bundle_processor 'text/css', # Sprockets::SassCompressor # # Or to pass options to the Sass::Engine class. # # environment.register_bundle_processor 'text/css', # Sprockets::SassCompressor.new({ ... }) # class SassCompressor VERSION = '1' # Public: Return singleton instance with default options. # # Returns SassCompressor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def self.cache_key instance.cache_key end attr_reader :cache_key def initialize(options = {}) @options = { syntax: :scss, cache: false, read_cache: false, style: :compressed }.merge(options).freeze @cache_key = "#{self.class.name}:#{Autoload::Sass::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze end def call(input) Autoload::Sass::Engine.new(input[:data], @options).render end end end sprockets-3.7.0/lib/sprockets/manifest_utils.rb0000644000004100000410000000302112750322502021716 0ustar www-datawww-datarequire 'securerandom' module Sprockets # Public: Manifest utilities. module ManifestUtils extend self MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/ LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/ # Public: Generate a new random manifest path. # # Manifests are not intended to be accessed publicly, but typically live # alongside public assets for convenience. To avoid being served, the # filename is prefixed with a "." which is usually hidden by web servers # like Apache. To help in other environments that may not control this, # a random hex string is appended to the filename to prevent people from # guessing the location. If directory indexes are enabled on the server, # all bets are off. # # Return String path. def generate_manifest_path ".sprockets-manifest-#{SecureRandom.hex(16)}.json" end # Public: Find or pick a new manifest filename for target build directory. # # dirname - String dirname # # Examples # # find_directory_manifest("/app/public/assets") # # => "/app/public/assets/.sprockets-manifest-abc123.json" # # Returns String filename. def find_directory_manifest(dirname) entries = File.directory?(dirname) ? Dir.entries(dirname) : [] entry = entries.find { |e| e =~ MANIFEST_RE } || # Deprecated: Will be removed in 4.x entries.find { |e| e =~ LEGACY_MANIFEST_RE } || generate_manifest_path File.join(dirname, entry) end end end sprockets-3.7.0/lib/sprockets/manifest.rb0000644000004100000410000002354612750322502020514 0ustar www-datawww-datarequire 'json' require 'time' require 'concurrent' require 'sprockets/manifest_utils' require 'sprockets/utils/gzip' module Sprockets # The Manifest logs the contents of assets compiled to a single directory. It # records basic attributes about the asset for fast lookup without having to # compile. A pointer from each logical path indicates which fingerprinted # asset is the current one. # # The JSON is part of the public API and should be considered stable. This # should make it easy to read from other programming languages and processes # that don't have sprockets loaded. See `#assets` and `#files` for more # infomation about the structure. class Manifest include ManifestUtils attr_reader :environment # Create new Manifest associated with an `environment`. `filename` is a full # path to the manifest json file. The file may or may not already exist. The # dirname of the `filename` will be used to write compiled assets to. # Otherwise, if the path is a directory, the filename will default a random # ".sprockets-manifest-*.json" file in that directory. # # Manifest.new(environment, "./public/assets/manifest.json") # def initialize(*args) if args.first.is_a?(Base) || args.first.nil? @environment = args.shift end @directory, @filename = args[0], args[1] # Whether the manifest file is using the old manifest-*.json naming convention @legacy_manifest = false # Expand paths @directory = File.expand_path(@directory) if @directory @filename = File.expand_path(@filename) if @filename # If filename is given as the second arg if @directory && File.extname(@directory) != "" @directory, @filename = nil, @directory end # Default dir to the directory of the filename @directory ||= File.dirname(@filename) if @filename # If directory is given w/o filename, pick a random manifest location @rename_filename = nil if @directory && @filename.nil? @filename = find_directory_manifest(@directory) # If legacy manifest name autodetected, mark to rename on save if File.basename(@filename).start_with?("manifest") @rename_filename = File.join(@directory, generate_manifest_path) end end unless @directory && @filename raise ArgumentError, "manifest requires output filename" end data = {} begin if File.exist?(@filename) data = json_decode(File.read(@filename)) end rescue JSON::ParserError => e logger.error "#{@filename} is invalid: #{e.class} #{e.message}" end @data = data end # Returns String path to manifest.json file. attr_reader :filename alias_method :path, :filename attr_reader :directory alias_method :dir, :directory # Returns internal assets mapping. Keys are logical paths which # map to the latest fingerprinted filename. # # Logical path (String): Fingerprint path (String) # # { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js", # "jquery.js" => "jquery-ae0908555a245f8266f77df5a8edca2e.js" } # def assets @data['assets'] ||= {} end # Returns internal file directory listing. Keys are filenames # which map to an attributes array. # # Fingerprint path (String): # logical_path: Logical path (String) # mtime: ISO8601 mtime (String) # digest: Base64 hex digest (String) # # { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" => # { 'logical_path' => "application.js", # 'mtime' => "2011-12-13T21:47:08-06:00", # 'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } } # def files @data['files'] ||= {} end # Public: Find all assets matching pattern set in environment. # # Returns Enumerator of Assets. def find(*args) unless environment raise Error, "manifest requires environment for compilation" end return to_enum(__method__, *args) unless block_given? paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) } filters = filters.map { |arg| self.class.compile_match_filter(arg) } environment = self.environment.cached paths.each do |path| environment.find_all_linked_assets(path) do |asset| yield asset end end if filters.any? environment.logical_paths do |logical_path, filename| if filters.any? { |f| f.call(logical_path, filename) } environment.find_all_linked_assets(filename) do |asset| yield asset end end end end nil end # Public: Find the source of assets by paths. # # Returns Enumerator of assets file content. def find_sources(*args) return to_enum(__method__, *args) unless block_given? if environment find(*args).each do |asset| yield asset.source end else args.each do |path| yield File.binread(File.join(dir, assets[path])) end end end # Compile and write asset to directory. The asset is written to a # fingerprinted filename like # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is # also inserted into the manifest file. # # compile("application.js") # def compile(*args) unless environment raise Error, "manifest requires environment for compilation" end filenames = [] concurrent_compressors = [] concurrent_writers = [] find(*args) do |asset| files[asset.digest_path] = { 'logical_path' => asset.logical_path, 'mtime' => asset.mtime.iso8601, 'size' => asset.bytesize, 'digest' => asset.hexdigest, # Deprecated: Remove beta integrity attribute in next release. # Callers should DigestUtils.hexdigest_integrity_uri to compute the # digest themselves. 'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest) } assets[asset.logical_path] = asset.digest_path if alias_logical_path = self.class.compute_alias_logical_path(asset.logical_path) assets[alias_logical_path] = asset.digest_path end target = File.join(dir, asset.digest_path) if File.exist?(target) logger.debug "Skipping #{target}, already exists" else logger.info "Writing #{target}" write_file = Concurrent::Future.execute { asset.write_to target } concurrent_writers << write_file end filenames << asset.filename next if environment.skip_gzip? gzip = Utils::Gzip.new(asset) next if gzip.cannot_compress?(environment.mime_types) if File.exist?("#{target}.gz") logger.debug "Skipping #{target}.gz, already exists" else logger.info "Writing #{target}.gz" concurrent_compressors << Concurrent::Future.execute do write_file.wait! if write_file gzip.compress(target) end end end concurrent_writers.each(&:wait!) concurrent_compressors.each(&:wait!) save filenames end # Removes file from directory and from manifest. `filename` must # be the name with any directory path. # # manifest.remove("application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js") # def remove(filename) path = File.join(dir, filename) gzip = "#{path}.gz" logical_path = files[filename]['logical_path'] if assets[logical_path] == filename assets.delete(logical_path) end files.delete(filename) FileUtils.rm(path) if File.exist?(path) FileUtils.rm(gzip) if File.exist?(gzip) save logger.info "Removed #{filename}" nil end # Cleanup old assets in the compile directory. By default it will # keep the latest version, 2 backups and any created within the past hour. # # Examples # # To force only 1 backup to be kept, set count=1 and age=0. # # To only keep files created within the last 10 minutes, set count=0 and # age=600. # def clean(count = 2, age = 3600) asset_versions = files.group_by { |_, attrs| attrs['logical_path'] } asset_versions.each do |logical_path, versions| current = assets[logical_path] versions.reject { |path, _| path == current }.sort_by { |_, attrs| # Sort by timestamp Time.parse(attrs['mtime']) }.reverse.each_with_index.drop_while { |(_, attrs), index| _age = [0, Time.now - Time.parse(attrs['mtime'])].max # Keep if under age or within the count limit _age < age || index < count }.each { |(path, _), _| # Remove old assets remove(path) } end end # Wipe directive def clobber FileUtils.rm_r(directory) if File.exist?(directory) logger.info "Removed #{directory}" nil end # Persist manfiest back to FS def save if @rename_filename logger.info "Renaming #{@filename} to #{@rename_filename}" FileUtils.mv(@filename, @rename_filename) @filename = @rename_filename @rename_filename = nil end data = json_encode(@data) FileUtils.mkdir_p File.dirname(@filename) PathUtils.atomic_write(@filename) do |f| f.write(data) end end private def json_decode(obj) JSON.parse(obj, create_additions: false) end def json_encode(obj) JSON.generate(obj) end def logger if environment environment.logger else logger = Logger.new($stderr) logger.level = Logger::FATAL logger end end end end sprockets-3.7.0/lib/sprockets/sass_processor.rb0000644000004100000410000001762412750322502021756 0ustar www-datawww-datarequire 'rack/utils' require 'sprockets/autoload' require 'uri' module Sprockets # Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem. # # For more infomation see: # # https://github.com/sass/sass # https://github.com/rails/sass-rails # class SassProcessor autoload :CacheStore, 'sprockets/sass_cache_store' # Internal: Defines default sass syntax to use. Exposed so the ScssProcessor # may override it. def self.syntax :sass end # Public: Return singleton instance with default options. # # Returns SassProcessor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def self.cache_key instance.cache_key end attr_reader :cache_key # Public: Initialize template with custom options. # # options - Hash # cache_version - String custom cache version. Used to force a cache # change after code changes are made to Sass Functions. # def initialize(options = {}, &block) @cache_version = options[:cache_version] @cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze @functions = Module.new do include Functions include options[:functions] if options[:functions] class_eval(&block) if block_given? end end def call(input) context = input[:environment].context_class.new(input) options = { filename: input[:filename], syntax: self.class.syntax, cache_store: build_cache_store(input, @cache_version), load_paths: input[:environment].paths, sprockets: { context: context, environment: input[:environment], dependencies: context.metadata[:dependencies] } } engine = Autoload::Sass::Engine.new(input[:data], options) css = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do engine.render end # Track all imported files sass_dependencies = Set.new([input[:filename]]) engine.dependencies.map do |dependency| sass_dependencies << dependency.options[:filename] context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename]) end context.metadata.merge(data: css, sass_dependencies: sass_dependencies) end # Public: Build the cache store to be used by the Sass engine. # # input - the input hash. # version - the cache version. # # Override this method if you need to use a different cache than the # Sprockets cache. def build_cache_store(input, version) CacheStore.new(input[:cache], version) end private :build_cache_store # Public: Functions injected into Sass context during Sprockets evaluation. # # This module may be extended to add global functionality to all Sprockets # Sass environments. Though, scoping your functions to just your environment # is preferred. # # module Sprockets::SassProcessor::Functions # def asset_path(path, options = {}) # end # end # module Functions # Public: Generate a url for asset path. # # Default implementation is deprecated. Currently defaults to # Context#asset_path. # # Will raise NotImplementedError in the future. Users should provide their # own base implementation. # # Returns a Sass::Script::String. def asset_path(path, options = {}) path = path.value path, _, query, fragment = URI.split(path)[5..8] path = sprockets_context.asset_path(path, options) query = "?#{query}" if query fragment = "##{fragment}" if fragment Autoload::Sass::Script::String.new("#{path}#{query}#{fragment}", :string) end # Public: Generate a asset url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def asset_url(path, options = {}) Autoload::Sass::Script::String.new("url(#{asset_path(path, options).value})") end # Public: Generate url for image path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def image_path(path) asset_path(path, type: :image) end # Public: Generate a image url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def image_url(path) asset_url(path, type: :image) end # Public: Generate url for video path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def video_path(path) asset_path(path, type: :video) end # Public: Generate a video url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def video_url(path) asset_url(path, type: :video) end # Public: Generate url for audio path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def audio_path(path) asset_path(path, type: :audio) end # Public: Generate a audio url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def audio_url(path) asset_url(path, type: :audio) end # Public: Generate url for font path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def font_path(path) asset_path(path, type: :font) end # Public: Generate a font url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def font_url(path) asset_url(path, type: :font) end # Public: Generate url for javascript path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def javascript_path(path) asset_path(path, type: :javascript) end # Public: Generate a javascript url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def javascript_url(path) asset_url(path, type: :javascript) end # Public: Generate url for stylesheet path. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def stylesheet_path(path) asset_path(path, type: :stylesheet) end # Public: Generate a stylesheet url() link. # # path - Sass::Script::String URL path # # Returns a Sass::Script::String. def stylesheet_url(path) asset_url(path, type: :stylesheet) end # Public: Generate a data URI for asset path. # # path - Sass::Script::String logical asset path # # Returns a Sass::Script::String. def asset_data_url(path) url = sprockets_context.asset_data_uri(path.value) Autoload::Sass::Script::String.new("url(" + url + ")") end protected # Public: The Environment. # # Returns Sprockets::Environment. def sprockets_environment options[:sprockets][:environment] end # Public: Mutatable set of dependencies. # # Returns a Set. def sprockets_dependencies options[:sprockets][:dependencies] end # Deprecated: Get the Context instance. Use APIs on # sprockets_environment or sprockets_dependencies directly. # # Returns a Context instance. def sprockets_context options[:sprockets][:context] end end end class ScssProcessor < SassProcessor def self.syntax :scss end end # Deprecated: Use Sprockets::SassProcessor::Functions instead. SassFunctions = SassProcessor::Functions end sprockets-3.7.0/lib/sprockets/configuration.rb0000644000004100000410000000440012750322502021541 0ustar www-datawww-datarequire 'sprockets/compressing' require 'sprockets/dependencies' require 'sprockets/engines' require 'sprockets/mime' require 'sprockets/paths' require 'sprockets/processing' require 'sprockets/transformers' require 'sprockets/utils' module Sprockets module Configuration include Paths, Mime, Engines, Transformers, Processing, Compressing, Dependencies, Utils def initialize_configuration(parent) @config = parent.config @computed_config = parent.computed_config @logger = parent.logger @context_class = Class.new(parent.context_class) end attr_reader :config attr_accessor :computed_config def config=(config) raise TypeError, "can't assign mutable config" unless config.frozen? @config = config end # Get and set `Logger` instance. attr_accessor :logger # 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. def version config[:version] end # Assign an environment version. # # environment.version = '2.0' # def version=(version) self.config = hash_reassoc(config, :version) { version.dup } end # Public: Returns a `Digest` implementation class. # # Defaults to `Digest::SHA256`. def digest_class config[:digest_class] end # Deprecated: Assign a `Digest` implementation class. This maybe any Ruby # `Digest::` implementation such as `Digest::SHA256` or # `Digest::MD5`. # # environment.digest_class = Digest::MD5 # def digest_class=(klass) self.config = config.merge(digest_class: klass).freeze end # Deprecated: 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 end end sprockets-3.7.0/lib/sprockets/deprecation.rb0000644000004100000410000000473112750322502021176 0ustar www-datawww-datamodule Sprockets class Deprecation THREAD_LOCAL__SILENCE_KEY = "_sprockets_deprecation_silence".freeze DEFAULT_BEHAVIORS = { raise: ->(message, callstack) { e = DeprecationException.new(message) e.set_backtrace(callstack.map(&:to_s)) raise e }, stderr: ->(message, callstack) { $stderr.puts(message) }, } attr_reader :callstack def self.silence(&block) Thread.current[THREAD_LOCAL__SILENCE_KEY] = true block.call ensure Thread.current[THREAD_LOCAL__SILENCE_KEY] = false end def initialize(callstack = nil) @callstack = callstack || caller(2) end def warn(message) return if Thread.current[THREAD_LOCAL__SILENCE_KEY] deprecation_message(message).tap do |m| behavior.each { |b| b.call(m, callstack) } end end private def behavior @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] end def behavior=(behavior) @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b } end def deprecation_message(message = nil) message ||= "You are using deprecated behavior which will be removed from the next major or minor release." "DEPRECATION WARNING: #{message} #{ deprecation_caller_message }" end def deprecation_caller_message file, line, method = extract_callstack if file if line && method "(called from #{method} at #{file}:#{line})" else "(called from #{file}:#{line})" end end end SPROCKETS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/" def ignored_callstack(path) path.start_with?(SPROCKETS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir']) end def extract_callstack return _extract_callstack if callstack.first.is_a? String offending_line = callstack.find { |frame| frame.absolute_path && !ignored_callstack(frame.absolute_path) } || callstack.first [offending_line.path, offending_line.lineno, offending_line.label] end def _extract_callstack offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first if offending_line if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) md.captures else offending_line end end end end private_constant :Deprecation end sprockets-3.7.0/lib/sprockets/encoding_utils.rb0000644000004100000410000001461012750322502021704 0ustar www-datawww-datarequire 'base64' require 'stringio' require 'zlib' module Sprockets # Internal: HTTP transport encoding and charset detecting related functions. # Mixed into Environment. module EncodingUtils extend self ## Binary encodings ## # Public: Use deflate to compress data. # # str - String data # # Returns a compressed String def deflate(str) deflater = Zlib::Deflate.new( Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS, Zlib::MAX_MEM_LEVEL, Zlib::DEFAULT_STRATEGY ) deflater << str deflater.finish end # Internal: Unmarshal optionally deflated data. # # Checks leading marshal header to see if the bytes are uncompressed # otherwise inflate the data an unmarshal. # # str - Marshaled String # window_bits - Integer deflate window size. See ZLib::Inflate.new() # # Returns unmarshaled Object or raises an Exception. def unmarshaled_deflated(str, window_bits = -Zlib::MAX_WBITS) major, minor = str[0], str[1] if major && major.ord == Marshal::MAJOR_VERSION && minor && minor.ord <= Marshal::MINOR_VERSION marshaled = str else begin marshaled = Zlib::Inflate.new(window_bits).inflate(str) rescue Zlib::DataError marshaled = str end end Marshal.load(marshaled) end # Public: Use gzip to compress data. # # str - String data # # Returns a compressed String def gzip(str) io = StringIO.new gz = Zlib::GzipWriter.new(io, Zlib::BEST_COMPRESSION) gz.mtime = 1 gz << str gz.finish io.string end # Public: Use base64 to encode data. # # str - String data # # Returns a encoded String def base64(str) Base64.strict_encode64(str) end ## Charset encodings ## # Internal: Shorthand aliases for detecter functions. CHARSET_DETECT = {} # Internal: Mapping unicode encodings to byte order markers. BOM = { Encoding::UTF_32LE => [0xFF, 0xFE, 0x00, 0x00], Encoding::UTF_32BE => [0x00, 0x00, 0xFE, 0xFF], Encoding::UTF_8 => [0xEF, 0xBB, 0xBF], Encoding::UTF_16LE => [0xFF, 0xFE], Encoding::UTF_16BE => [0xFE, 0xFF] } # Public: Basic string detecter. # # Attempts to parse any Unicode BOM otherwise falls back to the # environment's external encoding. # # str - ASCII-8BIT encoded String # # Returns encoded String. def detect(str) str = detect_unicode_bom(str) # Attempt Charlock detection if str.encoding == Encoding::BINARY charlock_detect(str) end # Fallback to environment's external encoding if str.encoding == Encoding::BINARY str.force_encoding(Encoding.default_external) end str end CHARSET_DETECT[:default] = method(:detect) # Internal: Use Charlock Holmes to detect encoding. # # To enable this code path, require 'charlock_holmes' # # Returns encoded String. def charlock_detect(str) if defined? CharlockHolmes::EncodingDetector if detected = CharlockHolmes::EncodingDetector.detect(str) str.force_encoding(detected[:encoding]) if detected[:encoding] end end str end # Public: Detect Unicode string. # # Attempts to parse Unicode BOM and falls back to UTF-8. # # str - ASCII-8BIT encoded String # # Returns encoded String. def detect_unicode(str) str = detect_unicode_bom(str) # Fallback to UTF-8 if str.encoding == Encoding::BINARY str.force_encoding(Encoding::UTF_8) end str end CHARSET_DETECT[:unicode] = method(:detect_unicode) # Public: Detect and strip BOM from possible unicode string. # # str - ASCII-8BIT encoded String # # Returns UTF 8/16/32 encoded String without BOM or the original String if # no BOM was present. def detect_unicode_bom(str) bom_bytes = str.byteslice(0, 4).bytes.to_a BOM.each do |encoding, bytes| if bom_bytes[0, bytes.size] == bytes str = str.dup str.force_encoding(Encoding::BINARY) str.slice!(0, bytes.size) str.force_encoding(encoding) return str end end return str end # Public: Detect and strip @charset from CSS style sheet. # # str - String. # # Returns a encoded String. def detect_css(str) str = detect_unicode_bom(str) if name = scan_css_charset(str) encoding = Encoding.find(name) str = str.dup str.force_encoding(encoding) len = "@charset \"#{name}\";".encode(encoding).size str.slice!(0, len) str end # Fallback to UTF-8 if str.encoding == Encoding::BINARY str.force_encoding(Encoding::UTF_8) end str end CHARSET_DETECT[:css] = method(:detect_css) # Internal: @charset bytes CHARSET_START = [0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22] CHARSET_SIZE = CHARSET_START.size # Internal: Scan binary CSS string for @charset encoding name. # # str - ASCII-8BIT encoded String # # Returns encoding String name or nil. def scan_css_charset(str) buf = [] i = 0 str.each_byte.each do |byte| # Halt on line breaks break if byte == 0x0A || byte == 0x0D # Only ascii bytes next unless 0x0 < byte && byte <= 0xFF if i < CHARSET_SIZE elsif i == CHARSET_SIZE if buf == CHARSET_START buf = [] else break end elsif byte == 0x22 return buf.pack('C*') end buf << byte i += 1 end nil end # Public: Detect charset from HTML document. # # Attempts to parse any Unicode BOM otherwise attempt Charlock detection # and finally falls back to the environment's external encoding. # # str - String. # # Returns a encoded String. def detect_html(str) str = detect_unicode_bom(str) # Attempt Charlock detection if str.encoding == Encoding::BINARY charlock_detect(str) end # Fallback to environment's external encoding if str.encoding == Encoding::BINARY str.force_encoding(Encoding.default_external) end str end CHARSET_DETECT[:html] = method(:detect_html) end end sprockets-3.7.0/lib/sprockets/loader.rb0000644000004100000410000003352212750322502020147 0ustar www-datawww-datarequire 'sprockets/asset' require 'sprockets/digest_utils' require 'sprockets/engines' require 'sprockets/errors' require 'sprockets/file_reader' require 'sprockets/mime' require 'sprockets/path_utils' require 'sprockets/processing' require 'sprockets/processor_utils' require 'sprockets/resolve' require 'sprockets/transformers' require 'sprockets/uri_utils' require 'sprockets/unloaded_asset' module Sprockets # The loader phase takes a asset URI location and returns a constructed Asset # object. module Loader include DigestUtils, PathUtils, ProcessorUtils, URIUtils include Engines, Mime, Processing, Resolve, Transformers # Public: Load Asset by Asset URI. # # uri - A String containing complete URI to a file including schema # and full path such as: # "file:///Path/app/assets/js/app.js?type=application/javascript" # # # Returns Asset. def load(uri) unloaded = UnloadedAsset.new(uri, self) if unloaded.params.key?(:id) unless asset = asset_from_cache(unloaded.asset_key) id = unloaded.params.delete(:id) uri_without_id = build_asset_uri(unloaded.filename, unloaded.params) asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self)) if asset[:id] != id @logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}" end end else asset = fetch_asset_from_dependency_cache(unloaded) do |paths| # When asset is previously generated, its "dependencies" are stored in the cache. # The presence of `paths` indicates dependencies were stored. # We can check to see if the dependencies have not changed by "resolving" them and # generating a digest key from the resolved entries. If this digest key has not # changed the asset will be pulled from cache. # # If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache` # will confusingly be called again with `paths` set to nil where the asset will be # loaded from disk. if paths digest = DigestUtils.digest(resolve_dependencies(paths)) if uri_from_cache = cache.get(unloaded.digest_key(digest), true) asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key) end else load_from_unloaded(unloaded) end end end Asset.new(self, asset) end private # Internal: Load asset hash from cache # # key - A String containing lookup information for an asset # # This method converts all "compressed" paths to absolute paths. # Returns a hash of values representing an asset def asset_from_cache(key) asset = cache.get(key, true) if asset asset[:uri] = expand_from_root(asset[:uri]) asset[:load_path] = expand_from_root(asset[:load_path]) asset[:filename] = expand_from_root(asset[:filename]) asset[:metadata][:included].map! { |uri| expand_from_root(uri) } if asset[:metadata][:included] asset[:metadata][:links].map! { |uri| expand_from_root(uri) } if asset[:metadata][:links] asset[:metadata][:stubbed].map! { |uri| expand_from_root(uri) } if asset[:metadata][:stubbed] asset[:metadata][:required].map! { |uri| expand_from_root(uri) } if asset[:metadata][:required] asset[:metadata][:dependencies].map! { |uri| uri.start_with?("file-digest://") ? expand_from_root(uri) : uri } if asset[:metadata][:dependencies] asset[:metadata].each_key do |k| next unless k =~ /_dependencies\z/ asset[:metadata][k].map! { |uri| expand_from_root(uri) } end end asset end # Internal: Loads an asset and saves it to cache # # unloaded - An UnloadedAsset # # This method is only called when the given unloaded asset could not be # successfully pulled from cache. def load_from_unloaded(unloaded) unless file?(unloaded.filename) raise FileNotFound, "could not find file: #{unloaded.filename}" end load_path, logical_path = paths_split(config[:paths], unloaded.filename) unless load_path raise FileOutsidePaths, "#{unloaded.filename} is no longer under a load path: #{self.paths.join(', ')}" end logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path) name = logical_path if pipeline = unloaded.params[:pipeline] logical_path += ".#{pipeline}" end if type = unloaded.params[:type] logical_path += config[:mime_types][type][:extensions].first end if type != file_type && !config[:transformers][file_type][type] raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}" end processors = processors_for(type, file_type, engine_extnames, pipeline) processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline) dependencies = config[:dependencies] + [processors_dep_uri] # Read into memory and process if theres a processor pipeline if processors.any? result = call_processors(processors, { environment: self, cache: self.cache, uri: unloaded.uri, filename: unloaded.filename, load_path: load_path, name: name, content_type: type, metadata: { dependencies: dependencies } }) validate_processor_result!(result) source = result.delete(:data) metadata = result metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset) metadata[:digest] = digest(source) metadata[:length] = source.bytesize else dependencies << build_file_digest_uri(unloaded.filename) metadata = { digest: file_digest(unloaded.filename), length: self.stat(unloaded.filename).size, dependencies: dependencies } end asset = { uri: unloaded.uri, load_path: load_path, filename: unloaded.filename, name: name, logical_path: logical_path, content_type: type, source: source, metadata: metadata, dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies])) } asset[:id] = pack_hexdigest(digest(asset)) asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id])) # Deprecated: Avoid tracking Asset mtime asset[:mtime] = metadata[:dependencies].map { |u| if u.start_with?("file-digest:") s = self.stat(parse_file_digest_uri(u)) s ? s.mtime.to_i : nil else nil end }.compact.max asset[:mtime] ||= self.stat(unloaded.filename).mtime.to_i store_asset(asset, unloaded) asset end # Internal: Save a given asset to the cache # # asset - A hash containing values of loaded asset # unloaded - The UnloadedAsset used to lookup the `asset` # # This method converts all absolute paths to "compressed" paths # which are relative if they're in the root. def store_asset(asset, unloaded) # Save the asset in the cache under the new URI cached_asset = asset.dup cached_asset[:uri] = compress_from_root(asset[:uri]) cached_asset[:filename] = compress_from_root(asset[:filename]) cached_asset[:load_path] = compress_from_root(asset[:load_path]) if cached_asset[:metadata] # Deep dup to avoid modifying `asset` cached_asset[:metadata] = cached_asset[:metadata].dup if cached_asset[:metadata][:included] && !cached_asset[:metadata][:included].empty? cached_asset[:metadata][:included] = cached_asset[:metadata][:included].dup cached_asset[:metadata][:included].map! { |uri| compress_from_root(uri) } end if cached_asset[:metadata][:links] && !cached_asset[:metadata][:links].empty? cached_asset[:metadata][:links] = cached_asset[:metadata][:links].dup cached_asset[:metadata][:links].map! { |uri| compress_from_root(uri) } end if cached_asset[:metadata][:stubbed] && !cached_asset[:metadata][:stubbed].empty? cached_asset[:metadata][:stubbed] = cached_asset[:metadata][:stubbed].dup cached_asset[:metadata][:stubbed].map! { |uri| compress_from_root(uri) } end if cached_asset[:metadata][:required] && !cached_asset[:metadata][:required].empty? cached_asset[:metadata][:required] = cached_asset[:metadata][:required].dup cached_asset[:metadata][:required].map! { |uri| compress_from_root(uri) } end if cached_asset[:metadata][:dependencies] && !cached_asset[:metadata][:dependencies].empty? cached_asset[:metadata][:dependencies] = cached_asset[:metadata][:dependencies].dup cached_asset[:metadata][:dependencies].map! do |uri| uri.start_with?("file-digest://".freeze) ? compress_from_root(uri) : uri end end # compress all _dependencies in metadata like `sass_dependencies` cached_asset[:metadata].each do |key, value| next unless key =~ /_dependencies\z/ cached_asset[:metadata][key] = value.dup cached_asset[:metadata][key].map! {|uri| compress_from_root(uri) } end end # Unloaded asset and stored_asset now have a different URI stored_asset = UnloadedAsset.new(asset[:uri], self) cache.set(stored_asset.asset_key, cached_asset, true) # Save the new relative path for the digest key of the unloaded asset cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true) end # Internal: Resolve set of dependency URIs. # # uris - An Array of "dependencies" for example: # ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css", # "file-digest:///Full/path/app/assets/stylesheets/application.css", # "processors:type=text/css&file_type=text/css&pipeline=self", # "file-digest:///Full/path/app/assets/stylesheets"] # # Returns back array of things that the given uri dpends on # For example the environment version, if you're using a different version of sprockets # then the dependencies should be different, this is used only for generating cache key # for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for # version "3.2.0" of sprockets. # # Any paths that are returned are converted to relative paths # # Returns array of resolved dependencies def resolve_dependencies(uris) uris.map { |uri| resolve_dependency(uri) } end # Internal: Retrieves an asset based on its digest # # unloaded - An UnloadedAsset # limit - A Fixnum which sets the maximum number of versions of "histories" # stored in the cache # # This method attempts to retrieve the last `limit` number of histories of an asset # from the cache a "history" which is an array of unresolved "dependencies" that the asset needs # to compile. In this case A dependency can refer to either an asset i.e. index.js # may rely on jquery.js (so jquery.js is a depndency), or other factors that may affect # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors" # are used. # # For example a history array may look something like this # # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css", # "file-digest:///Full/path/app/assets/stylesheets/application.css", # "processors:type=text/css&file_digesttype=text/css&pipeline=self", # "file-digest:///Full/path/app/assets/stylesheets"]] # # Where the first entry is a Set of dependencies for last generated version of that asset. # Multiple versions are stored since sprockets keeps the last `limit` number of assets # generated present in the system. # # If a "history" of dependencies is present in the cache, each version of "history" will be # yielded to the passed block which is responsible for loading the asset. If found, the existing # history will be saved with the dependency that found a valid asset moved to the front. # # If no history is present, or if none of the histories could be resolved to a valid asset then, # the block is yielded to and expected to return a valid asset. # When this happens the dependencies for the returned asset are added to the "history", and older # entries are removed if the "history" is above `limit`. def fetch_asset_from_dependency_cache(unloaded, limit = 3) key = unloaded.dependency_history_key history = cache.get(key) || [] history.each_with_index do |deps, index| expanded_deps = deps.map do |path| path.start_with?("file-digest://") ? expand_from_root(path) : path end if asset = yield(expanded_deps) cache.set(key, history.rotate!(index)) if index > 0 return asset end end asset = yield deps = asset[:metadata][:dependencies].dup.map! do |uri| uri.start_with?("file-digest://") ? compress_from_root(uri) : uri end cache.set(key, history.unshift(deps).take(limit)) asset end end end sprockets-3.7.0/lib/sprockets/file_reader.rb0000644000004100000410000000076312750322502021143 0ustar www-datawww-datarequire 'set' module Sprockets # Internal: The first processor in the pipeline that reads the file into # memory and passes it along as `input[:data]`. class FileReader def self.call(input) env = input[:environment] data = env.read_file(input[:filename], input[:content_type]) dependencies = Set.new(input[:metadata][:dependencies]) dependencies += [env.build_file_digest_uri(input[:filename])] { data: data, dependencies: dependencies } end end end sprockets-3.7.0/lib/sprockets/closure_compressor.rb0000644000004100000410000000220012750322502022616 0ustar www-datawww-datarequire 'sprockets/autoload' require 'sprockets/digest_utils' module Sprockets # Public: Closure Compiler minifier. # # To accept the default options # # environment.register_bundle_processor 'application/javascript', # Sprockets::ClosureCompressor # # Or to pass options to the Closure::Compiler class. # # environment.register_bundle_processor 'application/javascript', # Sprockets::ClosureCompressor.new({ ... }) # class ClosureCompressor VERSION = '1' # Public: Return singleton instance with default options. # # Returns ClosureCompressor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def self.cache_key instance.cache_key end attr_reader :cache_key def initialize(options = {}) @compiler = Autoload::Closure::Compiler.new(options) @cache_key = "#{self.class.name}:#{Autoload::Closure::VERSION}:#{Autoload::Closure::COMPILER_VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze end def call(input) @compiler.compile(input[:data]) end end end sprockets-3.7.0/lib/sprockets/errors.rb0000644000004100000410000000066212750322502020214 0ustar www-datawww-data# Define some basic Sprockets error classes module Sprockets class Error < StandardError; end class ArgumentError < Error; end class ContentTypeMismatch < Error; end class NotImplementedError < Error; end class NotFound < Error; end class ConversionError < NotFound; end class FileNotFound < NotFound; end class FileOutsidePaths < NotFound; end end sprockets-3.7.0/lib/sprockets/context.rb0000644000004100000410000001552012750322502020363 0ustar www-datawww-datarequire 'pathname' require 'rack/utils' require 'set' require 'sprockets/errors' module Sprockets # Deprecated: `Context` provides helper methods to all processors. # They are typically accessed by ERB templates. You can mix in custom helpers # by injecting them into `Environment#context_class`. Do not mix them into # `Context` directly. # # environment.context_class.class_eval do # include MyHelper # def asset_url; end # end # # <%= asset_url "foo.png" %> # # The `Context` also collects dependencies declared by # assets. See `DirectiveProcessor` for an example of this. class Context attr_reader :environment, :filename, :pathname # Deprecated attr_accessor :__LINE__ def initialize(input) @environment = input[:environment] @metadata = input[:metadata] @load_path = input[:load_path] @logical_path = input[:name] @filename = input[:filename] @dirname = File.dirname(@filename) @pathname = Pathname.new(@filename) @content_type = input[:content_type] @required = Set.new(@metadata[:required]) @stubbed = Set.new(@metadata[:stubbed]) @links = Set.new(@metadata[:links]) @dependencies = Set.new(input[:metadata][:dependencies]) end def metadata { required: @required, stubbed: @stubbed, links: @links, dependencies: @dependencies } end # Returns the environment path that contains the file. # # If `app/javascripts` and `app/stylesheets` are in your path, and # current file is `app/javascripts/foo/bar.js`, `load_path` would # return `app/javascripts`. attr_reader :load_path alias_method :root_path, :load_path # Returns logical path without any file extensions. # # 'app/javascripts/application.js' # # => 'application' # attr_reader :logical_path # Returns content type of file # # 'application/javascript' # 'text/css' # attr_reader :content_type # Public: Given a logical path, `resolve` will find and return an Asset URI. # Relative paths will also be resolved. An accept type maybe given to # restrict the search. # # resolve("foo.js") # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript" # # resolve("./bar.js") # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript" # # path - String logical or absolute path # options # accept - String content accept type # # Returns an Asset URI String. def resolve(path, options = {}) uri, deps = environment.resolve!(path, options.merge(base_path: @dirname)) @dependencies.merge(deps) uri end # Public: Load Asset by AssetURI and track it as a dependency. # # uri - AssetURI # # Returns Asset. def load(uri) asset = environment.load(uri) @dependencies.merge(asset.metadata[:dependencies]) asset end # `depend_on` 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 with invalidate the cache of the # source file. def depend_on(path) path = path.to_s if path.is_a?(Pathname) if environment.absolute_path?(path) && environment.stat(path) @dependencies << environment.build_file_digest_uri(path) else resolve(path, compat: false) end nil end # `depend_on_asset` allows you to state an asset dependency # without including it. # # This is used for caching purposes. Any changes that would # invalidate the dependency asset will invalidate the source # file. Unlike `depend_on`, this will include recursively include # the target asset's dependencies. def depend_on_asset(path) load(resolve(path, compat: false)) end # `require_asset` declares `path` as a dependency of the file. The # dependency will be inserted before the file and will only be # included once. # # If ERB processing is enabled, you can use it to dynamically # require assets. # # <%= require_asset "#{framework}.js" %> # def require_asset(path) @required << resolve(path, accept: @content_type, pipeline: :self, compat: false) nil end # `stub_asset` blacklists `path` from being included in the bundle. # `path` must be an asset which may or may not already be included # in the bundle. def stub_asset(path) @stubbed << resolve(path, accept: @content_type, pipeline: :self, compat: false) nil end # `link_asset` declares an external dependency on an asset without directly # including it. The target asset is returned from this function making it # easy to construct a link to it. # # Returns an Asset or nil. def link_asset(path) asset = depend_on_asset(path) @links << asset.uri asset end # Returns a Base64-encoded `data:` URI with the contents of the # asset at the specified path, and marks that path as a dependency # of the current file. # # Use `asset_data_uri` from ERB with CSS or JavaScript assets: # # #logo { background: url(<%= asset_data_uri 'logo.png' %>) } # # $('').attr('src', '<%= asset_data_uri 'avatar.jpg' %>') # def asset_data_uri(path) asset = depend_on_asset(path) data = EncodingUtils.base64(asset.source) "data:#{asset.content_type};base64,#{Rack::Utils.escape(data)}" end # Expands logical path to full url to asset. # # NOTE: This helper is currently not implemented and should be # customized by the application. Though, in the future, some # basics implemention may be provided with different methods that # are required to be overridden. def asset_path(path, options = {}) message = <<-EOS Custom asset_path helper is not implemented Extend your environment context with a custom method. environment.context_class.class_eval do def asset_path(path, options = {}) end end EOS raise NotImplementedError, message end # Expand logical image asset path. def image_path(path) asset_path(path, type: :image) end # Expand logical video asset path. def video_path(path) asset_path(path, type: :video) end # Expand logical audio asset path. def audio_path(path) asset_path(path, type: :audio) end # Expand logical font asset path. def font_path(path) asset_path(path, type: :font) end # Expand logical javascript asset path. def javascript_path(path) asset_path(path, type: :javascript) end # Expand logical stylesheet asset path. def stylesheet_path(path) asset_path(path, type: :stylesheet) end end end sprockets-3.7.0/lib/sprockets/http_utils.rb0000644000004100000410000000672312750322502021103 0ustar www-datawww-datamodule Sprockets # Internal: HTTP URI utilities. Many adapted from Rack::Utils. Mixed into # Environment. module HTTPUtils extend self # Public: Test mime type against mime range. # # match_mime_type?('text/html', 'text/*') => true # match_mime_type?('text/plain', '*') => true # match_mime_type?('text/html', 'application/json') => false # # Returns true if the given value is a mime match for the given mime match # specification, false otherwise. def match_mime_type?(value, matcher) v1, v2 = value.split('/', 2) m1, m2 = matcher.split('/', 2) (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2) end # Public: Return values from Hash where the key matches the mime type. # # hash - Hash of String matcher keys to Object values # mime_type - String mime type # # Returns Array of Object values. def match_mime_type_keys(hash, mime_type) type, subtype = mime_type.split('/', 2) [ hash["*"], hash["*/*"], hash["#{type}/*"], hash["#{type}/#{subtype}"] ].compact end # Internal: Parse Accept header quality values. # # Adapted from Rack::Utils#q_values. # # Returns an Array of [String, Float]. def parse_q_values(values) values.to_s.split(/\s*,\s*/).map do |part| value, parameters = part.split(/\s*;\s*/, 2) quality = 1.0 if md = /\Aq=([\d.]+)/.match(parameters) quality = md[1].to_f end [value, quality] end end # Internal: Find all qvalue matches from an Array of available options. # # Adapted from Rack::Utils#q_values. # # Returns Array of matched Strings from available Array or []. def find_q_matches(q_values, available, &matcher) matcher ||= lambda { |a, b| a == b } matches = [] case q_values when Array when String q_values = parse_q_values(q_values) when NilClass q_values = [] else raise TypeError, "unknown q_values type: #{q_values.class}" end q_values.each do |accepted, quality| if match = available.find { |option| matcher.call(option, accepted) } matches << [match, quality] end end matches.sort_by! { |match, quality| -quality } matches.map! { |match, quality| match } matches end # Internal: Find the best qvalue match from an Array of available options. # # Adapted from Rack::Utils#q_values. # # Returns the matched String from available Array or nil. def find_best_q_match(q_values, available, &matcher) find_q_matches(q_values, available, &matcher).first end # Internal: Find the all qvalue match from an Array of available mime type # options. # # Adapted from Rack::Utils#q_values. # # Returns Array of matched mime type Strings from available Array or []. def find_mime_type_matches(q_value_header, available) find_q_matches(q_value_header, available) do |a, b| match_mime_type?(a, b) end end # Internal: Find the best qvalue match from an Array of available mime type # options. # # Adapted from Rack::Utils#q_values. # # Returns the matched mime type String from available Array or nil. def find_best_mime_type_match(q_value_header, available) find_best_q_match(q_value_header, available) do |a, b| match_mime_type?(a, b) end end end end sprockets-3.7.0/lib/sprockets/coffee_script_processor.rb0000644000004100000410000000114512750322502023607 0ustar www-datawww-datarequire 'sprockets/autoload' module Sprockets # Processor engine class for the CoffeeScript compiler. # Depends on the `coffee-script` and `coffee-script-source` gems. # # For more infomation see: # # https://github.com/josh/ruby-coffee-script # module CoffeeScriptProcessor VERSION = '1' def self.cache_key @cache_key ||= "#{name}:#{Autoload::CoffeeScript::Source.version}:#{VERSION}".freeze end def self.call(input) data = input[:data] input[:cache].fetch([self.cache_key, data]) do Autoload::CoffeeScript.compile(data) end end end end sprockets-3.7.0/lib/sprockets/ejs_processor.rb0000644000004100000410000000127012750322502021554 0ustar www-datawww-datarequire 'sprockets/autoload' module Sprockets # Processor engine class for the EJS compiler. Depends on the `ejs` gem. # # For more infomation see: # # https://github.com/sstephenson/ruby-ejs # module EjsProcessor VERSION = '1' def self.cache_key @cache_key ||= "#{name}:#{VERSION}".freeze end # Compile template data with EJS compiler. # # Returns a JS function definition String. The result should be # assigned to a JS variable. # # # => "function(obj){...}" # def self.call(input) data = input[:data] input[:cache].fetch([cache_key, data]) do Autoload::EJS.compile(data) end end end end sprockets-3.7.0/lib/sprockets/eco_template.rb0000644000004100000410000000053312750322502021336 0ustar www-datawww-datarequire 'sprockets/eco_processor' module Sprockets # Deprecated module EcoTemplate VERSION = EcoProcessor::VERSION def self.cache_key EcoProcessor.cache_key end def self.call(*args) Deprecation.new.warn "EcoTemplate is deprecated please use EcoProcessor instead" EcoProcessor.call(*args) end end end sprockets-3.7.0/lib/sprockets/sass_importer.rb0000644000004100000410000000013212750322502021562 0ustar www-datawww-data# Deprecated: Require sprockets/sass_processor instead require 'sprockets/sass_processor' sprockets-3.7.0/lib/sprockets/mime.rb0000644000004100000410000000716412750322502017633 0ustar www-datawww-datarequire 'sprockets/encoding_utils' require 'sprockets/http_utils' require 'sprockets/utils' module Sprockets module Mime include HTTPUtils, Utils # Public: Mapping of MIME type Strings to properties Hash. # # key - MIME Type String # value - Hash # extensions - Array of extnames # charset - Default Encoding or function to detect encoding # # Returns Hash. def mime_types config[:mime_types] end # Internal: Mapping of MIME extension Strings to MIME type Strings. # # Used for internal fast lookup purposes. # # Examples: # # mime_exts['.js'] #=> 'application/javascript' # # key - MIME extension String # value - MIME Type String # # Returns Hash. def mime_exts config[:mime_exts] end # Public: Register a new mime type. # # mime_type - String MIME Type # options - Hash # extensions: Array of String extnames # charset: Proc/Method that detects the charset of a file. # See EncodingUtils. # # Returns nothing. def register_mime_type(mime_type, options = {}) # Legacy extension argument, will be removed from 4.x if options.is_a?(String) options = { extensions: [options] } end extnames = Array(options[:extensions]).map { |extname| Sprockets::Utils.normalize_extension(extname) } charset = options[:charset] charset ||= :default if mime_type.start_with?('text/') charset = EncodingUtils::CHARSET_DETECT[charset] if charset.is_a?(Symbol) self.computed_config = {} self.config = hash_reassoc(config, :mime_exts) do |mime_exts| extnames.each do |extname| mime_exts[extname] = mime_type end mime_exts end self.config = hash_reassoc(config, :mime_types) do |mime_types| type = { extensions: extnames } type[:charset] = charset if charset mime_types.merge(mime_type => type) end end # Internal: Get detecter function for MIME type. # # mime_type - String MIME type # # Returns Proc detector or nil if none is available. def mime_type_charset_detecter(mime_type) if type = config[:mime_types][mime_type] if detect = type[:charset] return detect end end end # Public: Read file on disk with MIME type specific encoding. # # filename - String path # content_type - String MIME type # # Returns String file contents transcoded to UTF-8 or in its external # encoding. def read_file(filename, content_type = nil) data = File.binread(filename) if detect = mime_type_charset_detecter(content_type) detect.call(data).encode(Encoding::UTF_8, :universal_newline => true) else data end end private def extname_map self.computed_config[:_extnames] ||= compute_extname_map end def compute_extname_map graph = {} ([nil] + pipelines.keys.map(&:to_s)).each do |pipeline| pipeline_extname = ".#{pipeline}" if pipeline ([[nil, nil]] + config[:mime_exts].to_a).each do |format_extname, format_type| 4.times do |n| config[:engines].keys.permutation(n).each do |engine_extnames| key = "#{pipeline_extname}#{format_extname}#{engine_extnames.join}" type = format_type || config[:engine_mime_types][engine_extnames.first] graph[key] = {type: type, engines: engine_extnames, pipeline: pipeline} end end end end graph end end end sprockets-3.7.0/lib/sprockets/jst_processor.rb0000644000004100000410000000224312750322502021574 0ustar www-datawww-datamodule Sprockets # Public: .jst engine. # # Exports server side compiled templates to an object. # # Name your template "users/show.jst.ejs", "users/new.jst.eco", etc. # # To accept the default options # # environment.register_engine '.jst', # JstProcessor, # mime_type: 'application/javascript' # # Change the default namespace. # # environment.register_engine '.jst', # JstProcessor.new(namespace: 'App.templates'), # mime_type: 'application/javascript' # class JstProcessor def self.default_namespace 'this.JST' end # Public: Return singleton instance with default options. # # Returns JstProcessor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def initialize(options = {}) @namespace = options[:namespace] || self.class.default_namespace end def call(input) data = input[:data].gsub(/$(.)/m, "\\1 ").strip key = input[:name] <<-JST (function() { #{@namespace} || (#{@namespace} = {}); #{@namespace}[#{key.inspect}] = #{data}; }).call(this); JST end end end sprockets-3.7.0/lib/sprockets/processing.rb0000644000004100000410000002010712750322502021050 0ustar www-datawww-datarequire 'sprockets/engines' require 'sprockets/file_reader' require 'sprockets/legacy_proc_processor' require 'sprockets/legacy_tilt_processor' require 'sprockets/mime' require 'sprockets/processor_utils' require 'sprockets/uri_utils' require 'sprockets/utils' module Sprockets # `Processing` is an internal mixin whose public methods are exposed on # the `Environment` and `CachedEnvironment` classes. module Processing include ProcessorUtils, URIUtils, Utils def pipelines config[:pipelines] end def register_pipeline(name, proc = nil, &block) proc ||= block self.config = hash_reassoc(config, :pipelines) do |pipelines| pipelines.merge(name.to_sym => proc) end end # Preprocessors are ran before Postprocessors and Engine # processors. def preprocessors config[:preprocessors] end alias_method :processors, :preprocessors # Postprocessors are ran after Preprocessors and Engine processors. def postprocessors config[:postprocessors] end # Registers a new Preprocessor `klass` for `mime_type`. # # register_preprocessor 'text/css', Sprockets::DirectiveProcessor # # A block can be passed for to create a shorthand processor. # # register_preprocessor 'text/css', :my_processor do |context, data| # data.gsub(...) # end # def register_preprocessor(*args, &block) register_config_processor(:preprocessors, *args, &block) end alias_method :register_processor, :register_preprocessor # Registers a new Postprocessor `klass` for `mime_type`. # # register_postprocessor 'application/javascript', Sprockets::DirectiveProcessor # # A block can be passed for to create a shorthand processor. # # register_postprocessor 'application/javascript', :my_processor do |context, data| # data.gsub(...) # end # def register_postprocessor(*args, &block) register_config_processor(:postprocessors, *args, &block) end # Remove Preprocessor `klass` for `mime_type`. # # unregister_preprocessor 'text/css', Sprockets::DirectiveProcessor # def unregister_preprocessor(*args) unregister_config_processor(:preprocessors, *args) end alias_method :unregister_processor, :unregister_preprocessor # Remove Postprocessor `klass` for `mime_type`. # # unregister_postprocessor 'text/css', Sprockets::DirectiveProcessor # def unregister_postprocessor(*args) unregister_config_processor(:postprocessors, *args) end # Bundle Processors are ran on concatenated assets rather than # individual files. def bundle_processors config[:bundle_processors] end # Registers a new Bundle Processor `klass` for `mime_type`. # # register_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor # # A block can be passed for to create a shorthand processor. # # register_bundle_processor 'application/javascript', :my_processor do |context, data| # data.gsub(...) # end # def register_bundle_processor(*args, &block) register_config_processor(:bundle_processors, *args, &block) end # Remove Bundle Processor `klass` for `mime_type`. # # unregister_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor # def unregister_bundle_processor(*args) unregister_config_processor(:bundle_processors, *args) end # Public: Register bundle metadata reducer function. # # Examples # # Sprockets.register_bundle_metadata_reducer 'application/javascript', :jshint_errors, [], :+ # # Sprockets.register_bundle_metadata_reducer 'text/css', :selector_count, 0 { |total, count| # total + count # } # # mime_type - String MIME Type. Use '*/*' applies to all types. # key - Symbol metadata key # initial - Initial memo to pass to the reduce funciton (default: nil) # block - Proc accepting the memo accumulator and current value # # Returns nothing. def register_bundle_metadata_reducer(mime_type, key, *args, &block) case args.size when 0 reducer = block when 1 if block_given? initial = args[0] reducer = block else initial = nil reducer = args[0].to_proc end when 2 initial = args[0] reducer = args[1].to_proc else raise ArgumentError, "wrong number of arguments (#{args.size} for 0..2)" end self.config = hash_reassoc(config, :bundle_reducers, mime_type) do |reducers| reducers.merge(key => [initial, reducer]) end end protected def resolve_processors_cache_key_uri(uri) params = parse_uri_query_params(uri[11..-1]) params[:engine_extnames] = params[:engines] ? params[:engines].split(',') : [] processors = processors_for(params[:type], params[:file_type], params[:engine_extnames], params[:pipeline]) processors_cache_keys(processors) end def build_processors_uri(type, file_type, engine_extnames, pipeline) engines = engine_extnames.join(',') if engine_extnames.any? query = encode_uri_query_params( type: type, file_type: file_type, engines: engines, pipeline: pipeline ) "processors:#{query}" end def processors_for(type, file_type, engine_extnames, pipeline) pipeline ||= :default config[:pipelines][pipeline.to_sym].call(self, type, file_type, engine_extnames) end def default_processors_for(type, file_type, engine_extnames) bundled_processors = config[:bundle_processors][type] if bundled_processors.any? bundled_processors else self_processors_for(type, file_type, engine_extnames) end end def self_processors_for(type, file_type, engine_extnames) processors = [] processors.concat config[:postprocessors][type] if type != file_type && processor = config[:transformers][file_type][type] processors << processor end processors.concat engine_extnames.map { |ext| engines[ext] } processors.concat config[:preprocessors][file_type] if processors.any? || mime_type_charset_detecter(type) processors << FileReader end processors end private def register_config_processor(type, mime_type, klass, proc = nil, &block) proc ||= block processor = wrap_processor(klass, proc) self.config = hash_reassoc(config, type, mime_type) do |processors| processors.unshift(processor) processors end compute_transformers! end def unregister_config_processor(type, mime_type, klass) if klass.is_a?(String) || klass.is_a?(Symbol) klass = config[type][mime_type].detect do |cls| cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})" end end self.config = hash_reassoc(config, type, mime_type) do |processors| processors.delete(klass) processors end compute_transformers! end def deprecate_legacy_processor_interface(interface) msg = "You are using the a deprecated processor interface #{ interface.inspect }.\n" + "Please update your processor interface:\n" + "https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors\n" Deprecation.new([caller[3]]).warn msg end def wrap_processor(klass, proc) if !proc if klass.respond_to?(:call) klass else deprecate_legacy_processor_interface(klass) LegacyTiltProcessor.new(klass) end elsif proc.respond_to?(:arity) && proc.arity == 2 deprecate_legacy_processor_interface(proc) LegacyProcProcessor.new(klass.to_s, proc) else proc end end end end sprockets-3.7.0/lib/sprockets/paths.rb0000644000004100000410000000377212750322502020024 0ustar www-datawww-datarequire 'sprockets/path_utils' require 'sprockets/utils' module Sprockets module Paths include PathUtils, Utils # Returns `Environment` root. # # All relative paths are expanded with root as its base. To be # useful set this to your applications root directory. (`Rails.root`) def root config[:root] end # Internal: Change Environment root. # # Only the initializer should change the root. def root=(path) self.config = hash_reassoc(config, :root) do File.expand_path(path) end end private :root= # Returns an `Array` of path `String`s. # # These paths will be used for asset logical path lookups. def paths config[:paths] end # Prepend a `path` to the `paths` list. # # Paths at the end of the `Array` have the least priority. def prepend_path(path) self.config = hash_reassoc(config, :paths) do |paths| path = File.expand_path(path, config[:root]).freeze paths.unshift(path) end end # Append a `path` to the `paths` list. # # Paths at the beginning of the `Array` have a higher priority. def append_path(path) self.config = hash_reassoc(config, :paths) do |paths| path = File.expand_path(path, config[:root]).freeze paths.push(path) end end # Clear all paths and start fresh. # # There is no mechanism for reordering paths, so its best to # completely wipe the paths list and reappend them in the order # you want. def clear_paths self.config = hash_reassoc(config, :paths) do |paths| paths.clear end end # Public: Iterate over every file under all load paths. # # Returns Enumerator if no block is given. def each_file return to_enum(__method__) unless block_given? paths.each do |root| stat_tree(root).each do |filename, stat| if stat.file? yield filename end end end nil end end end sprockets-3.7.0/lib/sprockets/bundle.rb0000644000004100000410000000416112750322502020147 0ustar www-datawww-datarequire 'set' require 'sprockets/utils' module Sprockets # Internal: Bundle processor takes a single file asset and prepends all the # `:required` URIs to the contents. # # Uses pipeline metadata: # # :required - Ordered Set of asset URIs to prepend # :stubbed - Set of asset URIs to substract from the required set. # # Also see DirectiveProcessor. class Bundle def self.call(input) env = input[:environment] type = input[:content_type] dependencies = Set.new(input[:metadata][:dependencies]) processed_uri, deps = env.resolve(input[:filename], accept: type, pipeline: :self, compat: false) dependencies.merge(deps) find_required = proc { |uri| env.load(uri).metadata[:required] } required = Utils.dfs(processed_uri, &find_required) stubbed = Utils.dfs(env.load(processed_uri).metadata[:stubbed], &find_required) required.subtract(stubbed) assets = required.map { |uri| env.load(uri) } (required + stubbed).each do |uri| dependencies.merge(env.load(uri).metadata[:dependencies]) end reducers = Hash[env.match_mime_type_keys(env.config[:bundle_reducers], type).flat_map(&:to_a)] process_bundle_reducers(assets, reducers).merge(dependencies: dependencies, included: assets.map(&:uri)) end # Internal: Run bundle reducers on set of Assets producing a reduced # metadata Hash. # # assets - Array of Assets # reducers - Array of [initial, reducer_proc] pairs # # Returns reduced asset metadata Hash. def self.process_bundle_reducers(assets, reducers) initial = {} reducers.each do |k, (v, _)| if v.respond_to?(:call) initial[k] = v.call elsif !v.nil? initial[k] = v end end assets.reduce(initial) do |h, asset| reducers.each do |k, (_, block)| value = k == :data ? asset.source : asset.metadata[k] if h.key?(k) if !value.nil? h[k] = block.call(h[k], value) end else h[k] = value end end h end end end end sprockets-3.7.0/lib/sprockets/version.rb0000644000004100000410000000005112750322502020355 0ustar www-datawww-datamodule Sprockets VERSION = "3.7.0" end sprockets-3.7.0/lib/sprockets/legacy_tilt_processor.rb0000644000004100000410000000122712750322502023275 0ustar www-datawww-datarequire 'delegate' module Sprockets # Deprecated: Wraps legacy engine and process Tilt templates with new # processor call signature. # # Will be removed in Sprockets 4.x. # # LegacyTiltProcessor.new(Tilt::CoffeeScriptProcessor) # class LegacyTiltProcessor < Delegator def initialize(klass) @klass = klass end def __getobj__ @klass end def call(input) filename = input[:filename] data = input[:data] context = input[:environment].context_class.new(input) data = @klass.new(filename) { data }.render(context, {}) context.metadata.merge(data: data.to_str) end end end sprockets-3.7.0/lib/sprockets/compressing.rb0000644000004100000410000000537512750322502021237 0ustar www-datawww-datarequire 'sprockets/utils' module Sprockets # `Compressing` is an internal mixin whose public methods are exposed on # the `Environment` and `CachedEnvironment` classes. module Compressing include Utils def compressors config[:compressors] end def register_compressor(mime_type, sym, klass) self.config = hash_reassoc(config, :compressors, mime_type) do |compressors| compressors[sym] = klass compressors end end # Return CSS compressor or nil if none is set def css_compressor if defined? @css_compressor @css_compressor end end # Assign a compressor to run on `text/css` assets. # # The compressor object must respond to `compress`. def css_compressor=(compressor) unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor @css_compressor = nil return unless compressor if compressor.is_a?(Symbol) @css_compressor = klass = config[:compressors]['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}") elsif compressor.respond_to?(:compress) klass = LegacyProcProcessor.new(:css_compressor, proc { |context, data| compressor.compress(data) }) @css_compressor = :css_compressor else @css_compressor = klass = compressor end register_bundle_processor 'text/css', klass end # Return JS compressor or nil if none is set def js_compressor if defined? @js_compressor @js_compressor end end # Assign a compressor to run on `application/javascript` assets. # # The compressor object must respond to `compress`. def js_compressor=(compressor) unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor @js_compressor = nil return unless compressor if compressor.is_a?(Symbol) @js_compressor = klass = config[:compressors]['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}") elsif compressor.respond_to?(:compress) klass = LegacyProcProcessor.new(:js_compressor, proc { |context, data| compressor.compress(data) }) @js_compressor = :js_compressor else @js_compressor = klass = compressor end register_bundle_processor 'application/javascript', klass end # Public: Checks if Gzip is enabled. def gzip? config[:gzip_enabled] end # Public: Checks if Gzip is disabled. def skip_gzip? !gzip? end # Public: Enable or disable the creation of Gzip files. # # Defaults to true. # # environment.gzip = false # def gzip=(gzip) self.config = config.merge(gzip_enabled: gzip).freeze end end end sprockets-3.7.0/lib/sprockets/path_utils.rb0000644000004100000410000001623712750322502021061 0ustar www-datawww-datamodule Sprockets # Internal: File and path related utilities. Mixed into Environment. # # Probably would be called FileUtils, but that causes namespace annoyances # when code actually wants to reference ::FileUtils. module PathUtils extend self # Public: Like `File.stat`. # # path - String file or directory path # # Returns nil if the file does not exist. def stat(path) if File.exist?(path) File.stat(path.to_s) else nil end end # Public: Like `File.file?`. # # path - String file path. # # Returns true path exists and is a file. def file?(path) if stat = self.stat(path) stat.file? else false end end # Public: Like `File.directory?`. # # path - String file path. # # Returns true path exists and is a directory. def directory?(path) if stat = self.stat(path) stat.directory? else false end end # Public: A version of `Dir.entries` that filters out `.` files and `~` # swap files. # # path - String directory path # # Returns an empty `Array` if the directory does not exist. def entries(path) if File.directory?(path) entries = Dir.entries(path, :encoding => Encoding.default_internal) entries.reject! { |entry| entry.start_with?(".".freeze) || (entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) || entry.end_with?("~".freeze) } entries.sort! else [] end end # Public: Check if path is absolute or relative. # # path - String path. # # Returns true if path is absolute, otherwise false. if File::ALT_SEPARATOR require 'pathname' # On Windows, ALT_SEPARATOR is \ # Delegate to Pathname since the logic gets complex. def absolute_path?(path) Pathname.new(path).absolute? end else def absolute_path?(path) path[0] == File::SEPARATOR end end if File::ALT_SEPARATOR SEPARATOR_PATTERN = "#{Regexp.quote(File::SEPARATOR)}|#{Regexp.quote(File::ALT_SEPARATOR)}" else SEPARATOR_PATTERN = "#{Regexp.quote(File::SEPARATOR)}" end # Public: Check if path is explicitly relative. # Starts with "./" or "../". # # path - String path. # # Returns true if path is relative, otherwise false. def relative_path?(path) path =~ /^\.\.?($|#{SEPARATOR_PATTERN})/ ? true : false end # Internal: Get relative path for root path and subpath. # # path - String path # subpath - String subpath of path # # Returns relative String path if subpath is a subpath of path, or nil if # subpath is outside of path. def split_subpath(path, subpath) return "" if path == subpath path = File.join(path, '') if subpath.start_with?(path) subpath[path.length..-1] else nil end end # Internal: Detect root path and base for file in a set of paths. # # paths - Array of String paths # filename - String path of file expected to be in one of the paths. # # Returns [String root, String path] def paths_split(paths, filename) paths.each do |path| if subpath = split_subpath(path, filename) return path, subpath end end nil end # Internal: Get path's extensions. # # path - String # # Returns an Array of String extnames. def path_extnames(path) File.basename(path).scan(/\.[^.]+/) end # Internal: Match path extnames against available extensions. # # path - String # extensions - Hash of String extnames to values # # Returns [String extname, Object value] or nil nothing matched. def match_path_extname(path, extensions) basename = File.basename(path) i = basename.index('.'.freeze) while i && i < basename.length - 1 extname = basename[i..-1] if value = extensions[extname] return extname, value end i = basename.index('.'.freeze, i+1) end nil end # Internal: Returns all parents for path # # path - String absolute filename or directory # root - String path to stop at (default: system root) # # Returns an Array of String paths. def path_parents(path, root = nil) root = "#{root}#{File::SEPARATOR}" if root parents = [] loop do parent = File.dirname(path) break if parent == path break if root && !path.start_with?(root) parents << path = parent end parents end # Internal: Find target basename checking upwards from path. # # basename - String filename: ".sprocketsrc" # path - String path to start search: "app/assets/javascripts/app.js" # root - String path to stop at (default: system root) # # Returns String filename or nil. def find_upwards(basename, path, root = nil) path_parents(path, root).each do |dir| filename = File.join(dir, basename) return filename if file?(filename) end nil end # Public: Stat all the files under a directory. # # dir - A String directory # # Returns an Enumerator of [path, stat]. def stat_directory(dir) return to_enum(__method__, dir) unless block_given? self.entries(dir).each do |entry| path = File.join(dir, entry) if stat = self.stat(path) yield path, stat end end nil end # Public: Recursive stat all the files under a directory. # # dir - A String directory # # Returns an Enumerator of [path, stat]. def stat_tree(dir, &block) return to_enum(__method__, dir) unless block_given? self.stat_directory(dir) do |path, stat| yield path, stat if stat.directory? stat_tree(path, &block) end end nil end # Public: Recursive stat all the files under a directory in alphabetical # order. # # dir - A String directory # # Returns an Enumerator of [path, stat]. def stat_sorted_tree(dir, &block) return to_enum(__method__, dir) unless block_given? self.stat_directory(dir).sort_by { |path, stat| stat.directory? ? "#{path}/" : path }.each do |path, stat| yield path, stat if stat.directory? stat_sorted_tree(path, &block) end end nil end # Public: Write to a file atomically. Useful for situations where you # don't want other processes or threads to see half-written files. # # Utils.atomic_write('important.file') do |file| # file.write('hello') # end # # Returns nothing. def atomic_write(filename) dirname, basename = File.split(filename) basename = [ basename, Thread.current.object_id, Process.pid, rand(1000000) ].join('.') tmpname = File.join(dirname, basename) File.open(tmpname, 'wb+') do |f| yield f end File.rename(tmpname, filename) ensure File.delete(tmpname) if File.exist?(tmpname) end end end sprockets-3.7.0/lib/sprockets/eco_processor.rb0000644000004100000410000000140412750322502021540 0ustar www-datawww-datarequire 'sprockets/autoload' module Sprockets # Processor engine class for the Eco compiler. Depends on the `eco` gem. # # For more infomation see: # # https://github.com/sstephenson/ruby-eco # https://github.com/sstephenson/eco # module EcoProcessor VERSION = '1' def self.cache_key @cache_key ||= "#{name}:#{Autoload::Eco::Source::VERSION}:#{VERSION}".freeze end # Compile template data with Eco compiler. # # Returns a JS function definition String. The result should be # assigned to a JS variable. # # # => "function(...) {...}" # def self.call(input) data = input[:data] input[:cache].fetch([cache_key, data]) do Autoload::Eco.compile(data) end end end end sprockets-3.7.0/lib/sprockets/uglifier_compressor.rb0000644000004100000410000000252312750322502022760 0ustar www-datawww-datarequire 'sprockets/autoload' require 'sprockets/digest_utils' module Sprockets # Public: Uglifier/Uglify compressor. # # To accept the default options # # environment.register_bundle_processor 'application/javascript', # Sprockets::UglifierCompressor # # Or to pass options to the Uglifier class. # # environment.register_bundle_processor 'application/javascript', # Sprockets::UglifierCompressor.new(comments: :copyright) # class UglifierCompressor VERSION = '1' # Public: Return singleton instance with default options. # # Returns UglifierCompressor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def self.cache_key instance.cache_key end attr_reader :cache_key def initialize(options = {}) # Feature detect Uglifier 2.0 option support if Autoload::Uglifier::DEFAULTS[:copyright] # Uglifier < 2.x options[:copyright] ||= false else # Uglifier >= 2.x options[:comments] ||= :none end @uglifier = Autoload::Uglifier.new(options) @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze end def call(input) @uglifier.compile(input[:data]) end end end sprockets-3.7.0/lib/sprockets/cached_environment.rb0000644000004100000410000000370312750322502022532 0ustar www-datawww-datarequire 'sprockets/base' module Sprockets # `Cached` is a special cached version of `Environment`. # # The expection is that all of its file system methods are cached # for the instances lifetime. This makes `Cached` much faster. This # behavior is ideal in production environments where the file system # is immutable. # # `Cached` should not be initialized directly. Instead use # `Environment#cached`. class CachedEnvironment < Base def initialize(environment) initialize_configuration(environment) @cache = environment.cache @stats = Hash.new { |h, k| h[k] = _stat(k) } @entries = Hash.new { |h, k| h[k] = _entries(k) } @uris = Hash.new { |h, k| h[k] = _load(k) } @processor_cache_keys = Hash.new { |h, k| h[k] = _processor_cache_key(k) } @resolved_dependencies = Hash.new { |h, k| h[k] = _resolve_dependency(k) } end # No-op return self as cached environment. def cached self end alias_method :index, :cached # Internal: Cache Environment#entries alias_method :_entries, :entries def entries(path) @entries[path] end # Internal: Cache Environment#stat alias_method :_stat, :stat def stat(path) @stats[path] end # Internal: Cache Environment#load alias_method :_load, :load def load(uri) @uris[uri] end # Internal: Cache Environment#processor_cache_key alias_method :_processor_cache_key, :processor_cache_key def processor_cache_key(str) @processor_cache_keys[str] end # Internal: Cache Environment#resolve_dependency alias_method :_resolve_dependency, :resolve_dependency def resolve_dependency(str) @resolved_dependencies[str] end private # Cache is immutable, any methods that try to change the runtime config # should bomb. def config=(config) raise RuntimeError, "can't modify immutable cached environment" end end end sprockets-3.7.0/lib/sprockets/sass_template.rb0000644000004100000410000000067212750322502021545 0ustar www-datawww-datarequire 'sprockets/sass_processor' module Sprockets # Deprecated class SassTemplate < SassProcessor def self.call(*args) Deprecation.new.warn "SassTemplate is deprecated please use SassProcessor instead" super end end # Deprecated class ScssTemplate < ScssProcessor def self.call(*args) Deprecation.new.warn "ScssTemplate is deprecated please use ScssProcessor instead" super end end end sprockets-3.7.0/lib/sprockets/uri_tar.rb0000644000004100000410000000524312750322502020345 0ustar www-datawww-datarequire 'sprockets/path_utils' module Sprockets # Internal: used to "expand" and "compress" values for storage class URITar attr_reader :scheme, :root, :path # Internal: Initialize object for compression or expansion # # uri - A String containing URI that may or may not contain the scheme # env - The current "environment" that assets are being loaded into. def initialize(uri, env) @root = env.root @env = env uri = uri.to_s if uri.include?("://".freeze) @scheme, _, @path = uri.partition("://".freeze) @scheme << "://".freeze else @scheme = "".freeze @path = uri end end # Internal: Converts full uri to a "compressed" uri # # If a uri is inside of an environment's root it will # be shortened to be a relative path. # # If a uri is outside of the environment's root the original # uri will be returned. # # Returns String def compress scheme + compressed_path end # Internal: Tells us if we are using an absolute path # # Nix* systems start with a `/` like /Users/schneems. # Windows systems start with a drive letter than colon and slash # like C:/Schneems. def absolute_path? PathUtils.absolute_path?(path) end # Internal: Convert a "compressed" uri to an absolute path # # If a uri is inside of the environment's root it will not # start with a slash for example: # # file://this/is/a/relative/path # # If a uri is outside the root, it will start with a slash: # # file:///This/is/an/absolute/path # # Returns String def expand if absolute_path? # Stored path was absolute, don't add root scheme + path else if scheme.empty? File.join(root, path) else # We always want to return an absolute uri, # make sure the path starts with a slash. scheme + File.join("/".freeze, root, path) end end end # Internal: Returns "compressed" path # # If the input uri is relative to the environment root # it will return a path relative to the environment root. # Otherwise an absolute path will be returned. # # Only path information is returned, and not scheme. # # Returns String def compressed_path # windows if !@root.start_with?("/".freeze) && path.start_with?("/".freeze) consistent_root = "/".freeze + @root else consistent_root = @root end if compressed_path = PathUtils.split_subpath(consistent_root, path) compressed_path else path end end end end sprockets-3.7.0/lib/sprockets/erb_template.rb0000644000004100000410000000035712750322502021344 0ustar www-datawww-datarequire 'sprockets/erb_processor' module Sprockets # Deprecated class ERBTemplate < ERBProcessor def call(*args) Deprecation.new.warn "ERBTemplate is deprecated please use ERBProcessor instead" super end end end sprockets-3.7.0/lib/sprockets/environment.rb0000644000004100000410000000207212750322502021241 0ustar www-datawww-datarequire 'sprockets/base' require 'sprockets/cache/memory_store' require 'sprockets/cached_environment' module Sprockets class Environment < Base # `Environment` should initialized with your application's root # directory. This should be the same as your Rails or Rack root. # # env = Environment.new(Rails.root) # def initialize(root = ".") initialize_configuration(Sprockets) self.root = root self.cache = Cache::MemoryStore.new yield self if block_given? end # Returns a cached version of the environment. # # All its file system calls are cached which makes `cached` much # faster. This behavior is ideal in production since the file # system only changes between deploys. def cached CachedEnvironment.new(self) end alias_method :index, :cached def find_asset(*args) cached.find_asset(*args) end def find_all_linked_assets(*args, &block) cached.find_all_linked_assets(*args, &block) end def load(*args) cached.load(*args) end end end sprockets-3.7.0/lib/sprockets/digest_utils.rb0000644000004100000410000001133412750322502021375 0ustar www-datawww-datarequire 'digest/md5' require 'digest/sha1' require 'digest/sha2' require 'set' module Sprockets # Internal: Hash functions and digest related utilities. Mixed into # Environment. module DigestUtils extend self # Internal: Default digest class. # # Returns a Digest::Base subclass. def digest_class Digest::SHA256 end # Internal: Maps digest bytesize to the digest class. DIGEST_SIZES = { 16 => Digest::MD5, 20 => Digest::SHA1, 32 => Digest::SHA256, 48 => Digest::SHA384, 64 => Digest::SHA512 } # Internal: Detect digest class hash algorithm for digest bytes. # # While not elegant, all the supported digests have a unique bytesize. # # Returns Digest::Base or nil. def detect_digest_class(bytes) DIGEST_SIZES[bytes.bytesize] end ADD_VALUE_TO_DIGEST = { String => ->(val, digest) { digest << val }, FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze }, TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze }, NilClass => ->(val, digest) { digest << 'NilClass'.freeze }, Symbol => ->(val, digest) { digest << 'Symbol'.freeze digest << val.to_s }, Fixnum => ->(val, digest) { digest << 'Fixnum'.freeze digest << val.to_s }, Bignum => ->(val, digest) { digest << 'Bignum'.freeze digest << val.to_s }, Array => ->(val, digest) { digest << 'Array'.freeze val.each do |element| ADD_VALUE_TO_DIGEST[element.class].call(element, digest) end }, Hash => ->(val, digest) { digest << 'Hash'.freeze val.sort.each do |array| ADD_VALUE_TO_DIGEST[Array].call(array, digest) end }, Set => ->(val, digest) { digest << 'Set'.freeze ADD_VALUE_TO_DIGEST[Array].call(val.to_a, digest) }, Encoding => ->(val, digest) { digest << 'Encoding'.freeze digest << val.name }, } ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) { raise TypeError, "couldn't digest #{ val }" } private_constant :ADD_VALUE_TO_DIGEST # Internal: Generate a hexdigest for a nested JSON serializable object. # # This is used for generating cache keys, so its pretty important its # wicked fast. Microbenchmarks away! # # obj - A JSON serializable object. # # Returns a String digest of the object. def digest(obj) digest = digest_class.new ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest) digest.digest end # Internal: Pack a binary digest to a hex encoded string. # # bin - String bytes # # Returns hex String. def pack_hexdigest(bin) bin.unpack('H*').first end # Internal: Unpack a hex encoded digest string into binary bytes. # # hex - String hex # # Returns binary String. def unpack_hexdigest(hex) [hex].pack('H*') end # Internal: Pack a binary digest to a base64 encoded string. # # bin - String bytes # # Returns base64 String. def pack_base64digest(bin) [bin].pack('m0') end # Internal: Pack a binary digest to a urlsafe base64 encoded string. # # bin - String bytes # # Returns urlsafe base64 String. def pack_urlsafe_base64digest(bin) str = pack_base64digest(bin) str.tr!('+/'.freeze, '-_'.freeze) str.tr!('='.freeze, ''.freeze) str end # Internal: Maps digest class to the CSP hash algorithm name. HASH_ALGORITHMS = { Digest::SHA256 => 'sha256'.freeze, Digest::SHA384 => 'sha384'.freeze, Digest::SHA512 => 'sha512'.freeze } # Public: Generate hash for use in the `integrity` attribute of an asset tag # as per the subresource integrity specification. # # digest - The String byte digest of the asset content. # # Returns a String or nil if hash algorithm is incompatible. def integrity_uri(digest) case digest when Digest::Base digest_class = digest.class digest = digest.digest when String digest_class = DIGEST_SIZES[digest.bytesize] else raise TypeError, "unknown digest: #{digest.inspect}" end if hash_name = HASH_ALGORITHMS[digest_class] "#{hash_name}-#{pack_base64digest(digest)}" end end # Public: Generate hash for use in the `integrity` attribute of an asset tag # as per the subresource integrity specification. # # digest - The String hexbyte digest of the asset content. # # Returns a String or nil if hash algorithm is incompatible. def hexdigest_integrity_uri(hexdigest) integrity_uri(unpack_hexdigest(hexdigest)) end end end sprockets-3.7.0/lib/sprockets/bower.rb0000644000004100000410000000317512750322502020020 0ustar www-datawww-datarequire 'json' module Sprockets module Bower # Internal: All supported bower.json files. # # https://github.com/bower/json/blob/0.4.0/lib/json.js#L7 POSSIBLE_BOWER_JSONS = ['bower.json', 'component.json', '.bower.json'] # Internal: Override resolve_alternates to install bower.json behavior. # # load_path - String environment path # logical_path - String path relative to base # # Returns candiate filenames. def resolve_alternates(load_path, logical_path) candidates, deps = super # bower.json can only be nested one level deep if !logical_path.index('/') dirname = File.join(load_path, logical_path) if directory?(dirname) filenames = POSSIBLE_BOWER_JSONS.map { |basename| File.join(dirname, basename) } filename = filenames.detect { |fn| self.file?(fn) } if filename deps << build_file_digest_uri(filename) read_bower_main(dirname, filename) do |path| candidates << path end end end end return candidates, deps end # Internal: Read bower.json's main directive. # # dirname - String path to component directory. # filename - String path to bower.json. # # Returns nothing. def read_bower_main(dirname, filename) bower = JSON.parse(File.read(filename), create_additions: false) case bower['main'] when String yield File.expand_path(bower['main'], dirname) when Array bower['main'].each do |name| yield File.expand_path(name, dirname) end end end end end sprockets-3.7.0/lib/sprockets/unloaded_asset.rb0000644000004100000410000001212412750322502021666 0ustar www-datawww-datarequire 'sprockets/uri_utils' require 'sprockets/uri_tar' module Sprockets # Internal: Used to parse and store the URI to an unloaded asset # Generates keys used to store and retrieve items from cache class UnloadedAsset # Internal: Initialize object for generating cache keys # # uri - A String containing complete URI to a file including scheme # and full path such as # "file:///Path/app/assets/js/app.js?type=application/javascript" # env - The current "environment" that assets are being loaded into. # We need it so we know where the +root+ (directory where sprockets # is being invoked). We also need for the `file_digest` method, # since, for some strange reason, memoization is provided by # overriding methods such as `stat` in the `PathUtils` module. # # Returns UnloadedAsset. def initialize(uri, env) @uri = uri.to_s @env = env @compressed_path = URITar.new(uri, env).compressed_path @params = nil # lazy loaded @filename = nil # lazy loaded end attr_reader :compressed_path, :uri # Internal: Full file path without schema # # This returns a string containing the full path to the asset without the schema. # Information is loaded lazilly since we want `UnloadedAsset.new(dep, self).relative_path` # to be fast. Calling this method the first time allocates an array and a hash. # # Example # # If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the # filename would be `"/Full/path/app/assets/javascripts/application.js"` # # Returns a String. def filename unless @filename load_file_params end @filename end # Internal: Hash of param values # # This information is generated and used internally by sprockets. # Known keys include `:type` which store the asset's mime-type, `:id` which is a fully resolved # digest for the asset (includes dependency digest as opposed to a digest of only file contents) # and `:pipeline`. Hash may be empty. # # Example # # If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript` # Then the params would be `{type: "application/javascript"}` # # Returns a Hash. def params unless @params load_file_params end @params end # Internal: Key of asset # # Used to retrieve an asset from the cache based on "compressed" path to asset. # A "compressed" path can either be relative to the root of the project or an # absolute path. # # Returns a String. def asset_key "asset-uri:#{compressed_path}" end # Public: Dependency History key # # Used to retrieve an array of "histories" each of which contain a set of stored dependencies # for a given asset path and filename digest. # # A dependency can refer to either an asset i.e. index.js # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors" # are used. # # For example a history array with one Set of dependencies may look like: # # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css", # "file-digest:///Full/path/app/assets/stylesheets/application.css", # "processors:type=text/css&file_type=text/css&pipeline=self", # "file-digest:///Full/path/app/assets/stylesheets"]] # # This method of asset lookup is used to ensure that none of the dependencies have been modified # since last lookup. If one of them has, the key will be different and a new entry must be stored. # # URI depndencies are later converted to "compressed" paths # # Returns a String. def dependency_history_key "asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }" end # Internal: Digest key # # Used to retrieve a string containing the "compressed" path to an asset based on # a digest. The digest is generated from dependencies stored via information stored in # the `dependency_history_key` after each of the "dependencies" is "resolved" for example # "environment-version" may be resolved to "environment-1.0-3.2.0" for version "3.2.0" of sprockets # # Returns a String. def digest_key(digest) "asset-uri-digest:#{compressed_path}:#{digest}" end # Internal: File digest key # # The digest for a given file won't change if the path and the stat time hasn't changed # We can save time by not re-computing this information and storing it in the cache # # Returns a String. def file_digest_key(stat) "file_digest:#{compressed_path}:#{stat}" end private # Internal: Parses uri into filename and params hash # # Returns Array with filename and params hash def load_file_params @filename, @params = URIUtils.parse_asset_uri(uri) end end end sprockets-3.7.0/lib/sprockets/coffee_script_template.rb0000644000004100000410000000063312750322502023404 0ustar www-datawww-datarequire 'sprockets/coffee_script_processor' module Sprockets # Deprecated module CoffeeScriptTemplate VERSION = CoffeeScriptProcessor::VERSION def self.cache_key CoffeeScriptProcessor.cache_key end def self.call(*args) Deprecation.new.warn "CoffeeScriptTemplate is deprecated please use CoffeeScriptProcessor instead" CoffeeScriptProcessor.call(*args) end end end sprockets-3.7.0/lib/sprockets/erb_processor.rb0000644000004100000410000000131312750322502021541 0ustar www-datawww-datarequire 'erb' module Sprockets class ERBProcessor # Public: Return singleton instance with default options. # # Returns ERBProcessor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def initialize(&block) @block = block end def call(input) engine = ::ERB.new(input[:data], nil, '<>') context = input[:environment].context_class.new(input) klass = (class << context; self; end) klass.class_eval(&@block) if @block engine.def_method(klass, :_evaluate_template, input[:filename]) data = context._evaluate_template context.metadata.merge(data: data) end end end sprockets-3.7.0/lib/sprockets/path_digest_utils.rb0000644000004100000410000000235412750322502022413 0ustar www-datawww-datarequire 'sprockets/digest_utils' require 'sprockets/path_utils' module Sprockets # Internal: Crossover of path and digest utilities functions. module PathDigestUtils include DigestUtils, PathUtils # Internal: Compute digest for file stat. # # path - String filename # stat - File::Stat # # Returns String digest bytes. def stat_digest(path, stat) if stat.directory? # If its a directive, digest the list of filenames digest_class.digest(self.entries(path).join(',')) elsif stat.file? # If its a file, digest the contents digest_class.file(path.to_s).digest else raise TypeError, "stat was not a directory or file: #{stat.ftype}" end end # Internal: Compute digest for path. # # path - String filename or directory path. # # Returns String digest bytes or nil. def file_digest(path) if stat = self.stat(path) self.stat_digest(path, stat) end end # Internal: Compute digest for a set of paths. # # paths - Array of filename or directory paths. # # Returns String digest bytes. def files_digest(paths) self.digest(paths.map { |path| self.file_digest(path) }) end end end sprockets-3.7.0/lib/sprockets/sass_cache_store.rb0000644000004100000410000000155112750322502022206 0ustar www-datawww-datarequire 'sass' module Sprockets class SassProcessor # Internal: Cache wrapper for Sprockets cache adapter. class CacheStore < ::Sass::CacheStores::Base VERSION = '1' def initialize(cache, version) @cache, @version = cache, "#{VERSION}/#{version}" end def _store(key, version, sha, contents) @cache.set("#{@version}/#{version}/#{key}/#{sha}", contents, true) end def _retrieve(key, version, sha) @cache.get("#{@version}/#{version}/#{key}/#{sha}", true) end def path_to(key) key end end end # Deprecated: Use Sprockets::SassProcessor::CacheStore instead. class SassCacheStore < SassProcessor::CacheStore def initialize(*args) Deprecation.new.warn "SassCacheStore is deprecated please use SassProcessor::CacheStore instead" super end end end sprockets-3.7.0/lib/sprockets/yui_compressor.rb0000644000004100000410000000245412750322502021763 0ustar www-datawww-datarequire 'sprockets/autoload' require 'sprockets/digest_utils' module Sprockets # Public: YUI compressor. # # To accept the default options # # environment.register_bundle_processor 'application/javascript', # Sprockets::YUICompressor # # Or to pass options to the YUI::JavaScriptCompressor class. # # environment.register_bundle_processor 'application/javascript', # Sprockets::YUICompressor.new(munge: true) # class YUICompressor VERSION = '1' # Public: Return singleton instance with default options. # # Returns YUICompressor object. def self.instance @instance ||= new end def self.call(input) instance.call(input) end def self.cache_key instance.cache_key end attr_reader :cache_key def initialize(options = {}) @options = options @cache_key = "#{self.class.name}:#{Autoload::YUI::Compressor::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze end def call(input) data = input[:data] case input[:content_type] when 'application/javascript' Autoload::YUI::JavaScriptCompressor.new(@options).compress(data) when 'text/css' Autoload::YUI::CssCompressor.new(@options).compress(data) else data end end end end sprockets-3.7.0/lib/sprockets/legacy.rb0000644000004100000410000002220112750322502020135 0ustar www-datawww-datarequire 'pathname' require 'sprockets/asset' require 'sprockets/base' require 'sprockets/cached_environment' require 'sprockets/context' require 'sprockets/manifest' require 'sprockets/resolve' module Sprockets autoload :CoffeeScriptTemplate, 'sprockets/coffee_script_template' autoload :EcoTemplate, 'sprockets/eco_template' autoload :EjsTemplate, 'sprockets/ejs_template' autoload :ERBTemplate, 'sprockets/erb_template' autoload :SassTemplate, 'sprockets/sass_template' autoload :ScssTemplate, 'sprockets/sass_template' # Deprecated Index = CachedEnvironment class Base include Resolve # Deprecated: Change default return type of resolve() to return 2.x # compatible plain filename String. 4.x will always return an Asset URI # and a set of file system dependencies that had to be read to compute the # result. # # 2.x # # resolve("foo.js") # # => "/path/to/app/javascripts/foo.js" # # 3.x # # resolve("foo.js") # # => "/path/to/app/javascripts/foo.js" # # resolve("foo.js", compat: true) # # => "/path/to/app/javascripts/foo.js" # # resolve("foo.js", compat: false) # # => [ # # "file:///path/to/app/javascripts/foo.js?type=application/javascript" # # # # # ] # # 4.x # # resolve("foo.js") # # => [ # # "file:///path/to/app/javascripts/foo.js?type=application/javascript" # # # # # ] # def resolve_with_compat(path, options = {}) options = options.dup if options.delete(:compat) { true } uri, _ = resolve_without_compat(path, options) if uri path, _ = parse_asset_uri(uri) path else nil end else resolve_without_compat(path, options) end end alias_method :resolve_without_compat, :resolve alias_method :resolve, :resolve_with_compat # Deprecated: Iterate over all logical paths with a matcher. # # Remove from 4.x. # # args - List of matcher objects. # # Returns Enumerator if no block is given. def each_logical_path(*args, &block) return to_enum(__method__, *args) unless block_given? filters = args.flatten.map { |arg| Manifest.compile_match_filter(arg) } logical_paths.each do |a, b| if filters.any? { |f| f.call(a, b) } if block.arity == 2 yield a, b else yield a end end end nil end # Deprecated: Enumerate over all logical paths in the environment. # # Returns an Enumerator of [logical_path, filename]. def logical_paths return to_enum(__method__) unless block_given? seen = Set.new paths.each do |load_path| stat_tree(load_path).each do |filename, stat| next unless stat.file? path = split_subpath(load_path, filename) path, mime_type, _, _ = parse_path_extnames(path) path = normalize_logical_path(path) path += mime_types[mime_type][:extensions].first if mime_type if !seen.include?(path) yield path, filename seen << path end end end nil end def cache_get(key) cache.get(key) end def cache_set(key, value) cache.set(key, value) end def normalize_logical_path(path) dirname, basename = File.split(path) path = dirname if basename == 'index' path end private # Deprecated: Seriously. def matches_filter(filters, logical_path, filename) return true if filters.empty? filters.any? do |filter| if filter.is_a?(Regexp) filter.match(logical_path) elsif filter.respond_to?(:call) if filter.arity == 1 filter.call(logical_path) else filter.call(logical_path, filename.to_s) end else File.fnmatch(filter.to_s, logical_path) end end end # URI.unescape is deprecated on 1.9. We need to use URI::Parser # if its available. if defined? URI::DEFAULT_PARSER def unescape(str) str = URI::DEFAULT_PARSER.unescape(str) str.force_encoding(Encoding.default_internal) if Encoding.default_internal str end else def unescape(str) URI.unescape(str) end end end class Asset # Deprecated: Use #filename instead. # # Returns Pathname. def pathname @pathname ||= Pathname.new(filename) end # Deprecated: Expand asset into an `Array` of parts. # # Appending all of an assets body parts together should give you # the asset's contents as a whole. # # This allows you to link to individual files for debugging # purposes. # # Use Asset#included instead. Keeping a full copy of the bundle's processed # assets in memory (and in cache) is expensive and redundant. The common use # case is to relink to the assets anyway. # # Returns Array of Assets. def to_a if metadata[:included] metadata[:included].map { |uri| @environment.load(uri) } else [self] end end # Deprecated: Get all required Assets. # # See Asset#to_a # # Returns Array of Assets. def dependencies to_a.reject { |a| a.filename.eql?(self.filename) } end # Deprecated: Returns Time of the last time the source was modified. # # Time resolution is normalized to the nearest second. # # Returns Time. def mtime Time.at(@mtime) end end class Context # Deprecated: Change default return type of resolve() to return 2.x # compatible plain filename String. 4.x will always return an Asset URI. # # 2.x # # resolve("foo.js") # # => "/path/to/app/javascripts/foo.js" # # 3.x # # resolve("foo.js") # # => "/path/to/app/javascripts/foo.js" # # resolve("foo.js", compat: true) # # => "/path/to/app/javascripts/foo.js" # # resolve("foo.js", compat: false) # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript" # # 4.x # # resolve("foo.js") # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript" # def resolve_with_compat(path, options = {}) options = options.dup # Support old :content_type option, prefer :accept going forward if type = options.delete(:content_type) type = self.content_type if type == :self options[:accept] ||= type end if options.delete(:compat) { true } uri = resolve_without_compat(path, options) path, _ = environment.parse_asset_uri(uri) path else resolve_without_compat(path, options) end end alias_method :resolve_without_compat, :resolve alias_method :resolve, :resolve_with_compat end class Manifest # Deprecated: Compile logical path matching filter into a proc that can be # passed to logical_paths.select(&proc). # # compile_match_filter(proc { |logical_path| # File.extname(logical_path) == '.js' # }) # # compile_match_filter(/application.js/) # # compile_match_filter("foo/*.js") # # Returns a Proc or raise a TypeError. def self.compile_match_filter(filter) # If the filter is already a proc, great nothing to do. if filter.respond_to?(:call) filter # If the filter is a regexp, wrap it in a proc that tests it against the # logical path. elsif filter.is_a?(Regexp) proc { |logical_path| filter.match(logical_path) } elsif filter.is_a?(String) # If its an absolute path, detect the matching full filename if PathUtils.absolute_path?(filter) proc { |logical_path, filename| filename == filter.to_s } else # Otherwise do an fnmatch against the logical path. proc { |logical_path| File.fnmatch(filter.to_s, logical_path) } end else raise TypeError, "unknown filter type: #{filter.inspect}" end end def self.simple_logical_path?(str) str.is_a?(String) && !PathUtils.absolute_path?(str) && str !~ /\*|\*\*|\?|\[|\]|\{|\}/ end def self.compute_alias_logical_path(path) dirname, basename = File.split(path) extname = File.extname(basename) if File.basename(basename, extname) == 'index' "#{dirname}#{extname}" else nil end end # Deprecated: Filter logical paths in environment. Useful for selecting what # files you want to compile. # # Returns an Enumerator. def filter_logical_paths(*args) filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) } environment.cached.logical_paths.select do |a, b| filters.any? { |f| f.call(a, b) } end end # Deprecated alias. alias_method :find_logical_paths, :filter_logical_paths end end sprockets-3.7.0/lib/sprockets/cache/0000755000004100000410000000000012750322502017412 5ustar www-datawww-datasprockets-3.7.0/lib/sprockets/cache/memory_store.rb0000644000004100000410000000316312750322502022466 0ustar www-datawww-datamodule Sprockets class Cache # Public: Basic in memory LRU cache. # # Assign the instance to the Environment#cache. # # environment.cache = Sprockets::Cache::MemoryStore.new(1000) # # See Also # # ActiveSupport::Cache::MemoryStore # class MemoryStore # Internal: Default key limit for store. DEFAULT_MAX_SIZE = 1000 # Public: Initialize the cache store. # # max_size - A Integer of the maximum number of keys the store will hold. # (default: 1000). def initialize(max_size = DEFAULT_MAX_SIZE) @max_size = max_size @cache = {} end # Public: Retrieve value from cache. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # # Returns Object or nil or the value is not set. def get(key) exists = true value = @cache.delete(key) { exists = false } if exists @cache[key] = value else nil end end # Public: Set a key and value in the cache. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # value - Object value. # # Returns Object value. def set(key, value) @cache.delete(key) @cache[key] = value @cache.shift if @cache.size > @max_size value end # Public: Pretty inspect # # Returns String. def inspect "#<#{self.class} size=#{@cache.size}/#{@max_size}>" end end end end sprockets-3.7.0/lib/sprockets/cache/file_store.rb0000644000004100000410000001161212750322502022073 0ustar www-datawww-datarequire 'fileutils' require 'logger' require 'sprockets/encoding_utils' require 'sprockets/path_utils' require 'zlib' module Sprockets class Cache # Public: A file system cache store that automatically cleans up old keys. # # Assign the instance to the Environment#cache. # # environment.cache = Sprockets::Cache::FileStore.new("/tmp") # # See Also # # ActiveSupport::Cache::FileStore # class FileStore # Internal: Default key limit for store. DEFAULT_MAX_SIZE = 25 * 1024 * 1024 # Internal: Default standard error fatal logger. # # Returns a Logger. def self.default_logger logger = Logger.new($stderr) logger.level = Logger::FATAL logger end # Public: Initialize the cache store. # # root - A String path to a directory to persist cached values to. # max_size - A Integer of the maximum number of keys the store will hold. # (default: 1000). def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger) @root = root @max_size = max_size @gc_size = max_size * 0.75 @logger = logger end # Public: Retrieve value from cache. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # # Returns Object or nil or the value is not set. def get(key) path = File.join(@root, "#{key}.cache") value = safe_open(path) do |f| begin EncodingUtils.unmarshaled_deflated(f.read, Zlib::MAX_WBITS) rescue Exception => e @logger.error do "#{self.class}[#{path}] could not be unmarshaled: " + "#{e.class}: #{e.message}" end nil end end if value FileUtils.touch(path) value end end # Public: Set a key and value in the cache. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # value - Object value. # # Returns Object value. def set(key, value) path = File.join(@root, "#{key}.cache") # Ensure directory exists FileUtils.mkdir_p File.dirname(path) # Check if cache exists before writing exists = File.exist?(path) # Serialize value marshaled = Marshal.dump(value) # Compress if larger than 4KB if marshaled.bytesize > 4 * 1024 deflater = Zlib::Deflate.new( Zlib::BEST_COMPRESSION, Zlib::MAX_WBITS, Zlib::MAX_MEM_LEVEL, Zlib::DEFAULT_STRATEGY ) deflater << marshaled raw = deflater.finish else raw = marshaled end # Write data PathUtils.atomic_write(path) do |f| f.write(raw) @size = size + f.size unless exists end # GC if necessary gc! if size > @max_size value end # Public: Pretty inspect # # Returns String. def inspect "#<#{self.class} size=#{size}/#{@max_size}>" end private # Internal: Get all cache files along with stats. # # Returns an Array of [String filename, File::Stat] pairs sorted by # mtime. def find_caches Dir.glob(File.join(@root, '**/*.cache')).reduce([]) { |stats, filename| stat = safe_stat(filename) # stat maybe nil if file was removed between the time we called # dir.glob and the next stat stats << [filename, stat] if stat stats }.sort_by { |_, stat| stat.mtime.to_i } end def size @size ||= compute_size(find_caches) end def compute_size(caches) caches.inject(0) { |sum, (_, stat)| sum + stat.size } end def safe_stat(fn) File.stat(fn) rescue Errno::ENOENT nil end def safe_open(path, &block) if File.exist?(path) File.open(path, 'rb', &block) end rescue Errno::ENOENT end def gc! start_time = Time.now caches = find_caches size = compute_size(caches) delete_caches, keep_caches = caches.partition { |filename, stat| deleted = size > @gc_size size -= stat.size deleted } return if delete_caches.empty? FileUtils.remove(delete_caches.map(&:first), force: true) @size = compute_size(keep_caches) @logger.warn do secs = Time.now.to_f - start_time.to_f "#{self.class}[#{@root}] garbage collected " + "#{delete_caches.size} files (#{(secs * 1000).to_i}ms)" end end end end end sprockets-3.7.0/lib/sprockets/cache/null_store.rb0000644000004100000410000000203512750322502022125 0ustar www-datawww-datamodule Sprockets class Cache # Public: A compatible cache store that doesn't store anything. Used by # default when no Environment#cache is configured. # # Assign the instance to the Environment#cache. # # environment.cache = Sprockets::Cache::NullStore.new # # See Also # # ActiveSupport::Cache::NullStore # class NullStore # Public: Simulate a cache miss. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # # Returns nil. def get(key) nil end # Public: Simulate setting a value in the cache. # # This API should not be used directly, but via the Cache wrapper API. # # key - String cache key. # value - Object value. # # Returns Object value. def set(key, value) value end # Public: Pretty inspect # # Returns String. def inspect "#<#{self.class}>" end end end end sprockets-3.7.0/lib/sprockets/base.rb0000644000004100000410000000561212750322502017612 0ustar www-datawww-datarequire 'sprockets/asset' require 'sprockets/bower' require 'sprockets/cache' require 'sprockets/configuration' require 'sprockets/digest_utils' require 'sprockets/errors' require 'sprockets/loader' require 'sprockets/path_digest_utils' require 'sprockets/path_dependency_utils' require 'sprockets/path_utils' require 'sprockets/resolve' require 'sprockets/server' require 'sprockets/loader' require 'sprockets/uri_tar' module Sprockets # `Base` class for `Environment` and `Cached`. class Base include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils include Configuration include Server include Resolve, Loader include Bower # Get persistent cache store attr_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)`. def cache=(cache) @cache = Cache.new(cache, logger) end # Return an `Cached`. Must be implemented by the subclass. def cached raise NotImplementedError end alias_method :index, :cached # Internal: Compute digest for path. # # path - String filename or directory path. # # Returns a String digest or nil. def file_digest(path) if stat = self.stat(path) # Caveat: Digests are cached by the path's current mtime. Its possible # for a files contents to have changed and its mtime to have been # negligently reset thus appearing as if the file hasn't changed on # disk. Also, the mtime is only read to the nearest second. It's # also possible the file was updated more than once in a given second. key = UnloadedAsset.new(path, self).file_digest_key(stat.mtime.to_i) cache.fetch(key) do self.stat_digest(path, stat) end end end # Find asset by logical path or expanded path. def find_asset(path, options = {}) uri, _ = resolve(path, options.merge(compat: false)) if uri load(uri) end end def find_all_linked_assets(path, options = {}) return to_enum(__method__, path, options) unless block_given? asset = find_asset(path, options) return unless asset yield asset stack = asset.links.to_a while uri = stack.shift yield asset = load(uri) stack = asset.links.to_a + stack end nil end # Preferred `find_asset` shorthand. # # environment['application.js'] # def [](*args) find_asset(*args) end # Pretty inspect def inspect "#<#{self.class}:0x#{object_id.to_s(16)} " + "root=#{root.to_s.inspect}, " + "paths=#{paths.inspect}>" end def compress_from_root(uri) URITar.new(uri, self).compress end def expand_from_root(uri) URITar.new(uri, self).expand end end end sprockets-3.7.0/lib/sprockets/uri_utils.rb0000644000004100000410000001216612750322502020721 0ustar www-datawww-datarequire 'uri' module Sprockets # Internal: Asset URI related parsing utilities. Mixed into Environment. # # An Asset URI identifies the compiled Asset result. It shares the file: # scheme and requires an absolute path. # # Other query parameters # # type - String output content type. Otherwise assumed from file extension. # This maybe different than the extension if the asset is transformed # from one content type to another. For an example .coffee -> .js. # # id - Unique fingerprint of the entire asset and all its metadata. Assets # will only have the same id if they serialize to an identical value. # # pipeline - String name of pipeline. # module URIUtils extend self # Internal: Parse URI into component parts. # # uri - String uri # # Returns Array of components. def split_uri(uri) URI.split(uri) end # Internal: Join URI component parts into String. # # Returns String. def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment) URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s end # Internal: Parse file: URI into component parts. # # uri - String uri # # Returns [scheme, host, path, query]. def split_file_uri(uri) scheme, _, host, _, _, path, _, query, _ = URI.split(uri) path = URI::Generic::DEFAULT_PARSER.unescape(path) path.force_encoding(Encoding::UTF_8) # Hack for parsing Windows "file:///C:/Users/IEUser" paths path.gsub!(/^\/([a-zA-Z]:)/, '\1'.freeze) [scheme, host, path, query] end # Internal: Join file: URI component parts into String. # # Returns String. def join_file_uri(scheme, host, path, query) str = "#{scheme}://" str << host if host path = "/#{path}" unless path.start_with?("/") str << URI::Generic::DEFAULT_PARSER.escape(path) str << "?#{query}" if query str end # Internal: Check if String is a valid Asset URI. # # str - Possible String asset URI. # # Returns true or false. def valid_asset_uri?(str) # Quick prefix check before attempting a full parse str.start_with?("file://") && parse_asset_uri(str) ? true : false rescue URI::InvalidURIError false end # Internal: Parse Asset URI. # # Examples # # parse("file:///tmp/js/application.coffee?type=application/javascript") # # => "/tmp/js/application.coffee", {type: "application/javascript"} # # uri - String asset URI # # Returns String path and Hash of symbolized parameters. def parse_asset_uri(uri) scheme, _, path, query = split_file_uri(uri) unless scheme == 'file' raise URI::InvalidURIError, "expected file:// scheme: #{uri}" end return path, parse_uri_query_params(query) end # Internal: Build Asset URI. # # Examples # # build("/tmp/js/application.coffee", type: "application/javascript") # # => "file:///tmp/js/application.coffee?type=application/javascript" # # path - String file path # params - Hash of optional parameters # # Returns String URI. def build_asset_uri(path, params = {}) join_file_uri("file", nil, path, encode_uri_query_params(params)) end # Internal: Parse file-digest dependency URI. # # Examples # # parse("file-digest:/tmp/js/application.js") # # => "/tmp/js/application.js" # # uri - String file-digest URI # # Returns String path. def parse_file_digest_uri(uri) scheme, _, path, _ = split_file_uri(uri) unless scheme == 'file-digest'.freeze raise URI::InvalidURIError, "expected file-digest scheme: #{uri}" end path end # Internal: Build file-digest dependency URI. # # Examples # # build("/tmp/js/application.js") # # => "file-digest:/tmp/js/application.js" # # path - String file path # # Returns String URI. def build_file_digest_uri(path) join_file_uri('file-digest'.freeze, nil, path, nil) end # Internal: Serialize hash of params into query string. # # params - Hash of params to serialize # # Returns String query or nil if empty. def encode_uri_query_params(params) query = [] params.each do |key, value| case value when Integer query << "#{key}=#{value}" when String, Symbol query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}" when TrueClass query << "#{key}" when FalseClass, NilClass else raise TypeError, "unexpected type: #{value.class}" end end "#{query.join('&')}" if query.any? end # Internal: Parse query string into hash of params # # query - String query string # # Return Hash of params. def parse_uri_query_params(query) query.to_s.split('&').reduce({}) do |h, p| k, v = p.split('=', 2) v = URI::Generic::DEFAULT_PARSER.unescape(v) if v h[k.to_sym] = v || true h end end end end sprockets-3.7.0/lib/sprockets/utils/0000755000004100000410000000000012750322502017507 5ustar www-datawww-datasprockets-3.7.0/lib/sprockets/utils/gzip.rb0000644000004100000410000000425112750322502021007 0ustar www-datawww-datamodule Sprockets module Utils class Gzip # Private: Generates a gzipped file based off of reference file. def initialize(asset) @content_type = asset.content_type @source = asset.source @charset = asset.charset end # What non-text mime types should we compress? This list comes from: # https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress COMPRESSABLE_MIME_TYPES = { "application/vnd.ms-fontobject" => true, "application/x-font-opentype" => true, "application/x-font-ttf" => true, "image/x-icon" => true, "image/svg+xml" => true } # Private: Returns whether or not an asset can be compressed. # # We want to compress any file that is text based. # You do not want to compress binary # files as they may already be compressed and running them # through a compression algorithm would make them larger. # # Return Boolean. def can_compress?(mime_types) # The "charset" of a mime type is present if the value is # encoded text. We can check this value to see if the asset # can be compressed. # # We also check against our list of non-text compressible mime types @charset || COMPRESSABLE_MIME_TYPES.include?(@content_type) end # Private: Opposite of `can_compress?`. # # Returns Boolean. def cannot_compress?(mime_types) !can_compress?(mime_types) end # Private: Generates a gzipped file based off of reference asset. # # Compresses the target asset's contents and puts it into a file with # the same name plus a `.gz` extension in the same folder as the original. # Does not modify the target asset. # # Returns nothing. def compress(target) mtime = PathUtils.stat(target).mtime PathUtils.atomic_write("#{target}.gz") do |f| gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) gz.mtime = mtime gz.write(@source) gz.close File.utime(mtime, mtime, f.path) end nil end end end end sprockets-3.7.0/lib/sprockets/ejs_template.rb0000644000004100000410000000053312750322502021351 0ustar www-datawww-datarequire 'sprockets/ejs_processor' module Sprockets # Deprecated module EjsTemplate VERSION = EjsProcessor::VERSION def self.cache_key EjsProcessor.cache_key end def self.call(*args) Deprecation.new.warn "EjsTemplate is deprecated please use EjsProcessor instead" EjsProcessor.call(*args) end end end sprockets-3.7.0/lib/sprockets/asset.rb0000644000004100000410000001143412750322502020016 0ustar www-datawww-datarequire 'fileutils' require 'sprockets/digest_utils' module Sprockets class Asset attr_reader :logical_path # Private: Intialize Asset wrapper from attributes Hash. # # Asset wrappers should not be initialized directly, only # Environment#find_asset should vend them. # # attributes - Hash of ivars # # Returns Asset. def initialize(environment, attributes = {}) @environment = environment @attributes = attributes @content_type = attributes[:content_type] @filename = attributes[:filename] @id = attributes[:id] @load_path = attributes[:load_path] @logical_path = attributes[:logical_path] @metadata = attributes[:metadata] @mtime = attributes[:mtime] @name = attributes[:name] @source = attributes[:source] @uri = attributes[:uri] end # Internal: Return all internal instance variables as a hash. # # Returns a Hash. def to_hash @attributes end # Public: Metadata accumulated from pipeline process. # # The API status of the keys is dependent on the pipeline processors # itself. So some values maybe considered public and others internal. # See the pipeline proccessor documentation itself. # # Returns Hash. attr_reader :metadata # Public: Returns String path of asset. attr_reader :filename # Internal: Unique asset object ID. # # Returns a String. attr_reader :id # Public: Internal URI to lookup asset by. # # NOT a publically accessible URL. # # Returns URI. attr_reader :uri # Public: Return logical path with digest spliced in. # # "foo/bar-37b51d194a7513e45b56f6524f2d51f2.js" # # Returns String. def digest_path logical_path.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" } end # Public: Returns String MIME type of asset. Returns nil if type is unknown. attr_reader :content_type # Public: Get all externally linked asset filenames from asset. # # All linked assets should be compiled anytime this asset is. # # Returns Set of String asset URIs. def links metadata[:links] || Set.new end # Public: Get all internally required assets that were concated into this # asset. # # Returns Array of String asset URIs. def included metadata[:included] end # Public: Return `String` of concatenated source. # # Returns String. def source if @source @source else # File is read everytime to avoid memory bloat of large binary files File.binread(filename) end end # Public: Alias for #source. # # Returns String. def to_s source end # Public: Get charset of source. # # Returns a String charset name or nil if binary. def charset metadata[:charset] end # Public: Returns Integer length of source. def length metadata[:length] end alias_method :bytesize, :length # Public: Returns String hexdigest of source. def hexdigest DigestUtils.pack_hexdigest(metadata[:digest]) end # Deprecated: Returns String hexdigest of source. # # In 4.x this will be changed to return a raw Digest byte String. alias_method :digest, :hexdigest # Pubic: ETag String of Asset. alias_method :etag, :hexdigest # Public: Returns String base64 digest of source. def base64digest DigestUtils.pack_base64digest(metadata[:digest]) end # Public: A "named information" URL for subresource integrity. def integrity DigestUtils.integrity_uri(metadata[:digest]) end # Public: Add enumerator to allow `Asset` instances to be used as Rack # compatible body objects. # # block # part - String body chunk # # Returns nothing. def each yield to_s end # Deprecated: Save asset to disk. # # filename - String target # # Returns nothing. def write_to(filename) FileUtils.mkdir_p File.dirname(filename) PathUtils.atomic_write(filename) do |f| f.write source end # Set mtime correctly File.utime(mtime, mtime, filename) nil end # Public: Pretty inspect # # Returns String. def inspect "#<#{self.class}:#{object_id.to_s(16)} #{uri.inspect}>" end # Public: Implements Object#hash so Assets can be used as a Hash key or # in a Set. # # Returns Integer hash of the id. def hash id.hash end # Public: Compare assets. # # Assets are equal if they share the same path and digest. # # Returns true or false. def eql?(other) self.class == other.class && self.id == other.id end alias_method :==, :eql? end end sprockets-3.7.0/lib/sprockets/server.rb0000644000004100000410000002017712750322502020211 0ustar www-datawww-datarequire 'time' require 'rack/utils' module Sprockets # `Server` is a concern mixed into `Environment` and # `CachedEnvironment` that provides a Rack compatible `call` # interface and url generation helpers. module Server # `call` implements the Rack 1.x specification which accepts an # `env` Hash and returns a three item tuple with the status code, # headers, and body. # # Mapping your environment at a url prefix will serve all assets # in the path. # # map "/assets" do # run Sprockets::Environment.new # end # # A request for `"/assets/foo/bar.js"` will search your # environment for `"foo/bar.js"`. def call(env) start_time = Time.now.to_f time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i } if env['REQUEST_METHOD'] != 'GET' return method_not_allowed_response end msg = "Served asset #{env['PATH_INFO']} -" # Extract the path from everything after the leading slash path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, '')) # Strip fingerprint if fingerprint = path_fingerprint(path) path = path.sub("-#{fingerprint}", '') end # URLs containing a `".."` are rejected for security reasons. if forbidden_request?(path) return forbidden_response end # Look up the asset. options = {} options[:pipeline] = :self if body_only?(env) asset = find_asset(path, options) # 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests. # 3.x/4.x prefers strong validation of fingerprint to body contents, but # 2.x just ignored it. if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self" fingerprint = nil end if fingerprint if_match = fingerprint elsif env['HTTP_IF_MATCH'] if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1] end if env['HTTP_IF_NONE_MATCH'] if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1] end if asset.nil? status = :not_found elsif fingerprint && asset.etag != fingerprint status = :not_found elsif if_match && asset.etag != if_match status = :precondition_failed elsif if_none_match && asset.etag == if_none_match status = :not_modified else status = :ok end case status when :ok logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)" ok_response(asset, env) when :not_modified logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)" not_modified_response(env, if_none_match) when :not_found logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)" not_found_response when :precondition_failed logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)" precondition_failed_response end rescue Exception => e logger.error "Error compiling asset #{path}:" logger.error "#{e.class.name}: #{e.message}" case File.extname(path) when ".js" # Re-throw JavaScript asset exceptions to the browser logger.info "#{msg} 500 Internal Server Error\n\n" return javascript_exception_response(e) when ".css" # Display CSS asset exceptions in the browser logger.info "#{msg} 500 Internal Server Error\n\n" return css_exception_response(e) else raise end end private def forbidden_request?(path) # Prevent access to files elsewhere on the file system # # http://example.org/assets/../../../etc/passwd # path.include?("..") || absolute_path?(path) end # Returns a 200 OK response tuple def ok_response(asset, env) [ 200, headers(env, asset, asset.length), asset ] end # Returns a 304 Not Modified response tuple def not_modified_response(env, etag) [ 304, cache_headers(env, etag), [] ] end # Returns a 403 Forbidden response tuple def forbidden_response [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ] end # Returns a 404 Not Found response tuple def not_found_response [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ] end def method_not_allowed_response [ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ] end def precondition_failed_response [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ] end # Returns a JavaScript response that re-throws a Ruby exception # in the browser def javascript_exception_response(exception) err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})" body = "throw Error(#{err.inspect})" [ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ] end # Returns a CSS response that hides all elements on the page and # displays the exception def css_exception_response(exception) message = "\n#{exception.class.name}: #{exception.message}" backtrace = "\n #{exception.backtrace.first}" body = <<-CSS html { padding: 18px 36px; } head { display: block; } body { margin: 0; padding: 0; } body > * { display: none !important; } head:after, body:before, body:after { display: block !important; } head:after { font-family: sans-serif; font-size: large; font-weight: bold; content: "Error compiling CSS asset"; } body:before, body:after { font-family: monospace; white-space: pre-wrap; } body:before { font-weight: bold; content: "#{escape_css_content(message)}"; } body:after { content: "#{escape_css_content(backtrace)}"; } CSS [ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ] end # Escape special characters for use inside a CSS content("...") string def escape_css_content(content) content. gsub('\\', '\\\\005c '). gsub("\n", '\\\\000a '). gsub('"', '\\\\0022 '). gsub('/', '\\\\002f ') end # Test if `?body=1` or `body=true` query param is set def body_only?(env) env["QUERY_STRING"].to_s =~ /body=(1|t)/ end def cache_headers(env, etag) headers = {} # Set caching headers headers["Cache-Control"] = "public" headers["ETag"] = %("#{etag}") # If the request url contains a fingerprint, set a long # expires on the response if path_fingerprint(env["PATH_INFO"]) headers["Cache-Control"] << ", max-age=31536000" # Otherwise set `must-revalidate` since the asset could be modified. else headers["Cache-Control"] << ", must-revalidate" headers["Vary"] = "Accept-Encoding" end headers end def headers(env, asset, length) headers = {} # Set content length header headers["Content-Length"] = length.to_s # Set content type header if type = asset.content_type # Set charset param for text/* mime types if type.start_with?("text/") && asset.charset type += "; charset=#{asset.charset}" end headers["Content-Type"] = type end headers.merge(cache_headers(env, asset.etag)) end # Gets ETag fingerprint. # # "foo-0aa2105d29558f3eb790d411d7d8fb66.js" # # => "0aa2105d29558f3eb790d411d7d8fb66" # def path_fingerprint(path) path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1] end end end sprockets-3.7.0/lib/sprockets/processor_utils.rb0000644000004100000410000001325612750322502022142 0ustar www-datawww-datarequire 'set' module Sprockets # Functional utilities for dealing with Processor functions. # # A Processor is a general function that my modify or transform an asset as # part of the pipeline. CoffeeScript to JavaScript conversion, Minification # or Concatenation are all implemented as seperate Processor steps. # # Processors maybe any object that responds to call. So procs or a class that # defines a self.call method. # # For ergonomics, processors may return a number of shorthand values. # Unfortunately, this means that processors can not compose via ordinary # function composition. The composition helpers here can help. module ProcessorUtils extend self # Public: Compose processors in right to left order. # # processors - Array of processors callables # # Returns a composed Proc. def compose_processors(*processors) context = self if processors.length == 1 obj = method(:call_processor).to_proc.curry[processors.first] else obj = method(:call_processors).to_proc.curry[processors] end metaclass = (class << obj; self; end) metaclass.send(:define_method, :cache_key) do context.processors_cache_keys(processors) end obj end # Public: Invoke list of processors in right to left order. # # The right to left order processing mirrors standard function composition. # Think about: # # bundle.call(uglify.call(coffee.call(input))) # # processors - Array of processor callables # input - Hash of input data to pass to each processor # # Returns a Hash with :data and other processor metadata key/values. def call_processors(processors, input) data = input[:data] || "" metadata = (input[:metadata] || {}).dup processors.reverse_each do |processor| result = call_processor(processor, input.merge(data: data, metadata: metadata)) data = result.delete(:data) metadata.merge!(result) end metadata.merge(data: data) end # Public: Invoke processor. # # processor - Processor callables # input - Hash of input data to pass to processor # # Returns a Hash with :data and other processor metadata key/values. def call_processor(processor, input) metadata = (input[:metadata] || {}).dup metadata[:data] = input[:data] case result = processor.call({data: "", metadata: {}}.merge(input)) when NilClass metadata when Hash metadata.merge(result) when String metadata.merge(data: result) else raise TypeError, "invalid processor return type: #{result.class}" end end # Internal: Get processor defined cached key. # # processor - Processor function # # Returns JSON serializable key or nil. def processor_cache_key(processor) processor.cache_key if processor.respond_to?(:cache_key) end # Internal: Get combined cache keys for set of processors. # # processors - Array of processor functions # # Returns Array of JSON serializable keys. def processors_cache_keys(processors) processors.map { |processor| processor_cache_key(processor) } end # Internal: Set of all "simple" value types allowed to be returned in # processor metadata. VALID_METADATA_VALUE_TYPES = Set.new([ String, Symbol, Fixnum, Bignum, TrueClass, FalseClass, NilClass ]).freeze # Internal: Set of all nested compound metadata types that can nest values. VALID_METADATA_COMPOUND_TYPES = Set.new([ Array, Hash, Set ]).freeze # Internal: Hash of all "simple" value types allowed to be returned in # processor metadata. VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES.each_with_object({}) do |type, hash| hash[type] = true end.freeze # Internal: Hash of all nested compound metadata types that can nest values. VALID_METADATA_COMPOUND_TYPES_HASH = VALID_METADATA_COMPOUND_TYPES.each_with_object({}) do |type, hash| hash[type] = true end.freeze # Internal: Set of all allowed metadata types. VALID_METADATA_TYPES = (VALID_METADATA_VALUE_TYPES + VALID_METADATA_COMPOUND_TYPES).freeze # Internal: Validate returned result of calling a processor pipeline and # raise a friendly user error message. # # result - Metadata Hash returned from call_processors # # Returns result or raises a TypeError. def validate_processor_result!(result) if !result.instance_of?(Hash) raise TypeError, "processor metadata result was expected to be a Hash, but was #{result.class}" end if !result[:data].instance_of?(String) raise TypeError, "processor :data was expected to be a String, but as #{result[:data].class}" end result.each do |key, value| if !key.instance_of?(Symbol) raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol" end if !valid_processor_metadata_value?(value) raise TypeError, "processor metadata[:#{key}] returned a complex type: #{value.inspect}\n" + "Only #{VALID_METADATA_TYPES.to_a.join(", ")} maybe used." end end result end # Internal: Validate object is in validate metadata whitelist. # # value - Any Object # # Returns true if class is in whitelist otherwise false. def valid_processor_metadata_value?(value) if VALID_METADATA_VALUE_TYPES_HASH[value.class] true elsif VALID_METADATA_COMPOUND_TYPES_HASH[value.class] value.all? { |v| valid_processor_metadata_value?(v) } else false end end end end sprockets-3.7.0/lib/sprockets/sass_functions.rb0000644000004100000410000000013212750322502021731 0ustar www-datawww-data# Deprecated: Require sprockets/sass_processor instead require 'sprockets/sass_processor' sprockets-3.7.0/lib/sprockets/engines.rb0000644000004100000410000000604712750322502020333 0ustar www-datawww-datarequire 'sprockets/legacy_tilt_processor' require 'sprockets/utils' module Sprockets # `Engines` provides a global and `Environment` instance registry. # # An engine is a type of processor that is bound to a filename # extension. `application.js.coffee` indicates that the # `CoffeeScriptProcessor` engine will be ran on the file. # # Extensions can be stacked and will be evaulated from right to # left. `application.js.coffee.erb` will first run `ERBProcessor` # then `CoffeeScriptProcessor`. # # All `Engine`s must follow the `Template` interface. It is # recommended to subclass `Template`. # # Its recommended that you register engine changes on your local # `Environment` instance. # # environment.register_engine '.foo', FooProcessor # # The global registry is exposed for plugins to register themselves. # # Sprockets.register_engine '.sass', SassProcessor # module Engines include Utils # Returns a `Hash` of `Engine`s registered on the `Environment`. # If an `ext` argument is supplied, the `Engine` associated with # that extension will be returned. # # environment.engines # # => {".coffee" => CoffeeScriptProcessor, ".sass" => SassProcessor, ...} # def engines config[:engines] end # Internal: Returns a `Hash` of engine extensions to mime types. # # # => { '.coffee' => 'application/javascript' } def engine_mime_types config[:engine_mime_types] end # Registers a new Engine `klass` for `ext`. If the `ext` already # has an engine registered, it will be overridden. # # environment.register_engine '.coffee', CoffeeScriptProcessor # def register_engine(ext, klass, options = {}) unless options[:silence_deprecation] msg = <<-MSG Sprockets method `register_engine` is deprecated. Please register a mime type using `register_mime_type` then use `register_compressor` or `register_transformer`. https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors MSG Deprecation.new([caller.first]).warn(msg) end ext = Sprockets::Utils.normalize_extension(ext) self.computed_config = {} if klass.respond_to?(:call) processor = klass self.config = hash_reassoc(config, :engines) do |engines| engines.merge(ext => klass) end if options[:mime_type] self.config = hash_reassoc(config, :engine_mime_types) do |mime_types| mime_types.merge(ext.to_s => options[:mime_type]) end end else processor = LegacyTiltProcessor.new(klass) self.config = hash_reassoc(config, :engines) do |engines| engines.merge(ext => processor) end if klass.respond_to?(:default_mime_type) && klass.default_mime_type self.config = hash_reassoc(config, :engine_mime_types) do |mime_types| mime_types.merge(ext.to_s => klass.default_mime_type) end end end end end end sprockets-3.7.0/lib/sprockets/resolve.rb0000644000004100000410000001543412750322502020362 0ustar www-datawww-datarequire 'set' require 'sprockets/http_utils' require 'sprockets/path_dependency_utils' require 'sprockets/uri_utils' module Sprockets module Resolve include HTTPUtils, PathDependencyUtils, URIUtils # Public: Find Asset URI for given a logical path by searching the # environment's load paths. # # resolve("application.js") # # => "file:///path/to/app/javascripts/application.js?type=application/javascript" # # An accept content type can be given if the logical path doesn't have a # format extension. # # resolve("application", accept: "application/javascript") # # => "file:///path/to/app/javascripts/application.coffee?type=application/javascript" # # The String Asset URI is returned or nil if no results are found. def resolve(path, options = {}) path = path.to_s paths = options[:load_paths] || config[:paths] accept = options[:accept] if valid_asset_uri?(path) uri, deps = resolve_asset_uri(path) elsif absolute_path?(path) filename, type, deps = resolve_absolute_path(paths, path, accept) elsif relative_path?(path) filename, type, pipeline, deps = resolve_relative_path(paths, path, options[:base_path], accept) else filename, type, pipeline, deps = resolve_logical_path(paths, path, accept) end if filename params = {} params[:type] = type if type params[:pipeline] = pipeline if pipeline params[:pipeline] = options[:pipeline] if options[:pipeline] uri = build_asset_uri(filename, params) end return uri, deps end # Public: Same as resolve() but raises a FileNotFound exception instead of # nil if no assets are found. def resolve!(path, options = {}) uri, deps = resolve(path, options.merge(compat: false)) unless uri message = "couldn't find file '#{path}'" if relative_path?(path) && options[:base_path] load_path, _ = paths_split(config[:paths], options[:base_path]) message << " under '#{load_path}'" end message << " with type '#{options[:accept]}'" if options[:accept] message << "\nChecked in these paths: \n #{ config[:paths].join("\n ") }" raise FileNotFound, message end return uri, deps end protected def resolve_asset_uri(uri) filename, _ = parse_asset_uri(uri) return uri, Set.new([build_file_digest_uri(filename)]) end def resolve_absolute_path(paths, filename, accept) deps = Set.new filename = File.expand_path(filename) # Ensure path is under load paths return nil, nil, deps unless paths_split(paths, filename) _, mime_type, _, _ = parse_path_extnames(filename) type = resolve_transform_type(mime_type, accept) return nil, nil, deps if accept && !type return nil, nil, deps unless file?(filename) deps << build_file_digest_uri(filename) return filename, type, deps end def resolve_relative_path(paths, path, dirname, accept) filename = File.expand_path(path, dirname) load_path, _ = paths_split(paths, dirname) if load_path && logical_path = split_subpath(load_path, filename) resolve_logical_path([load_path], logical_path, accept) else return nil, nil, Set.new end end def resolve_logical_path(paths, logical_path, accept) logical_name, mime_type, _, pipeline = parse_path_extnames(logical_path) parsed_accept = parse_accept_options(mime_type, accept) transformed_accepts = expand_transform_accepts(parsed_accept) filename, mime_type, deps = resolve_under_paths(paths, logical_name, transformed_accepts) if filename deps << build_file_digest_uri(filename) type = resolve_transform_type(mime_type, parsed_accept) return filename, type, pipeline, deps else return nil, nil, nil, deps end end def resolve_under_paths(paths, logical_name, accepts) all_deps = Set.new return nil, nil, all_deps if accepts.empty? logical_basename = File.basename(logical_name) paths.each do |load_path| candidates, deps = path_matches(load_path, logical_name, logical_basename) all_deps.merge(deps) candidate = find_best_q_match(accepts, candidates) do |c, matcher| match_mime_type?(c[1] || "application/octet-stream", matcher) end return candidate + [all_deps] if candidate end return nil, nil, all_deps end def parse_accept_options(mime_type, types) accepts = [] accepts += parse_q_values(types) if types if mime_type if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) } accepts = [[mime_type, 1.0]] else return [] end end if accepts.empty? accepts << ['*/*', 1.0] end accepts end def path_matches(load_path, logical_name, logical_basename) dirname = File.dirname(File.join(load_path, logical_name)) candidates = dirname_matches(dirname, logical_basename) deps = file_digest_dependency_set(dirname) result = resolve_alternates(load_path, logical_name) result[0].each do |fn| candidates << [fn, parse_path_extnames(fn)[1]] end deps.merge(result[1]) dirname = File.join(load_path, logical_name) if directory? dirname result = dirname_matches(dirname, "index") candidates.concat(result) end deps.merge(file_digest_dependency_set(dirname)) return candidates.select { |fn, _| file?(fn) }, deps end def dirname_matches(dirname, basename) candidates = [] entries = self.entries(dirname) entries.each do |entry| next unless File.basename(entry).start_with?(basename) name, type, _, _ = parse_path_extnames(entry) if basename == name candidates << [File.join(dirname, entry), type] end end candidates end def resolve_alternates(load_path, logical_name) return [], Set.new end # Internal: Returns the name, mime type and `Array` of engine extensions. # # "foo.js.coffee.erb" # # => ["foo", "application/javascript", [".coffee", ".erb"]] # def parse_path_extnames(path) engines = [] extname, value = match_path_extname(path, extname_map) if extname path = path.chomp(extname) type, engines, pipeline = value.values_at(:type, :engines, :pipeline) end return path, type, engines, pipeline end end end sprockets-3.7.0/lib/sprockets/legacy_proc_processor.rb0000644000004100000410000000131412750322502023261 0ustar www-datawww-datarequire 'delegate' module Sprockets # Deprecated: Wraps legacy process Procs with new processor call signature. # # Will be removed in Sprockets 4.x. # # LegacyProcProcessor.new(:compress, # proc { |context, data| data.gsub(...) }) # class LegacyProcProcessor < Delegator def initialize(name, proc) @name = name @proc = proc end def __getobj__ @proc end def name "Sprockets::LegacyProcProcessor (#{@name})" end def to_s name end def call(input) context = input[:environment].context_class.new(input) data = @proc.call(context, input[:data]) context.metadata.merge(data: data.to_str) end end end sprockets-3.7.0/lib/sprockets/autoload.rb0000644000004100000410000000061012750322502020501 0ustar www-datawww-datamodule Sprockets module Autoload autoload :Closure, 'sprockets/autoload/closure' autoload :CoffeeScript, 'sprockets/autoload/coffee_script' autoload :Eco, 'sprockets/autoload/eco' autoload :EJS, 'sprockets/autoload/ejs' autoload :Sass, 'sprockets/autoload/sass' autoload :Uglifier, 'sprockets/autoload/uglifier' autoload :YUI, 'sprockets/autoload/yui' end end sprockets-3.7.0/lib/sprockets.rb0000644000004100000410000001500412750322502016674 0ustar www-datawww-data# encoding: utf-8 require 'sprockets/version' require 'sprockets/cache' require 'sprockets/environment' require 'sprockets/errors' require 'sprockets/manifest' require 'sprockets/deprecation' module Sprockets require 'sprockets/processor_utils' extend ProcessorUtils # Extend Sprockets module to provide global registry require 'sprockets/configuration' require 'sprockets/context' require 'digest/sha2' extend Configuration self.config = { bundle_processors: Hash.new { |h, k| [].freeze }.freeze, bundle_reducers: Hash.new { |h, k| {}.freeze }.freeze, compressors: Hash.new { |h, k| {}.freeze }.freeze, dependencies: Set.new.freeze, dependency_resolvers: {}.freeze, digest_class: Digest::SHA256, engine_mime_types: {}.freeze, engines: {}.freeze, mime_exts: {}.freeze, mime_types: {}.freeze, paths: [].freeze, pipelines: {}.freeze, postprocessors: Hash.new { |h, k| [].freeze }.freeze, preprocessors: Hash.new { |h, k| [].freeze }.freeze, registered_transformers: Hash.new { |h, k| {}.freeze }.freeze, root: File.expand_path('..', __FILE__).freeze, transformers: Hash.new { |h, k| {}.freeze }.freeze, version: "", gzip_enabled: true }.freeze self.computed_config = {} @context_class = Context require 'logger' @logger = Logger.new($stderr) @logger.level = Logger::FATAL # Common asset text types register_mime_type 'application/javascript', extensions: ['.js'], charset: :unicode register_mime_type 'application/json', extensions: ['.json'], charset: :unicode register_mime_type 'application/xml', extensions: ['.xml'] register_mime_type 'text/css', extensions: ['.css'], charset: :css register_mime_type 'text/html', extensions: ['.html', '.htm'], charset: :html register_mime_type 'text/plain', extensions: ['.txt', '.text'] register_mime_type 'text/yaml', extensions: ['.yml', '.yaml'], charset: :unicode # Common image types register_mime_type 'image/x-icon', extensions: ['.ico'] register_mime_type 'image/bmp', extensions: ['.bmp'] register_mime_type 'image/gif', extensions: ['.gif'] register_mime_type 'image/webp', extensions: ['.webp'] register_mime_type 'image/png', extensions: ['.png'] register_mime_type 'image/jpeg', extensions: ['.jpg', '.jpeg'] register_mime_type 'image/tiff', extensions: ['.tiff', '.tif'] register_mime_type 'image/svg+xml', extensions: ['.svg'] # Common audio/video types register_mime_type 'video/webm', extensions: ['.webm'] register_mime_type 'audio/basic', extensions: ['.snd', '.au'] register_mime_type 'audio/aiff', extensions: ['.aiff'] register_mime_type 'audio/mpeg', extensions: ['.mp3', '.mp2', '.m2a', '.m3a'] register_mime_type 'application/ogg', extensions: ['.ogx'] register_mime_type 'audio/midi', extensions: ['.midi', '.mid'] register_mime_type 'video/avi', extensions: ['.avi'] register_mime_type 'audio/wave', extensions: ['.wav', '.wave'] register_mime_type 'video/mp4', extensions: ['.mp4', '.m4v'] # Common font types register_mime_type 'application/vnd.ms-fontobject', extensions: ['.eot'] register_mime_type 'application/x-font-opentype', extensions: ['.otf'] register_mime_type 'application/x-font-ttf', extensions: ['.ttf'] register_mime_type 'application/font-woff', extensions: ['.woff'] register_pipeline :source do |env| [] end register_pipeline :self do |env, type, file_type, engine_extnames| env.self_processors_for(type, file_type, engine_extnames) end register_pipeline :default do |env, type, file_type, engine_extnames| env.default_processors_for(type, file_type, engine_extnames) end require 'sprockets/directive_processor' register_preprocessor 'text/css', DirectiveProcessor.new( comments: ["//", ["/*", "*/"]] ) register_preprocessor 'application/javascript', DirectiveProcessor.new( comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]] ) require 'sprockets/bundle' register_bundle_processor 'application/javascript', Bundle register_bundle_processor 'text/css', Bundle register_bundle_metadata_reducer '*/*', :data, proc { "" }, :concat register_bundle_metadata_reducer 'application/javascript', :data, proc { "" }, Utils.method(:concat_javascript_sources) register_bundle_metadata_reducer '*/*', :links, :+ require 'sprockets/closure_compressor' require 'sprockets/sass_compressor' require 'sprockets/uglifier_compressor' require 'sprockets/yui_compressor' register_compressor 'text/css', :sass, SassCompressor register_compressor 'text/css', :scss, SassCompressor register_compressor 'text/css', :yui, YUICompressor register_compressor 'application/javascript', :closure, ClosureCompressor register_compressor 'application/javascript', :uglifier, UglifierCompressor register_compressor 'application/javascript', :uglify, UglifierCompressor register_compressor 'application/javascript', :yui, YUICompressor # Mmm, CoffeeScript require 'sprockets/coffee_script_processor' Deprecation.silence do register_engine '.coffee', CoffeeScriptProcessor, mime_type: 'application/javascript', silence_deprecation: true end # JST engines require 'sprockets/eco_processor' require 'sprockets/ejs_processor' require 'sprockets/jst_processor' Deprecation.silence do register_engine '.jst', JstProcessor, mime_type: 'application/javascript', silence_deprecation: true register_engine '.eco', EcoProcessor, mime_type: 'application/javascript', silence_deprecation: true register_engine '.ejs', EjsProcessor, mime_type: 'application/javascript', silence_deprecation: true end # CSS engines require 'sprockets/sass_processor' Deprecation.silence do register_engine '.sass', SassProcessor, mime_type: 'text/css', silence_deprecation: true register_engine '.scss', ScssProcessor, mime_type: 'text/css', silence_deprecation: true end register_bundle_metadata_reducer 'text/css', :sass_dependencies, Set.new, :+ # Other require 'sprockets/erb_processor' register_engine '.erb', ERBProcessor, mime_type: 'text/plain', silence_deprecation: true register_dependency_resolver 'environment-version' do |env| env.version end register_dependency_resolver 'environment-paths' do |env| env.paths.map {|path| env.compress_from_root(path) } end register_dependency_resolver 'file-digest' do |env, str| env.file_digest(env.parse_file_digest_uri(str)) end register_dependency_resolver 'processors' do |env, str| env.resolve_processors_cache_key_uri(str) end depend_on 'environment-version' depend_on 'environment-paths' end require 'sprockets/legacy' sprockets-3.7.0/sprockets.gemspec0000644000004100000410000001360212750322502017150 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "sprockets" s.version = "3.7.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Sam Stephenson", "Joshua Peek"] s.date = "2016-07-21" s.description = "Sprockets is a Rack-based asset packaging system that concatenates and serves JavaScript, CoffeeScript, CSS, LESS, Sass, and SCSS." s.email = ["sstephenson@gmail.com", "josh@joshpeek.com"] s.executables = ["sprockets"] s.files = ["CHANGELOG.md", "LICENSE", "README.md", "bin/sprockets", "lib/rake/sprocketstask.rb", "lib/sprockets.rb", "lib/sprockets/asset.rb", "lib/sprockets/autoload.rb", "lib/sprockets/autoload/closure.rb", "lib/sprockets/autoload/coffee_script.rb", "lib/sprockets/autoload/eco.rb", "lib/sprockets/autoload/ejs.rb", "lib/sprockets/autoload/sass.rb", "lib/sprockets/autoload/uglifier.rb", "lib/sprockets/autoload/yui.rb", "lib/sprockets/base.rb", "lib/sprockets/bower.rb", "lib/sprockets/bundle.rb", "lib/sprockets/cache.rb", "lib/sprockets/cache/file_store.rb", "lib/sprockets/cache/memory_store.rb", "lib/sprockets/cache/null_store.rb", "lib/sprockets/cached_environment.rb", "lib/sprockets/closure_compressor.rb", "lib/sprockets/coffee_script_processor.rb", "lib/sprockets/coffee_script_template.rb", "lib/sprockets/compressing.rb", "lib/sprockets/configuration.rb", "lib/sprockets/context.rb", "lib/sprockets/dependencies.rb", "lib/sprockets/deprecation.rb", "lib/sprockets/digest_utils.rb", "lib/sprockets/directive_processor.rb", "lib/sprockets/eco_processor.rb", "lib/sprockets/eco_template.rb", "lib/sprockets/ejs_processor.rb", "lib/sprockets/ejs_template.rb", "lib/sprockets/encoding_utils.rb", "lib/sprockets/engines.rb", "lib/sprockets/environment.rb", "lib/sprockets/erb_processor.rb", "lib/sprockets/erb_template.rb", "lib/sprockets/errors.rb", "lib/sprockets/file_reader.rb", "lib/sprockets/http_utils.rb", "lib/sprockets/jst_processor.rb", "lib/sprockets/legacy.rb", "lib/sprockets/legacy_proc_processor.rb", "lib/sprockets/legacy_tilt_processor.rb", "lib/sprockets/loader.rb", "lib/sprockets/manifest.rb", "lib/sprockets/manifest_utils.rb", "lib/sprockets/mime.rb", "lib/sprockets/path_dependency_utils.rb", "lib/sprockets/path_digest_utils.rb", "lib/sprockets/path_utils.rb", "lib/sprockets/paths.rb", "lib/sprockets/processing.rb", "lib/sprockets/processor_utils.rb", "lib/sprockets/resolve.rb", "lib/sprockets/sass_cache_store.rb", "lib/sprockets/sass_compressor.rb", "lib/sprockets/sass_functions.rb", "lib/sprockets/sass_importer.rb", "lib/sprockets/sass_processor.rb", "lib/sprockets/sass_template.rb", "lib/sprockets/server.rb", "lib/sprockets/transformers.rb", "lib/sprockets/uglifier_compressor.rb", "lib/sprockets/unloaded_asset.rb", "lib/sprockets/uri_tar.rb", "lib/sprockets/uri_utils.rb", "lib/sprockets/utils.rb", "lib/sprockets/utils/gzip.rb", "lib/sprockets/version.rb", "lib/sprockets/yui_compressor.rb"] s.homepage = "https://github.com/rails/sprockets" s.licenses = ["MIT"] s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new(">= 1.9.3") s.rubyforge_project = "sprockets" s.rubygems_version = "1.8.23" s.summary = "Rack-based asset packaging system" if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 1.1"]) s.add_development_dependency(%q, ["~> 2.2"]) s.add_development_dependency(%q, ["~> 1.6"]) s.add_runtime_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 2.0"]) s.add_development_dependency(%q, ["~> 5.0"]) s.add_development_dependency(%q, ["~> 1.3"]) s.add_runtime_dependency(%q, ["< 3", "> 1"]) s.add_development_dependency(%q, ["~> 0.6"]) s.add_development_dependency(%q, ["~> 10.0"]) s.add_development_dependency(%q, ["~> 3.1"]) s.add_development_dependency(%q, ["~> 2.3"]) s.add_development_dependency(%q, ["~> 0.12"]) else s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 2.2"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 5.0"]) s.add_dependency(%q, ["~> 1.3"]) s.add_dependency(%q, ["< 3", "> 1"]) s.add_dependency(%q, ["~> 0.6"]) s.add_dependency(%q, ["~> 10.0"]) s.add_dependency(%q, ["~> 3.1"]) s.add_dependency(%q, ["~> 2.3"]) s.add_dependency(%q, ["~> 0.12"]) end else s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 2.2"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 5.0"]) s.add_dependency(%q, ["~> 1.3"]) s.add_dependency(%q, ["< 3", "> 1"]) s.add_dependency(%q, ["~> 0.6"]) s.add_dependency(%q, ["~> 10.0"]) s.add_dependency(%q, ["~> 3.1"]) s.add_dependency(%q, ["~> 2.3"]) s.add_dependency(%q, ["~> 0.12"]) end end sprockets-3.7.0/LICENSE0000644000004100000410000000210112750322502014563 0ustar www-datawww-dataCopyright (c) 2014 Sam Stephenson Copyright (c) 2014 Joshua Peek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sprockets-3.7.0/CHANGELOG.md0000644000004100000410000002062212750322502015377 0ustar www-datawww-data** 3.7.0** (July 21, 2016) * Deprecated interfaces now emit deprecation warnings #345 **3.6.3** (July 1, 2016) * Faster asset lookup in large directories #336 * Faster PathUtils.match_path_extname https://github.com/rails/sprockets/commit/697269cf81e5261fdd7072e32bd489403027fd7e * Fixed uglifier comment stripping #326 * Error messages now show load path info #313 **3.6.2** (June 21, 2016) * More performance improvements. **3.6.1** (June 17, 2016) * Some performance improvements. **3.6.0** (April 6, 2016) * Add `Manifest#find_sources` to return the source of the compiled assets. * Fix the list of compressable mime types. * Improve performance of the `FileStore` cache. **3.5.2** (December 8, 2015) * Fix JRuby bug with concurrent-ruby. * Fix disabling gzip generation in cached environments. **3.5.1** (December 5, 2015) * Fix gzip asset generation for assets already on disk. **3.5.0** (December 3, 2015) * Reintroduce Gzip file generation for non-binary assets. **3.4.1** (November 25, 2015) * PathUtils::Entries will no longer error on an empty directory. **3.4.0** (October 5, 2015) * Expose method to override the sass cache in the SassProcessor. **3.3.5** (September 25, 2015) * Fix bug related to absolute path being reintroduced into history cache #141. **3.3.4** (September 1, 2015) * Relative cache contents now work with windows. **3.3.3** (August 21, 2015) * Remove more absolute paths from cache contents. **3.3.2** (August 19, 2015) * Fix cache contents to use relative paths instead of absolute paths. **3.3.1** (August 15, 2015) * Fix legacy Tilt integration when locals is required argument. **3.3.0** (August 12, 2015) * Change internal cache key to use relative asset paths instead of absolute paths. **3.2.0** (June 2, 2015) * Updated SRI integrity to align with spec changes * Deprecated Manifest integrity attribute * Cleanup concatenating JS sources with newlines **3.1.0** (May 10, 2015) * Removed "index" logical path normalization. Asset#logical_path is always the full logical path to the index file. * Fixed static asset mtimes * Fix manifest cleanup by age * Removed redundant minifier level cache * Updated SRI format according to spec changes **3.0.3** (April 27, 2015) * Fix static asset mtime fallback * Only warn when specified asset version can not be loaded. **3.0.2** (April 22, 2015) * Ensure legacy Tilt handlers return String class data. Fixes issues with Haml Tilt handler. * Type check and improve error messages raised on bad processor returned results. * Improve error message for relative paths not under load path. * Changed HTML encoding fallback from ISO-8859-1 to default external. * Avoid falling back to 0 mtimes which may cause warnings with tar **3.0.1** (April 14, 2015) * Fixed `Context#depend_on` with paths outside the load path **3.0.0** (April 12, 2015) [Guide to upgrading from Sprockets 2.x to 3.x](https://github.com/rails/sprockets/blob/master/UPGRADING.md) * New processor API. Tilt interface is deprecated. * Improved file store caching backend. * MIME Types now accept charset custom charset detecters. Improves support for UTF-16/32 files. * Environment#version no longer affects asset digests. Only used for busting the asset cache. * Removed builtin support for LESS. * Removed `//= include` directive support. * Deprecated `BundledAsset#to_a`. Use `BundledAsset#included` to access debugging subcomponents. * Support circular dependencies. For parity with ES6 modules. * Manifest compilation will no longer generate .gz files by default. [Mixing Content-Encoding and ETags is just a bad idea](https://issues.apache.org/bugzilla/show_bug.cgi?id=39727) * Added linked or referenced assets. When an asset is compiled, any of its links will be compiled as well. * Introduce some limitations around enumerating all logical paths. 4.x will deprecate it and favor linked manifests for compliation. * Add Asset integrity attribute for Subresource Integrity * Default digest changed to SHA256. Configuring `digest_class` is deprecated. * Rename `Asset#digest` to `Asset#hexdigest`. `Asset#digest` is deprecated and will return a raw byte String in 4.x. * Added transitional compatibility flag to `Environment#resolve(path, compat: true)`. 2.x mode operates with `compat: true` and 4.x with `compat: false` * `manifest-abc123.json` renamed to `.sprockets-abc123.json` **2.12.3** (October 28, 2014) * Security: Fix directory traversal bug in development mode server. **2.12.2** (September 5, 2014) * Ensure internal asset lookups calls are still restricted to load paths within asset compiles. Though, you should not depend on internal asset resolves to be completely restricted for security reasons. Assets themselves should be considered full scripting environments with filesystem access. **2.12.1** (April 17, 2014) * Fix making manifest target directory when its different than the output directory. **2.12.0** (March 13, 2014) * Avoid context reference in SassImporter hack so its Marshallable. Fixes issues with Sass 3.3.x. **2.11.0** (February 19, 2014) * Cache store must now be an LRU implementation. * Default digest changed to SHA1. To continue using MD5. `env.digest_class = Digest::MD5`. **2.10.0** (May 24, 2013) * Support for `bower.json` **2.9.3** (April 20, 2013) * Fixed sass caching bug **2.9.2** (April 8, 2013) * Improve file freshness check performance * Directive processor encoding fixes **2.9.1** (April 6, 2013) * Support for Uglifier 2.x **2.9.0** (February 25, 2013) * Write out gzipped variants of bundled assets. **2.8.2** (December 10, 2012) * Fixed top level Sass constant references * Fixed manifest logger when environment is disabled **2.8.1** (October 31, 2012) * Fixed Sass importer bug **2.8.0** (October 16, 2012) * Allow manifest location to be separated from output directory * Pass logical path and absolute path to each_logical_path iterator **2.7.0** (October 10, 2012) * Added --css-compressor and --js-compressor command line flags * Added css/js compressor shorthand * Change default manifest.json filename to be a randomized manifest-16HEXBYTES.json * Allow nil environment to be passed to manifest * Allow manifest instance to be set on rake task **2.6.0** (September 19, 2012) * Added bower component.json require support **2.5.0** (September 4, 2012) * Fixed Ruby 2.0 RegExp warning * Provide stubbed implementation of context *_path helpers * Add SassCompressor **2.4.5** (July 10, 2012) * Tweaked some logger levels **2.4.4** (July 2, 2012) * Canonicalize logical path extensions * Check absolute paths passed to depend_on **2.4.3** (May 16, 2012) * Exposed :sprockets in sass options * Include dependency paths in asset mtime **2.4.2** (May 7, 2012) * Fixed MultiJson feature detect **2.4.1** (April 26, 2012) * Fixed MultiJson API change * Fixed gzip mtime **2.4.0** (March 27, 2012) * Added global path registry * Added global processor registry **2.3.2** (March 26, 2012) * Fix Context#logical_path with dots **2.3.1** (February 11, 2012) * Added bytesize to manifest * Added Asset#bytesize alias * Security: Check path for forbidden access after unescaping **2.3.0** (January 16, 2012) * Added special Sass importer that automatically tracks any `@import`ed files. **2.2.0** (January 10, 2012) * Added `sprockets` command line utility. * Added rake/sprocketstask. * Added json manifest log of compiled assets. * Added `stub` directive that allows you to exclude files from the bundle. * Added per environment external encoding (Environment#default_external_encoding). Defaults to UTF-8. Fixes issues where LANG is not set correctly and Rubys default external is set to ASCII. **2.1.2** (November 20, 2011) * Disabled If-Modified-Since server checks. Fixes some browser caching issues when serving the asset body only. If-None-Match caching is sufficient. **2.1.1** (November 18, 2011) * Fix windows absolute path check bug. **2.1.0** (November 11, 2011) * Directive comment lines are now turned into empty lines instead of removed. This way line numbers in CoffeeScript syntax errors are correct. * Performance and caching bug fixes. **2.0.3** (October 17, 2011) * Detect format extensions from right to left. * Make JST namespace configurable. **2.0.2** (October 4, 2011) * Fixed loading stale cache from bundler gems. **2.0.1** (September 30, 2011) * Fixed bug with fingerprinting file names with multiple dots. * Decode URIs as default internal. * Fix symlinked asset directories. **2.0.0** (August 29, 2011) * Initial public release. sprockets-3.7.0/README.md0000644000004100000410000004402212750322502015045 0ustar www-datawww-data# Sprockets: Rack-based asset packaging Sprockets is a Ruby library for compiling and serving web assets. It features declarative dependency management for JavaScript and CSS assets, as well as a powerful preprocessor pipeline that allows you to write assets in languages like CoffeeScript, Sass and SCSS. ## Installation Install Sprockets from RubyGems: ``` sh $ gem install sprockets ``` Or include it in your project's `Gemfile` with Bundler: ``` ruby gem 'sprockets', '~> 3.0' ``` ## Using sprockets For most people interested in using sprockets you will want to see [End User Asset Generation](guides/end_user_asset_generation.md) guide. This contains information about sprocket's directive syntax, and default processing behavior. If you are a framework developer that is using sprockets, see [Building an Asset Processing Framework](guides/building_an_asset_processing_framework.md). If you are a library developer who is extending the functionality of sprockets, see [Extending Sprockets](guides/extending_sprockets.md). Below is a disjointed mix of documentation for all three of these roles. Eventually they will be moved to an appropriate guide, for now the recommended way to consume documentation is to view the appropriate guide first and then supplement with docs from the README. ## Behavior ### Index files are proxies for folders In sprockets index files such as `index.js` or `index.css` files inside of a folder will generate a file with the folder's name. So if you have a `foo/index.js` file it will compile down to `foo.js`. This is similar to NPM's behavior of using [folders as modules](https://nodejs.org/api/modules.html#modules_folders_as_modules). It is also somewhat similar to the way that a file in `public/my_folder/index.html` can be reached by a request to `/my_folder`. This means that you cannot directly use an index file. For example this would not work: ``` <%= asset_path("foo/index.js") %> ``` Instead you would need to use: ``` <%= asset_path("foo.js") %> ``` Why would you want to use this behavior? It is common behavior where you might want to include an entire directory of files in a top level javascript. You can do this in sprockets using `require_tree .` ``` //= require_tree . ``` This has the problem that files are required alphabetically. If your directory has `jquery-ui.js` and `jquery.min.js` then sprockets will require `jquery-ui.js` before `jquery` is required which won't work (because jquery-ui depends on jquery). Previously the only way to get the correct ordering would be to rename your files, something like `0-jquery-ui.js`. Instead of doing that you can use an index file. For example, if you have an `application.js` and want all the files in the `foo/` folder you could do this: ``` //= require foo.js ``` Then create a file `foo/index.js` that requires all the files in that folder in any order you want: ``` //= require foo.min.js //= require foo-ui.js ``` Now in your `application.js` will correctly load the `foo.min.js` before `foo-ui.js`. If you used `require_tree` it would not work correctly. ## Understanding the Sprockets Environment You'll need an instance of the `Sprockets::Environment` class to access and serve assets from your application. Under Rails 4.0 and later, `YourApp::Application.assets` is a preconfigured `Sprockets::Environment` instance. For Rack-based applications, create an instance in `config.ru`. The Sprockets `Environment` has methods for retrieving and serving assets, manipulating the load path, and registering processors. It is also a Rack application that can be mounted at a URL to serve assets over HTTP. ### The Load Path The *load path* is an ordered list of directories that Sprockets uses to search for assets. In the simplest case, a Sprockets environment's load path will consist of a single directory containing your application's asset source files. When mounted, the environment will serve assets from this directory as if they were static files in your public root. The power of the load path is that it lets you organize your source files into multiple directories -- even directories that live outside your application -- and combine those directories into a single virtual filesystem. That means you can easily bundle JavaScript, CSS and images into a Ruby library or [Bower](http://bower.io) package and import them into your application. #### Manipulating the Load Path To add a directory to your environment's load path, use the `append_path` and `prepend_path` methods. Directories at the beginning of the load path have precedence over subsequent directories. ``` ruby environment = Sprockets::Environment.new environment.append_path 'app/assets/javascripts' environment.append_path 'lib/assets/javascripts' environment.append_path 'vendor/assets/bower_components' ``` In general, you should append to the path by default and reserve prepending for cases where you need to override existing assets. ### Accessing Assets Once you've set up your environment's load path, you can mount the environment as a Rack server and request assets via HTTP. You can also access assets programmatically from within your application. #### Logical Paths Assets in Sprockets are always referenced by their *logical path*. The logical path is the path of the asset source file relative to its containing directory in the load path. For example, if your load path contains the directory `app/assets/javascripts`:
Asset source file Logical path
app/assets/javascripts/application.js application.js
app/assets/javascripts/models/project.js models/project.js
In this way, all directories in the load path are merged to create a virtual filesystem whose entries are logical paths. #### Serving Assets Over HTTP When you mount an environment, all of its assets are accessible as logical paths underneath the *mount point*. For example, if you mount your environment at `/assets` and request the URL `/assets/application.js`, Sprockets will search your load path for the file named `application.js` and serve it. Under Rails 4.0 and later, your Sprockets environment is automatically mounted at `/assets`. If you are using Sprockets with a Rack application, you will need to mount the environment yourself. A good way to do this is with the `map` method in `config.ru`: ``` ruby require 'sprockets' map '/assets' do environment = Sprockets::Environment.new environment.append_path 'app/assets/javascripts' environment.append_path 'app/assets/stylesheets' run environment end map '/' do run YourRackApp end ``` #### Accessing Assets Programmatically You can use the `find_asset` method (aliased as `[]`) to retrieve an asset from a Sprockets environment. Pass it a logical path and you'll get a `Sprockets::Asset` instance back: ``` ruby environment['application.js'] # => # ``` Call `to_s` on the resulting asset to access its contents, `length` to get its length in bytes, `mtime` to query its last-modified time, and `filename` to get its full path on the filesystem. ## Using Processors Asset source files can be written in another format, like SCSS or CoffeeScript, and automatically compiled to CSS or JavaScript by Sprockets. Processors that convert a file from one format to another are called *transformers*. ### Minifying Assets Several JavaScript and CSS minifiers are available through shorthand. ``` ruby environment.js_compressor = :uglify environment.css_compressor = :scss ``` ### Styling with Sass and SCSS [Sass](http://sass-lang.com/) is a language that compiles to CSS and adds features like nested rules, variables, mixins and selector inheritance. If the `sass` gem is available to your application, you can use Sass to write CSS assets in Sprockets. Sprockets supports both Sass syntaxes. For the original whitespace-sensitive syntax, use the extension `.sass`. For the new SCSS syntax, use the extension `.scss`. ### Scripting with CoffeeScript [CoffeeScript](http://jashkenas.github.com/coffee-script/) is a language that compiles to the "good parts" of JavaScript, featuring a cleaner syntax with array comprehensions, classes, and function binding. If the `coffee-script` gem is available to your application, you can use CoffeeScript to write JavaScript assets in Sprockets. Note that the CoffeeScript compiler is written in JavaScript, and you will need an [ExecJS](https://github.com/rails/execjs)-supported runtime on your system to invoke it. To write JavaScript assets with CoffeeScript, use the extension `.coffee`. ### JavaScript Templating with EJS and Eco Sprockets supports *JavaScript templates* for client-side rendering of strings or markup. JavaScript templates have the special format extension `.jst` and are compiled to JavaScript functions. When loaded, a JavaScript template function can be accessed by its logical path as a property on the global `JST` object. Invoke a template function to render the template as a string. The resulting string can then be inserted into the DOM. ```
Hello, <%= name %>!
// application.js //= require templates/hello $("#hello").html(JST["templates/hello"]({ name: "Sam" })); ``` Sprockets supports two JavaScript template languages: [EJS](https://github.com/sstephenson/ruby-ejs), for embedded JavaScript, and [Eco](https://github.com/sstephenson/ruby-eco), for embedded CoffeeScript. Both languages use the familiar `<% … %>` syntax for embedding logic in templates. If the `ejs` gem is available to your application, you can use EJS templates in Sprockets. EJS templates have the extension `.jst.ejs`. If the `eco` gem is available to your application, you can use [Eco templates](https://github.com/sstephenson/eco) in Sprockets. Eco templates have the extension `.jst.eco`. Note that the `eco` gem depends on the CoffeeScript compiler, so the same caveats apply as outlined above for the CoffeeScript engine. ### Invoking Ruby with ERB Sprockets provides an ERB engine for preprocessing assets using embedded Ruby code. Append `.erb` to a CSS or JavaScript asset's filename to enable the ERB engine. Ruby code embedded in an asset is evaluated in the context of a `Sprockets::Context` instance for the given asset. Common uses for ERB include: - embedding another asset as a Base64-encoded `data:` URI with the `asset_data_uri` helper - inserting the URL to another asset, such as with the `asset_path` helper provided by the Sprockets Rails plugin - embedding other application resources, such as a localized string database, in a JavaScript asset via JSON - embedding version constants loaded from another file See the [Helper Methods](lib/sprockets/context.rb) section for more information about interacting with `Sprockets::Context` instances via ERB. ## Managing and Bundling Dependencies You can create *asset bundles* -- ordered concatenations of asset source files -- by specifying dependencies in a special comment syntax at the top of each source file. Sprockets reads these comments, called *directives*, and processes them to recursively build a dependency graph. When you request an asset with dependencies, the dependencies will be included in order at the top of the file. ### The Directive Processor Sprockets runs the *directive processor* on each CSS and JavaScript source file. The directive processor scans for comment lines beginning with `=` in comment blocks at the top of the file. ``` js //= require jquery //= require jquery-ui //= require backbone //= require_tree . ``` The first word immediately following `=` specifies the directive name. Any words following the directive name are treated as arguments. Arguments may be placed in single or double quotes if they contain spaces, similar to commands in the Unix shell. **Note**: Non-directive comment lines will be preserved in the final asset, but directive comments are stripped after processing. Sprockets will not look for directives in comment blocks that occur after the first line of code. #### Supported Comment Types The directive processor understands comment blocks in three formats: ``` css /* Multi-line comment blocks (CSS, SCSS, JavaScript) *= require foo */ ``` ``` js // Single-line comment blocks (SCSS, JavaScript) //= require foo ``` ``` coffee # Single-line comment blocks (CoffeeScript) #= require foo ``` ### Sprockets Directives You can use the following directives to declare dependencies in asset source files. For directives that take a *path* argument, you may specify either a logical path or a relative path. Relative paths begin with `./` and reference files relative to the location of the current file. #### The `require` Directive `require` *path* inserts the contents of the asset source file specified by *path*. If the file is required multiple times, it will appear in the bundle only once. ### The `require_directory` Directive ### `require_directory` *path* requires all source files of the same format in the directory specified by *path*. Files are required in alphabetical order. #### The `require_tree` Directive `require_tree` *path* works like `require_directory`, but operates recursively to require all files in all subdirectories of the directory specified by *path*. #### The `require_self` Directive `require_self` tells Sprockets to insert the body of the current source file before any subsequent `require` directives. #### The `link` Directive `link` *path* declares a dependency on the target *path* and adds it to a list of subdependencies to automatically be compiled when the asset is written out to disk. For an example, in a CSS file you might reference an external image that always needs to be compiled along with the css file. ``` css /*= link "logo.png" */ .logo { background-image: url(logo.png) } ``` However, if you use a `asset-path` or `asset-url` SCSS helper, these links will automatically be defined for you. ``` css .logo { background-image: asset-url("logo.png") } ``` #### The `depend_on` Directive `depend_on` *path* declares a dependency on the given *path* without including it in the bundle. This is useful when you need to expire an asset's cache in response to a change in another file. #### The `depend_on_asset` Directive `depend_on_asset` *path* works like `depend_on`, but operates recursively reading the file and following the directives found. This is automatically implied if you use `link`, so consider if it just makes sense using `link` instead of `depend_on_asset`. #### The `stub` Directive `stub` *path* 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. `stub` should only be used at the top level bundle, not within any subdependencies. ## Processor Interface Sprockets 2.x was originally design around [Tilt](https://github.com/rtomayko/tilt)'s engine interface. However, starting with 3.x, a new interface has been introduced deprecating Tilt. Similar to Rack, a processor is a any "callable" (an object that responds to `call`). This maybe a simple Proc or a full class that defines a `def self.call(input)` method. The `call` method accepts an `input` Hash and returns a Hash of metadata. Also see [`Sprockets::ProcessorUtils`](https://github.com/rails/sprockets/blob/master/lib/sprockets/processor_utils.rb) for public helper methods. ### input Hash The `input` Hash defines the following public fields. * `:data` - String asset contents * `:environment` - Current `Sprockets::Environment` instance. * `:cache` - A `Sprockets::Cache` instance. See [`Sprockets::Cache#fetch`](https://github.com/rails/sprockets/blob/master/lib/sprockets/cache.rb). * `:uri` - String Asset URI. * `:filename` - String full path to original file. * `:load_path` - String current load path for filename. * `:name` - String logical path for filename. * `:content_type` - String content type of the output asset. * `:metadata` - Hash of processor metadata. ``` ruby def self.call(input) input[:cache].fetch("my:cache:key:v1") do # Remove all semicolons from source input[:data].gsub(";", "") end end ``` ### return Hash The processor should return metadata `Hash`. With the exception of the `:data` key, the processor can store arbitrary JSON valid values in this Hash. The data will be stored and exposed on `Asset#metadata`. The returned `:data` replaces the assets `input[:data]` to the next processor in the chain. Returning a `String` is shorthand for returning `{ data: str }`. And returning `nil` is shorthand for a no-op where the input data is not transformed, `{ data: input[:data] }`. ### metadata The metadata Hash provides an open format for processors to extend the pipeline processor. Internally, built-in processors use it for passing data to each other. * `:required` - A `Set` of String Asset URIs that the Bundle processor should concatenate together. * `:stubbed` - A `Set` of String Asset URIs that will be omitted from the `:required` set. * `:links` - A `Set` of String Asset URIs that should be compiled along with this asset. * `:dependencies` - A `Set` of String Cache URIs that should be monitored for caching. ``` ruby def self.call(input) # Any metadata may start off as nil, so initialize it the value required = Set.new(input[:metadata][:required]) # Manually add "foo.js" asset uri to our bundle required << input[:environment].resolve("foo.js") { required: required } end ``` ## Development ### Contributing The Sprockets source code is [hosted on GitHub](https://github.com/rails/sprockets). You can check out a copy of the latest code using Git: $ git clone https://github.com/rails/sprockets If you've found a bug or have a question, please open an issue on the [Sprockets issue tracker](https://github.com/rails/sprockets/issues). Or, clone the Sprockets repository, write a failing test case, fix the bug and submit a pull request. ### Version History Please see the [CHANGELOG](https://github.com/rails/sprockets/tree/master/CHANGELOG.md) ## License Copyright © 2014 Sam Stephenson <> Copyright © 2014 Joshua Peek <> Sprockets is distributed under an MIT-style license. See LICENSE for details.