wikyd-lemonade 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ Changelog
2
+ =========
3
+
4
+ TODO
5
+ ----
6
+
7
+ * Rebuild sprite image only if images have been modified (including integration specs)
8
+ * Horizontal sprites
9
+ * Add more validations and error messages
10
+ * Rebuild Sass files when images but not Sass sources have been updated
11
+
12
+
13
+ 1.0.0.beta.1
14
+ ------------
15
+
16
+ * Fixed warnings generated by Compass
17
+ * Now works perfectly, if larger spacing is defined after smaller spacing
18
+ * Removed Compass dependencies
19
+ * New Sass functions: `sprite-url()` and `sprite-position()`
20
+ * Mixins: `sprite-image`, `sizes-sprite-image`, `sprite-folder`, `sized-sprite-folder`
21
+ * Internal changes:
22
+ * Switched to Bundler
23
+ * Rewritten calculation (all Y positions are calculated after CSS is generated by Sass)
24
+ * Moved integration to `Sass::Tree::RootNode` (works the same for Rails 2.3, 3.0, Compass & Staticmatic)
25
+ * Added more specs
26
+
27
+
28
+ 0.3.4
29
+ -----
30
+
31
+ * Updated to chunky_png 0.8.0 (PNG color issues have been fixed there)
32
+
33
+
34
+ 0.3.3
35
+ -----
36
+
37
+ * Fixed Rails 3.0 integration (Beta 4)
38
+
39
+
40
+ 0.3.2
41
+ -----
42
+
43
+ * Fixed 0.3.1 composition
44
+
45
+
46
+ 0.3.1
47
+ -----
48
+
49
+ * Fixed rendering of images with RGBA values (no dark borders anymore)
50
+ * (buggy composition)
51
+
52
+
53
+ 0.3.0
54
+ -----
55
+
56
+ * Switched from RMagick to chunky_png gem
57
+ * No RMagick/ImageMagick required anymore (Rails 2.3.x sometimes crashed)
58
+ * Only PNG files are supported (both input and output)
59
+ * Don’t compose the same image twice (use background-position of first image instead)
60
+ * Space between images now works as expected if more than 1 output image (path) is used
61
+ * Wrote this changelog
62
+
63
+
64
+ 0.2.0
65
+ -----
66
+
67
+ * Support for background-positions
68
+ * Support for 100%/right aligned images
69
+
70
+
71
+ 0.1.0
72
+ -----
73
+
74
+ * Initial release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Nico Hagenburger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ Lemonade—On the fly sprite generator for Sass/Compass
2
+ =====================================================
3
+
4
+ Please read my [blog post on CSS sprites for Sass/Compass](http://www.hagenburger.net/BLOG/Lemonade-CSS-Sprites-for-Sass-Compass.html) or have a look at the presentation [3 steps to make better and faster frontends](http://www.hagenburger.net/BLOG/3-Steps-to-Make-Better-And-Faster-Frontends.html) (slides 23—37).
5
+
6
+
7
+ **Usage ([SCSS or Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html)):**
8
+
9
+ .fanta {
10
+ background: sprite-image("bottles/fanta.png");
11
+ }
12
+ .seven-up {
13
+ background: sprite-image("bottles/seven-up.png");
14
+ }
15
+ .coke {
16
+ background: sprite-image("cans/coke.png") no-repeat;
17
+ }
18
+
19
+ **Output (CSS):**
20
+
21
+ .fanta {
22
+ background: url('/images/bottles.png');
23
+ }
24
+ .seven-up {
25
+ background: url('/images/bottles.png') 0 -50px;
26
+ }
27
+ .coke {
28
+ background: url('/images/cans.png') no-repeat;
29
+ }
30
+
31
+
32
+ Background
33
+ ----------
34
+
35
+ * Generates a sprite image for each folder (e. g. “bottles” and “cans”)
36
+ * Sets the background position (unless “0 0”)
37
+ * It uses the `images_dir` defined by Compass (just like `image-url()`)
38
+ * No Rake task needed
39
+ * No additional classes
40
+ * No configuration
41
+ * No RMagick required (but full support for PNG)
42
+
43
+
44
+ Installation
45
+ ------------
46
+
47
+ (1)
48
+
49
+ gem install lemonade
50
+
51
+ (2)
52
+ Now open your `config.rb` ([Compass cofiguration file](http://compass-style.org/docs/tutorials/configuration-reference/)) and add one line after this comment:
53
+
54
+ # Require any additional compass plugins here.
55
+ require "lemonade"
56
+
57
+
58
+ Current State
59
+ -------------
60
+
61
+ * Compass standalone finished
62
+ * Rails Sass integration finished
63
+ * Staticmatic integration finished
64
+ * Haml integration (with “:sass” filter): work in progress
65
+
66
+
67
+ Options
68
+ -------
69
+
70
+ You can pass an additional background position.
71
+ It will be added to the calculated position:
72
+
73
+ .seven-up {
74
+ background: sprite-image("bottles/seven-up.png", 12px, 3px);
75
+ }
76
+
77
+ Output (assuming the calculated position would be “0 -50px” as shown above):
78
+
79
+ .seven-up {
80
+ background: url('/images/bottles.png') 12px -47px;
81
+ }
82
+
83
+ If you need empty space around the current image, this will add 20px transparent space above and below.
84
+
85
+ .seven-up {
86
+ background: sprite-image("bottles/seven-up.png", 0, 0, 20px);
87
+ }
88
+
89
+ This one adds 20px above, 30px below:
90
+
91
+ .seven-up {
92
+ background: sprite-image("bottles/seven-up.png", 0, 0, 20px, 30px);
93
+ }
94
+
95
+ Right aligned images are possible:
96
+
97
+ .seven-up {
98
+ background: sprite-image("bottles/seven-up.png", 100%, 4px);
99
+ }
100
+
101
+ The original image will be placed on the right side of the sprite image.
102
+ Use this, if you have a link with an arrow on the right side (like Apple).
103
+
104
+ For 1 pixel wide type repeats, you can fill out the image so repeat-x will work.
105
+
106
+ background: sprite-image("bottles/vertical-orange.png", 0, 0, 0, 0, true);
107
+
108
+
109
+ Note on Patches/Pull Requests
110
+ -----------------------------
111
+
112
+ * Fork the project.
113
+ * Make your feature addition or bug fix.
114
+ * Add tests for it. This is important so I don't break it in a
115
+ future version unintentionally.
116
+ * Commit, do not mess with rakefile, version, or history.
117
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
118
+ * Send me a pull request. Bonus points for topic branches.
119
+
120
+
121
+ Rails 3 Troubleshooting
122
+ -----------------------
123
+
124
+ If you want to use Lemonade with Rails 3 Please use this compass and haml versions in your Gemfile
125
+
126
+ gem 'compass', '~> 0.10.2'
127
+ gem 'haml-edge', '~> 3.1.49', :require => 'haml'
128
+
129
+
130
+ Contributors
131
+ ------------
132
+
133
+ * [Chris Hoffman](http://github.com/cehoffman)
134
+ * [Jason Weathered](http://github.com/jasoncodes) ([twitter](http://twitter.com/jasoncodes))
135
+ * [Tobias Kraze](http://github.com/kratob)
136
+ * [Sebastian Deutsch](http://github.com/sebastiandeutsch) ([twitter](http://twitter.com/sippndipp))
137
+ * [Nico Hagenburger](http://github.com/hagenburger) ([twitter](http://twitter.com/hagenburger))
138
+
139
+
140
+ Copyright
141
+ ---------
142
+
143
+ Copyright (c) 2010 [Nico Hagenburger](http://www.hagenburger.net).
144
+ See MIT-LICENSE for details.
145
+ [Follow me](http://twitter.com/hagenburger) on twitter.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+
5
+ begin
6
+ require 'spec/rake/spectask'
7
+ Spec::Rake::SpecTask.new(:spec) do |spec|
8
+ spec.libs << 'lib' << 'spec'
9
+ spec.spec_files = FileList['spec/**/*_spec.rb']
10
+ end
11
+
12
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
13
+ spec.libs << 'lib' << 'spec'
14
+ spec.pattern = 'spec/**/*_spec.rb'
15
+ spec.rcov = true
16
+ end
17
+
18
+ task :default => :spec
19
+ rescue LoadError
20
+ puts "Rspec (or a dependency) is not available. Try running bundler install"
21
+ end
22
+
23
+ require 'rake/rdoctask'
24
+ Rake::RDocTask.new do |rdoc|
25
+ require File.expand_path('../lib/lemonade/version', __FILE__)
26
+
27
+ rdoc.rdoc_dir = 'rdoc'
28
+ rdoc.title = "lemonade #{Lemonade::Version}"
29
+ rdoc.rdoc_files.include('README*')
30
+ rdoc.rdoc_files.include('lib/**/*.rb')
31
+ end
@@ -0,0 +1,21 @@
1
+ module Sass
2
+
3
+ module Tree
4
+
5
+ class RootNode < Node
6
+
7
+ alias_method :render_without_lemonade, :render
8
+ def render
9
+ if result = render_without_lemonade
10
+ Lemonade.generate_sprites
11
+ result = ERB.new(result).result(binding)
12
+ Lemonade.reset
13
+ return result
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,123 @@
1
+ module Sass::Script::Functions
2
+
3
+ def sprite_url(file)
4
+ dir, name, basename = extract_names(file)
5
+ sprite = sprite_for("#{dir}#{name}")
6
+ Sass::Script::SpriteInfo.new(:url, sprite)
7
+ end
8
+
9
+ def sprite_position(file, position_x = nil, position_y_shift = nil, margin_top_or_both = nil, margin_bottom = nil, repeat = nil)
10
+ sprite, sprite_item = sprite_url_and_position(file, position_x, position_y_shift, margin_top_or_both, margin_bottom, repeat)
11
+ Sass::Script::SpriteInfo.new(:position, sprite, sprite_item, position_x, position_y_shift)
12
+ end
13
+
14
+ def sprite_image(file, position_x = nil, position_y_shift = nil, margin_top_or_both = nil, margin_bottom = nil, repeat = nil)
15
+ sprite, sprite_item = sprite_url_and_position(file, position_x, position_y_shift, margin_top_or_both, margin_bottom, repeat)
16
+ Sass::Script::SpriteInfo.new(:both, sprite, sprite_item, position_x, position_y_shift)
17
+ end
18
+ alias_method :sprite_img, :sprite_image
19
+
20
+ def sprite_files_in_folder(folder)
21
+ assert_type folder, :String
22
+ count = sprite_file_list_from_folder(folder).length
23
+ Sass::Script::Number.new(count)
24
+ end
25
+
26
+ def sprite_file_from_folder(folder, n)
27
+ assert_type folder, :String
28
+ assert_type n, :Number
29
+ file = sprite_file_list_from_folder(folder)[n.to_i]
30
+ file = File.basename(file)
31
+ Sass::Script::String.new(File.join(folder.value, file))
32
+ end
33
+
34
+ def sprite_name(file)
35
+ dir, name, basename = extract_names(file)
36
+ Sass::Script::String.new(name)
37
+ end
38
+
39
+ def image_basename(file)
40
+ dir, name, basename = extract_names(file, :check_file => true)
41
+ Sass::Script::String.new(basename)
42
+ end
43
+
44
+ private
45
+
46
+ def sprite_file_list_from_folder(folder)
47
+ dir = File.join(Lemonade.sprites_path, folder.value)
48
+ Dir.glob(File.join(dir, '*.png')).sort
49
+ end
50
+
51
+ def sprite_url_and_position(file, position_x = nil, position_y_shift = nil, margin_top_or_both = nil, margin_bottom = nil, repeat = nil)
52
+ dir, name, basename = extract_names(file, :check_file => true)
53
+ filestr = File.join(Lemonade.sprites_path, file.value)
54
+
55
+ sprite_file = "#{dir}#{name}.png"
56
+ sprite = sprite_for(sprite_file)
57
+ sprite_item = image_for(sprite, filestr, position_x, position_y_shift, margin_top_or_both, margin_bottom, repeat)
58
+
59
+ # Create a temporary destination file so compass doesn't complain about a missing image
60
+ FileUtils.touch File.join(Lemonade.images_path, sprite_file)
61
+
62
+ [sprite, sprite_item]
63
+ end
64
+
65
+ def extract_names(file, options = {})
66
+ assert_type file, :String
67
+ unless (file.value =~ %r(^(.+/)?([^\.]+?)(/(.+?)\.(png))?$)) == 0
68
+ raise Sass::SyntaxError, 'Please provide a file in a folder: e.g. sprites/button.png'
69
+ end
70
+ dir, name, basename = $1, $2, $4
71
+ if options[:check_file] and basename.nil?
72
+ raise Sass::SyntaxError, 'Please provide a file in a folder: e.g. sprites/button.png'
73
+ end
74
+ [dir, name, basename]
75
+ end
76
+
77
+ def sprite_for(file)
78
+ file = "#{file}.png" unless file =~ /\.png$/
79
+ Lemonade.sprites[file] ||= {
80
+ :file => "#{file}",
81
+ :height => 0,
82
+ :width => 0,
83
+ :images => [],
84
+ :margin_bottom => 0
85
+ }
86
+ end
87
+
88
+ def image_for(sprite, file, position_x, position_y_shift, margin_top_or_both, margin_bottom, repeat)
89
+ image = sprite[:images].detect{ |image| image[:file] == file }
90
+ margin_top_or_both ||= Sass::Script::Number.new(0)
91
+ margin_top = margin_top_or_both.value #calculate_margin_top(sprite, margin_top_or_both, margin_bottom)
92
+ margin_bottom = (margin_bottom || margin_top_or_both).value
93
+ if repeat
94
+ repeat == 'true' ? true: false
95
+ end
96
+ if image
97
+ image[:margin_top] = margin_top if margin_top > image[:margin_top]
98
+ image[:margin_bottom] = margin_bottom if margin_bottom > image[:margin_bottom]
99
+ else
100
+ width, height = ChunkyPNG::Image.from_file(file).size
101
+ x = (position_x and position_x.numerator_units == %w(%)) ? position_x : Sass::Script::Number.new(0)
102
+ y = sprite[:height] + margin_top
103
+ y = Sass::Script::Number.new(y, y == 0 ? [] : ['px'])
104
+ image = {
105
+ :file => file,
106
+ :height => height,
107
+ :width => width,
108
+ :x => x,
109
+ :margin_top => margin_top,
110
+ :margin_bottom => margin_bottom,
111
+ :index => sprite[:images].length,
112
+ :repeat => repeat
113
+ }
114
+ sprite[:images] << image
115
+ end
116
+ image
117
+ rescue Errno::ENOENT
118
+ raise Sass::SyntaxError, "#{file} does not exist in sprites_dir #{Lemonade.sprites_path}"
119
+ rescue ChunkyPNG::SignatureMismatch
120
+ raise Sass::SyntaxError, "#{file} is not a recognized png file, can't use for sprite creation"
121
+ end
122
+
123
+ end
@@ -0,0 +1,63 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+
5
+ class SpriteInfo < Literal
6
+ attr_reader :sprite
7
+ attr_reader :sprite_item
8
+ attr_reader :type
9
+
10
+ def initialize(type, sprite, sprite_item = nil, position_x = nil, position_y_shift = nil)
11
+ super(nil)
12
+ @type = type
13
+ @sprite = sprite
14
+ @sprite_item = sprite_item
15
+ @position_x = position_x
16
+ @position_y_shift = position_y_shift
17
+ end
18
+
19
+ def to_s(opts = {})
20
+ case @type
21
+ when :position
22
+ position
23
+ when :url
24
+ url
25
+ when :both
26
+ pos = position
27
+ if pos == '0 0'
28
+ url
29
+ else
30
+ "#{url} #{pos}"
31
+ end
32
+ end
33
+ end
34
+
35
+ def to_sass
36
+ to_s
37
+ end
38
+
39
+ private
40
+
41
+ def position
42
+ x = @position_x || 0
43
+ if @sprite_item[:index] == 0 and (@position_y_shift.nil? or @position_y_shift.value == 0)
44
+ "#{x.inspect} 0"
45
+ else
46
+ expression = "Lemonade.sprites['#{@sprite[:file]}'][:images][#{@sprite_item[:index]}][:y].unary_minus"
47
+ expression << ".plus(Sass::Script::Number.new(#{@position_y_shift.value}, ['px']))" if @position_y_shift
48
+ "#{x.inspect} <%= #{expression} %>"
49
+ end
50
+ end
51
+
52
+ def url
53
+ if defined?(Compass)
54
+ compass = Class.new.extend(Compass::SassExtensions::Functions::Urls)
55
+ compass.image_url(Sass::Script::String.new(@sprite[:file])).to_s
56
+ else
57
+ "url('/#{@sprite[:file]}')"
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,3 @@
1
+ module Lemonade
2
+ Version = "1.0.0.beta.1"
3
+ end
data/lib/lemonade.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'chunky_png'
2
+ require 'lemonade/sprite_info.rb'
3
+
4
+ module Lemonade
5
+ @@sprites = {}
6
+ @@sprites_path = nil
7
+ @@images_path = nil
8
+
9
+ class << self
10
+
11
+ def sprites
12
+ @@sprites
13
+ end
14
+
15
+ def sprites_path
16
+ @@sprites_path || images_path
17
+ end
18
+
19
+ def sprites_path=(path)
20
+ @@sprites_path = path
21
+ end
22
+
23
+ def images_path
24
+ @@images_path || (defined?(Compass) ? Compass.configuration.images_path : 'public/images')
25
+ end
26
+
27
+ def images_path=(path)
28
+ @@images_path = path
29
+ end
30
+
31
+ def reset
32
+ @@sprites = {}
33
+ end
34
+
35
+ def generate_sprites
36
+ sprites.each do |sprite_name, sprite|
37
+ calculate_sprite sprite
38
+ if sprite_changed?(sprite_name, sprite)
39
+ generate_sprite_image sprite
40
+ remember_sprite_info! sprite_name, sprite
41
+ end
42
+ end
43
+ end
44
+
45
+ def extend_sass!
46
+ require 'sass'
47
+ require 'sass/plugin'
48
+ require File.expand_path('../lemonade/sass_functions', __FILE__)
49
+ require File.expand_path('../lemonade/sass_extension', __FILE__)
50
+ end
51
+
52
+ def extend_compass!
53
+ base_directory = File.join(File.dirname(__FILE__), '..')
54
+ Compass::Frameworks.register('lemonade', :path => base_directory)
55
+ end
56
+
57
+ def sprite_changed?(sprite_name, sprite)
58
+ existing_sprite_info = YAML.load(File.read(sprite_info_file(sprite_name)))
59
+ existing_sprite_info[:sprite] != sprite or existing_sprite_info[:timestamps] != timestamps(sprite)
60
+ rescue
61
+ true
62
+ end
63
+
64
+ def remember_sprite_info!(sprite_name, sprite)
65
+ File.open(sprite_info_file(sprite_name), 'w') do |file|
66
+ file << {
67
+ :sprite => sprite,
68
+ :timestamps => timestamps(sprite),
69
+ }.to_yaml
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def sprite_info_file(sprite_name)
76
+ File.join(Lemonade.images_path, "#{sprite_name}.sprite_info.yml")
77
+ end
78
+
79
+ def timestamps(sprite)
80
+ result = {}
81
+ sprite[:images].each do |image|
82
+ file_name = image[:file]
83
+ result[file_name] = File.ctime(file_name)
84
+ end
85
+ result
86
+ end
87
+
88
+ def calculate_sprite(sprite)
89
+ width, margin_bottom, y = 0, 0, 0
90
+ sprite[:images].each do |sprite_item|
91
+ if sprite_item[:index] == 0
92
+ margin_top = 0
93
+ elsif sprite_item[:margin_top] > margin_bottom
94
+ margin_top = sprite_item[:margin_top]
95
+ else
96
+ margin_top = margin_bottom
97
+ end
98
+ y += margin_top
99
+ sprite_item[:y] = Sass::Script::Number.new(y, ['px'])
100
+ y += sprite_item[:height]
101
+ width = sprite_item[:width] if sprite_item[:width] > width
102
+ margin_bottom = sprite_item[:margin_bottom]
103
+ end
104
+ sprite[:height] = y
105
+ sprite[:width] = width
106
+ end
107
+
108
+ def generate_sprite_image(sprite)
109
+ sprite_image = ChunkyPNG::Image.new(sprite[:width], sprite[:height], ChunkyPNG::Color::TRANSPARENT)
110
+ sprite[:images].each do |sprite_item|
111
+ sprite_item_image = ChunkyPNG::Image.from_file(sprite_item[:file])
112
+ x = (sprite[:width] - sprite_item[:width]) * (sprite_item[:x].value / 100)
113
+ y = sprite_item[:y].value
114
+
115
+ if sprite_item[:repeat]
116
+ copies = (sprite[:width]/sprite_item[:width]).to_i
117
+ else
118
+ copies = 1
119
+ end
120
+
121
+ copies.times do |i|
122
+ sprite_image.replace sprite_item_image, x + i*sprite_item[:width], y
123
+ end
124
+ end
125
+ sprite_image.save File.join(Lemonade.images_path, sprite[:file])
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
132
+
133
+ if defined?(Compass)
134
+ Lemonade.extend_compass!
135
+ end
136
+
137
+
138
+ if defined?(ActiveSupport) and Haml::Util.has?(:public_method, ActiveSupport, :on_load)
139
+ # Rails 3.0
140
+ ActiveSupport.on_load :before_initialize do
141
+ Lemonade.extend_sass!
142
+ end
143
+ else
144
+ Lemonade.extend_sass!
145
+ end
146
+
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Lemonade do
4
+
5
+ before :each do
6
+ @sprite = {
7
+ :info => 'info',
8
+ :images => [
9
+ { :file => 'file1' },
10
+ { :file => 'file2' },
11
+ ]
12
+ }
13
+
14
+ @file = ""
15
+ File.stub!(:read => @file)
16
+ Lemonade.stub(:images_path).and_return('image_path')
17
+ File.stub!(:ctime => Time.parse('2010-01-01 12:00'))
18
+ end
19
+
20
+ ###
21
+
22
+ describe '#remember_sprite_info' do
23
+ subject { Lemonade }
24
+
25
+ it 'should save sprite info into a file' do
26
+ File.should_receive(:open).with(File.join('image_path', 'the_sprite.sprite_info.yml'), 'w').and_yield(@file)
27
+ @file.should_receive(:<<)
28
+ subject.remember_sprite_info!('the_sprite', @sprite)
29
+ end
30
+ end
31
+
32
+ ###
33
+
34
+ describe '#sprite_changed?' do
35
+ subject { Lemonade }
36
+
37
+ it 'should be false if nothing changed' do
38
+ File.should_receive(:open).and_yield(@file)
39
+ subject.remember_sprite_info!('the sprite', @sprite)
40
+ subject.sprite_changed?('the sprite', @sprite).should be_false
41
+ end
42
+
43
+ it 'should be true if the sprite info has changed' do
44
+ File.should_receive(:open).and_yield(@file)
45
+ subject.remember_sprite_info!('the sprite', @sprite)
46
+ @sprite[:info] = 'changed info'
47
+ subject.sprite_changed?('the sprite', @sprite).should be_true
48
+ end
49
+
50
+ it 'should be true if the images changed' do
51
+ File.should_receive(:open).and_yield(@file)
52
+ subject.remember_sprite_info!('the sprite', @sprite)
53
+ @sprite[:images] = []
54
+ subject.sprite_changed?('the sprite', @sprite).should be_true
55
+ end
56
+
57
+ it 'should be true if a images timestamp changed' do
58
+ File.should_receive(:open).and_yield(@file)
59
+ subject.remember_sprite_info!('the sprite', @sprite)
60
+ File.stub!(:ctime => Time.now)
61
+ subject.sprite_changed?('the sprite', @sprite).should be_true
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,228 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Sass::Script::Functions do
4
+
5
+ before :each do
6
+ Lemonade.reset
7
+ FileUtils.cp_r File.dirname(__FILE__) + '/images', IMAGES_TMP_PATH
8
+ end
9
+
10
+ after :each do
11
+ FileUtils.rm_r IMAGES_TMP_PATH
12
+ end
13
+
14
+ def image_size(file)
15
+ IO.read(IMAGES_TMP_PATH + '/' + file)[0x10..0x18].unpack('NN')
16
+ end
17
+
18
+ def evaluate(*values)
19
+ sass = 'div' + values.map{ |value| "\n background: #{value}" }.join
20
+ css = Sass::Engine.new(sass, :syntax => :sass).render
21
+ # find rendered CSS values
22
+ # strip selectors, porperty names, semicolons and whitespace
23
+ css = css.gsub(/div {\s*background: (.+?);\s*}\s*/m, '\\1').split(/;\s*background: /)
24
+ css = css.first if css.length == 1
25
+ return css
26
+ end
27
+
28
+ ###
29
+
30
+ it "should return the sprite file name" do
31
+ evaluate('sprite-image("sprites/30x30.png")').should == "url('/sprites.png')"
32
+ end
33
+
34
+ it "should also work with `sprite-img`" do
35
+ evaluate('sprite-img("sprites/30x30.png")').should == "url('/sprites.png')"
36
+ end
37
+
38
+ it "should work in folders with dashes and underscores" do
39
+ evaluate('sprite-image("other_images/more-images/sprites/test.png")').should ==
40
+ "url('/other_images/more-images/sprites.png')"
41
+ end
42
+
43
+ it "should not work without any folder" do
44
+ lambda { evaluate('sprite-image("test.png")') }.should raise_exception(Sass::SyntaxError)
45
+ end
46
+
47
+ it "should set the background position" do
48
+ evaluate('sprite-image("sprites/30x30.png") sprite-image("sprites/150x10.png")').should ==
49
+ "url('/sprites.png') url('/sprites.png') 0 -30px"
50
+ image_size('sprites.png').should == [150, 40]
51
+ end
52
+
53
+ it "should use the X position" do
54
+ evaluate('sprite-image("sprites/30x30.png", 5px, 0)').should == "url('/sprites.png') 5px 0"
55
+ image_size('sprites.png').should == [30, 30]
56
+ end
57
+
58
+ it "should include the Y position" do
59
+ evaluate('sprite-image("sprites/30x30.png", 0, 3px) sprite-image("sprites/150x10.png", 0, -6px)').should ==
60
+ "url('/sprites.png') 0 3px url('/sprites.png') 0 -36px"
61
+ end
62
+
63
+ it "should calculate 20px empty space between sprites" do
64
+ # Resulting sprite should look like (1 line = 10px height, X = placed image):
65
+
66
+ # X
67
+ #
68
+ #
69
+ # XX
70
+ # XX
71
+ #
72
+ #
73
+ # XXX
74
+ # XXX
75
+ # XXX
76
+
77
+ evaluate(
78
+ 'sprite-image("sprites/10x10.png")',
79
+ 'sprite-image("sprites/20x20.png", 0, 0, 20px)',
80
+ 'sprite-image("sprites/30x30.png", 0, 0, 20px)'
81
+ ).should == [
82
+ "url('/sprites.png')",
83
+ "url('/sprites.png') 0 -30px",
84
+ "url('/sprites.png') 0 -70px"
85
+ ]
86
+ image_size('sprites.png').should == [30, 100]
87
+ end
88
+
89
+ it "should calculate empty space between sprites and combine space like CSS margins" do
90
+ # Resulting sprite should look like (1 line = 10px height, X = placed image):
91
+
92
+ # X
93
+ #
94
+ #
95
+ #
96
+ # XX
97
+ # XX
98
+ #
99
+ # XXX
100
+ # XXX
101
+ # XXX
102
+
103
+ evaluate(
104
+ 'sprite-image("sprites/10x10.png", 0, 0, 0, 30px)',
105
+ 'sprite-image("sprites/20x20.png", 0, 0, 20px, 5px)',
106
+ 'sprite-image("sprites/30x30.png", 0, 0, 10px)'
107
+ ).should == [
108
+ "url('/sprites.png')",
109
+ "url('/sprites.png') 0 -40px",
110
+ "url('/sprites.png') 0 -70px"
111
+ ]
112
+ image_size('sprites.png').should == [30, 100]
113
+ end
114
+
115
+ it "should calculate empty space correctly when 2 output images are uses" do
116
+ evaluate(
117
+ 'sprite-image("sprites/10x10.png", 0, 0, 0, 30px)',
118
+ 'sprite-image("other_images/test.png")',
119
+ 'sprite-image("sprites/20x20.png", 0, 0, 20px, 5px)'
120
+ ).should == [
121
+ "url('/sprites.png')",
122
+ "url('/other_images.png')",
123
+ "url('/sprites.png') 0 -40px"
124
+ ]
125
+ end
126
+
127
+ it "should allow % for x positions" do
128
+ # Resulting sprite should look like (1 line = 10px height, X = placed image):
129
+
130
+ # XXXXXXXXXXXXXXX
131
+ # X
132
+
133
+ evaluate(
134
+ 'sprite-image("sprites/150x10.png")',
135
+ 'sprite-image("sprites/10x10.png", 100%)'
136
+ ).should == [
137
+ "url('/sprites.png')",
138
+ "url('/sprites.png') 100% -10px"
139
+ ]
140
+ end
141
+
142
+ it "should not compose the same image twice" do
143
+ evaluate(
144
+ 'sprite-image("sprites/10x10.png")',
145
+ 'sprite-image("sprites/20x20.png")',
146
+ 'sprite-image("sprites/20x20.png")' # reuse image from line above
147
+ ).should == [
148
+ "url('/sprites.png')",
149
+ "url('/sprites.png') 0 -10px",
150
+ "url('/sprites.png') 0 -10px"
151
+ ]
152
+ image_size('sprites.png').should == [20, 30]
153
+ end
154
+
155
+ it "should calculate the maximum spacing between images" do
156
+ evaluate(
157
+ 'sprite-image("sprites/10x10.png")',
158
+ 'sprite-image("sprites/20x20.png", 0, 0, 10px)',
159
+ 'sprite-image("sprites/20x20.png", 0, 0, 99px)' # 99px > 10px
160
+ ).should == [
161
+ "url('/sprites.png')",
162
+ "url('/sprites.png') 0 -109px", # use 99px spacing
163
+ "url('/sprites.png') 0 -109px"
164
+ ]
165
+ image_size('sprites.png').should == [20, 129]
166
+ end
167
+
168
+ it "should calculate the maximum spacing between images for margin bottom" do
169
+ evaluate(
170
+ 'sprite-image("sprites/10x10.png", 0, 0, 0, 10px)',
171
+ 'sprite-image("sprites/10x10.png", 0, 0, 0, 99px)', # 99px > 10px
172
+ 'sprite-image("sprites/20x20.png")'
173
+ ).should == [
174
+ "url('/sprites.png')",
175
+ "url('/sprites.png')",
176
+ "url('/sprites.png') 0 -109px" # use 99px spacing
177
+ ]
178
+ image_size('sprites.png').should == [20, 129]
179
+ end
180
+
181
+ it "should output the background-position" do
182
+ evaluate(
183
+ 'sprite-position("sprites/10x10.png")',
184
+ 'sprite-position("sprites/20x20.png")'
185
+ ).should == [
186
+ "0 0",
187
+ "0 -10px"
188
+ ]
189
+ end
190
+
191
+ it "should output the background-image URL" do
192
+ evaluate('sprite-url("sprites")').should == "url('/sprites.png')"
193
+ evaluate('sprite-url("sprites/10x10.png")').should == "url('/sprites.png')"
194
+ evaluate('sprite-url("sprites/20x20.png")').should == "url('/sprites.png')"
195
+ evaluate('sprite-url("other_images/test.png")').should == "url('/other_images.png')"
196
+ end
197
+
198
+ it "should count the PNG files in a folder" do
199
+ evaluate('sprite-files-in-folder("sprites")').to_i.should == 4
200
+ end
201
+
202
+ it "should output the n-th file in a folder" do
203
+ evaluate('sprite-file-from-folder("sprites", 0)').should == "sprites/10x10.png"
204
+ evaluate('sprite-file-from-folder("sprites", 1)').should == "sprites/150x10.png"
205
+ end
206
+
207
+ it "should output the filename without extention for the sprite" do
208
+ evaluate('sprite-name("sprites")').should == "sprites"
209
+ evaluate('sprite-name("sprites/10x10.png")').should == "sprites"
210
+ end
211
+
212
+ it "should output the filename without extention for the sprite item" do
213
+ evaluate('image-basename("sprites/10x10.png")').should == "10x10"
214
+ end
215
+
216
+ // FIXME How can we test it actually repeated the 10x10 twice?
217
+ it "should accept a repeat argument" do
218
+ evaluate(
219
+ 'sprite-image("sprites/10x10.png", 0, 0, 0, 0, true)',
220
+ 'sprite-image("sprites/20x20.png")'
221
+ ).should == [
222
+ "url('/sprites.png')",
223
+ "url('/sprites.png') 0 -10px"
224
+ ]
225
+ image_size('sprites.png').should == [20, 30]
226
+ end
227
+
228
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format s
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'sass'
6
+ require 'lemonade'
7
+ require 'spec'
8
+ require 'spec/autorun'
9
+
10
+ IMAGES_TMP_PATH = File.join(File.dirname(__FILE__), 'images-tmp')
11
+ Lemonade.images_path = IMAGES_TMP_PATH
12
+
13
+ Spec::Runner.configure do |config|
14
+
15
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Sass::Script::SpriteInfo do
4
+
5
+ def sprite_info(*args)
6
+ Sass::Script::SpriteInfo.new(*args).to_s
7
+ end
8
+
9
+ ##
10
+
11
+ it "should output the position for the first sprite" do
12
+ sprite = { :file => "sprites.png" }
13
+ sprite_item = { :y => Sass::Script::Number.new(20, ['px']), :index => 0 }
14
+ x = Sass::Script::Number.new(10, ['px'])
15
+ sprite_info(:position, sprite, sprite_item, x).should == "10px 0"
16
+ end
17
+
18
+ it "should output the position for the second+ sprite" do
19
+ sprite = { :file => "sprites.png" }
20
+ sprite_item = { :y => Sass::Script::Number.new(20, ['px']), :index => 1 }
21
+ x = Sass::Script::Number.new(10, ['px'])
22
+ sprite_info(:position, sprite, sprite_item, x).should ==
23
+ "10px <%= Lemonade.sprites['sprites.png'][:images][1][:y].unary_minus %>"
24
+ end
25
+
26
+ it "should output the position with y shift" do
27
+ sprite = { :file => "sprites.png" }
28
+ sprite_item = { :y => Sass::Script::Number.new(20, ['px']), :index => 1 }
29
+ x = Sass::Script::Number.new(10, ['px'])
30
+ y_shift = Sass::Script::Number.new(3, ['px'])
31
+ sprite_info(:position, sprite, sprite_item, x, y_shift).should ==
32
+ "10px <%= Lemonade.sprites['sprites.png'][:images][1][:y].unary_minus.plus(Sass::Script::Number.new(3, ['px'])) %>"
33
+ end
34
+
35
+ it "should output the position with percentage" do
36
+ sprite = { :file => "sprites.png" }
37
+ sprite_item = { :y => Sass::Script::Number.new(20, ['px']), :index => 2 }
38
+ x = Sass::Script::Number.new(100, ['%'])
39
+ sprite_info(:position, sprite, sprite_item, x).should ==
40
+ "100% <%= Lemonade.sprites['sprites.png'][:images][2][:y].unary_minus %>"
41
+ end
42
+
43
+ it "should output the url" do
44
+ sprite = { :file => "sprites.png" }
45
+ sprite_item = { }
46
+ sprite_info(:url, sprite, sprite_item).should == "url('/sprites.png')"
47
+ end
48
+
49
+ end
@@ -0,0 +1,38 @@
1
+ @mixin image-dimensions($file) {
2
+ height: image-height($file);
3
+ width: image-width($file);
4
+ }
5
+
6
+ @mixin sprite-image($file) {
7
+ background: sprite-image($file) $repeat;
8
+ }
9
+
10
+ @mixin sized-sprite-image($file) {
11
+ background: sprite-image($file);
12
+ @include image-dimensions($file);
13
+ }
14
+
15
+ @mixin sprite-folder($folder, $image-dimensions: false) {
16
+ .#{$folder} {
17
+ @if $image-dimensions {
18
+ background: sprite-url($folder);
19
+ }
20
+ @else {
21
+ background: sprite-url($folder) no-repeat;
22
+ }
23
+ }
24
+ @for $i from 0 to sprite-files-in-folder($folder) {
25
+ $file: sprite-file-from-folder($folder, $i);
26
+ .#{$folder}-#{image-basename($file)} {
27
+ @extend .#{$folder};
28
+ background-position: sprite-position(sprite-file-from-folder($folder, $i));
29
+ @if $image-dimensions {
30
+ @include image-dimensions($file);
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ @mixin sized-sprite-folder($folder) {
37
+ @include sprite-folder($folder, true);
38
+ }
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wikyd-lemonade
3
+ version: !ruby/object:Gem::Version
4
+ hash: 62196353
5
+ prerelease: 6
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ - beta
11
+ - 1
12
+ version: 1.0.0.beta.1
13
+ platform: ruby
14
+ authors:
15
+ - Nico Hagenburger
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-02-20 00:00:00 -08:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: haml
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ hash: 7
32
+ segments:
33
+ - 3
34
+ - 0
35
+ - 0
36
+ version: 3.0.0
37
+ type: :runtime
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: chunky_png
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ hash: 59
48
+ segments:
49
+ - 0
50
+ - 9
51
+ - 0
52
+ version: 0.9.0
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ hash: 49
64
+ segments:
65
+ - 0
66
+ - 8
67
+ - 7
68
+ version: 0.8.7
69
+ type: :development
70
+ version_requirements: *id003
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ prerelease: false
74
+ requirement: &id004 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ hash: 27
80
+ segments:
81
+ - 1
82
+ - 3
83
+ - 0
84
+ version: 1.3.0
85
+ type: :development
86
+ version_requirements: *id004
87
+ description: "Generates sprites on the fly by using `background: sprite-image(\"sprites/logo.png\")`. No Photoshop, no RMagick, no Rake task, save your time and have a lemonade."
88
+ email: kai@wikyd.org
89
+ executables: []
90
+
91
+ extensions: []
92
+
93
+ extra_rdoc_files:
94
+ - README.md
95
+ files:
96
+ - lib/lemonade/sass_extension.rb
97
+ - lib/lemonade/sass_functions.rb
98
+ - lib/lemonade/sprite_info.rb
99
+ - lib/lemonade/version.rb
100
+ - lib/lemonade.rb
101
+ - stylesheets/lemonade.scss
102
+ - CHANGELOG.md
103
+ - MIT-LICENSE
104
+ - Rakefile
105
+ - README.md
106
+ - spec/images/other_images/more-images/sprites/test.png
107
+ - spec/images/other_images/test.png
108
+ - spec/images/sprites/10x10.png
109
+ - spec/images/sprites/150x10.png
110
+ - spec/images/sprites/20x20.png
111
+ - spec/images/sprites/30x30.png
112
+ - spec/lemonade_spec.rb
113
+ - spec/sass_functions_spec.rb
114
+ - spec/spec.opts
115
+ - spec/spec_helper.rb
116
+ - spec/sprite_info_spec.rb
117
+ has_rdoc: true
118
+ homepage: http://github.com/wikyd/lemonade
119
+ licenses: []
120
+
121
+ post_install_message:
122
+ rdoc_options:
123
+ - --charset=UTF-8
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 23
141
+ segments:
142
+ - 1
143
+ - 3
144
+ - 6
145
+ version: 1.3.6
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.4.2
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: On the fly sprite generator for Sass/Compass; Forked from hagenburger/lemonade
153
+ test_files:
154
+ - spec/images/other_images/more-images/sprites/test.png
155
+ - spec/images/other_images/test.png
156
+ - spec/images/sprites/10x10.png
157
+ - spec/images/sprites/150x10.png
158
+ - spec/images/sprites/20x20.png
159
+ - spec/images/sprites/30x30.png
160
+ - spec/lemonade_spec.rb
161
+ - spec/sass_functions_spec.rb
162
+ - spec/spec.opts
163
+ - spec/spec_helper.rb
164
+ - spec/sprite_info_spec.rb