Callbacks for the following events:

* sprite_saved
* sprite_generated
* stylesheet_saved
* stylesheet_error

From the compass configuration file you can attach code to run when the
event occurs like so:

on_stylesheet_error do |filename, message|
  # do something
end
This commit is contained in:
Scott Davis 2011-02-17 02:30:11 -05:00 committed by Chris Eppstein
parent 9fd7ac6e52
commit 2142430735
9 changed files with 160 additions and 46 deletions

View File

@ -217,13 +217,13 @@ later on.
approach. approach.
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="vertical-align:top;"><code>disable_warnings</code> </td> <td style="vertical-align:top;"><code>disable_warnings</code> </td>
<td style="vertical-align:top;">Boolean </td> <td style="vertical-align:top;">Boolean </td>
<td style="vertical-align:top;"> <td style="vertical-align:top;">
Set this to true to silence deprecation warnings. Set this to true to silence deprecation warnings.
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="vertical-align:top;"><code>sass_options</code> </td> <td style="vertical-align:top;"><code>sass_options</code> </td>
<td style="vertical-align:top;">Hash </td> <td style="vertical-align:top;">Hash </td>
@ -330,4 +330,37 @@ more than once. Example:
end end
This code will be called if the file is added, updated, or removed. Be sure to check for existence This code will be called if the file is added, updated, or removed. Be sure to check for existence
to avoid crashing the watcher in the case where the file has been removed. to avoid crashing the watcher in the case where the file has been removed.
## Callbacks
**`on_sprite_saved`** -- Pass this function a block of code that gets executed after a sprite is saved to disk. The block will be passed the filename. Can be invoked more then once. Example:
on_sprite_saved do |filename|
post_process(filename) if File.exists?(filename)
end
**`on_sprite_generated`** -- Pass this function a block of code that gets executed after a sprite is generated but before its saved to disk. The block will be passed an instance of `ChunkyPNG::Image`. Can be invoked more then once. Example:
on_sprite_generated do |sprite_data|
sprite_data.metadata['Caption'] = "This Image is &copy; My Company 2011"
end
**`on_stylesheet_saved`** -- Pass this function a block of code that gets executed after a stylesheet is processed. The block will be passed the filename. Can be invoked more then once. Example:
on_stylesheet_saved do |filename|
Growl.notify {
self.message "#{filename} updated!"
self.icon = '/path/to/success.jpg'
}
end
**`on_stylesheet_error`** -- Pass this function a block of code that gets executed if a stylesheet has an error while processing. The block will be passed the filename and the error message. Can be invoked more then once. Example:
on_stylesheet_error do |filename, message|
Growl.notify {
self.message = "#{filename}: #{message}"
self.icon = '/path/to/fail.jpg'
sticky!
}
end

View File

@ -5,6 +5,8 @@ end
require "compass/#{lib}" require "compass/#{lib}"
end end
require 'sass/callbacks'
module Compass module Compass
def base_directory def base_directory
File.expand_path(File.join(File.dirname(__FILE__), '..')) File.expand_path(File.join(File.dirname(__FILE__), '..'))

View File

@ -119,6 +119,7 @@ module Compass
end end
duration = additional_options[:time] ? "(#{(css_content.__duration * 1000).round / 1000.0}s)" : "" duration = additional_options[:time] ? "(#{(css_content.__duration * 1000).round / 1000.0}s)" : ""
write_file(css_filename, css_content, options.merge(:force => true, :extra => duration)) write_file(css_filename, css_content, options.merge(:force => true, :extra => duration))
Compass.configuration.send(:run_stylesheet_saved, File.basename(css_filename)) #run callback
end end
def should_compile?(sass_filename, css_filename) def should_compile?(sass_filename, css_filename)
@ -136,7 +137,10 @@ module Compass
# formatted to display in the browser (in development mode) # formatted to display in the browser (in development mode)
# if there's an error. # if there's an error.
def handle_exception(sass_filename, css_filename, e) def handle_exception(sass_filename, css_filename, e)
logger.record :error, basename(sass_filename), "(Line #{e.sass_line}: #{e.message})" formatted_error = "(Line #{e.sass_line}: #{e.message})"
file = basename(sass_filename)
logger.record :error, file, formatted_error
Compass.configuration.send(:run_styesheet_error, file, formatted_error) #run callback
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

@ -43,6 +43,6 @@ module Compass
end end
end end
['adapters', 'comments', 'defaults', 'helpers', 'inheritance', 'serialization', 'paths', 'data'].each do |lib| ['adapters', 'callbacks', 'comments', 'defaults', 'helpers', 'inheritance', 'serialization', 'paths', 'data'].each do |lib|
require "compass/configuration/#{lib}" require "compass/configuration/#{lib}"
end end

View File

@ -0,0 +1,25 @@
module Compass
module Configuration
module CallbackMethods
extend ::Sass::Callbacks
# on_sprite_generated
# yields the filename
# usage: on_sprite_save {|filename| do_somethign(filename) }
define_callback :sprite_saved
# on_sprite_generated
# yields 'ChunkyPNG::Image'
# usage: on_sprite_generated {|sprite_data| do_something(sprite_data) }
define_callback :sprite_generated
define_callback :stylesheet_saved
define_callback :stylesheet_error
end
class Callbacks
extend CallbackMethods
end
end
end

View File

@ -141,9 +141,11 @@ module Compass
end end
end end
def method_missing(meth) def method_missing(meth, *args, &block)
if inherited_data if inherited_data
inherited_data.send(meth) inherited_data.send(meth, *args, &block)
elsif Callbacks.respond_to?(meth, true)
Callbacks.send(meth, *args, &block)
else else
raise NoMethodError, meth.to_s raise NoMethodError, meth.to_s
end end

View File

@ -2,7 +2,7 @@ require 'digest/md5'
module Compass::SassExtensions::Functions::Sprites module Compass::SassExtensions::Functions::Sprites
ZERO = Sass::Script::Number::new(0) ZERO = Sass::Script::Number::new(0)
# Provides a consistent interface for getting a variable in ruby # Provides a consistent interface for getting a variable in ruby
# from a keyword argument hash that accounts for underscores/dash equivalence # from a keyword argument hash that accounts for underscores/dash equivalence
# and allows the caller to pass a symbol instead of a string. # and allows the caller to pass a symbol instead of a string.
@ -124,6 +124,7 @@ module Compass::SassExtensions::Functions::Sprites
def generate def generate
if generation_required? if generation_required?
save!(construct_sprite) save!(construct_sprite)
Compass.configuration.send(:run_sprite_generated, construct_sprite)
end end
end end
@ -155,7 +156,7 @@ module Compass::SassExtensions::Functions::Sprites
end end
end end
end end
output_png output_png
end end
# The on-the-disk filename of the sprite # The on-the-disk filename of the sprite
@ -180,7 +181,9 @@ module Compass::SassExtensions::Functions::Sprites
# saves the sprite for later retrieval # saves the sprite for later retrieval
def save!(output_png) def save!(output_png)
output_png.save filename saved = output_png.save filename
Compass.configuration.send(:run_sprite_saved, filename)
saved
end end
# All the full-path filenames involved in this sprite # All the full-path filenames involved in this sprite
@ -285,7 +288,7 @@ module Compass::SassExtensions::Functions::Sprites
end end
end end
Sass::Script::Functions.declare :sprite_file, [:map, :sprite] Sass::Script::Functions.declare :sprite_file, [:map, :sprite]
# Returns a url to the sprite image. # Returns a url to the sprite image.
def sprite_url(map) def sprite_url(map)
unless map.is_a?(SpriteMap) unless map.is_a?(SpriteMap)

View File

@ -3,7 +3,7 @@ require "compass/sprites"
require 'digest/md5' require 'digest/md5'
describe Compass::Sprites do describe Compass::Sprites do
before :each do before :each do
@images_src_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images') @images_src_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images')
@images_tmp_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images-tmp') @images_tmp_path = File.join(File.dirname(__FILE__), 'test_project', 'public', 'images-tmp')
@ -29,7 +29,7 @@ describe Compass::Sprites do
md5.update IO.read(map_location(file)) md5.update IO.read(map_location(file))
md5.hexdigest md5.hexdigest
end end
def render(scss) def render(scss)
scss = %Q(@import "compass"; #{scss}) scss = %Q(@import "compass"; #{scss})
options = Compass.sass_engine_options options = Compass.sass_engine_options
@ -40,9 +40,34 @@ describe Compass::Sprites do
# reformat to fit result of heredoc: # reformat to fit result of heredoc:
" #{css.gsub('@charset "UTF-8";', '').gsub(/\n/, "\n ").strip}\n" " #{css.gsub('@charset "UTF-8";', '').gsub(/\n/, "\n ").strip}\n"
end end
# DEFAULT USAGE:
#Callbacks
describe 'callbacks' do
it "should fire on_sprite_saved" do
saved = false
path = nil
Compass.configuration.on_sprite_saved {|filepath| path = filepath; saved = true }
render <<-SCSS
@import "squares/*.png";
@include all-squares-sprites;
SCSS
saved.should eq true
path.should be_kind_of String
end
it "should fire on_sprite_generated" do
saved = false
sprite_data = nil
Compass.configuration.on_sprite_generated {|data| sprite_data = data; saved = true }
render <<-SCSS
@import "squares/*.png";
@include all-squares-sprites;
SCSS
sprite_data.should be_kind_of ChunkyPNG::Image
saved.should eq true
end
end
# DEFAULT USAGE:
it "should generate sprite classes" do it "should generate sprite classes" do
css = render <<-SCSS css = render <<-SCSS
@import "squares/*.png"; @import "squares/*.png";
@ -90,15 +115,15 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 30] image_size('squares-*.png').should == [20, 30]
end end
it "should provide sprite mixin" do it "should provide sprite mixin" do
css = render <<-SCSS css = render <<-SCSS
@import "squares/*.png"; @import "squares/*.png";
.cubicle { .cubicle {
@include squares-sprite("ten-by-ten"); @include squares-sprite("ten-by-ten");
} }
.large-cube { .large-cube {
@include squares-sprite("twenty-by-twenty", true); @include squares-sprite("twenty-by-twenty", true);
} }
@ -120,9 +145,9 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 30] image_size('squares-*.png').should == [20, 30]
end end
# CUSTOMIZATIONS: # CUSTOMIZATIONS:
it "should be possible to change the base class" do it "should be possible to change the base class" do
css = render <<-SCSS css = render <<-SCSS
$squares-sprite-base-class: ".circles"; $squares-sprite-base-class: ".circles";
@ -135,7 +160,7 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 30] image_size('squares-*.png').should == [20, 30]
end end
it "should calculate the spacing between images but not before first image" do it "should calculate the spacing between images but not before first image" do
css = render <<-SCSS css = render <<-SCSS
$squares-ten-by-ten-spacing: 33px; $squares-ten-by-ten-spacing: 33px;
@ -157,7 +182,7 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 63] image_size('squares-*.png').should == [20, 63]
end end
it "should calculate the spacing between images" do it "should calculate the spacing between images" do
css = render <<-SCSS css = render <<-SCSS
$squares-twenty-by-twenty-spacing: 33px; $squares-twenty-by-twenty-spacing: 33px;
@ -179,7 +204,7 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 63] image_size('squares-*.png').should == [20, 63]
end end
it "should calculate the maximum spacing between images" do it "should calculate the maximum spacing between images" do
css = render <<-SCSS css = render <<-SCSS
$squares-ten-by-ten-spacing: 44px; $squares-ten-by-ten-spacing: 44px;
@ -202,7 +227,7 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 74] image_size('squares-*.png').should == [20, 74]
end end
it "should calculate the maximum spacing between images in reversed order" do it "should calculate the maximum spacing between images in reversed order" do
css = render <<-SCSS css = render <<-SCSS
$squares-ten-by-ten-spacing: 33px; $squares-ten-by-ten-spacing: 33px;
@ -225,7 +250,7 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 74] image_size('squares-*.png').should == [20, 74]
end end
it "should calculate the default spacing between images" do it "should calculate the default spacing between images" do
css = render <<-SCSS css = render <<-SCSS
$squares-spacing: 22px; $squares-spacing: 22px;
@ -247,22 +272,22 @@ describe Compass::Sprites do
CSS CSS
image_size('squares-*.png').should == [20, 52] image_size('squares-*.png').should == [20, 52]
end end
it "should use position adjustments in functions" do it "should use position adjustments in functions" do
css = render <<-SCSS css = render <<-SCSS
$squares: sprite-map("squares/*.png", $position: 100%); $squares: sprite-map("squares/*.png", $position: 100%);
.squares-sprite { .squares-sprite {
background: $squares no-repeat; background: $squares no-repeat;
} }
.adjusted-percentage { .adjusted-percentage {
background-position: sprite-position($squares, ten-by-ten, 100%); background-position: sprite-position($squares, ten-by-ten, 100%);
} }
.adjusted-px-1 { .adjusted-px-1 {
background-position: sprite-position($squares, ten-by-ten, 4px); background-position: sprite-position($squares, ten-by-ten, 4px);
} }
.adjusted-px-2 { .adjusted-px-2 {
background-position: sprite-position($squares, twenty-by-twenty, -3px, 2px); background-position: sprite-position($squares, twenty-by-twenty, -3px, 2px);
} }
@ -287,20 +312,20 @@ describe Compass::Sprites do
image_size('squares-*.png').should == [20, 30] image_size('squares-*.png').should == [20, 30]
image_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012' image_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012'
end end
it "should use position adjustments in mixins" do it "should use position adjustments in mixins" do
css = render <<-SCSS css = render <<-SCSS
$squares-position: 100%; $squares-position: 100%;
@import "squares/*.png"; @import "squares/*.png";
.adjusted-percentage { .adjusted-percentage {
@include squares-sprite("ten-by-ten", $offset-x: 100%); @include squares-sprite("ten-by-ten", $offset-x: 100%);
} }
.adjusted-px-1 { .adjusted-px-1 {
@include squares-sprite("ten-by-ten", $offset-x: 4px); @include squares-sprite("ten-by-ten", $offset-x: 4px);
} }
.adjusted-px-2 { .adjusted-px-2 {
@include squares-sprite("twenty-by-twenty", $offset-x: -3px, $offset-y: 2px); @include squares-sprite("twenty-by-twenty", $offset-x: -3px, $offset-y: 2px);
} }
@ -325,7 +350,7 @@ describe Compass::Sprites do
image_size('squares-*.png').should == [20, 30] image_size('squares-*.png').should == [20, 30]
image_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012' image_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012'
end end
it "should repeat the image" do it "should repeat the image" do
css = render <<-SCSS css = render <<-SCSS
$squares-repeat: repeat; $squares-repeat: repeat;
@ -372,7 +397,7 @@ describe Compass::Sprites do
image_size('squares-*.png').should == [30, 30] image_size('squares-*.png').should == [30, 30]
image_md5('squares-*.png').should == '2fb19ef9c83018c93c6f147af3a56cb2' image_md5('squares-*.png').should == '2fb19ef9c83018c93c6f147af3a56cb2'
end end
it "should provide a nice errors for lemonade's old users" do it "should provide a nice errors for lemonade's old users" do
proc do proc do
render <<-SCSS render <<-SCSS
@ -393,7 +418,7 @@ describe Compass::Sprites do
proc do proc do
render <<-SCSS render <<-SCSS
@import "squares/*.png"; @import "squares/*.png";
.squares { .squares {
background: sprite-position("squares/twenty-by-twenty.png") no-repeat; background: sprite-position("squares/twenty-by-twenty.png") no-repeat;
} }
@ -401,7 +426,7 @@ describe Compass::Sprites do
end.should raise_error Sass::SyntaxError, end.should raise_error Sass::SyntaxError,
%q(The first argument to sprite-position() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.) %q(The first argument to sprite-position() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
end end
it "should work even if @import is missing" do it "should work even if @import is missing" do
actual_css = render <<-SCSS actual_css = render <<-SCSS
.squares { .squares {
@ -414,5 +439,5 @@ describe Compass::Sprites do
} }
CSS CSS
end end
end end

View File

@ -20,6 +20,26 @@ class CompassTest < Test::Unit::TestCase
end end
end end
def test_on_stylesheet_saved_callback
saved = false
filepath = nil
Compass.configuration.on_stylesheet_saved {|filepath| path = filepath; saved = true }
within_project(:blueprint) { } #requires a block but we don't need to pass anything - sdavis
assert saved, "Stylesheet callback didn't get called"
assert filepath.is_a?(String), "Path is not a string"
end
# no project with errors exists to test aginst - leep of FAITH!
# *chriseppstein flogs himself*
# def test_on_stylesheet_error_callback
# error = false
# file = nil
# Compass.configuration.on_stylesheet_error {|filename, message| file = filename; error = true }
# within_project(:error) { } #requires a block but we don't need to pass anything - sdavis
# 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.
within_project(:empty) do |proj| within_project(:empty) do |proj|
@ -114,10 +134,10 @@ private
end end
yield Compass.configuration yield Compass.configuration
rescue rescue
save_output(project_name) save_output(project_name)
raise raise
end end
def each_css_file(dir, &block) def each_css_file(dir, &block)
Dir.glob("#{dir}/**/*.css").each(&block) Dir.glob("#{dir}/**/*.css").each(&block)
end end
@ -145,15 +165,15 @@ private
def tempfile_path(project_name) def tempfile_path(project_name)
File.join(project_path(project_name), "tmp") File.join(project_path(project_name), "tmp")
end end
def template_path(project_name) def template_path(project_name)
File.join(project_path(project_name), "sass") File.join(project_path(project_name), "sass")
end end
def result_path(project_name) def result_path(project_name)
File.join(project_path(project_name), "css") File.join(project_path(project_name), "css")
end end
def save_path(project_name) def save_path(project_name)
File.join(project_path(project_name), "saved") File.join(project_path(project_name), "saved")
end end