Permalink
Browse files

Merge pull request #213 from sstephenson/2.1.x

Release 2.1.0
  • Loading branch information...
2 parents ae7f3b7 + 6fd49f9 commit 20db9ed6cb67a4e923fb12ade5b864d9b9fcf2c2 @josh josh committed Nov 5, 2011
View
@@ -1,31 +1,63 @@
require 'sprockets/version'
module Sprockets
- autoload :ArgumentError, "sprockets/errors"
+ # Environment
+ autoload :Base, "sprockets/base"
+ autoload :Engines, "sprockets/engines"
+ autoload :Environment, "sprockets/environment"
+ autoload :Index, "sprockets/index"
+
+ # Assets
autoload :Asset, "sprockets/asset"
- autoload :AssetAttributes, "sprockets/asset_attributes"
autoload :BundledAsset, "sprockets/bundled_asset"
+ autoload :ProcessedAsset, "sprockets/processed_asset"
+ autoload :StaticAsset, "sprockets/static_asset"
+
+ # Processing
autoload :CharsetNormalizer, "sprockets/charset_normalizer"
- autoload :CircularDependencyError, "sprockets/errors"
- autoload :ContentTypeMismatch, "sprockets/errors"
autoload :Context, "sprockets/context"
autoload :DirectiveProcessor, "sprockets/directive_processor"
autoload :EcoTemplate, "sprockets/eco_template"
autoload :EjsTemplate, "sprockets/ejs_template"
+ autoload :JstProcessor, "sprockets/jst_processor"
+ autoload :Processor, "sprockets/processor"
+ autoload :SafetyColons, "sprockets/safety_colons"
+
+ # Internal utilities
+ autoload :ArgumentError, "sprockets/errors"
+ autoload :AssetAttributes, "sprockets/asset_attributes"
+ autoload :CircularDependencyError, "sprockets/errors"
+ autoload :ContentTypeMismatch, "sprockets/errors"
autoload :EngineError, "sprockets/errors"
- autoload :Engines, "sprockets/engines"
- autoload :Environment, "sprockets/environment"
autoload :Error, "sprockets/errors"
autoload :FileNotFound, "sprockets/errors"
- autoload :Index, "sprockets/index"
- autoload :JstProcessor, "sprockets/jst_processor"
- autoload :Processing, "sprockets/processing"
- autoload :Processor, "sprockets/processor"
- autoload :Server, "sprockets/server"
- autoload :StaticAsset, "sprockets/static_asset"
autoload :Utils, "sprockets/utils"
module Cache
autoload :FileStore, "sprockets/cache/file_store"
end
+
+ # Extend Sprockets module to provide global registry
+ extend Engines
+ @engines = {}
+
+ # Cherry pick the default Tilt engines that make sense for
+ # Sprockets. We don't need ones that only generate html like HAML.
+
+ # Mmm, CoffeeScript
+ register_engine '.coffee', Tilt::CoffeeScriptTemplate
+
+ # JST engines
+ register_engine '.jst', JstProcessor
+ register_engine '.eco', EcoTemplate
+ register_engine '.ejs', EjsTemplate
+
+ # CSS engines
+ register_engine '.less', Tilt::LessTemplate
+ register_engine '.sass', Tilt::SassTemplate
+ register_engine '.scss', Tilt::ScssTemplate
+
+ # Other
+ register_engine '.erb', Tilt::ERBTemplate
+ register_engine '.str', Tilt::StringTemplate
end
View
@@ -1,98 +1,87 @@
require 'time'
+require 'set'
module Sprockets
# `Asset` is the base class for `BundledAsset` and `StaticAsset`.
class Asset
# Internal initializer to load `Asset` from serialized `Hash`.
def self.from_hash(environment, hash)
- asset = allocate
- asset.init_with(environment, hash)
- asset
- end
+ return unless hash.is_a?(Hash)
+
+ klass = case hash['class']
+ when 'BundledAsset'
+ BundledAsset
+ when 'ProcessedAsset'
+ ProcessedAsset
+ when 'StaticAsset'
+ StaticAsset
+ else
+ nil
+ end
- # Define base set of attributes to be serialized.
- def self.serialized_attributes
- %w( id logical_path pathname )
+ if klass
+ asset = klass.allocate
+ asset.init_with(environment, hash)
+ asset
+ end
+ rescue UnserializeError
+ nil
end
- attr_reader :environment
- attr_reader :id, :logical_path, :pathname
+ attr_reader :logical_path, :pathname
+ attr_reader :content_type, :mtime, :length, :digest
def initialize(environment, logical_path, pathname)
- @environment = environment
+ @root = environment.root
@logical_path = logical_path.to_s
@pathname = Pathname.new(pathname)
- @id = environment.digest.update(object_id.to_s).to_s
+ @content_type = environment.content_type_of(pathname)
+ @mtime = environment.stat(pathname).mtime
+ @length = environment.stat(pathname).size
+ @digest = environment.file_digest(pathname).hexdigest
end
# Initialize `Asset` from serialized `Hash`.
def init_with(environment, coder)
- @environment = environment
- @pathname = @mtime = @length = nil
+ @root = environment.root
- self.class.serialized_attributes.each do |attr|
- instance_variable_set("@#{attr}", coder[attr].to_s) if coder[attr]
- end
+ @logical_path = coder['logical_path']
+ @content_type = coder['content_type']
+ @digest = coder['digest']
- if @pathname && @pathname.is_a?(String)
+ if pathname = coder['pathname']
# Expand `$root` placeholder and wrapper string in a `Pathname`
- @pathname = Pathname.new(expand_root_path(@pathname))
+ @pathname = Pathname.new(expand_root_path(pathname))
end
- if @mtime && @mtime.is_a?(String)
+ if mtime = coder['mtime']
# Parse time string
- @mtime = Time.parse(@mtime)
+ @mtime = Time.parse(mtime)
end
- if @length && @length.is_a?(String)
+ if length = coder['length']
# Convert length to an `Integer`
- @length = Integer(@length)
+ @length = Integer(length)
end
end
# Copy serialized attributes to the coder object
def encode_with(coder)
- coder['class'] = self.class.name.sub(/Sprockets::/, '')
-
- self.class.serialized_attributes.each do |attr|
- value = send(attr)
- coder[attr] = case value
- when Time
- value.iso8601
- else
- value.to_s
- end
- end
-
- coder['pathname'] = relativize_root_path(coder['pathname'])
- end
-
- # Returns `Content-Type` from pathname.
- def content_type
- @content_type ||= environment.content_type_of(pathname)
- end
-
- # Get mtime at the time the `Asset` is built.
- def mtime
- @mtime ||= environment.stat(pathname).mtime
- end
-
- # Get length at the time the `Asset` is built.
- def length
- @length ||= environment.stat(pathname).size
- end
-
- # Get content digest at the time the `Asset` is built.
- def digest
- @digest ||= environment.file_digest(pathname).hexdigest
+ coder['class'] = self.class.name.sub(/Sprockets::/, '')
+ coder['logical_path'] = logical_path
+ coder['pathname'] = relativize_root_path(pathname).to_s
+ coder['content_type'] = content_type
+ coder['mtime'] = mtime.iso8601
+ coder['length'] = length
+ coder['digest'] = digest
end
# Return logical path with digest spliced in.
#
# "foo/bar-37b51d194a7513e45b56f6524f2d51f2.js"
#
def digest_path
- environment.attributes_for(logical_path).path_with_fingerprint(digest)
+ logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
end
# Return an `Array` of `Asset` files that are declared dependencies.
@@ -111,6 +100,16 @@ def to_a
[self]
end
+ # `body` is aliased to source by default if it can't have any dependencies.
+ def body
+ source
+ end
+
+ # Return `String` of concatenated source.
+ def to_s
+ source
+ end
+
# Add enumerator to allow `Asset` instances to be used as Rack
# compatible body objects.
def each
@@ -121,18 +120,47 @@ def each
# digest to the inmemory model.
#
# Used to test if cached models need to be rebuilt.
- #
- # Subclass must override `fresh?` or `stale?`.
- def fresh?
- !stale?
+ def fresh?(environment)
+ # Check current mtime and digest
+ dependency_fresh?(environment, self)
end
# Checks if Asset is stale by comparing the actual mtime and
# digest to the inmemory model.
#
# Subclass must override `fresh?` or `stale?`.
- def stale?
- !fresh?
+ def stale?(environment)
+ !fresh?(environment)
+ end
+
+ # Save asset to disk.
+ def write_to(filename, options = {})
+ # Gzip contents if filename has '.gz'
+ options[:compress] ||= File.extname(filename) == '.gz'
+
+ File.open("#{filename}+", 'wb') do |f|
+ if options[:compress]
+ # Run contents through `Zlib`
+ gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
+ gz.write to_s
+ gz.close
+ else
+ # Write out as is
+ f.write to_s
+ f.close
+ end
+ end
+
+ # Atomic write
+ FileUtils.mv("#{filename}+", filename)
+
+ # Set mtime correctly
+ File.utime(mtime, mtime, filename)
+
+ nil
+ ensure
+ # Ensure tmp file gets cleaned up
+ FileUtils.rm("#{filename}+") if File.exist?("#{filename}+")
end
# Pretty inspect
@@ -144,29 +172,47 @@ def inspect
">"
end
+ def hash
+ digest.hash
+ end
+
# Assets are equal if they share the same path, mtime and digest.
def eql?(other)
other.class == self.class &&
- other.relative_pathname == self.relative_pathname &&
+ other.logical_path == self.logical_path &&
other.mtime.to_i == self.mtime.to_i &&
other.digest == self.digest
end
alias_method :==, :eql?
protected
+ # Internal: String paths that are marked as dependencies after processing.
+ #
+ # Default to an empty `Array`.
+ def dependency_paths
+ @dependency_paths ||= []
+ end
+
+ # Internal: `ProccessedAsset`s that are required after processing.
+ #
+ # Default to an empty `Array`.
+ def required_assets
+ @required_assets ||= []
+ end
+
# Get pathname with its root stripped.
def relative_pathname
- Pathname.new(relativize_root_path(pathname))
+ @relative_pathname ||= Pathname.new(relativize_root_path(pathname))
end
# Replace `$root` placeholder with actual environment root.
def expand_root_path(path)
- environment.attributes_for(path).expand_root
+ path.to_s.sub(/^\$root/, @root)
end
# Replace actual environment root with `$root` placeholder.
def relativize_root_path(path)
- environment.attributes_for(path).relativize_root
+ path.to_s.sub(/^#{Regexp.escape(@root)}/, '$root')
end
# Check if dependency is fresh.
@@ -175,8 +221,8 @@ def relativize_root_path(path)
#
# A `Hash` is used rather than other `Asset` object because we
# want to test non-asset files and directories.
- def dependency_fresh?(dep = {})
- path, mtime, hexdigest = dep.values_at('path', 'mtime', 'hexdigest')
+ def dependency_fresh?(environment, dep)
+ path, mtime, hexdigest = dep.pathname.to_s, dep.mtime, dep.digest
stat = environment.stat(path)
Oops, something went wrong.

0 comments on commit 20db9ed

Please sign in to comment.