This commit is contained in:
Scott Davis 2011-06-07 12:01:50 -04:00
commit 387bf3665a
19 changed files with 485 additions and 226 deletions

View File

@ -7,7 +7,7 @@ author: eric
It's easy! Follow these two simple steps: It's easy! Follow these two simple steps:
1. Use Compass/Sass. 1. Use Compass/Sass.
2. Use Django. 2. Use Django.
That's it. Compass works great as a stand-alone tool. Run "compass --watch" on the command line or use [compass.app](http://compass.handlino.com/) to compile your stylesheets, and then commit the CSS to your Django project, just like you always have. Done. That's it. Compass works great as a stand-alone tool. Run "compass --watch" on the command line or use [compass.app](http://compass.handlino.com/) to compile your stylesheets, and then commit the CSS to your Django project, just like you always have. Done.
@ -27,10 +27,8 @@ The disadvantage to our approach is that you are committing generated code to th
And I, as the designer/front-end developer, keep full control of the css-generation process without needing to touch the server. If I want to update the gems and make some changes, I can do that. I make the change, I commit the change, and it just works. For everyone. That's important to me. It removes all the pretense of dark magic that can come with Sass/Compass. I'm writing CSS. I'm committing CSS. Compass, Sass and all their plugins are just tools towards that end. And I, as the designer/front-end developer, keep full control of the css-generation process without needing to touch the server. If I want to update the gems and make some changes, I can do that. I make the change, I commit the change, and it just works. For everyone. That's important to me. It removes all the pretense of dark magic that can come with Sass/Compass. I'm writing CSS. I'm committing CSS. Compass, Sass and all their plugins are just tools towards that end.
Of course, you'll want to commit the Sass as well, especially if you have multiple front-end developers on the team. That way the source is available for anyone who needs to update it, even though it's not needed by the server. You might also want a way of documenting the latest gems that should be used to compile it. That's easy enough to add in a comment or doc of it's own. Of course, you'll want to commit the Sass as well, especially if you have multiple front-end developers on the team. That way the source is available for anyone who needs to update it, even though it's not needed by the server. You might also want a way of documenting the latest gems that should be used to compile it. That's easy enough to add in a comment or doc of it's own.
## Just Tools. ## Just Tools.
I want to say that again because I think it is the most important and most often forgotten rule of using a css pre-processor. Compass and Sass are simply tools for writing CSS. They are not a new styling language. They are not magic. They make writing css easier - and that is all. The css output is the only thing that matters. I want to say that again because I think it is the most important and most often forgotten rule of using a css pre-processor. **Compass and Sass are simply tools for writing CSS. They are not a new styling language. They are not magic. They make writing css easier - and that is all. The css output is the only thing that matters.**
_This was written in collaboration with [Carl Meyer](http://stackoverflow.com/users/3207/carl-meyer), in response to a [question on stack overflow](http://stackoverflow.com/questions/5900208/best-method-for-adding-compass-to-a-django-project)_

View File

@ -4,7 +4,7 @@ module Compass
attr_writer :logger attr_writer :logger
def logger def logger
@logger ||= Logger.new @logger ||= ::Compass::Logger.new
end end
# copy/process a template in the compass template directory to the project directory. # copy/process a template in the compass template directory to the project directory.
@ -17,13 +17,14 @@ module Compass
# create a directory and all the directories necessary to reach it. # create a directory and all the directories necessary to reach it.
def directory(dir, options = nil) def directory(dir, options = nil)
options ||= self.options if self.respond_to?(:options) options ||= self.options if self.respond_to?(:options)
options ||= {}
if File.exists?(dir) && File.directory?(dir) if File.exists?(dir) && File.directory?(dir)
# logger.record :exists, basename(dir) unless options[:quiet] # do nothing
elsif File.exists?(dir) elsif File.exists?(dir)
msg = "#{basename(dir)} already exists and is not a directory." msg = "#{basename(dir)} already exists and is not a directory."
raise Compass::FilesystemConflict.new(msg) raise Compass::FilesystemConflict.new(msg)
else else
logger.record :directory, separate("#{basename(dir)}/") log_action :directory, separate("#{basename(dir)}/"), options
FileUtils.mkdir_p(dir) unless options[:dry_run] FileUtils.mkdir_p(dir) unless options[:dry_run]
end end
end end
@ -33,20 +34,19 @@ module Compass
options ||= self.options if self.respond_to?(:options) options ||= self.options if self.respond_to?(:options)
skip_write = options[:dry_run] skip_write = options[:dry_run]
contents = process_erb(contents, options[:erb]) if options[:erb] contents = process_erb(contents, options[:erb]) if options[:erb]
extra = options[:extra] || ""
if File.exists?(file_name) if File.exists?(file_name)
existing_contents = IO.read(file_name) existing_contents = IO.read(file_name)
if existing_contents == contents if existing_contents == contents
logger.record :identical, basename(file_name), extra log_action :identical, basename(file_name), options
skip_write = true skip_write = true
elsif options[:force] elsif options[:force]
logger.record :overwrite, basename(file_name), extra log_action :overwrite, basename(file_name), options
else else
msg = "File #{basename(file_name)} already exists. Run with --force to force overwrite." msg = "File #{basename(file_name)} already exists. Run with --force to force overwrite."
raise Compass::FilesystemConflict.new(msg) raise Compass::FilesystemConflict.new(msg)
end end
else else
logger.record :create, basename(file_name), extra log_action :create, basename(file_name), options
end end
if skip_write if skip_write
FileUtils.touch file_name unless options[:dry_run] FileUtils.touch file_name unless options[:dry_run]
@ -67,7 +67,7 @@ module Compass
def remove(file_name) def remove(file_name)
if File.exists?(file_name) if File.exists?(file_name)
File.unlink file_name File.unlink file_name
logger.record :remove, basename(file_name) log_action :remove, basename(file_name), options
end end
end end
@ -95,5 +95,14 @@ module Compass
(path[-1..-1] == File::SEPARATOR) ? path[0..-2] : path (path[-1..-1] == File::SEPARATOR) ? path[0..-2] : path
end end
def log_action(action, file, options)
quiet = !!options[:quiet]
quiet = false if options[:loud] && options[:loud] == true
quiet = false if options[:loud] && options[:loud].is_a?(Array) && options[:loud].include?(action)
unless quiet
logger.record(action, file, options[:extra].to_s)
end
end
end end
end end

View File

@ -39,7 +39,7 @@ module Compass
def perform def perform
relative_uri = options[:uri].gsub(/^#{Compass.configuration.images_dir}\//, '') relative_uri = options[:uri].gsub(/^#{Compass.configuration.images_dir}\//, '')
sprites = Compass::SpriteMap.new(relative_uri, Compass.sass_engine_options) sprites = Compass::SpriteImporter.new(relative_uri, Compass.sass_engine_options)
options[:output_file] ||= File.join(Compass.configuration.sass_path, "sprites", "_#{sprites.name}.#{Compass.configuration.preferred_syntax}") options[:output_file] ||= File.join(Compass.configuration.sass_path, "sprites", "_#{sprites.name}.#{Compass.configuration.preferred_syntax}")
options[:skip_overrides] ||= false options[:skip_overrides] ||= false
contents = sprites.content_for_images(options[:skip_overrides]) contents = sprites.content_for_images(options[:skip_overrides])

View File

@ -33,6 +33,7 @@ module Compass
def perform def perform
compiler = new_compiler_instance compiler = new_compiler_instance
check_for_sass_files!(compiler) check_for_sass_files!(compiler)
compiler.clean! if compiler.new_config?
compiler.run compiler.run
end end

View File

@ -135,7 +135,7 @@ module Compass
def recompile(base = nil, relative = nil) def recompile(base = nil, relative = nil)
@memory_cache.reset! if @memory_cache @memory_cache.reset! if @memory_cache
compiler = new_compiler_instance(:quiet => true) compiler = new_compiler_instance(:quiet => true, :loud => [:identical, :overwrite, :create])
if file = compiler.out_of_date? if file = compiler.out_of_date?
begin begin
puts ">>> Change detected to: #{relative || compiler.relative_stylesheet_name(file)}" puts ">>> Change detected to: #{relative || compiler.relative_stylesheet_name(file)}"

View File

@ -153,7 +153,7 @@ module Compass
formatted_error = "(Line #{e.sass_line}: #{e.message})" formatted_error = "(Line #{e.sass_line}: #{e.message})"
file = basename(sass_filename) file = basename(sass_filename)
logger.record :error, file, formatted_error logger.record :error, file, formatted_error
Compass.configuration.run_callback(:styesheet_error, sass_filename, formatted_error) Compass.configuration.run_callback(:stylesheet_error, sass_filename, formatted_error)
write_file css_filename, error_contents(e, sass_filename), options.merge(:force => true) write_file css_filename, error_contents(e, sass_filename), options.merge(:force => true)
end end

View File

@ -23,7 +23,7 @@ module Compass
plugin_opts[:cache_location] = cache_path unless cache_path.nil? plugin_opts[:cache_location] = cache_path unless cache_path.nil?
plugin_opts.merge!(sass_options || {}) plugin_opts.merge!(sass_options || {})
plugin_opts[:load_paths] ||= [] plugin_opts[:load_paths] ||= []
plugin_opts[:load_paths] << Compass::SpriteMap.new plugin_opts[:load_paths] << Compass::SpriteImporter.new
plugin_opts plugin_opts
end end
@ -63,7 +63,7 @@ module Compass
next p if p.respond_to?(:find_relative) next p if p.respond_to?(:find_relative)
Sass::Importers::Filesystem.new(p.to_s) Sass::Importers::Filesystem.new(p.to_s)
end end
load_paths << Compass::SpriteMap.new load_paths << Compass::SpriteImporter.new
load_paths load_paths
end end
end end

View File

@ -10,7 +10,7 @@ module Compass::SassExtensions::Functions::Sprites
end end
end end
# Creates a Compass::SassExtensions::Sprites::Base object. A sprite map, when used in a property is the same # Creates a Compass::SassExtensions::Sprites::SpriteMap object. A sprite map, when used in a property is the same
# as calling sprite-url. So the following background properties are equivalent: # as calling sprite-url. So the following background properties are equivalent:
# #
# $icons: sprite-map("icons/*.png"); # $icons: sprite-map("icons/*.png");
@ -21,7 +21,7 @@ module Compass::SassExtensions::Functions::Sprites
# the first time it is converted to a url. Simply constructing it has no side-effects. # the first time it is converted to a url. Simply constructing it has no side-effects.
def sprite_map(glob, kwargs = {}) def sprite_map(glob, kwargs = {})
kwargs.extend VariableReader kwargs.extend VariableReader
Compass::SassExtensions::Sprites::Base.from_uri(glob, self, kwargs) Compass::SassExtensions::Sprites::SpriteMap.from_uri(glob, self, kwargs)
end end
Sass::Script::Functions.declare :sprite_map, [:glob], :var_kwargs => true Sass::Script::Functions.declare :sprite_map, [:glob], :var_kwargs => true
@ -160,7 +160,7 @@ protected
end end
def verify_map(map, error = "sprite") def verify_map(map, error = "sprite")
unless map.is_a?(Compass::SassExtensions::Sprites::Base) unless map.is_a?(Compass::SassExtensions::Sprites::SpriteMap)
missing_sprite!(error) missing_sprite!(error)
end end
end end

View File

@ -1,4 +1,14 @@
require 'digest/md5' require 'digest/md5'
require 'compass/sprite_importer'
module Compass
module SassExtensions
module Sprites
end
end
end
#modules #modules
require 'compass/sass_extensions/sprites/sprite' require 'compass/sass_extensions/sprites/sprite'
require 'compass/sass_extensions/sprites/processing' require 'compass/sass_extensions/sprites/processing'
@ -8,12 +18,4 @@ require 'compass/sass_extensions/sprites/sprite_map'
require 'compass/sass_extensions/sprites/image' require 'compass/sass_extensions/sprites/image'
require 'compass/sass_extensions/sprites/row_fitter' require 'compass/sass_extensions/sprites/row_fitter'
require 'compass/sass_extensions/sprites/image_row' require 'compass/sass_extensions/sprites/image_row'
require 'compass/sass_extensions/sprites/base' require 'compass/sass_extensions/sprites/engines'
require 'compass/sass_extensions/sprites/engines'
module Compass
module SassExtensions
module Sprites
end
end
end

View File

@ -1,185 +1,213 @@
module Compass module Compass
class SpriteMap < Sass::Importers::Base module SassExtensions
attr_accessor :uri, :options module Sprites
VAILD_FILE_NAME = /\A#{Sass::SCSS::RX::IDENT}\Z/ class SpriteMap < Sass::Script::Literal
SPRITE_IMPORTER_REGEX = %r{((.+/)?([^\*.]+))/(.+?)\.png}
def self.load(uri, options) # Initialize a new sprite object from a relative file path
Compass.quick_cache "Sprite_map:#{uri}#{options.inspect}", 5 do # the path is relative to the <tt>images_path</tt> confguration option
klass = Compass::SpriteMap.new def self.from_uri(uri, context, kwargs)
klass.uri, klass.options = uri, options importer = ::Compass::SpriteImporter.new(:uri => uri.value, :options => {})
klass sprites = importer.files.map do |sprite|
end sprite.gsub(Compass.configuration.images_path+"/", "")
end end
new(sprites, importer.path, importer.name, context, kwargs)
def initialize(options ={})
@uri, @options = '', {}
options.each do |key, value|
send("#{key}=", value)
end
end
def find(uri, options)
@uri, @options = uri, options
if uri =~ SPRITE_IMPORTER_REGEX
return sass_engine
end
end
def find_relative(uri, base, options)
@uri, @options = uri, options
find(File.join(base, uri), options)
end
def to_s
content_for_images
end
def hash
self.class.name.hash
end
def eql?(other)
other.class == self.class
end
def key(uri, options={})
@uri, @options = uri, options
[self.class.name + ":" + File.dirname(File.expand_path(uri)), File.basename(uri)]
end
def self.path_and_name(uri)
Compass.quick_cache "Sprite_map_name:#{uri}", 5 do
if uri =~ SPRITE_IMPORTER_REGEX
[$1, $3]
else
[nil, nil]
end end
end
end # Loads the sprite engine
def require_engine!
# Name of this spite self.class.send(:include, eval("::Compass::SassExtensions::Sprites::#{modulize}Engine"))
def name
ensure_path_and_name!
@name
end
# The on-disk location of this sprite
def path
ensure_path_and_name!
@path
end
# Returns the Glob of image files for this sprite
def files
@files ||= Dir[File.join(Compass.configuration.images_path, uri)].sort
end
# Returns an Array of image names without the file extension
def sprite_names
@sprite_names ||= files.collect do |file|
filename = File.basename(file, '.png')
unless VAILD_FILE_NAME =~ filename
raise Compass::Error, "Sprite file names must be legal css identifiers. Please rename #{File.basename(file)}"
end end
filename
# Changing this string will invalidate all previously generated sprite images.
# We should do so only when the packing algorithm changes
SPRITE_VERSION = "1"
attr_accessor :image_names, :path, :name, :kwargs
attr_accessor :images, :width, :height
def initialize(sprites, path, name, context, kwargs)
require_engine!
@image_names = sprites
@path = path
@name = name
@kwargs = kwargs
@kwargs['cleanup'] ||= Sass::Script::Bool.new(true)
@images = nil
@width = nil
@height = nil
@evaluation_context = context
validate!
compute_image_metadata!
end
# Calculate the size of the sprite
def size
[width, height]
end
# Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
# Calculates the height
def compute_image_metadata!
@width = 0
init_images
compute_image_positions!
@height = @images.last.top + @images.last.height
end
# Creates the Sprite::Image objects for each image and calculates the width
def init_images
@images = image_names.collect do |relative_file|
image = Compass::SassExtensions::Sprites::Image.new(self, relative_file, kwargs)
@width = [ @width, image.width + image.offset ].max
image
end
end
# Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
def compute_image_positions!
@images.each_with_index do |image, index|
image.left = image.position.unit_str == "%" ? (@width - image.width) * (image.position.value / 100) : image.position.value
next if index == 0
last_image = @images[index-1]
image.top = last_image.top + last_image.height + [image.spacing, last_image.spacing].max
end
end
# Fetches the Sprite::Image object for the supplied name
def image_for(name)
@images.detect { |img| img.name == name}
end
# Returns true if the image name has a hover selector image
def has_hover?(name)
!image_for("#{name}_hover").nil?
end
# Returns true if the image name has a target selector image
def has_target?(name)
!image_for("#{name}_target").nil?
end
# Returns true if the image name has an active selector image
def has_active?(name)
!image_for("#{name}_active").nil?
end
# Return and array of image names that make up this sprite
def sprite_names
image_names.map { |f| File.basename(f, '.png') }
end
# Validates that the sprite_names are valid sass
def validate!
for sprite_name in sprite_names
unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/
raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier"
end
end
end
# The on-the-disk filename of the sprite
def filename
File.join(Compass.configuration.images_path, "#{path}-#{uniqueness_hash}.png")
end
# Generate a sprite image if necessary
def generate
if generation_required?
if kwargs.get_var('cleanup').value
cleanup_old_sprites
end
sprite_data = construct_sprite
save!(sprite_data)
Compass.configuration.run_callback(:sprite_generated, sprite_data)
end
end
def cleanup_old_sprites
Dir[File.join(Compass.configuration.images_path, "#{path}-*.png")].each do |file|
FileUtils.rm file
end
end
# Does this sprite need to be generated
def generation_required?
!File.exists?(filename) || outdated?
end
# Returns the uniqueness hash for this sprite object
def uniqueness_hash
@uniqueness_hash ||= begin
sum = Digest::MD5.new
sum << SPRITE_VERSION
sum << path
images.each do |image|
[:relative_file, :height, :width, :repeat, :spacing, :position, :digest].each do |attr|
sum << image.send(attr).to_s
end
end
sum.hexdigest[0...10]
end
@uniqueness_hash
end
# Saves the sprite engine
def save!(output_png)
saved = output_png.save filename
Compass.configuration.run_callback(:sprite_saved, filename)
saved
end
# All the full-path filenames involved in this sprite
def image_filenames
@images.map(&:file)
end
# Checks whether this sprite is outdated
def outdated?
if File.exists?(filename)
return @images.map(&:mtime).any? { |imtime| imtime.to_i > self.mtime.to_i }
end
true
end
# Mtime of the sprite file
def mtime
@mtime ||= File.mtime(filename)
end
def inspect
to_s
end
def to_s(kwargs = self.kwargs)
sprite_url(self).value
end
def respond_to?(meth)
super || @evaluation_context.respond_to?(meth)
end
def method_missing(meth, *args, &block)
if @evaluation_context.respond_to?(meth)
@evaluation_context.send(meth, *args, &block)
else
super
end
end
private
def modulize
@modulize ||= Compass::configuration.sprite_engine.to_s.scan(/([^_.]+)/).flatten.map {|chunk| "#{chunk[0].chr.upcase}#{chunk[1..-1]}" }.join
end
end end
end end
# Returns the sass options for this sprite
def sass_options
@sass_options ||= options.merge(:filename => name, :syntax => :scss, :importer => self)
end
# Returns a Sass::Engine for this sprite object
def sass_engine
Sass::Engine.new(content_for_images, sass_options)
end
def ensure_path_and_name!
@path, @name = self.class.path_and_name(uri)
end
# Generates the Sass for this sprite file
def content_for_images(skip_overrides = false)
<<-SCSS
@import "compass/utilities/sprites/base";
// General Sprite Defaults
// You can override them before you import this file.
$#{name}-sprite-base-class: ".#{name}-sprite" !default;
$#{name}-sprite-dimensions: false !default;
$#{name}-position: 0% !default;
$#{name}-spacing: 0 !default;
$#{name}-repeat: no-repeat !default;
$#{name}-prefix: '' !default;
$#{name}-clean-up: true !default;
$#{name}-smart-pack: false !default;
#{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\", $smart-pack: $#{name}-smart-pack, $cleanup: $#{name}-clean-up);" : generate_overrides }
// All sprites should extend this class
// The #{name}-sprite mixin will do so for you.
\#{$#{name}-sprite-base-class} {
background: $#{name}-sprites no-repeat;
}
// Use this to set the dimensions of an element
// based on the size of the original image.
@mixin #{name}-sprite-dimensions($name) {
@include sprite-dimensions($#{name}-sprites, $name)
}
// Move the background position to display the sprite.
@mixin #{name}-sprite-position($name, $offset-x: 0, $offset-y: 0) {
@include sprite-background-position($#{name}-sprites, $name, $offset-x, $offset-y)
}
// Extends the sprite base class and set the background position for the desired sprite.
// It will also apply the image dimensions if $dimensions is true.
@mixin #{name}-sprite($name, $dimensions: $#{name}-sprite-dimensions, $offset-x: 0, $offset-y: 0) {
@extend \#{$#{name}-sprite-base-class};
@include sprite($#{name}-sprites, $name, $dimensions, $offset-x, $offset-y)
}
@mixin #{name}-sprites($sprite-names, $dimensions: $#{name}-sprite-dimensions, $prefix: sprite-map-name($#{name}-sprites)) {
@include sprites($#{name}-sprites, $sprite-names, $#{name}-sprite-base-class, $dimensions, $prefix)
}
// Generates a class for each sprited image.
@mixin all-#{name}-sprites($dimensions: $#{name}-sprite-dimensions, $prefix: sprite-map-name($#{name}-sprites)) {
@include #{name}-sprites(#{sprite_names.join(" ")}, $dimensions, $prefix);
}
SCSS
end
# Generates the override defaults for this Sprite
# <tt>$#{name}-#{sprite_name}-position </tt>
# <tt> $#{name}-#{sprite_name}-spacing </tt>
# <tt> #{name}-#{sprite_name}-repeat: </tt>
def generate_overrides
content = <<-TXT
// These variables control the generated sprite output
// You can override them selectively before you import this file.
TXT
sprite_names.map do |sprite_name|
content += <<-SCSS
$#{name}-#{sprite_name}-position: $#{name}-position !default;
$#{name}-#{sprite_name}-spacing: $#{name}-spacing !default;
$#{name}-#{sprite_name}-repeat: $#{name}-repeat !default;
SCSS
end.join
content += "\n$#{name}-sprites: sprite-map(\"#{uri}\", \n $smart-pack: $#{name}-smart-pack, $cleanup: $#{name}-clean-up,\n"
content += sprite_names.map do |sprite_name|
%Q{ $#{sprite_name}-position: $#{name}-#{sprite_name}-position,
$#{sprite_name}-spacing: $#{name}-#{sprite_name}-spacing,
$#{sprite_name}-repeat: $#{name}-#{sprite_name}-repeat}
end.join(",\n")
content += ");"
end
end end
end end

View File

@ -0,0 +1,184 @@
module Compass
class SpriteImporter < Sass::Importers::Base
attr_accessor :uri, :options
VAILD_FILE_NAME = /\A#{Sass::SCSS::RX::IDENT}\Z/
SPRITE_IMPORTER_REGEX = %r{((.+/)?([^\*.]+))/(.+?)\.png}
def self.load(uri, options)
Compass.quick_cache "Sprite_map:#{uri}#{options.inspect}", 5 do
klass = Compass::SpriteImporter.new
klass.uri, klass.options = uri, options
klass
end
end
def initialize(options ={})
@uri, @options = '', {}
options.each do |key, value|
send("#{key}=", value)
end
end
def find(uri, options)
@uri, @options = uri, options
if uri =~ SPRITE_IMPORTER_REGEX
return sass_engine
end
end
def find_relative(uri, base, options)
@uri, @options = uri, options
find(File.join(base, uri), options)
end
def to_s
content_for_images
end
def hash
self.class.name.hash
end
def eql?(other)
other.class == self.class
end
def key(uri, options={})
@uri, @options = uri, options
[self.class.name + ":" + File.dirname(File.expand_path(uri)), File.basename(uri)]
end
def self.path_and_name(uri)
Compass.quick_cache "Sprite_map_name:#{uri}", 5 do
if uri =~ SPRITE_IMPORTER_REGEX
[$1, $3]
else
[nil, nil]
end
end
end
# Name of this spite
def name
ensure_path_and_name!
@name
end
# The on-disk location of this sprite
def path
ensure_path_and_name!
@path
end
# Returns the Glob of image files for this sprite
def files
@files ||= Dir[File.join(Compass.configuration.images_path, uri)].sort
end
# Returns an Array of image names without the file extension
def sprite_names
@sprite_names ||= files.collect do |file|
filename = File.basename(file, '.png')
unless VAILD_FILE_NAME =~ filename
raise Compass::Error, "Sprite file names must be legal css identifiers. Please rename #{File.basename(file)}"
end
filename
end
end
# Returns the sass options for this sprite
def sass_options
@sass_options ||= options.merge(:filename => name, :syntax => :scss, :importer => self)
end
# Returns a Sass::Engine for this sprite object
def sass_engine
Sass::Engine.new(content_for_images, sass_options)
end
def ensure_path_and_name!
@path, @name = self.class.path_and_name(uri)
end
# Generates the Sass for this sprite file
def content_for_images(skip_overrides = false)
<<-SCSS
@import "compass/utilities/sprites/base";
// General Sprite Defaults
// You can override them before you import this file.
$#{name}-sprite-base-class: ".#{name}-sprite" !default;
$#{name}-sprite-dimensions: false !default;
$#{name}-position: 0% !default;
$#{name}-spacing: 0 !default;
$#{name}-repeat: no-repeat !default;
$#{name}-prefix: '' !default;
$#{name}-clean-up: true !default;
#{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\", $cleanup: $#{name}-clean-up);" : generate_overrides }
// All sprites should extend this class
// The #{name}-sprite mixin will do so for you.
\#{$#{name}-sprite-base-class} {
background: $#{name}-sprites no-repeat;
}
// Use this to set the dimensions of an element
// based on the size of the original image.
@mixin #{name}-sprite-dimensions($name) {
@include sprite-dimensions($#{name}-sprites, $name)
}
// Move the background position to display the sprite.
@mixin #{name}-sprite-position($name, $offset-x: 0, $offset-y: 0) {
@include sprite-background-position($#{name}-sprites, $name, $offset-x, $offset-y)
}
// Extends the sprite base class and set the background position for the desired sprite.
// It will also apply the image dimensions if $dimensions is true.
@mixin #{name}-sprite($name, $dimensions: $#{name}-sprite-dimensions, $offset-x: 0, $offset-y: 0) {
@extend \#{$#{name}-sprite-base-class};
@include sprite($#{name}-sprites, $name, $dimensions, $offset-x, $offset-y)
}
@mixin #{name}-sprites($sprite-names, $dimensions: $#{name}-sprite-dimensions, $prefix: sprite-map-name($#{name}-sprites)) {
@include sprites($#{name}-sprites, $sprite-names, $#{name}-sprite-base-class, $dimensions, $prefix)
}
// Generates a class for each sprited image.
@mixin all-#{name}-sprites($dimensions: $#{name}-sprite-dimensions, $prefix: sprite-map-name($#{name}-sprites)) {
@include #{name}-sprites(#{sprite_names.join(" ")}, $dimensions, $prefix);
}
SCSS
end
# Generates the override defaults for this Sprite
# <tt>$#{name}-#{sprite_name}-position </tt>
# <tt> $#{name}-#{sprite_name}-spacing </tt>
# <tt> #{name}-#{sprite_name}-repeat: </tt>
def generate_overrides
content = <<-TXT
// These variables control the generated sprite output
// You can override them selectively before you import this file.
TXT
sprite_names.map do |sprite_name|
content += <<-SCSS
$#{name}-#{sprite_name}-position: $#{name}-position !default;
$#{name}-#{sprite_name}-spacing: $#{name}-spacing !default;
$#{name}-#{sprite_name}-repeat: $#{name}-repeat !default;
SCSS
end.join
content += "\n$#{name}-sprites: sprite-map(\"#{uri}\", \n$cleanup: $#{name}-clean-up,\n"
content += sprite_names.map do |sprite_name|
%Q{ $#{sprite_name}-position: $#{name}-#{sprite_name}-position,
$#{sprite_name}-spacing: $#{name}-#{sprite_name}-spacing,
$#{sprite_name}-repeat: $#{name}-#{sprite_name}-repeat}
end.join(",\n")
content += ");"
end
end
end

View File

@ -11,7 +11,7 @@ describe Compass::SassExtensions::Sprites::Base do
Compass.configure_sass_plugin! Compass.configure_sass_plugin!
#fix this eww #fix this eww
options = Compass.sass_engine_options.extend Compass::SassExtensions::Functions::Sprites::VariableReader options = Compass.sass_engine_options.extend Compass::SassExtensions::Functions::Sprites::VariableReader
@map = Compass::SpriteMap.new("selectors/*.png", options) @map = Compass::SpriteImporter.new("selectors/*.png", options)
@base = Compass::SassExtensions::Sprites::Base.new(@map.sprite_names.map{|n| "selectors/#{n}.png"}, @map.path, 'selectors', @map.sass_engine, @map.options) @base = Compass::SassExtensions::Sprites::Base.new(@map.sprite_names.map{|n| "selectors/#{n}.png"}, @map.path, 'selectors', @map.sass_engine, @map.options)
end end

View File

@ -2,7 +2,7 @@ require 'spec_helper'
require 'fakefs/spec_helpers' require 'fakefs/spec_helpers'
require 'timecop' require 'timecop'
describe Compass::SpriteMap do describe Compass::SpriteImporter do
include FakeFS::SpecHelpers include FakeFS::SpecHelpers
let(:sprite_map) { self.class.describes.new(uri, options) } let(:sprite_map) { self.class.describes.new(uri, options) }

View File

@ -0,0 +1,10 @@
# Require any additional compass plugins here.
css_dir = "tmp"
sass_dir = "sass"
images_dir = "assets/images"
javascripts_dir = "assets/javascripts"
# Set this to the root of your project when deployed:
http_path = "/"
# To enable relative paths to assets via compass helper functions. Uncomment:
output_style = :compact
relative_assets = true

View File

@ -0,0 +1,2 @@
test
background: image_url("testing.png)

View File

@ -30,14 +30,16 @@ class CompassTest < Test::Unit::TestCase
# no project with errors exists to test aginst - leep of FAITH! # no project with errors exists to test aginst - leep of FAITH!
# *chriseppstein flogs himself* # *chriseppstein flogs himself*
# def test_on_stylesheet_error_callback def test_on_stylesheet_error_callback
# error = false error = false
# file = nil file = nil
# Compass.configuration.on_stylesheet_error {|filename, message| file = filename; error = true } before_compile = Proc.new do |config|
# within_project(:error) { } #requires a block but we don't need to pass anything - sdavis config.on_stylesheet_error {|filename, message| file = filename; error = true }
# assert error, "Project did not throw a compile error" end
# assert file.is_a?(String), "Filename was not a string" within_project(:error, before_compile) rescue nil;
# end assert error, "Project did not throw a compile error"
assert file.is_a?(String), "Filename was not a string"
end
def test_empty_project def test_empty_project
# With no sass files, we should have no css files. # With no sass files, we should have no css files.

View File

@ -0,0 +1,24 @@
require 'test_helper'
require 'compass'
class ActionsTest < Test::Unit::TestCase
class BaseActionExtender
include Compass::Actions
def options
@@options ||= {}
end
def working_path
"/tmp"
end
end
# When log4r is included, it sometimes breaks the Actions
def test_quiet_option
b = BaseActionExtender.new
b.logger = ""
b.options[:quiet] = true
# logger shouldn't be called... if it is, this will error
b.directory("/tmp/#{(rand * 1000000).to_i}")
end
end

View File

@ -3,7 +3,6 @@ require 'mocha'
require 'ostruct' require 'ostruct'
class SpritesImageTest < Test::Unit::TestCase class SpritesImageTest < Test::Unit::TestCase
def setup def setup
@images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images') @images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images')
file = StringIO.new("images_path = #{@images_src_path.inspect}\n") file = StringIO.new("images_path = #{@images_src_path.inspect}\n")
@ -19,8 +18,8 @@ class SpritesImageTest < Test::Unit::TestCase
let(:sprite_name) { File.basename(sprite_filename, '.png') } let(:sprite_name) { File.basename(sprite_filename, '.png') }
def parent def parent
map = Compass::SpriteMap.new(:uri => "selectors/*.png", :options => {'cleanup' => Sass::Script::Bool.new(true), 'smart_pack' => Sass::Script::Bool.new(false)}) importer = Compass::SpriteImporter.new(:uri => "selectors/*.png", :options => options)
@parent ||= Compass::SassExtensions::Sprites::Base.new(map.sprite_names.map{|n| "selectors/#{n}.png"}, map, map.sass_engine, map.options) @parent ||= Compass::SassExtensions::Sprites::SpriteMap.new(importer.sprite_names.map{|n| "selectors/#{n}.png"}, importer.path, importer.name, importer.sass_engine, importer.options)
end end
let(:options) do let(:options) do

View File

@ -1,7 +1,7 @@
require 'test_helper' require 'test_helper'
class SpritesBaseTest < Test::Unit::TestCase class SpriteMapTest < Test::Unit::TestCase
attr_accessor :options
def setup def setup
Hash.send(:include, Compass::SassExtensions::Functions::Sprites::VariableReader) Hash.send(:include, Compass::SassExtensions::Functions::Sprites::VariableReader)
@images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images') @images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images')
@ -16,8 +16,8 @@ class SpritesBaseTest < Test::Unit::TestCase
end end
def setup_map def setup_map
@map = Compass::SpriteMap.new(:uri => "selectors/*.png", :options => options) @importer = Compass::SpriteImporter.new(:uri => "selectors/*.png", :options => @options)
@base = Compass::SassExtensions::Sprites::Base.new(@map.sprite_names.map{|n| "selectors/#{n}.png"}, @map, @map.sass_engine, @map.options) @base = Compass::SassExtensions::Sprites::SpriteMap.new(@importer.sprite_names.map{|n| "selectors/#{n}.png"}, @importer.path, @importer.name, @importer.sass_engine, @importer.options)
end end
def teardown def teardown
@ -29,7 +29,7 @@ class SpritesBaseTest < Test::Unit::TestCase
end end
it "should have the sprite names" do it "should have the sprite names" do
assert_equal @map.sprite_names, @base.sprite_names assert_equal @importer.sprite_names, @base.sprite_names
end end
it 'should have image filenames' do it 'should have image filenames' do