diff --git a/doc-src/content/posts/2011-05-09-compass-django.markdown b/doc-src/content/posts/2011-05-09-compass-django.markdown index 29c4bc29..61bc91fd 100644 --- a/doc-src/content/posts/2011-05-09-compass-django.markdown +++ b/doc-src/content/posts/2011-05-09-compass-django.markdown @@ -7,7 +7,7 @@ author: eric It's easy! Follow these two simple steps: 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. @@ -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. -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. -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)_ +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.** \ No newline at end of file diff --git a/lib/compass/actions.rb b/lib/compass/actions.rb index fe4df6e1..bc4a2b82 100644 --- a/lib/compass/actions.rb +++ b/lib/compass/actions.rb @@ -4,7 +4,7 @@ module Compass attr_writer :logger def logger - @logger ||= Logger.new + @logger ||= ::Compass::Logger.new end # 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. def directory(dir, options = nil) options ||= self.options if self.respond_to?(:options) + options ||= {} if File.exists?(dir) && File.directory?(dir) - # logger.record :exists, basename(dir) unless options[:quiet] + # do nothing elsif File.exists?(dir) msg = "#{basename(dir)} already exists and is not a directory." raise Compass::FilesystemConflict.new(msg) else - logger.record :directory, separate("#{basename(dir)}/") + log_action :directory, separate("#{basename(dir)}/"), options FileUtils.mkdir_p(dir) unless options[:dry_run] end end @@ -33,20 +34,19 @@ module Compass options ||= self.options if self.respond_to?(:options) skip_write = options[:dry_run] contents = process_erb(contents, options[:erb]) if options[:erb] - extra = options[:extra] || "" if File.exists?(file_name) existing_contents = IO.read(file_name) if existing_contents == contents - logger.record :identical, basename(file_name), extra + log_action :identical, basename(file_name), options skip_write = true elsif options[:force] - logger.record :overwrite, basename(file_name), extra + log_action :overwrite, basename(file_name), options else msg = "File #{basename(file_name)} already exists. Run with --force to force overwrite." raise Compass::FilesystemConflict.new(msg) end else - logger.record :create, basename(file_name), extra + log_action :create, basename(file_name), options end if skip_write FileUtils.touch file_name unless options[:dry_run] @@ -67,7 +67,7 @@ module Compass def remove(file_name) if File.exists?(file_name) File.unlink file_name - logger.record :remove, basename(file_name) + log_action :remove, basename(file_name), options end end @@ -95,5 +95,14 @@ module Compass (path[-1..-1] == File::SEPARATOR) ? path[0..-2] : path 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 diff --git a/lib/compass/commands/sprite.rb b/lib/compass/commands/sprite.rb index 2b33f9fc..b1c8bee9 100644 --- a/lib/compass/commands/sprite.rb +++ b/lib/compass/commands/sprite.rb @@ -39,7 +39,7 @@ module Compass def perform 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[:skip_overrides] ||= false contents = sprites.content_for_images(options[:skip_overrides]) diff --git a/lib/compass/commands/update_project.rb b/lib/compass/commands/update_project.rb index b98fa93a..1eb0a595 100644 --- a/lib/compass/commands/update_project.rb +++ b/lib/compass/commands/update_project.rb @@ -33,6 +33,7 @@ module Compass def perform compiler = new_compiler_instance check_for_sass_files!(compiler) + compiler.clean! if compiler.new_config? compiler.run end diff --git a/lib/compass/commands/watch_project.rb b/lib/compass/commands/watch_project.rb index cdc2bf2f..0246aca9 100644 --- a/lib/compass/commands/watch_project.rb +++ b/lib/compass/commands/watch_project.rb @@ -135,7 +135,7 @@ module Compass def recompile(base = nil, relative = nil) @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? begin puts ">>> Change detected to: #{relative || compiler.relative_stylesheet_name(file)}" diff --git a/lib/compass/compiler.rb b/lib/compass/compiler.rb index 0f4f70f9..e7f6feef 100644 --- a/lib/compass/compiler.rb +++ b/lib/compass/compiler.rb @@ -153,7 +153,7 @@ module Compass formatted_error = "(Line #{e.sass_line}: #{e.message})" file = basename(sass_filename) 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) end diff --git a/lib/compass/configuration/adapters.rb b/lib/compass/configuration/adapters.rb index 6a35c91a..352eb3c4 100644 --- a/lib/compass/configuration/adapters.rb +++ b/lib/compass/configuration/adapters.rb @@ -23,7 +23,7 @@ module Compass plugin_opts[:cache_location] = cache_path unless cache_path.nil? plugin_opts.merge!(sass_options || {}) plugin_opts[:load_paths] ||= [] - plugin_opts[:load_paths] << Compass::SpriteMap.new + plugin_opts[:load_paths] << Compass::SpriteImporter.new plugin_opts end @@ -63,7 +63,7 @@ module Compass next p if p.respond_to?(:find_relative) Sass::Importers::Filesystem.new(p.to_s) end - load_paths << Compass::SpriteMap.new + load_paths << Compass::SpriteImporter.new load_paths end end diff --git a/lib/compass/sass_extensions/functions/sprites.rb b/lib/compass/sass_extensions/functions/sprites.rb index 69b7599e..0bd54ff5 100644 --- a/lib/compass/sass_extensions/functions/sprites.rb +++ b/lib/compass/sass_extensions/functions/sprites.rb @@ -10,7 +10,7 @@ module Compass::SassExtensions::Functions::Sprites 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: # # $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. def sprite_map(glob, kwargs = {}) kwargs.extend VariableReader - Compass::SassExtensions::Sprites::Base.from_uri(glob, self, kwargs) + Compass::SassExtensions::Sprites::SpriteMap.from_uri(glob, self, kwargs) end Sass::Script::Functions.declare :sprite_map, [:glob], :var_kwargs => true @@ -160,7 +160,7 @@ protected end 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) end end diff --git a/lib/compass/sass_extensions/sprites.rb b/lib/compass/sass_extensions/sprites.rb index faf6afec..919de7fd 100644 --- a/lib/compass/sass_extensions/sprites.rb +++ b/lib/compass/sass_extensions/sprites.rb @@ -1,4 +1,14 @@ require 'digest/md5' +require 'compass/sprite_importer' + + +module Compass + module SassExtensions + module Sprites + end + end +end + #modules require 'compass/sass_extensions/sprites/sprite' 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/row_fitter' require 'compass/sass_extensions/sprites/image_row' -require 'compass/sass_extensions/sprites/base' -require 'compass/sass_extensions/sprites/engines' - -module Compass - module SassExtensions - module Sprites - end - end -end \ No newline at end of file +require 'compass/sass_extensions/sprites/engines' \ No newline at end of file diff --git a/lib/compass/sass_extensions/sprites/sprite_map.rb b/lib/compass/sass_extensions/sprites/sprite_map.rb index 69e3edf9..2bdf905c 100644 --- a/lib/compass/sass_extensions/sprites/sprite_map.rb +++ b/lib/compass/sass_extensions/sprites/sprite_map.rb @@ -1,185 +1,213 @@ module Compass - class SpriteMap < 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::SpriteMap.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] + module SassExtensions + module Sprites + class SpriteMap < Sass::Script::Literal + + + # Initialize a new sprite object from a relative file path + # the path is relative to the <tt>images_path</tt> confguration option + def self.from_uri(uri, context, kwargs) + importer = ::Compass::SpriteImporter.new(:uri => uri.value, :options => {}) + sprites = importer.files.map do |sprite| + sprite.gsub(Compass.configuration.images_path+"/", "") + end + new(sprites, importer.path, importer.name, context, kwargs) 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)}" + + # Loads the sprite engine + def require_engine! + self.class.send(:include, eval("::Compass::SassExtensions::Sprites::#{modulize}Engine")) 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 - - # 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 - diff --git a/lib/compass/sprite_importer.rb b/lib/compass/sprite_importer.rb new file mode 100644 index 00000000..1b02ccba --- /dev/null +++ b/lib/compass/sprite_importer.rb @@ -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 + diff --git a/spec/compass/sass_extensions/sprites/base_spec.rb b/spec/compass/sass_extensions/sprites/base_spec.rb index 6a086737..1d8cfedc 100644 --- a/spec/compass/sass_extensions/sprites/base_spec.rb +++ b/spec/compass/sass_extensions/sprites/base_spec.rb @@ -11,7 +11,7 @@ describe Compass::SassExtensions::Sprites::Base do Compass.configure_sass_plugin! #fix this eww 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) end diff --git a/spec/compass/sass_extensions/sprites/sprite_map_spec.rb b/spec/compass/sass_extensions/sprites/sprite_map_spec.rb index a6eee9ea..30a3895d 100644 --- a/spec/compass/sass_extensions/sprites/sprite_map_spec.rb +++ b/spec/compass/sass_extensions/sprites/sprite_map_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'fakefs/spec_helpers' require 'timecop' -describe Compass::SpriteMap do +describe Compass::SpriteImporter do include FakeFS::SpecHelpers let(:sprite_map) { self.class.describes.new(uri, options) } diff --git a/test/fixtures/stylesheets/error/config.rb b/test/fixtures/stylesheets/error/config.rb new file mode 100644 index 00000000..f4a6649f --- /dev/null +++ b/test/fixtures/stylesheets/error/config.rb @@ -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 diff --git a/test/fixtures/stylesheets/error/sass/screen.sass b/test/fixtures/stylesheets/error/sass/screen.sass new file mode 100644 index 00000000..6b797912 --- /dev/null +++ b/test/fixtures/stylesheets/error/sass/screen.sass @@ -0,0 +1,2 @@ +test + background: image_url("testing.png) diff --git a/test/integrations/compass_test.rb b/test/integrations/compass_test.rb index 26491328..ebe2bc54 100644 --- a/test/integrations/compass_test.rb +++ b/test/integrations/compass_test.rb @@ -30,14 +30,16 @@ class CompassTest < Test::Unit::TestCase # 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_on_stylesheet_error_callback + error = false + file = nil + before_compile = Proc.new do |config| + config.on_stylesheet_error {|filename, message| file = filename; error = true } + end + within_project(:error, before_compile) rescue nil; + 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. diff --git a/test/units/actions_test.rb b/test/units/actions_test.rb new file mode 100644 index 00000000..21b26414 --- /dev/null +++ b/test/units/actions_test.rb @@ -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 \ No newline at end of file diff --git a/test/units/sprites/image_test.rb b/test/units/sprites/image_test.rb index 36dcc93b..592cedbd 100644 --- a/test/units/sprites/image_test.rb +++ b/test/units/sprites/image_test.rb @@ -3,7 +3,6 @@ require 'mocha' require 'ostruct' class SpritesImageTest < Test::Unit::TestCase - def setup @images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images') 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') } def parent - map = Compass::SpriteMap.new(:uri => "selectors/*.png", :options => {'cleanup' => Sass::Script::Bool.new(true), 'smart_pack' => Sass::Script::Bool.new(false)}) - @parent ||= Compass::SassExtensions::Sprites::Base.new(map.sprite_names.map{|n| "selectors/#{n}.png"}, map, map.sass_engine, map.options) + importer = Compass::SpriteImporter.new(:uri => "selectors/*.png", :options => 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 let(:options) do diff --git a/test/units/sprites/base_test.rb b/test/units/sprites/sprite_map_test.rb similarity index 87% rename from test/units/sprites/base_test.rb rename to test/units/sprites/sprite_map_test.rb index 511a5501..69c375ef 100644 --- a/test/units/sprites/base_test.rb +++ b/test/units/sprites/sprite_map_test.rb @@ -1,7 +1,7 @@ require 'test_helper' -class SpritesBaseTest < Test::Unit::TestCase - attr_accessor :options +class SpriteMapTest < Test::Unit::TestCase + def setup Hash.send(:include, Compass::SassExtensions::Functions::Sprites::VariableReader) @images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images') @@ -16,8 +16,8 @@ class SpritesBaseTest < Test::Unit::TestCase end def setup_map - @map = Compass::SpriteMap.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) + @importer = Compass::SpriteImporter.new(:uri => "selectors/*.png", :options => @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 def teardown @@ -29,7 +29,7 @@ class SpritesBaseTest < Test::Unit::TestCase end it "should have the sprite names" do - assert_equal @map.sprite_names, @base.sprite_names + assert_equal @importer.sprite_names, @base.sprite_names end it 'should have image filenames' do