diff --git a/Gemfile b/Gemfile index 706e9e85..d884daa8 100644 --- a/Gemfile +++ b/Gemfile @@ -14,4 +14,9 @@ gem "livereload" gem "ruby-prof" unless RUBY_PLATFORM == "java" -#gem 'rmagick' \ No newline at end of file +gem 'autotest' +gem 'fakefs', :git => 'git://github.com/johnbintz/fakefs.git' +gem 'mocha' +gem 'timecop' + +#gem 'rmagick' diff --git a/Gemfile.lock b/Gemfile.lock index 6783942b..85b70cc1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,20 @@ +GIT + remote: git://github.com/johnbintz/fakefs.git + revision: 005ddaaeb2b2881391c31ac9846a55ce5a42c206 + specs: + fakefs (0.3.1) + PATH remote: . specs: - compass (0.11.beta.3.46eec53) + compass (0.11.beta.3.6e1daf6) chunky_png (~> 1.1.0) sass (>= 3.1.0.alpha.249) GEM remote: http://rubygems.org/ specs: + ZenTest (4.5.0) abstract (1.0.0) actionmailer (3.0.5) actionpack (= 3.0.5) @@ -37,6 +44,8 @@ GEM activesupport (3.0.5) addressable (2.2.4) arel (2.0.9) + autotest (4.4.6) + ZenTest (>= 4.4.1) builder (2.1.2) chunky_png (1.1.0) compass-validator (3.0.0) @@ -76,6 +85,7 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.16) + mocha (0.9.12) polyglot (0.3.1) rack (1.2.2) rack-mount (0.6.13) @@ -114,6 +124,7 @@ GEM sass (3.1.0.alpha.249) term-ansicolor (1.0.5) thor (0.14.6) + timecop (0.3.5) treetop (1.4.9) polyglot (>= 0.3.1) tzinfo (0.3.25) @@ -123,15 +134,19 @@ PLATFORMS ruby DEPENDENCIES + autotest compass! compass-validator (= 3.0.0) css_parser (~> 1.0.1) cucumber (~> 0.9.2) + fakefs! haml (~> 3.1.0.alpha) livereload + mocha rails (~> 3.0.0.rc) rcov rspec (~> 2.0.0) ruby-prof rubyzip sass (= 3.1.0.alpha.249) + timecop diff --git a/autotest/discover.rb b/autotest/discover.rb new file mode 100644 index 00000000..d02f3b31 --- /dev/null +++ b/autotest/discover.rb @@ -0,0 +1,4 @@ +Autotest.add_discovery { 'rspec2' } + + + diff --git a/lib/compass/sass_extensions/sprites/base.rb b/lib/compass/sass_extensions/sprites/base.rb index 3d2e20c2..18120922 100644 --- a/lib/compass/sass_extensions/sprites/base.rb +++ b/lib/compass/sass_extensions/sprites/base.rb @@ -1,14 +1,16 @@ +require 'compass/sprite_map' + module Compass module SassExtensions module Sprites class Base < Sass::Script::Literal - def self.from_uri(uri, context, kwargs) - path, name = Compass::Sprites.path_and_name(uri.value) - sprites = Compass::Sprites.discover_sprites(uri.value).map do |sprite| + sprite_map = ::Compass::SpriteMap.new(uri.value, {}) + + sprites = sprite_map.files.map do |sprite| sprite.gsub(Compass.configuration.images_path+"/", "") end - new(sprites, path, name, context, kwargs) + new(sprites, sprite_map.path, sprite_map.name, context, kwargs) end def require_engine! @@ -50,7 +52,7 @@ module Compass def init_images @images = image_names.collect do |relative_file| - image = Compass::SassExtensions::Sprites::Image.new(relative_file, options) + image = Compass::SassExtensions::Sprites::Image.new(self, relative_file, options) @width = [ @width, image.width + image.offset ].max image end @@ -84,7 +86,7 @@ module Compass end def sprite_names - image_names.map{|f| Compass::Sprites.sprite_name(f) } + image_names.map { |f| File.basename(f, '.png') } end def validate! diff --git a/lib/compass/sass_extensions/sprites/image.rb b/lib/compass/sass_extensions/sprites/image.rb index e28de0e3..11fc5c7e 100644 --- a/lib/compass/sass_extensions/sprites/image.rb +++ b/lib/compass/sass_extensions/sprites/image.rb @@ -2,11 +2,11 @@ module Compass module SassExtensions module Sprites class Image - attr_reader :relative_file, :options + attr_reader :relative_file, :options, :base attr_accessor :top, :left - def initialize(relative_file, options) - @relative_file, @options = relative_file, options + def initialize(base, relative_file, options) + @base, @relative_file, @options = base, relative_file, options @left = @top = 0 end @@ -23,7 +23,7 @@ module Compass end def name - Compass::Sprites.sprite_name(relative_file) + File.basename(relative_file, '.png') end def repeat diff --git a/lib/compass/sprite_map.rb b/lib/compass/sprite_map.rb new file mode 100644 index 00000000..ece478a7 --- /dev/null +++ b/lib/compass/sprite_map.rb @@ -0,0 +1,121 @@ +module Compass + class SpriteMap + attr_reader :uri, :options + + def initialize(uri, options) + @uri, @options = uri, options + end + + def name + ensure_path_and_name! + @name + end + + def path + ensure_path_and_name! + @path + end + + def files + @files ||= Dir[File.join(Compass.configuration.images_path, uri)].sort + end + + def sprite_names + @sprite_names ||= files.collect { |file| File.basename(file, '.png') } + end + + def sass_options + @sass_options ||= options.merge(:filename => name, :syntax => :scss, :importer => self) + end + + def mtime + Compass.quick_cache("mtime:#{uri}") do + files.collect { |file| File.mtime(file) }.max + end + end + + def sass_engine + Sass::Engine.new(content_for_images, options) + end + + private + def ensure_path_and_name! + return if @path && @name + uri =~ %r{((.+/)?(.+))/(.+?)\.png} + @path, @name = $1, $3 + end + + 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; + +#{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\");" : 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 + + 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" + 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/sprites.rb b/lib/compass/sprites.rb index 26e48159..5dea8570 100644 --- a/lib/compass/sprites.rb +++ b/lib/compass/sprites.rb @@ -1,128 +1,26 @@ module Compass class Sprites < Sass::Importers::Base - attr_accessor :name - attr_accessor :path - - class << self - def path_and_name(uri) - if uri =~ %r{((.+/)?(.+))/(.+?)\.png} - [$1, $3, $4] - end - end - - def discover_sprites(uri) - glob = File.join(Compass.configuration.images_path, uri) - Dir.glob(glob).sort - end - - def sprite_name(file) - File.basename(file, '.png') - end - - end - def find_relative(*args) nil end def find(uri, options) if uri =~ /\.png$/ - self.path, self.name = Compass::Sprites.path_and_name(uri) - options.merge! :filename => name, :syntax => :scss, :importer => self - sprite_files = Compass::Sprites.discover_sprites(uri) - image_names = sprite_files.map {|i| Compass::Sprites.sprite_name(i) } - Sass::Engine.new(content_for_images(uri, name, image_names), options) + SpriteMap.new(uri, options).sass_engine end end - def content_for_images(uri, name, 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; - -#{skip_overrides ? "$#{name}-sprites: sprite-map(\"#{uri}\");" : generate_overrides(uri, name, images) } - -// 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(#{images.join(" ")}, $dimensions, $prefix); -} -SCSS - end - def key(uri, options) [self.class.name + ":" + File.dirname(File.expand_path(uri)), File.basename(uri)] end def mtime(uri, options) - Compass.quick_cache("mtime:#{uri}") do - self.path, self.name = Compass::Sprites.path_and_name(uri) - glob = File.join(Compass.configuration.images_path, uri) - Dir.glob(glob).inject(Time.at(0)) do |max_time, file| - (t = File.mtime(file)) > max_time ? t : max_time - end - end + SpriteMap.new(uri, options).mtime end def to_s "" end - - def generate_overrides(uri, name,images) - content = <<-TXT -// These variables control the generated sprite output -// You can override them selectively before you import this file. - TXT - images.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" - content += images.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/image_spec.rb b/spec/compass/sass_extensions/sprites/image_spec.rb index df62faab..7708233f 100644 --- a/spec/compass/sass_extensions/sprites/image_spec.rb +++ b/spec/compass/sass_extensions/sprites/image_spec.rb @@ -5,7 +5,7 @@ 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(:image) { self.class.describes.new(File.join(sprite_filename), options)} + let(:image) { self.class.describes.new(nil, File.join(sprite_filename), options)} let(:digest) { Digest::MD5.file(sprite_path).hexdigest } subject { image } @@ -26,9 +26,13 @@ describe Compass::SassExtensions::Sprites::Image do its(:left) { should == 0 } end + let(:get_var_expects) { nil } + let(:get_var_return) { nil } + let(:options) { - options = Object.new - options.stub(:get_var) { |which| (which == get_var_expects) ? get_var_return : nil } + options = mock + options.stubs(:get_var).with(anything).returns(nil) + options.stubs(:get_var).with(get_var_expects).returns(get_var_return) options } @@ -105,32 +109,30 @@ describe Compass::SassExtensions::Sprites::Image do end describe '#offset' do - before { image.stub(:position) { stub_position } } + before { image.stubs(:position).returns(stub_position) } let(:offset) { 100 } let(:stub_position) { - stub = double - stub.stub(:value) { offset } - stub + stub(:value => offset) } context 'unitless' do - before { stub_position.stub(:unitless?) { true } } - before { stub_position.stub(:unit_str) { 'em' } } + 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.stub(:unitless?) { false } } - before { stub_position.stub(:unit_str) { 'px' } } + 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.stub(:unitless?) { false } } - before { stub_position.stub(:unit_str) { 'em' } } + before { stub_position.stubs(:unitless?).returns(false) } + before { stub_position.stubs(:unit_str).returns('em') } its(:offset) { should == 0 } end diff --git a/spec/compass/sprite_map_spec.rb b/spec/compass/sprite_map_spec.rb new file mode 100644 index 00000000..8df74e48 --- /dev/null +++ b/spec/compass/sprite_map_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' +require 'compass/sprite_map' +require 'fakefs/spec_helpers' +require 'timecop' + +describe Compass::SpriteMap 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) } + + its(:mtime) { should == mtime } + + it "should have a test for the sass engine" + end +end diff --git a/spec/compass/sprites_spec.rb b/spec/compass/sprites_spec.rb new file mode 100644 index 00000000..648fc929 --- /dev/null +++ b/spec/compass/sprites_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +require 'compass/sprites' +require 'fakefs/spec_helpers' + +describe Compass::Sprites do +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ccc17e29..1eb4ea2e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,7 @@ require 'rubygems' require 'compass' require 'rspec' require 'rspec/autorun' +require 'mocha' module CompassGlobalInclude class << self @@ -17,4 +18,6 @@ end RSpec.configure do |config| config.include(CompassGlobalInclude) + + config.mock_with :mocha end