compass 0.11.alpha.1 → 0.11.alpha.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Rakefile +21 -1
  2. data/VERSION.yml +1 -1
  3. data/bin/compass +17 -2
  4. data/features/command_line.feature +15 -24
  5. data/features/step_definitions/command_line_steps.rb +3 -3
  6. data/frameworks/compass/stylesheets/_lemonade.scss +38 -0
  7. data/frameworks/compass/stylesheets/compass/css3/_background-size.scss +3 -1
  8. data/frameworks/compass/stylesheets/compass/css3/_box-shadow.scss +1 -1
  9. data/frameworks/compass/stylesheets/compass/css3/_images.scss +8 -6
  10. data/frameworks/compass/stylesheets/compass/css3/_transform.scss +3 -3
  11. data/frameworks/compass/stylesheets/compass/utilities/sprites/_base.scss +40 -0
  12. data/frameworks/compass/templates/pie/PIE.htc +69 -69
  13. data/frameworks/compass/templates/pie/manifest.rb +2 -2
  14. data/lib/compass.rb +3 -2
  15. data/lib/compass/app_integration/stand_alone/installer.rb +1 -1
  16. data/lib/compass/commands.rb +1 -1
  17. data/lib/compass/commands/sprite.rb +87 -0
  18. data/lib/compass/configuration/adapters.rb +3 -0
  19. data/lib/compass/dependencies.rb +7 -1
  20. data/lib/compass/installers/bare_installer.rb +1 -1
  21. data/lib/compass/quick_cache.rb +15 -0
  22. data/lib/compass/sass_extensions/functions.rb +3 -3
  23. data/lib/compass/sass_extensions/functions/gradient_support.rb +60 -21
  24. data/lib/compass/sass_extensions/functions/image_size.rb +11 -11
  25. data/lib/compass/sass_extensions/functions/sprites.rb +357 -0
  26. data/lib/compass/sass_extensions/functions/urls.rb +16 -3
  27. data/lib/compass/sass_extensions/monkey_patches/traversal.rb +1 -1
  28. data/lib/compass/sprites.rb +122 -0
  29. data/test/fixtures/stylesheets/compass/css/gradients.css +18 -1
  30. data/test/fixtures/stylesheets/compass/css/pie.css +1 -0
  31. data/test/fixtures/stylesheets/compass/css/transform.css +28 -0
  32. data/test/fixtures/stylesheets/compass/sass/gradients.sass +9 -0
  33. data/test/fixtures/stylesheets/image_urls/css/screen.css +2 -0
  34. data/test/fixtures/stylesheets/image_urls/sass/screen.sass +3 -0
  35. data/test/fixtures/stylesheets/valid/config.rb +9 -0
  36. data/test/fixtures/stylesheets/valid/sass/simple.sass +2 -0
  37. data/test/sass_extensions_test.rb +8 -0
  38. metadata +40 -8
  39. data/lib/compass/sass_extensions/functions/if.rb +0 -9
@@ -21,7 +21,7 @@ For more information see:
21
21
 
22
22
  CSS PIE is written by and copyright to: Jason Johnston
23
23
 
24
- Compass is using css3pie version 1.0-beta2. It can be upgraded by downloading
24
+ Compass is using css3pie version 1.0-beta3. It can be upgraded by downloading
25
25
  a newer behavior file and replacing the one that comes with compass.
26
26
  }
27
27
 
@@ -36,4 +36,4 @@ Unfornately, due to the suckiness of IE, PIE does not work with relative assets.
36
36
 
37
37
  For more information see:
38
38
  http://css3pie.com/
39
- }
39
+ }
@@ -1,7 +1,7 @@
1
1
  module Compass
2
2
  end
3
3
 
4
- %w(dependencies util sass_extensions core_ext version errors).each do |lib|
4
+ %w(dependencies util sass_extensions core_ext version errors quick_cache).each do |lib|
5
5
  require "compass/#{lib}"
6
6
  end
7
7
 
@@ -13,8 +13,9 @@ module Compass
13
13
  File.expand_path(File.join(File.dirname(__FILE__)))
14
14
  end
15
15
  module_function :base_directory, :lib_directory
16
+ extend QuickCache
16
17
  end
17
18
 
18
- %w(configuration frameworks app_integration actions compiler).each do |lib|
19
+ %w(configuration frameworks app_integration actions compiler sprites).each do |lib|
19
20
  require "compass/#{lib}"
20
21
  end
@@ -61,7 +61,7 @@ This can be done in one of the following ways:
61
61
  compass watch [path/to/project]
62
62
 
63
63
  More Resources:
64
- * Wiki: http://wiki.github.com/chriseppstein/compass
64
+ * Website: http://compass-style.org/
65
65
  * Sass: http://sass-lang.com
66
66
  * Community: http://groups.google.com/group/compass-users/
67
67
 
@@ -5,7 +5,7 @@ require 'compass/commands/registry'
5
5
 
6
6
  %w(base generate_grid_background help list_frameworks project_base
7
7
  update_project watch_project create_project imports installer_command
8
- print_version project_stats stamp_pattern validate_project
8
+ print_version project_stats stamp_pattern sprite validate_project
9
9
  write_configuration interactive unpack_extension).each do |lib|
10
10
  require "compass/commands/#{lib}"
11
11
  end
@@ -0,0 +1,87 @@
1
+ require 'compass/commands/project_base'
2
+ require 'compass/commands/update_project'
3
+
4
+ module Compass
5
+ module Commands
6
+ module SpriteOptionsParser
7
+ def set_options(opts)
8
+ opts.on("-f SPRITE_FILE") do |output_file|
9
+ self.options[:output_file] = output_file
10
+ end
11
+ opts.banner = %Q{
12
+ Usage: compass sprite [options] "images/path/to/sprites/*.png"
13
+
14
+ Description:
15
+ Generate a sprite import based on the given sprite directory.
16
+ Alternatively, you can simply do this in your sass files:
17
+
18
+ @import "sprite-folder/*.png"
19
+
20
+ And a magical, custom made sprite file will be imported.
21
+
22
+ Options:
23
+ }.strip.split("\n").map{|l| l.gsub(/^ {0,10}/,'')}.join("\n")
24
+
25
+ super
26
+ end
27
+ end
28
+ class Sprite < ProjectBase
29
+
30
+ register :sprite
31
+
32
+ def initialize(working_path, options)
33
+ super
34
+ assert_project_directory_exists!
35
+ end
36
+
37
+ def perform
38
+ sprites = Compass::Sprites.new
39
+ relative_uri = options[:uri].gsub(/^#{Compass.configuration.images_dir}\//, '')
40
+ sprite_images = Compass::Sprites.discover_sprites(relative_uri)
41
+ image_names = sprite_images.map{|i| File.basename(i, '.png')}
42
+ sprites.path, sprites.name = Compass::Sprites.path_and_name(relative_uri)
43
+ options[:output_file] ||= File.join(Compass.configuration.sass_path, "sprites", "_#{sprites.name}.#{Compass.configuration.preferred_syntax}")
44
+ contents = sprites.content_for_images(relative_uri, sprites.name, image_names)
45
+ if options[:output_file][-4..-1] != "scss"
46
+ contents = Sass::Engine.new(contents, Compass.sass_engine_options.merge(:syntax => :scss)).to_tree.to_sass
47
+ end
48
+ directory File.dirname(options[:output_file])
49
+ write_file options[:output_file], contents
50
+ end
51
+
52
+ class << self
53
+
54
+ def option_parser(arguments)
55
+ parser = Compass::Exec::CommandOptionParser.new(arguments)
56
+ parser.extend(Compass::Exec::GlobalOptionsParser)
57
+ parser.extend(Compass::Exec::ProjectOptionsParser)
58
+ parser.extend(SpriteOptionsParser)
59
+ end
60
+
61
+ def usage
62
+ option_parser([]).to_s
63
+ end
64
+
65
+ def description(command)
66
+ "Generate an import for your sprites."
67
+ end
68
+
69
+ def parse!(arguments)
70
+ parser = option_parser(arguments)
71
+ parser.parse!
72
+ parse_arguments!(parser, arguments)
73
+ parser.options
74
+ end
75
+
76
+ def parse_arguments!(parser, arguments)
77
+ parser.options[:uri] = arguments.shift
78
+ unless arguments.size == 0
79
+ raise Compass::Error, "Please specify at least one image to sprite."
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -22,6 +22,8 @@ module Compass
22
22
  plugin_opts[:cache] = cache unless cache.nil?
23
23
  plugin_opts[:cache_location] = cache_path unless cache_path.nil?
24
24
  plugin_opts.merge!(sass_options || {})
25
+ plugin_opts[:load_paths] ||= []
26
+ plugin_opts[:load_paths] << Compass::Sprites.new
25
27
  plugin_opts
26
28
  end
27
29
 
@@ -57,6 +59,7 @@ module Compass
57
59
  load_paths << framework.stylesheets_directory if File.exists?(framework.stylesheets_directory)
58
60
  end
59
61
  load_paths += resolve_additional_import_paths
62
+ load_paths << Compass::Sprites.new
60
63
  load_paths
61
64
  end
62
65
  end
@@ -2,5 +2,11 @@ begin
2
2
  require 'sass'
3
3
  rescue LoadError
4
4
  require 'rubygems'
5
- require 'sass'
5
+ begin
6
+ require 'sass'
7
+ rescue LoadError
8
+ puts "Unable to load Sass. Please install it with one of the following commands:"
9
+ puts " gem install sass --pre"
10
+ raise
11
+ end
6
12
  end
@@ -47,7 +47,7 @@ This can be done in one of the following ways:
47
47
  compass watch [path/to/project]
48
48
 
49
49
  More Resources:
50
- * Wiki: http://wiki.github.com/chriseppstein/compass
50
+ * Website: http://compass-style.org/
51
51
  * Sass: http://sass-lang.com
52
52
  * Community: http://groups.google.com/group/compass-users/
53
53
  NEXTSTEPS
@@ -0,0 +1,15 @@
1
+ module QuickCache
2
+
3
+ # cache a value in memory for just a few seconds
4
+ # This can speed up reads of values that change relatively infrequently
5
+ # but might be read many times in a short burst of reads.
6
+ def quick_cache(key, ttl = 1)
7
+ @quick_cache ||= {}
8
+ if @quick_cache[key] && @quick_cache[key].first > Time.now - ttl
9
+ @quick_cache[key].last
10
+ else
11
+ (@quick_cache[key] = [Time.now, yield]).last
12
+ end
13
+ end
14
+
15
+ end
@@ -2,9 +2,9 @@ module Compass::SassExtensions::Functions
2
2
  end
3
3
 
4
4
  %w(
5
- selectors enumerate urls display if
5
+ selectors enumerate urls display
6
6
  inline_image image_size constants gradient_support
7
- font_files lists colors trig
7
+ font_files lists colors trig sprites
8
8
  ).each do |func|
9
9
  require "compass/sass_extensions/functions/#{func}"
10
10
  end
@@ -22,7 +22,7 @@ module Sass::Script::Functions
22
22
  include Compass::SassExtensions::Functions::Lists
23
23
  include Compass::SassExtensions::Functions::Colors
24
24
  include Compass::SassExtensions::Functions::Trig
25
- include Compass::SassExtensions::Functions::If
25
+ include Compass::SassExtensions::Functions::Sprites
26
26
  end
27
27
 
28
28
  # Wierd that this has to be re-included to pick up sub-modules. Ruby bug?
@@ -138,21 +138,29 @@ module Compass::SassExtensions::Functions::GradientSupport
138
138
  Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)})
139
139
  end
140
140
 
141
- %w(webkit moz o ms svg pie).each do |prefix|
141
+ %w(webkit moz o ms svg pie css2).each do |prefix|
142
142
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
143
143
  def _#{prefix}(*args)
144
- List.new(*args.map! do |a|
145
- if a.is_a?(List)
146
- a.class.new(*a.values.map{|v| v.respond_to?(:to_#{prefix}) ? v.to_#{prefix} : v})
147
- else
148
- a.respond_to?(:to_#{prefix}) ? a.to_#{prefix} : a
149
- end
150
- end)
144
+ List.new(*args.map! {|a| add_prefix(:to_#{prefix}, a)})
151
145
  end
152
146
  RUBY
153
147
  end
154
148
 
155
149
  protected
150
+
151
+ def add_prefix(prefix_method, object)
152
+ if object.is_a?(List)
153
+ object.class.new(object.value.map{|e|
154
+ add_prefix(prefix_method, e)
155
+ })
156
+ elsif object.respond_to?(prefix_method)
157
+ object.options = options
158
+ object.send(prefix_method)
159
+ else
160
+ object
161
+ end
162
+ end
163
+
156
164
  def color_stop?(arg)
157
165
  parse_color_stop(arg)
158
166
  rescue
@@ -219,7 +227,12 @@ module Compass::SassExtensions::Functions::GradientSupport
219
227
 
220
228
  # Returns a comma-delimited list after removing any non-true values
221
229
  def compact(*args)
222
- Sass::Script::List.new(args.reject{|a| !a.to_bool}, :comma)
230
+ sep = :comma
231
+ if args.size == 1 && args.first.is_a?(Sass::Script::List)
232
+ args = args.first.value
233
+ sep = args.first.separator
234
+ end
235
+ Sass::Script::List.new(args.reject{|a| !a.to_bool}, sep)
223
236
  end
224
237
 
225
238
  # Returns a list object from a value that was passed.
@@ -259,28 +272,35 @@ module Compass::SassExtensions::Functions::GradientSupport
259
272
  # Check if any of the arguments passed have a tendency towards vendor prefixing.
260
273
  def prefixed(prefix, *args)
261
274
  method = prefix.value.sub(/^-/,"to_").to_sym
262
- args.map!{|a| a.is_a?(Sass::Script::List) ? a.value : a}.flatten!
275
+ 2.times do
276
+ args.map!{|a| a.is_a?(Sass::Script::List) ? a.value : a}.flatten!
277
+ end
263
278
  Sass::Script::Bool.new(args.any?{|a| a.respond_to?(method)})
264
279
  end
265
280
 
266
- %w(webkit moz o ms svg pie).each do |prefix|
281
+ %w(webkit moz o ms svg pie css2).each do |prefix|
267
282
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
268
283
  def _#{prefix}(*args)
269
- Sass::Script::List.new(args.map! do |a|
270
- a.options = options
271
- if a.is_a?(Sass::Script::List)
272
- Sass::Script::List.new(a.value.map do |v|
273
- v.respond_to?(:to_#{prefix}) ? v.to_#{prefix} : v
274
- end, a.separator)
275
- else
276
- a.respond_to?(:to_#{prefix}) ? a.to_#{prefix} : a
277
- end
278
- end, :comma)
284
+ Sass::Script::List.new(args.map! {|a| add_prefix(:to_#{prefix}, a)}, :comma)
279
285
  end
280
286
  RUBY
281
287
  end
282
288
 
283
289
  protected
290
+
291
+ def add_prefix(prefix_method, object)
292
+ if object.is_a?(Sass::Script::List)
293
+ Sass::Script::List.new(object.value.map{|e|
294
+ add_prefix(prefix_method, e)
295
+ }, object.separator)
296
+ elsif object.respond_to?(prefix_method)
297
+ object.options = options
298
+ object.send(prefix_method)
299
+ else
300
+ object
301
+ end
302
+ end
303
+
284
304
  def color_stop?(arg)
285
305
  arg.is_a?(ColorStop) ||
286
306
  (arg.is_a?(Sass::Script::List) && ColorStop.new(*arg.value)) ||
@@ -375,6 +395,9 @@ module Compass::SassExtensions::Functions::GradientSupport
375
395
  Compass::Logger.new.record(:warning, "PIE does not support radial-gradient.")
376
396
  Sass::Script::String.new("-pie-radial-gradient(unsupported)")
377
397
  end
398
+ def to_css2(options = self.options)
399
+ Sass::Script::String.new("")
400
+ end
378
401
  end
379
402
 
380
403
  class LinearGradient < Sass::Script::Literal
@@ -417,6 +440,9 @@ module Compass::SassExtensions::Functions::GradientSupport
417
440
  # the presence of this attribute helps flag when to render a special rule.
418
441
  Sass::Script::String.new to_s(options)
419
442
  end
443
+ def to_css2(options = self.options)
444
+ Sass::Script::String.new("")
445
+ end
420
446
  end
421
447
 
422
448
  module Functions
@@ -562,6 +588,19 @@ module Compass::SassExtensions::Functions::GradientSupport
562
588
  end
563
589
  end
564
590
 
591
+ def blank(obj)
592
+ case obj
593
+ when Sass::Script::Bool
594
+ Sass::Script::Bool.new !obj.to_bool
595
+ when Sass::Script::String
596
+ Sass::Script::Bool.new obj.value.strip.size == 0
597
+ when Sass::Script::List
598
+ Sass::Script::Bool.new obj.value.size == 0 || obj.value.all?{|el| blank(el).to_bool}
599
+ else
600
+ Sass::Script::Bool.new false
601
+ end
602
+ end
603
+
565
604
  private
566
605
 
567
606
  # After using the sass script parser to parse a string, this reconstructs
@@ -13,17 +13,6 @@ module Compass::SassExtensions::Functions::ImageSize
13
13
  Sass::Script::Number.new(height, ["px"])
14
14
  end
15
15
 
16
- private
17
- def real_path(image_file)
18
- path = image_file.value
19
- # Compute the real path to the image on the file stystem if the images_dir is set.
20
- if Compass.configuration.images_path
21
- File.join(Compass.configuration.images_path, path)
22
- else
23
- File.join(Compass.configuration.project_path, path)
24
- end
25
- end
26
-
27
16
  class ImageProperties
28
17
  def initialize(file)
29
18
  @file = file
@@ -55,6 +44,17 @@ private
55
44
  end
56
45
  end
57
46
 
47
+ private
48
+ def real_path(image_file)
49
+ path = image_file.value
50
+ # Compute the real path to the image on the file stystem if the images_dir is set.
51
+ if Compass.configuration.images_path
52
+ File.join(Compass.configuration.images_path, path)
53
+ else
54
+ File.join(Compass.configuration.project_path, path)
55
+ end
56
+ end
57
+
58
58
  class JPEG
59
59
  attr_reader :width, :height, :bits
60
60
 
@@ -0,0 +1,357 @@
1
+ require 'digest/md5'
2
+
3
+ module Compass::SassExtensions::Functions::Sprites
4
+ ZERO = Sass::Script::Number::new(0)
5
+
6
+ # Provides a consistent interface for getting a variable in ruby
7
+ # from a keyword argument hash that accounts for underscores/dash equivalence
8
+ # and allows the caller to pass a symbol instead of a string.
9
+ module VariableReader
10
+ def get_var(variable_name)
11
+ self[variable_name.to_s.gsub(/-/,"_")]
12
+ end
13
+ end
14
+
15
+ class SpriteMap < Sass::Script::Literal
16
+
17
+ # Changing this string will invalidate all previously generated sprite images.
18
+ # We should do so only when the packing algorithm changes
19
+ SPRITE_VERSION = "1"
20
+
21
+ attr_accessor :image_names, :path, :name, :options
22
+ attr_accessor :images, :width, :height
23
+
24
+ def self.from_uri(uri, context, kwargs)
25
+ path, name = Compass::Sprites.path_and_name(uri.value)
26
+ sprites = Compass::Sprites.discover_sprites(uri.value).map do |sprite|
27
+ sprite.gsub(Compass.configuration.images_path+"/", "")
28
+ end
29
+ new(sprites, path, name, context, kwargs)
30
+ end
31
+
32
+ def initialize(image_names, path, name, context, options)
33
+ @image_names, @path, @name, @options = image_names, path, name, options
34
+ @images = nil
35
+ @width = nil
36
+ @height = nil
37
+ @evaluation_context = context
38
+ validate!
39
+ compute_image_metadata!
40
+ end
41
+
42
+ def sprite_names
43
+ image_names.map{|f| Compass::Sprites.sprite_name(f) }
44
+ end
45
+
46
+ def validate!
47
+ for sprite_name in sprite_names
48
+ unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/
49
+ raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier"
50
+ end
51
+ end
52
+ end
53
+
54
+ # Calculates the overal image dimensions
55
+ # collects image sizes and input parameters for each sprite
56
+ def compute_image_metadata!
57
+ @images = []
58
+ @width = 0
59
+ image_names.each do |relative_file|
60
+ file = File.join(Compass.configuration.images_path, relative_file)
61
+ width, height = Compass::SassExtensions::Functions::ImageSize::ImageProperties.new(file).size
62
+ sprite_name = Compass::Sprites.sprite_name(relative_file)
63
+ @width = [@width, width].max
64
+ @images << {
65
+ :name => sprite_name,
66
+ :file => file,
67
+ :relative_file => relative_file,
68
+ :height => height,
69
+ :width => width,
70
+ :repeat => repeat_for(sprite_name),
71
+ :spacing => spacing_for(sprite_name),
72
+ :position => position_for(sprite_name),
73
+ :digest => MD5.file(file).hexdigest
74
+ }
75
+ end
76
+ @images.each_with_index do |image, index|
77
+ if index == 0
78
+ image[:top] = 0
79
+ else
80
+ last_image = @images[index-1]
81
+ image[:top] = last_image[:top] + last_image[:height] + [image[:spacing], last_image[:spacing]].max
82
+ end
83
+ if image[:position].unit_str == "%"
84
+ image[:left] = (@width - image[:width]) * (image[:position].value / 100)
85
+ else
86
+ image[:left] = image[:position].value
87
+ end
88
+ end
89
+ @height = @images.last[:top] + @images.last[:height]
90
+ end
91
+
92
+ def position_for(name)
93
+ options.get_var("#{name}-position") || options.get_var("position") || Sass::Script::Number.new(0, ["px"])
94
+ end
95
+
96
+ def repeat_for(name)
97
+ if (var = options.get_var("#{name}-repeat"))
98
+ var.value
99
+ elsif (var = options.get_var("repeat"))
100
+ var.value
101
+ else
102
+ "no-repeat"
103
+ end
104
+ end
105
+
106
+ def spacing_for(name)
107
+ (options.get_var("#{name}-spacing") ||
108
+ options.get_var("spacing") ||
109
+ ZERO).value
110
+ end
111
+
112
+ def image_for(name)
113
+ @images.detect{|img| img[:name] == name}
114
+ end
115
+
116
+ # Calculate the size of the sprite
117
+ def size
118
+ [width, height]
119
+ end
120
+
121
+ # Generate a sprite image if necessary
122
+ def generate
123
+ if generation_required?
124
+ save!(construct_sprite)
125
+ end
126
+ end
127
+
128
+ def generation_required?
129
+ !File.exists?(filename) || outdated?
130
+ end
131
+
132
+ def require_png_library!
133
+ begin
134
+ require 'oily_png'
135
+ rescue LoadError
136
+ require 'chunky_png'
137
+ end
138
+ end
139
+
140
+ # Returns a PNG object
141
+ def construct_sprite
142
+ require_png_library!
143
+ output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
144
+ images.each do |image|
145
+ input_png = ChunkyPNG::Image.from_file(image[:file])
146
+ if image[:repeat] == "no-repeat"
147
+ output_png.replace input_png, image[:left], image[:top]
148
+ else
149
+ x = image[:left] - (image[:left] / image[:width]).ceil * image[:width]
150
+ while x < width do
151
+ output_png.replace input_png, x, image[:top]
152
+ x += image[:width]
153
+ end
154
+ end
155
+ end
156
+ output_png
157
+ end
158
+
159
+ # The on-the-disk filename of the sprite
160
+ def filename
161
+ File.join(Compass.configuration.images_path, "#{path}-#{uniqueness_hash}.png")
162
+ end
163
+
164
+ def uniqueness_hash
165
+ @uniqueness_hash ||= begin
166
+ sum = MD5.md5(SPRITE_VERSION)
167
+ sum << path
168
+ images.each do |image|
169
+ [:relative_file, :height, :width, :repeat, :spacing, :position, :digest].each do |attr|
170
+ sum << image[attr].to_s
171
+ end
172
+ end
173
+ sum.hexdigest[0...10]
174
+ end
175
+ @uniqueness_hash
176
+ end
177
+
178
+ # saves the sprite for later retrieval
179
+ def save!(output_png)
180
+ output_png.save filename
181
+ end
182
+
183
+ # All the full-path filenames involved in this sprite
184
+ def image_filenames
185
+ image_names.map do |image_name|
186
+ File.join(Compass.configuration.images_path, image_name)
187
+ end
188
+ end
189
+
190
+ # Checks whether this sprite is outdated
191
+ def outdated?
192
+ last_update = self.mtime
193
+ image_filenames.each do |image|
194
+ return true if File.mtime(image) > last_update
195
+ end
196
+ false
197
+ end
198
+
199
+ def mtime
200
+ File.mtime(filename)
201
+ end
202
+
203
+ def inspect
204
+ to_s
205
+ end
206
+
207
+ def to_s(options = self.options)
208
+ sprite_url(self).value
209
+ end
210
+
211
+ def respond_to?(meth)
212
+ super || @evaluation_context.respond_to?(meth)
213
+ end
214
+
215
+ def method_missing(meth, *args, &block)
216
+ if @evaluation_context.respond_to?(meth)
217
+ @evaluation_context.send(meth, *args, &block)
218
+ else
219
+ super
220
+ end
221
+ end
222
+
223
+ end
224
+
225
+ # Creates a SpriteMap object. A sprite map, when used in a property is the same
226
+ # as calling sprite-url. So the following background properties are equivalent:
227
+ #
228
+ # $icons: sprite-map("icons/*.png");
229
+ # background: sprite-url($icons) no-repeat;
230
+ # background: $icons no-repeat;
231
+ #
232
+ # The sprite map object will generate the sprite map image, if necessary,
233
+ # the first time it is converted to a url. Simply constructing it has no side-effects.
234
+ def sprite_map(glob, kwargs = {})
235
+ kwargs.extend VariableReader
236
+ SpriteMap.from_uri(glob, self, kwargs)
237
+ end
238
+ Sass::Script::Functions.declare :sprite_map, [:glob], :var_kwargs => true
239
+
240
+ # Returns the image and background position for use in a single shorthand property:
241
+ #
242
+ # $icons: sprite-map("icons/*.png"); // contains icons/new.png among others.
243
+ # background: sprite($icons, new) no-repeat;
244
+ #
245
+ # Becomes:
246
+ #
247
+ # background: url('/images/icons.png?12345678') 0 -24px no-repeat;
248
+ def sprite(map, sprite, offset_x = ZERO, offset_y = ZERO)
249
+ unless map.is_a?(SpriteMap)
250
+ missing_sprite!("sprite")
251
+ end
252
+ unless sprite.is_a?(Sass::Script::String)
253
+ raise Sass::SyntaxError, %Q(The second argument to sprite() must be a sprite name. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
254
+ end
255
+ url = sprite_url(map)
256
+ position = sprite_position(map, sprite, offset_x, offset_y)
257
+ Sass::Script::List.new([url] + position.value, :space)
258
+ end
259
+ Sass::Script::Functions.declare :sprite, [:map, :sprite]
260
+ Sass::Script::Functions.declare :sprite, [:map, :sprite, :offset_x]
261
+ Sass::Script::Functions.declare :sprite, [:map, :sprite, :offset_x, :offset_y]
262
+
263
+ # Returns the name of a sprite map
264
+ # The name is derived from the folder than contains the sprites.
265
+ def sprite_map_name(map)
266
+ unless map.is_a?(SpriteMap)
267
+ missing_sprite!("sprite-map-name")
268
+ end
269
+ Sass::Script::String.new(map.name)
270
+ end
271
+ Sass::Script::Functions.declare :sprite_name, [:sprite]
272
+
273
+ # Returns the path to the original image file for the sprite with the given name
274
+ def sprite_file(map, sprite)
275
+ unless map.is_a?(SpriteMap)
276
+ missing_sprite!("sprite-file")
277
+ end
278
+ if image = map.image_for(sprite.value)
279
+ Sass::Script::String.new(image[:relative_file])
280
+ else
281
+ missing_image!(map, sprite)
282
+ end
283
+ end
284
+ Sass::Script::Functions.declare :sprite_file, [:map, :sprite]
285
+
286
+ # Returns a url to the sprite image.
287
+ def sprite_url(map)
288
+ unless map.is_a?(SpriteMap)
289
+ missing_sprite!("sprite-url")
290
+ end
291
+ map.generate
292
+ image_url(Sass::Script::String.new("#{map.path}-#{map.uniqueness_hash}.png"),
293
+ Sass::Script::Bool.new(false),
294
+ Sass::Script::Bool.new(false))
295
+ end
296
+ Sass::Script::Functions.declare :sprite_url, [:map]
297
+
298
+ # Returns the position for the original image in the sprite.
299
+ # This is suitable for use as a value to background-position:
300
+ #
301
+ # $icons: sprite-map("icons/*.png");
302
+ # background-position: sprite-position($icons, new);
303
+ #
304
+ # Might generate something like:
305
+ #
306
+ # background-position: 0 -34px;
307
+ #
308
+ # You can adjust the background relative to this position by passing values for
309
+ # `$offset-x` and `$offset-y`:
310
+ #
311
+ # $icons: sprite-map("icons/*.png");
312
+ # background-position: sprite-position($icons, new, 3px, -2px);
313
+ #
314
+ # Would change the above output to:
315
+ #
316
+ # background-position: 3px -36px;
317
+ def sprite_position(map, sprite = nil, offset_x = ZERO, offset_y = ZERO)
318
+ unless map.is_a?(SpriteMap)
319
+ missing_sprite!("sprite-position")
320
+ end
321
+ unless sprite && sprite.is_a?(Sass::Script::String)
322
+ raise Sass::SyntaxError, %Q(The second argument to sprite-position must be a sprite name. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
323
+ end
324
+ image = map.image_for(sprite.value)
325
+ unless image
326
+ missing_image!(map, sprite)
327
+ end
328
+ if offset_x.unit_str == "%"
329
+ x = offset_x # CE: Shouldn't this be a percentage of the total width?
330
+ else
331
+ x = offset_x.value - image[:left]
332
+ x = Sass::Script::Number.new(x, x == 0 ? [] : ["px"])
333
+ end
334
+ y = offset_y.value - image[:top]
335
+ y = Sass::Script::Number.new(y, y == 0 ? [] : ["px"])
336
+ Sass::Script::List.new([x, y],:space)
337
+ end
338
+ Sass::Script::Functions.declare :sprite_position, [:map]
339
+ Sass::Script::Functions.declare :sprite_position, [:map, :sprite]
340
+ Sass::Script::Functions.declare :sprite_position, [:map, :sprite, :offset_x]
341
+ Sass::Script::Functions.declare :sprite_position, [:map, :sprite, :offset_x, :offset_y]
342
+
343
+ def sprite_image(*args)
344
+ raise Sass::SyntaxError, %Q(The sprite-image() function has been replaced by sprite(). See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
345
+ end
346
+
347
+ protected
348
+
349
+ def missing_image!(map, sprite)
350
+ raise Sass::SyntaxError, "No sprite called #{sprite} found in sprite map #{map.path}/#{map.name}. Did you mean one of: #{map.sprite_names.join(", ")}"
351
+ end
352
+
353
+ def missing_sprite!(function_name)
354
+ raise Sass::SyntaxError, %Q(The first argument to #{function_name}() must be a sprite map. See http://beta.compass-style.org/help/tutorials/spriting/ for more information.)
355
+ end
356
+
357
+ end