From 214243073541fc8f90236b8af114fb3af0551b55 Mon Sep 17 00:00:00 2001 From: Scott Davis Date: Thu, 17 Feb 2011 02:30:11 -0500 Subject: [PATCH] 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 --- .../configuration-reference.markdown | 45 +++++++++-- lib/compass.rb | 2 + lib/compass/compiler.rb | 6 +- lib/compass/configuration.rb | 2 +- lib/compass/configuration/callbacks.rb | 25 ++++++ lib/compass/configuration/inheritance.rb | 6 +- .../sass_extensions/functions/sprites.rb | 11 ++- spec/sprites_spec.rb | 79 ++++++++++++------- test/compass_test.rb | 30 +++++-- 9 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 lib/compass/configuration/callbacks.rb diff --git a/doc-src/content/help/tutorials/configuration-reference.markdown b/doc-src/content/help/tutorials/configuration-reference.markdown index 8bf0d11b..0f6475e9 100644 --- a/doc-src/content/help/tutorials/configuration-reference.markdown +++ b/doc-src/content/help/tutorials/configuration-reference.markdown @@ -217,13 +217,13 @@ later on. approach. - - disable_warnings - Boolean + + disable_warnings + Boolean Set this to true to silence deprecation warnings. - - + + sass_options Hash @@ -330,4 +330,37 @@ more than once. Example: end 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. \ No newline at end of file +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 © 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 \ No newline at end of file diff --git a/lib/compass.rb b/lib/compass.rb index 2f0b7ec7..5ce7268d 100644 --- a/lib/compass.rb +++ b/lib/compass.rb @@ -5,6 +5,8 @@ end require "compass/#{lib}" end +require 'sass/callbacks' + module Compass def base_directory File.expand_path(File.join(File.dirname(__FILE__), '..')) diff --git a/lib/compass/compiler.rb b/lib/compass/compiler.rb index b259dfce..bccb6489 100644 --- a/lib/compass/compiler.rb +++ b/lib/compass/compiler.rb @@ -119,6 +119,7 @@ module Compass end duration = additional_options[:time] ? "(#{(css_content.__duration * 1000).round / 1000.0}s)" : "" 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 def should_compile?(sass_filename, css_filename) @@ -136,7 +137,10 @@ module Compass # formatted to display in the browser (in development mode) # if there's an error. 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) end diff --git a/lib/compass/configuration.rb b/lib/compass/configuration.rb index c51e9a88..f094570b 100644 --- a/lib/compass/configuration.rb +++ b/lib/compass/configuration.rb @@ -43,6 +43,6 @@ module Compass 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}" end diff --git a/lib/compass/configuration/callbacks.rb b/lib/compass/configuration/callbacks.rb new file mode 100644 index 00000000..2f034ee9 --- /dev/null +++ b/lib/compass/configuration/callbacks.rb @@ -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 \ No newline at end of file diff --git a/lib/compass/configuration/inheritance.rb b/lib/compass/configuration/inheritance.rb index b44954ca..24de6b4d 100644 --- a/lib/compass/configuration/inheritance.rb +++ b/lib/compass/configuration/inheritance.rb @@ -141,9 +141,11 @@ module Compass end end - def method_missing(meth) + def method_missing(meth, *args, &block) 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 raise NoMethodError, meth.to_s end diff --git a/lib/compass/sass_extensions/functions/sprites.rb b/lib/compass/sass_extensions/functions/sprites.rb index 7df04e20..45343df9 100644 --- a/lib/compass/sass_extensions/functions/sprites.rb +++ b/lib/compass/sass_extensions/functions/sprites.rb @@ -2,7 +2,7 @@ require 'digest/md5' module Compass::SassExtensions::Functions::Sprites ZERO = Sass::Script::Number::new(0) - + # Provides a consistent interface for getting a variable in ruby # from a keyword argument hash that accounts for underscores/dash equivalence # and allows the caller to pass a symbol instead of a string. @@ -124,6 +124,7 @@ module Compass::SassExtensions::Functions::Sprites def generate if generation_required? save!(construct_sprite) + Compass.configuration.send(:run_sprite_generated, construct_sprite) end end @@ -155,7 +156,7 @@ module Compass::SassExtensions::Functions::Sprites end end end - output_png + output_png end # The on-the-disk filename of the sprite @@ -180,7 +181,9 @@ module Compass::SassExtensions::Functions::Sprites # saves the sprite for later retrieval def save!(output_png) - output_png.save filename + saved = output_png.save filename + Compass.configuration.send(:run_sprite_saved, filename) + saved end # All the full-path filenames involved in this sprite @@ -285,7 +288,7 @@ module Compass::SassExtensions::Functions::Sprites end end Sass::Script::Functions.declare :sprite_file, [:map, :sprite] - + # Returns a url to the sprite image. def sprite_url(map) unless map.is_a?(SpriteMap) diff --git a/spec/sprites_spec.rb b/spec/sprites_spec.rb index c619e9f5..9f2b2f12 100644 --- a/spec/sprites_spec.rb +++ b/spec/sprites_spec.rb @@ -3,7 +3,7 @@ require "compass/sprites" require 'digest/md5' describe Compass::Sprites do - + before :each do @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') @@ -29,7 +29,7 @@ describe Compass::Sprites do md5.update IO.read(map_location(file)) md5.hexdigest end - + def render(scss) scss = %Q(@import "compass"; #{scss}) options = Compass.sass_engine_options @@ -40,9 +40,34 @@ describe Compass::Sprites do # reformat to fit result of heredoc: " #{css.gsub('@charset "UTF-8";', '').gsub(/\n/, "\n ").strip}\n" 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 css = render <<-SCSS @import "squares/*.png"; @@ -90,15 +115,15 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 30] end - + it "should provide sprite mixin" do css = render <<-SCSS @import "squares/*.png"; - + .cubicle { @include squares-sprite("ten-by-ten"); } - + .large-cube { @include squares-sprite("twenty-by-twenty", true); } @@ -120,9 +145,9 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 30] end - + # CUSTOMIZATIONS: - + it "should be possible to change the base class" do css = render <<-SCSS $squares-sprite-base-class: ".circles"; @@ -135,7 +160,7 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 30] end - + it "should calculate the spacing between images but not before first image" do css = render <<-SCSS $squares-ten-by-ten-spacing: 33px; @@ -157,7 +182,7 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 63] end - + it "should calculate the spacing between images" do css = render <<-SCSS $squares-twenty-by-twenty-spacing: 33px; @@ -179,7 +204,7 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 63] end - + it "should calculate the maximum spacing between images" do css = render <<-SCSS $squares-ten-by-ten-spacing: 44px; @@ -202,7 +227,7 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 74] end - + it "should calculate the maximum spacing between images in reversed order" do css = render <<-SCSS $squares-ten-by-ten-spacing: 33px; @@ -225,7 +250,7 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 74] end - + it "should calculate the default spacing between images" do css = render <<-SCSS $squares-spacing: 22px; @@ -247,22 +272,22 @@ describe Compass::Sprites do CSS image_size('squares-*.png').should == [20, 52] end - + it "should use position adjustments in functions" do css = render <<-SCSS $squares: sprite-map("squares/*.png", $position: 100%); .squares-sprite { background: $squares no-repeat; } - + .adjusted-percentage { background-position: sprite-position($squares, ten-by-ten, 100%); } - + .adjusted-px-1 { background-position: sprite-position($squares, ten-by-ten, 4px); } - + .adjusted-px-2 { 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_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012' end - + it "should use position adjustments in mixins" do css = render <<-SCSS $squares-position: 100%; @import "squares/*.png"; - + .adjusted-percentage { @include squares-sprite("ten-by-ten", $offset-x: 100%); } - + .adjusted-px-1 { @include squares-sprite("ten-by-ten", $offset-x: 4px); } - + .adjusted-px-2 { @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_md5('squares-*.png').should == '652b67f5e9092520d6f26caae7e18012' end - + it "should repeat the image" do css = render <<-SCSS $squares-repeat: repeat; @@ -372,7 +397,7 @@ describe Compass::Sprites do image_size('squares-*.png').should == [30, 30] image_md5('squares-*.png').should == '2fb19ef9c83018c93c6f147af3a56cb2' end - + it "should provide a nice errors for lemonade's old users" do proc do render <<-SCSS @@ -393,7 +418,7 @@ describe Compass::Sprites do proc do render <<-SCSS @import "squares/*.png"; - + .squares { background: sprite-position("squares/twenty-by-twenty.png") no-repeat; } @@ -401,7 +426,7 @@ describe Compass::Sprites do 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.) end - + it "should work even if @import is missing" do actual_css = render <<-SCSS .squares { @@ -414,5 +439,5 @@ describe Compass::Sprites do } CSS end - + end \ No newline at end of file diff --git a/test/compass_test.rb b/test/compass_test.rb index f86f94c3..ed3b8581 100644 --- a/test/compass_test.rb +++ b/test/compass_test.rb @@ -20,6 +20,26 @@ class CompassTest < Test::Unit::TestCase 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 # With no sass files, we should have no css files. within_project(:empty) do |proj| @@ -114,10 +134,10 @@ private end yield Compass.configuration rescue - save_output(project_name) + save_output(project_name) raise end - + def each_css_file(dir, &block) Dir.glob("#{dir}/**/*.css").each(&block) end @@ -145,15 +165,15 @@ private def tempfile_path(project_name) File.join(project_path(project_name), "tmp") end - + def template_path(project_name) File.join(project_path(project_name), "sass") end - + def result_path(project_name) File.join(project_path(project_name), "css") end - + def save_path(project_name) File.join(project_path(project_name), "saved") end