wikyd-lemonade 1.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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