merged version.yml

This commit is contained in:
Scott Davis 2011-06-27 00:56:29 -04:00
commit cc81fe6fac
60 changed files with 666 additions and 1157 deletions

View File

@ -4,4 +4,4 @@ rvm:
- jruby - jruby
- rbx - rbx
- ree - ree
script: "bundle exec rake test" script: "bundle exec rake test features"

View File

@ -5,7 +5,7 @@ gemspec
gem "cucumber", "~> 0.9.2" gem "cucumber", "~> 0.9.2"
gem "rspec", "~>2.0.0" gem "rspec", "~>2.0.0"
gem "rails", "~>3.0.0.rc" gem "rails", "~>3.0.0.rc"
gem "compass-validator", "3.0.0" gem "compass-validator", "3.0.1"
gem "css_parser", "~> 1.0.1" gem "css_parser", "~> 1.0.1"
gem "sass", "~>3.1" gem "sass", "~>3.1"
gem "haml", "~> 3.1" gem "haml", "~> 3.1"

View File

@ -7,8 +7,8 @@ GIT
PATH PATH
remote: . remote: .
specs: specs:
compass (0.11.1.f248c22) compass (0.11.3.b352e8b)
chunky_png (~> 1.1) chunky_png (~> 1.2)
fssm (>= 0.2.7) fssm (>= 0.2.7)
sass (~> 3.1) sass (~> 3.1)
@ -51,7 +51,7 @@ GEM
sys-uname sys-uname
builder (2.1.2) builder (2.1.2)
chunky_png (1.2.0) chunky_png (1.2.0)
compass-validator (3.0.0) compass-validator (3.0.1)
css_parser (1.0.1) css_parser (1.0.1)
cucumber (0.9.4) cucumber (0.9.4)
builder (~> 2.1.2) builder (~> 2.1.2)
@ -136,7 +136,7 @@ DEPENDENCIES
autotest autotest
autotest-fsevent autotest-fsevent
compass! compass!
compass-validator (= 3.0.0) compass-validator (= 3.0.1)
css_parser (~> 1.0.1) css_parser (~> 1.0.1)
cucumber (~> 0.9.2) cucumber (~> 0.9.2)
diff-lcs (~> 1.1.2) diff-lcs (~> 1.1.2)

View File

@ -16,7 +16,7 @@ Gem::Specification.new do |gemspec|
gemspec.summary = %q{A Real Stylesheet Framework} gemspec.summary = %q{A Real Stylesheet Framework}
gemspec.add_dependency 'sass', '~> 3.1' gemspec.add_dependency 'sass', '~> 3.1'
gemspec.add_dependency 'chunky_png', '~> 1.1' gemspec.add_dependency 'chunky_png', '~> 1.2'
gemspec.add_dependency 'fssm', '>= 0.2.7' gemspec.add_dependency 'fssm', '>= 0.2.7'
gemspec.files = %w(README.markdown LICENSE.markdown VERSION.yml Rakefile) gemspec.files = %w(README.markdown LICENSE.markdown VERSION.yml Rakefile)

View File

@ -14,6 +14,29 @@ The Documentation for the [latest stable release](http://compass-style.org/docs/
The Documentation for the [latest preview release](http://beta.compass-style.org/) The Documentation for the [latest preview release](http://beta.compass-style.org/)
0.11.3 (06/11/2011)
-------------------
**Note:** Due to some internal changes to compass you may have issue with your sass cache. Run `compass clean` to clear your cache.
* The `pie-clearfix` mixin has been updated. If you have to
support Firefox < 3.5, please update your stylesheets
to use `legacy-pie-clearfix` instead.
* Added a new command: `compass clean` which removes any generated
css files and clears the sass cache.
* Enable IE 10 support for flexible box with the -ms prefix.
* A small change to how generated sprites are named for better
rails 3.1 compatibility.
* Fixes for the compass --quiet mode.
* It is now possible to generate cache buster urls that manipulate
the path of the image instead of the query string. This makes
images work better with proxies, but will require some web server
configuration. [Docs](/help/tutorials/configuration-reference/#asset-cache-buster)
* Numerous small bug fixes to sprites.
* Sprite Engines are now classes see [Docs](/help/tutorials/extending) for more information
* Sprite classes have bee re-factored into modules for readability
* Sprites will no longer cause `undefined method 'find' for #<Compass::SpriteMap>` when adding or removing sprite files
0.11.2 (06/10/2011) 0.11.2 (06/10/2011)
------------------- -------------------
* Sprites will now by default remove any old versions of the sprite. A new configuration * Sprites will now by default remove any old versions of the sprite. A new configuration

View File

@ -307,10 +307,14 @@ the asset host configuration is ignored.
--- ---
<a name="asset-cache-buster"></a>
**`asset_cache_buster`** Pass this function a block of code that defines the **`asset_cache_buster`** Pass this function a block of code that defines the
cache buster strategy to be used. The block must return nil or a string that can cache buster strategy to be used. The block must return nil, a string or a hash.
be appended to a url as a query parameter. The returned string must not include If the returned value is a hash the values of :path and/or :query is used to generate
the starting `?`. The block will be passed the root-relative url of the asset. a cache busted path to the asset. If a string value is returned, it is added as a query string.
The returned values for query strings must not include the starting `?`.
The block will be passed the root-relative url of the asset.
If the block accepts two arguments, it will also be passed a path If the block accepts two arguments, it will also be passed a path
that points to the asset on disk — which may or may not exist. that points to the asset on disk — which may or may not exist.
@ -324,6 +328,18 @@ that points to the asset on disk — which may or may not exist.
end end
end end
Busting the cache via path:
asset_cache_buster do |path, real_path|
if File.exists?(real_path)
pathname = Pathname.new(path)
modified_time = File.mtime(real_path).strftime("%s")
new_path = "%s/%s-%s%s" % [pathname.dirname, pathname.basename(pathname.extname), modified_time, pathname.extname]
{:path => new_path, :query => nil}
end
end
To disable the asset cache buster: To disable the asset cache buster:
asset_cache_buster :none asset_cache_buster :none

View File

@ -14,11 +14,9 @@ The sprite engine is the work horse of sprite generation it's the interface for
### Requirements ### Requirements
A sprite engine requires only one method and that is `construct_sprite` which must return an object that responds to `save(filepath)` A sprite engine requires two methods `construct_sprite`, and `save(filename)`
Once inside this method you have access to `images` which is a collection of [Compass::SassExtensions::Sprites::Image](http://rdoc.info/github/chriseppstein/compass/dda7c9/Compass/SassExtensions/Sprites/Image) Once inside the class you have access to `images` which is a collection of [Compass::SassExtensions::Sprites::Image](http://rdoc.info/github/chriseppstein/compass/dda7c9/Compass/SassExtensions/Sprites/Image)
Since the Engine module extends base you also have access to all methods in [Compass::SassExtensions::Sprites::Base](http://rdoc.info/github/chriseppstein/compass/dda7c9/Compass/SassExtensions/Sprites/Base)
### Configuration ### Configuration
@ -26,7 +24,7 @@ To enable your sprite engine from the config file set
sprite_engine = :<engine name> sprite_engine = :<engine name>
The example below will load `Compass::SassExtension::Sprites::ChunkyPngEngine` The example below will load `Compass::SassExtension::Sprites::ChunkyPngEngine.new(width, height, images)`
sprite_engine = :chunky_png sprite_engine = :chunky_png
@ -35,13 +33,16 @@ The example below will load `Compass::SassExtension::Sprites::ChunkyPngEngine`
module Compass module Compass
module SassExtensions module SassExtensions
module Sprites module Sprites
module <engine name>Engine class ChunkyPngEngine < Compass::SassExtensions::Sprites::Engine
# Returns an object
def construct_sprite def construct_sprite
#must return a image object that responds to save(filename) #do something
end
def save(filename)
#save file
end end
end end
end end
end end

View File

@ -34,7 +34,7 @@ And you'll get the following CSS output:
.icon-delete, .icon-delete,
.icon-edit, .icon-edit,
.icon-new, .icon-new,
.icon-save { background: url('/images/icon-34fe0604ab.png') no-repeat; } .icon-save { background: url('/images/icon-s34fe0604ab.png') no-repeat; }
.icon-delete { background-position: 0 0; } .icon-delete { background-position: 0 0; }
.icon-edit { background-position: 0 -32px; } .icon-edit { background-position: 0 -32px; }
@ -74,7 +74,7 @@ And your stylesheet will compile to:
.actions .new, .actions .new,
.actions .edit, .actions .edit,
.actions .save, .actions .save,
.actions .delete { background: url('/images/icon-34fe0604ab.png') no-repeat; } .actions .delete { background: url('/images/icon-s34fe0604ab.png') no-repeat; }
.actions .new { background-position: 0 -64px; } .actions .new { background-position: 0 -64px; }
.actions .edit { background-position: 0 -32px; } .actions .edit { background-position: 0 -32px; }
@ -122,7 +122,7 @@ Now in our sass file we add:
And your stylesheet will compile to: And your stylesheet will compile to:
.selectors-sprite, a { .selectors-sprite, a {
background: url('/selectors-edfef809e2.png') no-repeat; background: url('/selectors-sedfef809e2.png') no-repeat;
} }
a { a {
@ -141,7 +141,7 @@ And your stylesheet will compile to:
Alternatively you can use the `@include all-selectors-sprites;` after the import and get the following output: Alternatively you can use the `@include all-selectors-sprites;` after the import and get the following output:
.selectors-sprite, .selectors-ten-by-ten { .selectors-sprite, .selectors-ten-by-ten {
background: url('/selectors-edfef809e2.png') no-repeat; background: url('/selectors-sedfef809e2.png') no-repeat;
} }
.selectors-ten-by-ten { .selectors-ten-by-ten {

View File

@ -145,6 +145,7 @@ Feature: Command Line
Scenario: Basic help Scenario: Basic help
When I run: compass help When I run: compass help
Then I should see the following "primary" commands: Then I should see the following "primary" commands:
| clean |
| compile | | compile |
| create | | create |
| init | | init |
@ -179,6 +180,27 @@ Feature: Command Line
And I run: compass compile And I run: compass compile
And a css file tmp/layout.css is reported overwritten And a css file tmp/layout.css is reported overwritten
Scenario: Cleaning a project
Given I am using the existing project in test/fixtures/stylesheets/compass
When I run: compass compile
And I run: compass clean
Then the following files are reported removed:
| .sass-cache/ |
| tmp/border_radius.css |
| tmp/box.css |
| tmp/box_shadow.css |
| tmp/columns.css |
| tmp/fonts.css |
| images/flag-s03c3b29b35.png |
And the following files are removed:
| .sass-cache/ |
| tmp/border_radius.css |
| tmp/box.css |
| tmp/box_shadow.css |
| tmp/columns.css |
| tmp/fonts.css |
| images/flag-s03c3b29b35.png |
Scenario: Watching a project for changes Scenario: Watching a project for changes
Given ruby supports fork Given ruby supports fork
Given I am using the existing project in test/fixtures/stylesheets/compass Given I am using the existing project in test/fixtures/stylesheets/compass
@ -218,7 +240,6 @@ Feature: Command Line
| sass_dir | sass | | sass_dir | sass |
| css_dir | assets/css | | css_dir | assets/css |
@now
Scenario Outline: Print out a configuration value Scenario Outline: Print out a configuration value
Given I am using the existing project in test/fixtures/stylesheets/compass Given I am using the existing project in test/fixtures/stylesheets/compass
When I run: compass config -p <property> When I run: compass config -p <property>

View File

@ -76,7 +76,7 @@ When /^I run in a separate process: compass ([^\s]+) ?(.+)?$/ do |command, args|
file.puts $stdout.string file.puts $stdout.string
end end
open('/tmp/last_error.compass_test.txt', 'w') do |file| open('/tmp/last_error.compass_test.txt', 'w') do |file|
file.puts @stderr.string file.puts $stderr.string
end end
exit! exit!
end end
@ -116,10 +116,30 @@ Then /^a directory ([^ ]+) is (not )?created$/ do |directory, negated|
File.directory?(directory).should == !negated File.directory?(directory).should == !negated
end end
Then /an? \w+ file ([^ ]+) is (not )?removed/ do |filename, negated|
File.exists?(filename).should == !!negated
end
Then /an? \w+ file ([^ ]+) is (not )?created/ do |filename, negated| Then /an? \w+ file ([^ ]+) is (not )?created/ do |filename, negated|
File.exists?(filename).should == !negated File.exists?(filename).should == !negated
end end
Then "the following files are reported removed:" do |table|
table.rows.each do |css_file|
Then %Q{a css file #{css_file.first} is reported removed}
end
end
Then "the following files are removed:" do |table|
table.rows.each do |css_file|
Then %Q{a css file #{css_file.first} is removed}
end
end
Then /an? \w+ file ([^ ]+) is reported removed/ do |filename|
@last_result.should =~ /remove.*#{Regexp.escape(filename)}/
end
Then /an? \w+ file ([^ ]+) is reported created/ do |filename| Then /an? \w+ file ([^ ]+) is reported created/ do |filename|
@last_result.should =~ /create.*#{Regexp.escape(filename)}/ @last_result.should =~ /create.*#{Regexp.escape(filename)}/
end end

View File

@ -18,7 +18,7 @@
// [Easy Clearing](http://www.positioniseverything.net/easyclearing.html) // [Easy Clearing](http://www.positioniseverything.net/easyclearing.html)
// has the advantage of allowing positioned elements to hang // has the advantage of allowing positioned elements to hang
// outside the bounds of the container at the expense of more tricky CSS. // outside the bounds of the container at the expense of more tricky CSS.
@mixin pie-clearfix { @mixin legacy-pie-clearfix {
&:after { &:after {
content : "\0020"; content : "\0020";
display : block; display : block;
@ -29,3 +29,16 @@
} }
@include has-layout; @include has-layout;
} }
// This is an updated version of the PIE clearfix method that reduces the amount of CSS output.
// If you need to support Firefox before 3.5 you need to use `legacy-pie-clearfix` instead.
//
// Adapted from: [A new micro clearfix hack](http://nicolasgallagher.com/micro-clearfix-hack/)
@mixin pie-clearfix {
&:after {
content: "";
display: table;
clear: both;
}
@include has-layout;
}

View File

@ -65,7 +65,10 @@ module Compass
end end
def remove(file_name) def remove(file_name)
if File.exists?(file_name) if File.directory?(file_name)
FileUtils.rm_rf file_name
log_action :remove, basename(file_name)+"/", options
elsif File.exists?(file_name)
File.unlink file_name File.unlink file_name
log_action :remove, basename(file_name), options log_action :remove, basename(file_name), options
end end

View File

@ -4,7 +4,7 @@ end
require 'compass/commands/registry' require 'compass/commands/registry'
%w(base generate_grid_background default help list_frameworks project_base %w(base generate_grid_background default help list_frameworks project_base
update_project watch_project create_project imports installer_command update_project watch_project create_project clean_project imports installer_command
print_version project_stats stamp_pattern sprite validate_project print_version project_stats stamp_pattern sprite validate_project
write_configuration interactive unpack_extension).each do |lib| write_configuration interactive unpack_extension).each do |lib|
require "compass/commands/#{lib}" require "compass/commands/#{lib}"

View File

@ -0,0 +1,79 @@
require 'compass/commands/project_base'
require 'compass/compiler'
module Compass
module Commands
module CleanProjectOptionsParser
def set_options(opts)
opts.banner = %Q{
Usage: compass clean [path/to/project] [options]
Description:
Remove generated files and the sass cache.
Options:
}.split("\n").map{|l| l.gsub(/^ */,'')}.join("\n")
super
end
end
class CleanProject < UpdateProject
register :clean
def initialize(working_path, options)
super
assert_project_directory_exists!
end
def perform
compiler = new_compiler_instance
compiler.clean!
Compass::SpriteImporter.find_all_sprite_map_files(Compass.configuration.images_path).each do |sprite|
remove sprite
end
end
def determine_cache_location
Compass.configuration.cache_path || Sass::Plugin.options[:cache_location] || File.join(working_path, ".sass-cache")
end
class << self
def option_parser(arguments)
parser = Compass::Exec::CommandOptionParser.new(arguments)
parser.extend(Compass::Exec::GlobalOptionsParser)
parser.extend(Compass::Exec::ProjectOptionsParser)
parser.extend(CleanProjectOptionsParser)
end
def usage
option_parser([]).to_s
end
def primary; true; end
def description(command)
"Remove generated files and the sass cache"
end
def parse!(arguments)
parser = option_parser(arguments)
parser.parse!
parse_arguments!(parser, arguments)
parser.options
end
def parse_arguments!(parser, arguments)
if arguments.size > 0
parser.options[:project_name] = arguments.shift if File.directory?(arguments.first)
unless arguments.empty?
parser.options[:sass_files] = arguments.dup
parser.options[:force] = true
end
end
end
end
end
end
end

View File

@ -16,8 +16,10 @@ module Compass::Commands
matching.first matching.first
elsif name =~ /^-/ elsif name =~ /^-/
nil nil
else elsif matching.size > 1
raise Compass::Error, "Ambiguous abbreviation '#{name}'. Did you mean one of: #{matching.join(", ")}" raise Compass::Error, "Ambiguous abbreviation '#{name}'. Did you mean one of: #{matching.join(", ")}"
else
raise Compass::Error, "Command not found: #{name}"
end end
end end
def abbreviation?(name) def abbreviation?(name)

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::SpriteImporter.new(relative_uri, Compass.sass_engine_options) sprites = Compass::SpriteImporter.new(:uri => relative_uri, :options => 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

@ -51,12 +51,10 @@ module Compass
def new_compiler_instance(additional_options = {}) def new_compiler_instance(additional_options = {})
@compiler_opts ||= begin @compiler_opts ||= begin
compiler_opts = Compass.sass_engine_options compiler_opts = {:sass => Compass.sass_engine_options}
compiler_opts.merge!(:force => options[:force], compiler_opts.merge!(options)
:sass_files => explicit_sass_files, compiler_opts[:sass_files] = explicit_sass_files
:dry_run => options[:dry_run]) compiler_opts[:cache_location] = determine_cache_location
compiler_opts[:quiet] = options[:quiet] if options[:quiet]
compiler_opts[:time] = options[:time] if options[:time]
compiler_opts compiler_opts
end end

View File

@ -3,16 +3,19 @@ module Compass
include Actions include Actions
attr_accessor :working_path, :from, :to, :options, :staleness_checker, :importer attr_accessor :working_path, :from, :to, :options, :sass_options, :staleness_checker, :importer
def initialize(working_path, from, to, options) def initialize(working_path, from, to, options)
self.working_path = working_path self.working_path = working_path
self.from, self.to = from.gsub('./', ''), to self.from, self.to = from.gsub('./', ''), to
self.logger = options.delete(:logger) self.logger = options.delete(:logger)
sass_opts = options.delete(:sass) || {}
self.options = options self.options = options
self.options[:cache_location] ||= determine_cache_location self.sass_options = options.dup
options[:importer] = self.importer = Sass::Importers::Filesystem.new(from) self.sass_options.update(sass_opts)
self.staleness_checker = Sass::Plugin::StalenessChecker.new(options) self.sass_options[:cache_location] ||= determine_cache_location
self.sass_options[:importer] = self.importer = Sass::Importers::Filesystem.new(from)
self.staleness_checker = Sass::Plugin::StalenessChecker.new(sass_options)
end end
def determine_cache_location def determine_cache_location
@ -72,16 +75,16 @@ module Compass
end end
def clean! def clean!
FileUtils.rm_rf options[:cache_location] remove options[:cache_location]
css_files.each do |css_file| css_files.each do |css_file|
FileUtils.rm_f css_file remove css_file
end end
end end
def run def run
if new_config? if new_config?
# Wipe out the cache and force compilation if the configuration has changed. # Wipe out the cache and force compilation if the configuration has changed.
FileUtils.rm_rf options[:cache_location] remove options[:cache_location]
options[:force] = true options[:force] = true
end end
@ -142,7 +145,7 @@ module Compass
# A sass engine for compiling a single file. # A sass engine for compiling a single file.
def engine(sass_filename, css_filename) def engine(sass_filename, css_filename)
syntax = (sass_filename =~ /\.(s[ac]ss)$/) && $1.to_sym || :sass syntax = (sass_filename =~ /\.(s[ac]ss)$/) && $1.to_sym || :sass
opts = options.merge :filename => sass_filename, :css_filename => css_filename, :syntax => syntax opts = sass_options.merge(:filename => sass_filename, :css_filename => css_filename, :syntax => syntax)
Sass::Engine.new(open(sass_filename).read, opts) Sass::Engine.new(open(sass_filename).read, opts)
end end

View File

@ -71,8 +71,10 @@ module Compass
end end
# When called with a block, defines the cache buster strategy to be used. # When called with a block, defines the cache buster strategy to be used.
# The block must return nil or a string that can be appended to a url as a query parameter. # If the block returns nil or a string, then it is appended to the url as a query parameter.
# The returned string must not include the starting '?'. # In this case, the returned string must not include the starting '?'.
# The block may also return a hash with :path and/or :query values and it
# will replace the original path and query string with the busted values returned.
# The block will be passed the root-relative url of the asset. # The block will be passed the root-relative url of the asset.
# If the block accepts two arguments, it will also be passed a File object # If the block accepts two arguments, it will also be passed a File object
# that points to the asset on disk -- which may or may not exist. # that points to the asset on disk -- which may or may not exist.

View File

@ -13,6 +13,7 @@ module Compass::Exec
def run! def run!
begin begin
perform! perform!
return 0
rescue Exception => e rescue Exception => e
raise e if e.is_a? SystemExit raise e if e.is_a? SystemExit
if e.is_a?(::Compass::Error) || e.is_a?(OptionParser::ParseError) if e.is_a?(::Compass::Error) || e.is_a?(OptionParser::ParseError)
@ -22,7 +23,6 @@ module Compass::Exec
end end
return 1 return 1
end end
return 0
end end
protected protected

View File

@ -91,9 +91,7 @@ module Compass::SassExtensions::Functions::Urls
if cache_buster.is_a?(Sass::Script::String) if cache_buster.is_a?(Sass::Script::String)
path += "?#{cache_buster.value}" path += "?#{cache_buster.value}"
else else
if buster = compute_cache_buster(path, real_path) path = cache_busted_path(path, real_path)
path += "?#{buster}"
end
end end
end end
@ -137,6 +135,23 @@ module Compass::SassExtensions::Functions::Urls
end end
end end
def cache_busted_path(path, real_path)
cache_buster = compute_cache_buster(path, real_path)
if cache_buster.nil?
return path
elsif cache_buster.is_a?(String)
cache_buster = {:query => cache_buster}
else
path = cache_buster[:path] if cache_buster[:path]
end
if cache_buster[:query]
"%s?%s" % [path, cache_buster[:query]]
else
path
end
end
def compute_cache_buster(path, real_path) def compute_cache_buster(path, real_path)
if Compass.configuration.asset_cache_buster if Compass.configuration.asset_cache_buster
args = [path] args = [path]

View File

@ -9,6 +9,8 @@ module Compass
end end
require 'compass/sass_extensions/sprites/image' require 'compass/sass_extensions/sprites/image'
require 'compass/sass_extensions/sprites/sprite_methods'
require 'compass/sass_extensions/sprites/image_methods'
require 'compass/sass_extensions/sprites/sprite_map' require 'compass/sass_extensions/sprites/sprite_map'
require 'compass/sass_extensions/sprites/engines' require 'compass/sass_extensions/sprites/engines'

View File

@ -1 +1,25 @@
module Compass
module SassExtensions
module Sprites
class Engine
attr_accessor :width, :height, :images, :canvas
def initialize(width, height, images)
@width, @height, @images = width, height, images
@canvas = nil
end
def construct_sprite
raise ::Compass::Error, "You must impliment construct_sprite"
end
def save(filename)
raise ::Compass::Error, "You must impliment save(filename)"
end
end
end
end
end
require 'compass/sass_extensions/sprites/engines/chunky_png_engine' require 'compass/sass_extensions/sprites/engines/chunky_png_engine'

View File

@ -7,20 +7,19 @@ end
module Compass module Compass
module SassExtensions module SassExtensions
module Sprites module Sprites
module ChunkyPngEngine class ChunkyPngEngine < Compass::SassExtensions::Sprites::Engine
# Returns a PNG object
def construct_sprite def construct_sprite
output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT) @canvas = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
images.each do |image| images.each do |image|
input_png = ChunkyPNG::Image.from_file(image.file) input_png = ChunkyPNG::Image.from_file(image.file)
if image.repeat == "no-repeat" if image.repeat == "no-repeat"
output_png.replace! input_png, image.left, image.top canvas.replace! input_png, image.left, image.top
else else
x = image.left - (image.left / image.width).ceil * image.width x = image.left - (image.left / image.width).ceil * image.width
while x < width do while x < width do
begin begin
output_png.replace! input_png, x, image.top canvas.replace! input_png, x, image.top
x += image.width x += image.width
rescue ChunkyPNG::OutOfBounds rescue ChunkyPNG::OutOfBounds
break; break;
@ -28,8 +27,15 @@ module Compass
end end
end end
end end
output_png end
def save(filename)
if canvas.nil?
construct_sprite
end
canvas.save(filename, :best_compression)
end end
end end
end end
end end

View File

@ -0,0 +1,32 @@
module Compass
module SassExtensions
module Sprites
module ImageMethods
# 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
end
end
end
end

View File

@ -2,8 +2,13 @@ module Compass
module SassExtensions module SassExtensions
module Sprites module Sprites
class SpriteMap < Sass::Script::Literal class SpriteMap < Sass::Script::Literal
attr_accessor :image_names, :path, :name, :map, :kwargs
attr_accessor :images, :width, :height, :engine
include SpriteMethods
include ImageMethods
# Initialize a new sprite object from a relative file path # Initialize a new sprite object from a relative file path
# the path is relative to the <tt>images_path</tt> confguration option # the path is relative to the <tt>images_path</tt> confguration option
def self.from_uri(uri, context, kwargs) def self.from_uri(uri, context, kwargs)
@ -13,22 +18,8 @@ module Compass
end end
new(sprites, importer.path, importer.name, context, kwargs) new(sprites, importer.path, importer.name, context, kwargs)
end end
# Loads the sprite engine
def require_engine!
self.class.send(:include, eval("::Compass::SassExtensions::Sprites::#{modulize}Engine"))
end
# 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) def initialize(sprites, path, name, context, kwargs)
require_engine!
@image_names = sprites @image_names = sprites
@path = path @path = path
@name = name @name = name
@ -37,150 +28,12 @@ module Compass
@images = nil @images = nil
@width = nil @width = nil
@height = nil @height = nil
@engine = nil
@evaluation_context = context @evaluation_context = context
validate! validate!
compute_image_metadata! compute_image_metadata!
end 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}-s#{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 def inspect
to_s to_s
end end
@ -200,14 +53,14 @@ module Compass
super super
end end
end end
private private
def modulize def modulize
@modulize ||= Compass::configuration.sprite_engine.to_s.scan(/([^_.]+)/).flatten.map {|chunk| "#{chunk[0].chr.upcase}#{chunk[1..-1]}" }.join @modulize ||= Compass::configuration.sprite_engine.to_s.scan(/([^_.]+)/).flatten.map {|chunk| "#{chunk[0].chr.upcase}#{chunk[1..-1]}" }.join
end end
end end
end end
end end
end end

View File

@ -0,0 +1,134 @@
module Compass
module SassExtensions
module Sprites
module SpriteMethods
# Changing this string will invalidate all previously generated sprite images.
# We should do so only when the packing algorithm changes
SPRITE_VERSION = "1"
# 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
init_engine
end
def init_engine
@engine = eval("::Compass::SassExtensions::Sprites::#{modulize}Engine.new(nil, nil, nil)")
@engine.width = @width
@engine.height = @height
@engine.images = @images
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
# 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}-s#{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
engine.construct_sprite
Compass.configuration.run_callback(:sprite_generated, engine.canvas)
save!
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!
saved = engine.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
# Calculate the size of the sprite
def size
[width, height]
end
end
end
end
end

View File

@ -4,7 +4,14 @@ module Compass
VAILD_FILE_NAME = /\A#{Sass::SCSS::RX::IDENT}\Z/ VAILD_FILE_NAME = /\A#{Sass::SCSS::RX::IDENT}\Z/
SPRITE_IMPORTER_REGEX = %r{((.+/)?([^\*.]+))/(.+?)\.png} SPRITE_IMPORTER_REGEX = %r{((.+/)?([^\*.]+))/(.+?)\.png}
VALID_EXTENSIONS = ['.png'] VALID_EXTENSIONS = ['.png']
# finds all sprite files
def self.find_all_sprite_map_files(path)
hex = "[0-9a-f]"
glob = "*-{,s}#{hex*10}{#{VALID_EXTENSIONS.join(",")}}"
Dir.glob(File.join(path, "**", glob))
end
def self.load(uri, options) def self.load(uri, options)
klass = Compass::SpriteImporter.new klass = Compass::SpriteImporter.new
klass.uri, klass.options = uri, options klass.uri, klass.options = uri, options
@ -31,7 +38,7 @@ module Compass
end end
def to_s def to_s
content_for_images self.class.name
end end
def hash def hash

View File

@ -1,9 +1,16 @@
begin begin
require 'rubygems' require 'rubygems'
require 'compass-validator' require 'compass-validator'
rescue LoadError rescue LoadError => e
raise Compass::MissingDependency, %Q{The Compass CSS Validator could not be loaded. Please install it: if e.message =~ /core_ext/
raise Compass::MissingDependency, <<-ERRORMSG
The Compass CSS Validator is out of date. Please upgrade it:
sudo gem install compass-validator --version ">= 3.0.1"
ERRORMSG
else
raise Compass::MissingDependency, <<-ERRORMSG
The Compass CSS Validator could not be loaded. Please install it:
sudo gem install compass-validator sudo gem install compass-validator
} ERRORMSG
end
end end

View File

@ -1,57 +0,0 @@
require 'spec_helper'
describe Compass::SassExtensions::Sprites::Base 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')
FileUtils.cp_r @images_src_path, @images_tmp_path
config = Compass::Configuration::Data.new('config')
config.images_path = @images_tmp_path
Compass.add_configuration(config)
Compass.configure_sass_plugin!
#fix this eww
options = Compass.sass_engine_options.extend Compass::SassExtensions::Functions::Sprites::VariableReader
@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
after :each do
FileUtils.rm_r @images_tmp_path
end
subject { @base }
its(:size) { should == [10,40] }
its(:sprite_names) { should == @map.sprite_names }
its(:image_filenames) { should == Dir["#{@images_tmp_path}/selectors/*.png"].sort }
its(:generation_required?) { should be_true }
its(:uniqueness_hash) { should == 'ef52c5c63a'}
its(:outdated?) { should be_true }
its(:filename) { should == File.join(@images_tmp_path, "#{@base.path}-s#{@base.uniqueness_hash}.png")}
it "should return the 'ten-by-ten' image" do
subject.image_for('ten-by-ten').name.should == 'ten-by-ten'
subject.image_for('ten-by-ten').should be_a Compass::SassExtensions::Sprites::Image
end
%w(target hover active).each do |selector|
it "should have a #{selector}" do
subject.send(:"has_#{selector}?", 'ten-by-ten').should be_true
end
it "should return #{selector} image class" do
subject.image_for('ten-by-ten').send(:"#{selector}").name.should == "ten-by-ten_#{selector}"
end
end
context "#generate" do
before { @base.generate }
it "should generate sprite" do
File.exists?(@base.filename).should be_true
end
its(:generation_required?) { should be_false }
its(:outdated?) { should be_false }
end
end

View File

@ -1,161 +0,0 @@
require 'spec_helper'
require 'compass/sass_extensions/sprites/image'
describe Compass::SassExtensions::Sprites::Image do
let(:sprite_filename) { 'squares/ten-by-ten.png' }
let(:sprite_path) { File.join(images_src_path, sprite_filename) }
let(:sprite_name) { File.basename(sprite_filename, '.png') }
let(:parent) do
mock
end
before do
parent.stubs(:image_for).with('ten-by-ten').returns(image)
parent.stubs(:image_for).with('ten-by-ten_hover').returns(hover_image)
end
let(:image) { self.class.describes.new(parent, File.join(sprite_filename), options)}
let(:hover_image) { self.class.describes.new(parent, File.join('selectors/ten-by-ten_hover.png'), options)}
let(:digest) { Digest::MD5.file(sprite_path).hexdigest }
subject { image }
before {
file = StringIO.new("images_path = #{images_src_path.inspect}\n")
Compass.add_configuration(file, "sprite_config")
}
describe '#initialize' do
its(:name) { should == sprite_name }
its(:file) { should == sprite_path }
its(:relative_file) { should == sprite_filename }
its(:width) { should == 10 }
its(:height) { should == 10 }
its(:digest) { should == digest }
its(:top) { should == 0 }
its(:left) { should == 0 }
end
let(:get_var_expects) { nil }
let(:get_var_return) { nil }
let(:options) {
options = mock
options.stubs(:get_var).with(anything).returns(nil)
options.stubs(:get_var).with(get_var_expects).returns(get_var_return)
options
}
describe '#parent' do
context '_hover' do
subject { hover_image }
its(:parent) { should == image }
end
context 'no parent' do
subject { image }
its(:parent) { should be_nil }
end
end
describe '#repeat' do
let(:type) { nil }
let(:get_var_return) { OpenStruct.new(:value => type) }
context 'specific image' do
let(:type) { 'specific' }
let(:get_var_expects) { "#{sprite_name}-repeat" }
its(:repeat) { should == type }
end
context 'global' do
let(:type) { 'global' }
let(:get_var_expects) { 'repeat' }
its(:repeat) { should == type }
end
context 'default' do
let(:get_var_expects) { nil }
its(:repeat) { should == "no-repeat" }
end
end
describe '#position' do
let(:type) { nil }
let(:get_var_return) { type }
context 'specific image' do
let(:type) { 'specific' }
let(:get_var_expects) { "#{sprite_name}-position" }
its(:position) { should == type }
end
context 'global' do
let(:type) { 'global' }
let(:get_var_expects) { 'position' }
its(:position) { should == type }
end
context 'default' do
let(:get_var_expects) { nil }
its(:position) { should == Sass::Script::Number.new(0, ["px"]) }
end
end
describe '#spacing' do
let(:type) { nil }
let(:get_var_return) { OpenStruct.new(:value => type) }
context 'specific image' do
let(:type) { 'specific' }
let(:get_var_expects) { "#{sprite_name}-spacing" }
its(:spacing) { should == type }
end
context 'global' do
let(:type) { 'global' }
let(:get_var_expects) { 'spacing' }
its(:spacing) { should == type }
end
context 'default' do
let(:get_var_expects) { nil }
its(:spacing) { should == Sass::Script::Number.new(0).value }
end
end
describe '#offset' do
before { image.stubs(:position).returns(stub_position) }
let(:offset) { 100 }
let(:stub_position) {
stub(:value => offset)
}
context 'unitless' do
before { stub_position.stubs(:unitless?).returns(true) }
before { stub_position.stubs(:unit_str).returns('em') }
its(:offset) { should == offset }
end
context 'pixels' do
before { stub_position.stubs(:unitless?).returns(false) }
before { stub_position.stubs(:unit_str).returns('px') }
its(:offset) { should == offset }
end
context 'neither, use 0' do
before { stub_position.stubs(:unitless?).returns(false) }
before { stub_position.stubs(:unit_str).returns('em') }
its(:offset) { should == 0 }
end
end
end

View File

@ -1,54 +0,0 @@
require 'spec_helper'
require 'fakefs/spec_helpers'
require 'timecop'
describe Compass::SpriteImporter do
include FakeFS::SpecHelpers
let(:sprite_map) { self.class.describes.new(uri, options) }
let(:options) { { :test => :test2 } }
subject { sprite_map }
let(:path) { 'path' }
let(:dir) { "dir/#{name}" }
let(:name) { 'subdir' }
let(:sprite_path) { File.join(path, dir) }
let(:files) { (1..3).collect { |i| File.join(sprite_path, "#{i}.png") } }
let(:expanded_files) { files.collect { |file| File.expand_path(file) } }
let(:configuration) { stub(:images_path => path) }
let(:mtime) { Time.now - 30 }
before {
Compass.stubs(:configuration).returns(configuration)
FileUtils.mkdir_p(sprite_path)
Timecop.freeze(mtime) do
files.each { |file| File.open(file, 'w') }
end
Timecop.return
}
describe '#initialize' do
let(:uri) { 'dir/subdir/*.png' }
its(:uri) { should == uri }
its(:path) { should == dir }
its(:name) { should == name }
its(:files) { should == expanded_files }
its(:sass_options) { should == options.merge(:filename => name, :syntax => :scss, :importer => sprite_map) }
it "should have a correct mtime" do
sprite_map.mtime(uri, subject.sass_options).should == mtime
end
it "should have a test for the sass engine" do
pending 'sass'
end
end
end

View File

@ -1,5 +0,0 @@
require 'spec_helper'
require 'fakefs/spec_helpers'
describe Compass::Sprites do
end

View File

@ -1,37 +0,0 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'rubygems'
require 'compass'
require 'rspec'
require 'rspec/autorun'
require 'mocha'
module CompassGlobalInclude
class << self
def included(klass)
klass.instance_eval do
let(:images_src_path) { File.join(File.dirname(__FILE__), 'test_project', 'public', 'images') }
end
end
end
end
module CompassSpriteHelpers
def create_sprite_temp
::FileUtils.cp_r @images_src_path, @images_tmp_path
end
def clean_up_sprites
::FileUtils.rm_r @images_tmp_path
end
end
RSpec.configure do |config|
config.include(CompassGlobalInclude)
config.include(CompassSpriteHelpers)
config.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')
end
config.mock_with :mocha
end

View File

@ -1,571 +0,0 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'digest/md5'
describe Compass::Sprites do
before :each do
create_sprite_temp
file = StringIO.new("images_path = #{@images_tmp_path.inspect}\n")
Compass.add_configuration(file, "sprite_config")
Compass.configure_sass_plugin!
end
after :each do
clean_up_sprites
end
def map_location(file)
Dir.glob(File.join(@images_tmp_path, file)).first
end
def image_size(file)
IO.read(map_location(file))[0x10..0x18].unpack('NN')
end
def image_md5(file)
md5 = Digest::MD5.new
md5.update IO.read(map_location(file))
md5.hexdigest
end
def render(scss)
scss = %Q(@import "compass"; #{scss})
options = Compass.sass_engine_options
options[:line_comments] = false
options[:style] = :expanded
options[:syntax] = :scss
css = Sass::Engine.new(scss, options).render
# reformat to fit result of heredoc:
" #{css.gsub('@charset "UTF-8";', '').gsub(/\n/, "\n ").strip}\n"
end
#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";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-161c60ad78.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
}
CSS
image_size('squares-*.png').should == [20, 30]
image_md5('squares-*.png').should == 'fcc93d7b279c2ad6898fbca49cbd01e1'
end
it "should generate sprite classes with dimensions" do
css = render <<-SCSS
$squares-sprite-dimensions: true;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-161c60ad78.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
height: 10px;
width: 10px;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
height: 20px;
width: 20px;
}
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);
}
SCSS
css.should == <<-CSS
.squares-sprite, .cubicle, .large-cube {
background: url('/squares-161c60ad78.png') no-repeat;
}
.cubicle {
background-position: 0 0;
}
.large-cube {
background-position: 0 -10px;
height: 20px;
width: 20px;
}
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";
@import "squares/*.png";
SCSS
css.should == <<-CSS
.circles {
background: url('/squares-161c60ad78.png') no-repeat;
}
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;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-89450808af.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -43px;
}
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;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-673837183a.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -43px;
}
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;
$squares-twenty-by-twenty-spacing: 33px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-1cd84c9068.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -54px;
}
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;
$squares-twenty-by-twenty-spacing: 44px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-f25b7090ca.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -54px;
}
CSS
image_size('squares-*.png').should == [20, 74]
end
it "should calculate the default spacing between images" do
css = render <<-SCSS
$squares-spacing: 22px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-d66bf24bab.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -32px;
}
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);
}
SCSS
css.should == <<-CSS
.squares-sprite {
background: url('/squares-8e490168dd.png') no-repeat;
}
.adjusted-percentage {
background-position: 100% 0;
}
.adjusted-px-1 {
background-position: -6px 0;
}
.adjusted-px-2 {
background-position: -3px -8px;
}
CSS
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);
}
SCSS
css.should == <<-CSS
.squares-sprite, .adjusted-percentage, .adjusted-px-1, .adjusted-px-2 {
background: url('/squares-8e490168dd.png') no-repeat;
}
.adjusted-percentage {
background-position: 100% 0;
}
.adjusted-px-1 {
background-position: -6px 0;
}
.adjusted-px-2 {
background-position: -3px -8px;
}
CSS
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;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-a5550fd132.png') no-repeat;
}
.squares-ten-by-ten {
background-position: 0 0;
}
.squares-twenty-by-twenty {
background-position: 0 -10px;
}
CSS
image_size('squares-*.png').should == [20, 30]
image_md5('squares-*.png').should == '94abae8440f1b58617f52920b70aaed2'
end
it "should allow the position of a sprite to be specified in absolute pixels" do
css = render <<-SCSS
$squares-ten-by-ten-position: 10px;
$squares-twenty-by-twenty-position: 10px;
@import "squares/*.png";
@include all-squares-sprites;
SCSS
css.should == <<-CSS
.squares-sprite, .squares-ten-by-ten, .squares-twenty-by-twenty {
background: url('/squares-89a274044e.png') no-repeat;
}
.squares-ten-by-ten {
background-position: -10px 0;
}
.squares-twenty-by-twenty {
background-position: -10px -10px;
}
CSS
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
.squares {
background: sprite-url("squares/*.png") no-repeat;
}
SCSS
end.should raise_error Sass::SyntaxError,
%q(The first argument to sprite-url() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
proc do
render <<-SCSS
.squares {
background: sprite-image("squares/twenty-by-twenty.png") no-repeat;
}
SCSS
end.should raise_error Sass::SyntaxError,
%q(The sprite-image() function has been replaced by sprite(). See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
proc do
render <<-SCSS
@import "squares/*.png";
.squares {
background: sprite-position("squares/twenty-by-twenty.png") no-repeat;
}
SCSS
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 {
background: sprite(sprite-map("squares/*.png"), twenty-by-twenty) no-repeat;
}
SCSS
actual_css.should == <<-CSS
.squares {
background: url('/squares-145869726f.png') 0 -10px no-repeat;
}
CSS
end
it "should calculate corret sprite demsions when givin spacing via issue#253" do
css = render <<-SCSS
$squares-spacing: 10px;
@import "squares/*.png";
.foo {
@include sprite-background-position($squares-sprites, "twenty-by-twenty");
}
.bar {
@include sprite-background-position($squares-sprites, "ten-by-ten");
}
SCSS
image_size('squares-*.png').should == [20, 40]
css.should == <<-CSS
.squares-sprite {
background: url('/squares-e3c68372d9.png') no-repeat;
}
.foo {
background-position: 0 -20px;
}
.bar {
background-position: 0 0;
}
CSS
end
it "should render corret sprite with css selectors via issue#248" do
css = render <<-SCSS
@import "selectors/*.png";
@include all-selectors-sprites;
SCSS
css.should == <<-CSS
.selectors-sprite, .selectors-ten-by-ten {
background: url('/selectors-edfef809e2.png') no-repeat;
}
.selectors-ten-by-ten {
background-position: 0 0;
}
.selectors-ten-by-ten:hover, .selectors-ten-by-ten.ten-by-ten_hover, .selectors-ten-by-ten.ten-by-ten-hover {
background-position: 0 -20px;
}
.selectors-ten-by-ten:target, .selectors-ten-by-ten.ten-by-ten_target, .selectors-ten-by-ten.ten-by-ten-target {
background-position: 0 -30px;
}
.selectors-ten-by-ten:active, .selectors-ten-by-ten.ten-by-ten_active, .selectors-ten-by-ten.ten-by-ten-active {
background-position: 0 -10px;
}
CSS
end
it "should render corret sprite with css selectors via magic mixin" do
css = render <<-SCSS
@import "selectors/*.png";
a {
@include selectors-sprite(ten-by-ten)
}
SCSS
css.should == <<-CSS
.selectors-sprite, a {
background: url('/selectors-edfef809e2.png') no-repeat;
}
a {
background-position: 0 0;
}
a:hover, a.ten-by-ten_hover, a.ten-by-ten-hover {
background-position: 0 -20px;
}
a:target, a.ten-by-ten_target, a.ten-by-ten-target {
background-position: 0 -30px;
}
a:active, a.ten-by-ten_active, a.ten-by-ten-active {
background-position: 0 -10px;
}
CSS
end
it "should not render corret sprite with css selectors via magic mixin" do
css = render <<-SCSS
@import "selectors/*.png";
a {
$disable-magic-sprite-selectors:true;
@include selectors-sprite(ten-by-ten)
}
SCSS
css.should == <<-CSS
.selectors-sprite, a {
background: url('/selectors-edfef809e2.png') no-repeat;
}
a {
background-position: 0 0;
}
CSS
end
it "should raise error on filenames that are not valid sass syntax" do
lambda do
render <<-SCSS
@import "prefix/*.png";
a {
@include squares-sprite(20-by-20);
}
SCSS
end.should raise_error Compass::Error
end
it "should generate sprite with bad repeat-x dimensions" do
css = render <<-SCSS
$ko-starbg26x27-repeat: repeat-x;
@import "ko/*.png";
@include all-ko-sprites;
SCSS
css.should == <<-CSS
.ko-sprite, .ko-default_background, .ko-starbg26x27 {
background: url('/ko-cc3f80660d.png') no-repeat;
}
.ko-default_background {
background-position: 0 0;
}
.ko-starbg26x27 {
background-position: 0 -128px;
}
CSS
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,29 @@
# Require any additional compass plugins here.
project_type = :stand_alone
css_dir = "tmp"
sass_dir = "sass"
images_dir = "images"
output_style = :compact
# To enable relative image paths using the images_url() function:
# http_images_path = :relative
http_images_path = "/images"
line_comments = false
asset_cache_buster do |path, file|
pathname = Pathname.new(path)
case pathname.basename(pathname.extname).to_s
when "grid"
new_path = "%s/%s-BUSTED%s" % [pathname.dirname, pathname.basename(pathname.extname), pathname.extname]
{:path => new_path, :query => nil}
when "feed"
"query_string"
when "dk"
{:query => "query_string"}
end
end
asset_host do |path|
"http://assets%d.example.com" % (path.size % 4)
end

View File

@ -0,0 +1,9 @@
.showgrid { background-image: url('http://assets0.example.com/images/grid-BUSTED.png'); }
.inlinegrid { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUEAYAAACv1qP4AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAZ0lEQVRYw+3QwQ2AIBAFUTEUwI3+uzN7gDscsIgxEuO8An52J11X73OudfxMraXkzHfO3Y98nQEhA0IGhAwIGRAyIGRAyICQASEDQgaEDAgZEDIgZEDIgJABoZzSGK3tPuN9ERFP7Nw4fg+c5g8V1wAAAABJRU5ErkJggg=='); }
.no-buster { background-image: url('http://assets0.example.com/images/grid.png'); }
.feed { background-image: url('http://assets0.example.com/images/feed.png?query_string'); }
.dk { background-image: url('http://assets0.example.com/images/flags/dk.png?query_string'); }

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

View File

@ -0,0 +1,14 @@
.showgrid
background-image: image-url("grid.png")
.inlinegrid
background-image: inline-image("grid.png")
.no-buster
background-image: image-url("grid.png", $only-path: false, $cache-buster: false)
.feed
background-image: image-url("feed.png")
.dk
background-image: image-url("flags/dk.png")

View File

@ -15,3 +15,12 @@
visibility: hidden; } visibility: hidden; }
.pie-clearfix { .pie-clearfix {
display: block; } display: block; }
.simplified-pie-clearfix {
display: inline-block; }
.simplified-pie-clearfix:after {
content: "";
display: table;
clear: both; }
.simplified-pie-clearfix {
display: block; }

View File

@ -12,6 +12,13 @@
overflow: hidden; overflow: hidden;
visibility: hidden; } visibility: hidden; }
.simple-pie-clearfix {
*zoom: 1; }
.simple-pie-clearfix:after {
content: "";
display: table;
clear: both; }
p.light { p.light {
background-color: #b0201e; background-color: #b0201e;
color: black; } color: black; }

View File

@ -7,5 +7,8 @@ $default-has-layout-approach: block;
} }
.pie-clearfix { .pie-clearfix {
@include legacy-pie-clearfix;
}
.simplified-pie-clearfix {
@include pie-clearfix; @include pie-clearfix;
} }

View File

@ -5,7 +5,10 @@
} }
.pie-clearfix { .pie-clearfix {
@include pie-clearfix; @include legacy-pie-clearfix;
}
.simple-pie-clearfix {
@include pie-clearfix;
} }
p.light { @include contrasted(#B0201E); } p.light { @include contrasted(#B0201E); }

View File

@ -73,6 +73,17 @@ class CompassTest < Test::Unit::TestCase
end end
end end
def test_busted_image_urls
within_project('busted_image_urls') do |proj|
each_css_file(proj.css_path) do |css_file|
assert_no_errors css_file, 'busted_image_urls'
end
each_sass_file do |sass_file|
assert_renders_correctly sass_file
end
end
end
def test_image_urls def test_image_urls
within_project('image_urls') do |proj| within_project('image_urls') do |proj|
each_css_file(proj.css_path) do |css_file| each_css_file(proj.css_path) do |css_file|

View File

@ -71,7 +71,7 @@ class SpritesTest < Test::Unit::TestCase
} }
CSS CSS
assert_equal image_size('squares-s*.png'), [20, 30] assert_equal image_size('squares-s*.png'), [20, 30]
assert_equal image_md5('squares-s*.png'), 'fcc93d7b279c2ad6898fbca49cbd01e1' assert_equal image_md5('squares-s*.png'), '7349a0f4e88ea80abddcf6ac2486abe3'
end end
it "should generate sprite classes with dimensions" do it "should generate sprite classes with dimensions" do
@ -294,7 +294,7 @@ class SpritesTest < Test::Unit::TestCase
} }
CSS CSS
assert_equal image_size('squares-s*.png'), [20, 30] assert_equal image_size('squares-s*.png'), [20, 30]
assert_equal image_md5('squares-s*.png'), '652b67f5e9092520d6f26caae7e18012' assert_equal image_md5('squares-s*.png'), '9cc7ce48cfaf304381c2d08adefd2fb6'
end end
it "should use position adjustments in mixins" do it "should use position adjustments in mixins" do
@ -332,7 +332,7 @@ class SpritesTest < Test::Unit::TestCase
} }
CSS CSS
assert_equal image_size('squares-s*.png'), [20, 30] assert_equal image_size('squares-s*.png'), [20, 30]
assert_equal image_md5('squares-s*.png'), '652b67f5e9092520d6f26caae7e18012' assert_equal image_md5('squares-s*.png'), '9cc7ce48cfaf304381c2d08adefd2fb6'
end end
it "should repeat the image" do it "should repeat the image" do
@ -355,7 +355,7 @@ class SpritesTest < Test::Unit::TestCase
} }
CSS CSS
assert_equal image_size('squares-s*.png'), [20, 30] assert_equal image_size('squares-s*.png'), [20, 30]
assert_equal image_md5('squares-s*.png'), '94abae8440f1b58617f52920b70aaed2' assert_equal image_md5('squares-s*.png'), 'a77a2fd43f04d791722b706aa7c9f1c1'
end end
it "should allow the position of a sprite to be specified in absolute pixels" do it "should allow the position of a sprite to be specified in absolute pixels" do
@ -379,7 +379,7 @@ class SpritesTest < Test::Unit::TestCase
} }
CSS CSS
assert_equal image_size('squares-s*.png'), [30, 30] assert_equal image_size('squares-s*.png'), [30, 30]
assert_equal image_md5('squares-s*.png'), '2fb19ef9c83018c93c6f147af3a56cb2' assert_equal image_md5('squares-s*.png'), '9856ced9e8211b6b28ff782019a0d905'
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

View File

@ -0,0 +1,43 @@
require 'test_helper'
class EngineTest < Test::Unit::TestCase
def setup
sprite_filename = 'squares/ten-by-ten.png'
@images = [
Compass::SassExtensions::Sprites::Image.new(nil, File.join(sprite_filename), {})
]
@engine = Compass::SassExtensions::Sprites::Engine.new(100, 100, @images)
end
test "should have width of 100" do
assert_equal 100, @engine.width
end
test "should have height of 100" do
assert_equal 100, @engine.height
end
test "should have correct images" do
assert_equal @images, @engine.images
end
test "raises Compass::Error when calling save" do
begin
@engine.save('foo')
assert false, '#save did not raise an exception'
rescue Compass::Error
assert true
end
end
test "raises Compass::Error when calling construct_sprite" do
begin
@engine.construct_sprite
assert false, '#construct_sprite did not raise an exception'
rescue Compass::Error
assert true
end
end
end

View File

@ -9,6 +9,10 @@ class ImporterTest < Test::Unit::TestCase
Compass.add_configuration(file, "sprite_config") Compass.add_configuration(file, "sprite_config")
@importer = Compass::SpriteImporter.new(:uri => URI, :options => options) @importer = Compass::SpriteImporter.new(:uri => URI, :options => options)
end end
def teardown
Compass.reset_configuration!
end
def options def options
{:foo => 'bar'} {:foo => 'bar'}
@ -46,21 +50,17 @@ class ImporterTest < Test::Unit::TestCase
assert_equal 'bar', @importer.sass_options[:foo] assert_equal 'bar', @importer.sass_options[:foo]
end end
test "should fail givin bad sprite extensions" do test "should fail given bad sprite extensions" do
@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")
Compass.add_configuration(file, "sprite_config") Compass.add_configuration(file, "sprite_config")
importer = Compass::SpriteImporter.new(:uri => 'bad_extensions/*.jpg', :options => options) importer = Compass::SpriteImporter.new(:uri => 'bad_extensions/*.jpg', :options => options)
begin begin
importer.sass_engine importer.sass_engine
assert false, "Somthing happened an invalid sprite file made it past validation" assert false, "An invalid sprite file made it past validation."
rescue Compass::Error => e rescue Compass::Error => e
assert e.message.include?('.png') assert e.message.include?('.png')
end end
end end
def taredown
Compass.reset_configuration!
end
end end

View File

@ -1,32 +1,11 @@
require 'spec_helper' require 'test_helper'
require 'compass/commands'
require 'compass/exec' class SpriteCommandTest < Test::Unit::TestCase
require 'compass/commands/sprite' attr_reader :test_dir
describe Compass::Commands::Sprite do
def config_data
return <<-CONFIG
images_path = #{@images_tmp_path.inspect}
CONFIG
end
def create_temp_cli_dir def setup
directory = File.join(File.expand_path('../', __FILE__), 'test') @images_src_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images')
::FileUtils.mkdir_p directory @images_tmp_path = File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sprites', 'public', 'images-tmp')
@test_dir = directory
end
def run_compass_with_options(options)
output = 'foo'
::Dir.chdir @test_dir
%x{compass #{options.join(' ')}}
end
def options_to_cli(options)
options.map.flatten!
end
let(:test_dir) { @test_dir }
before :each do
@before_dir = ::Dir.pwd @before_dir = ::Dir.pwd
create_temp_cli_dir create_temp_cli_dir
create_sprite_temp create_sprite_temp
@ -34,22 +13,48 @@ describe Compass::Commands::Sprite do
f << config_data f << config_data
end end
end end
after :each do
def create_sprite_temp
::FileUtils.cp_r @images_src_path, @images_tmp_path
end
def clean_up_sprites
::FileUtils.rm_r @images_tmp_path
end
def config_data
return <<-CONFIG
images_path = #{@images_tmp_path.inspect}
CONFIG
end
def create_temp_cli_dir
directory = File.join(File.expand_path('../', __FILE__), 'test')
::FileUtils.mkdir_p directory
@test_dir = directory
end
def run_compass_with_options(options)
output = 'foo'
::Dir.chdir @test_dir
%x{compass #{options.join(' ')}}
end
def options_to_cli(options)
options.map.flatten!
end
def teardown
::Dir.chdir @before_dir ::Dir.chdir @before_dir
clean_up_sprites clean_up_sprites
if File.exists?(@test_dir) if File.exists?(@test_dir)
::FileUtils.rm_r @test_dir ::FileUtils.rm_r @test_dir
end end
end end
it "should create sprite file" do it "should create sprite file" do
run_compass_with_options(['sprite', "-f", "stylesheet.scss", "'#{@images_tmp_path}/*.png'"]).to_i.should == 0 assert_equal 0, run_compass_with_options(['sprite', "-f", 'stylesheet.scss', "'#{@images_tmp_path}/*.png'"]).to_i
File.exists?(File.join(test_dir, 'stylesheet.scss')).should be_true assert File.exists?(File.join(test_dir, 'stylesheet.scss'))
end end
it "should fail gracfuly when giving bad arguments" do
pending
end
end end