sass 3.1.0.alpha.214 → 3.1.0.alpha.216

Sign up to get free protection for your applications and to get access to all the features.
data/REVISION CHANGED
@@ -1 +1 @@
1
- e18890b4746317cad3919e6461554eb0415b12fc
1
+ d4f5f05b36ae3e07d9c417174bf655b8f62969d9
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0.alpha.214
1
+ 3.1.0.alpha.216
@@ -12,3 +12,4 @@ end
12
12
  require 'sass/cache_stores/base'
13
13
  require 'sass/cache_stores/filesystem'
14
14
  require 'sass/cache_stores/memory'
15
+ require 'sass/cache_stores/chain'
@@ -48,7 +48,7 @@ module Sass
48
48
  # @param sha [String] The checksum for the contents that are being stored.
49
49
  # @param obj [Object] The object to cache.
50
50
  def store(key, sha, root)
51
- _store(key, Sass::VERSION, sha, Sass::Util.dump(root))
51
+ _store(key, Sass::VERSION, sha, Marshal.dump(root))
52
52
  end
53
53
 
54
54
  # Retrieve a {Sass::Tree::RootNode}.
@@ -58,7 +58,7 @@ module Sass
58
58
  # @return [Object] The cached object.
59
59
  def retrieve(key, sha)
60
60
  contents = _retrieve(key, Sass::VERSION, sha)
61
- Sass::Util.load(contents) if contents
61
+ Marshal.load(contents) if contents
62
62
  rescue EOFError, TypeError, ArgumentError => e
63
63
  Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}"
64
64
  end
@@ -0,0 +1,33 @@
1
+ module Sass
2
+ module CacheStores
3
+ # A meta-cache that chains multiple caches together.
4
+ # Specifically:
5
+ #
6
+ # * All `#store`s are passed to all caches.
7
+ # * `#retrieve`s are passed to each cache until one has a hit.
8
+ # * When one cache has a hit, the value is `#store`d in all earlier caches.
9
+ class Chain < Base
10
+ # Create a new cache chaining the given caches.
11
+ #
12
+ # @param caches [Array<Sass::CacheStores::Base>] The caches to chain.
13
+ def initialize(*caches)
14
+ @caches = caches
15
+ end
16
+
17
+ # @see Base#store
18
+ def store(key, sha, obj)
19
+ @caches.each {|c| c.store(key, sha, obj)}
20
+ end
21
+
22
+ # @see Base#retrieve
23
+ def retrieve(key, sha)
24
+ @caches.each_with_index do |c, i|
25
+ next unless obj = c.retrieve(key, sha)
26
+ @caches[0...i].each {|c| c.store(key, sha, obj)}
27
+ return obj
28
+ end
29
+ nil
30
+ end
31
+ end
32
+ end
33
+ end
@@ -24,22 +24,18 @@ module Sass
24
24
  @contents = {}
25
25
  end
26
26
 
27
- # @see Base#_retrieve
28
- def _retrieve(key, version, sha)
27
+ # @see Base#retrieve
28
+ def retrieve(key, sha)
29
29
  if @contents.has_key?(key)
30
- return unless @contents[key][:version] == version
31
30
  return unless @contents[key][:sha] == sha
32
- return @contents[key][:contents]
31
+ obj = @contents[key][:obj]
32
+ obj.respond_to?(:deep_copy) ? obj.deep_copy : obj.dup
33
33
  end
34
34
  end
35
-
36
- # @see Base#_store
37
- def _store(key, version, sha, contents)
38
- @contents[key] = {
39
- :version => version,
40
- :sha => sha,
41
- :contents => contents
42
- }
35
+
36
+ # @see Base#store
37
+ def store(key, sha, obj)
38
+ @contents[key] = {:sha => sha, :obj => obj}
43
39
  end
44
40
 
45
41
  # Destructively clear the cache.
data/lib/sass/engine.rb CHANGED
@@ -164,7 +164,8 @@ module Sass
164
164
  # Tracks the original filename of the top-level Sass file
165
165
  options[:original_filename] = options[:original_filename] || options[:filename]
166
166
 
167
- options[:cache_store] ||= Sass::CacheStores::Filesystem.new(options[:cache_location])
167
+ options[:cache_store] ||= Sass::CacheStores::Chain.new(
168
+ Sass::CacheStores::Memory.new, Sass::CacheStores::Filesystem.new(options[:cache_location]))
168
169
  # Support both, because the docs said one and the other actually worked
169
170
  # for quite a long time.
170
171
  options[:line_comments] ||= options[:line_numbers]
@@ -333,7 +334,15 @@ module Sass
333
334
  end
334
335
 
335
336
  root.options = @options
336
- @options[:cache_store].store(key, sha, root) if @options[:cache] && key && sha
337
+ if @options[:cache] && key && sha
338
+ begin
339
+ old_options = root.options
340
+ root.options = {:importer => root.options[:importer]}
341
+ @options[:cache_store].store(key, sha, root)
342
+ ensure
343
+ root.options = old_options
344
+ end
345
+ end
337
346
  root
338
347
  rescue SyntaxError => e
339
348
  e.modify_backtrace(:filename => @options[:filename], :line => @line)
@@ -75,15 +75,15 @@ module Sass
75
75
  # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
76
76
  def _perform(environment)
77
77
  args = @args.map {|a| a.perform(environment)}
78
- keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
79
78
  if fn = environment.function(@name)
79
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
80
80
  return perform_sass_fn(fn, args, keywords)
81
81
  end
82
82
 
83
83
  ruby_name = @name.tr('-', '_')
84
- args = construct_ruby_args(ruby_name, args, keywords)
84
+ args = construct_keyword_args(ruby_name, args, environment) unless @keywords.empty?
85
85
 
86
- unless Sass::Util.has?(:public_instance_method, Functions, ruby_name) && ruby_name !~ /^__/
86
+ unless Functions.callable?(ruby_name)
87
87
  opts(to_literal(args))
88
88
  else
89
89
  opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
@@ -102,7 +102,9 @@ module Sass
102
102
 
103
103
  private
104
104
 
105
- def construct_ruby_args(name, args, keywords)
105
+ def construct_keyword_args(name, args, environment)
106
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
107
+
106
108
  unless signature = Functions.signature(name.to_sym, args.size, keywords.size)
107
109
  return args if keywords.empty?
108
110
  raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
@@ -93,12 +93,12 @@ module Sass::Script
93
93
  # \{#adjust adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
94
94
  # : Increase or decrease any of the components of a color.
95
95
  #
96
+ # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
97
+ # : Fluidly scale one or more components of a color.
98
+ #
96
99
  # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
97
100
  # : Changes one or more properties of a color.
98
101
  #
99
- # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
100
- # : Scales one or more properties of a color by a percentage value.
101
- #
102
102
  # ## String Functions
103
103
  #
104
104
  # \{#unquote unquote($string)}
@@ -295,6 +295,8 @@ module Sass::Script
295
295
  # That means that all instance methods of {EvaluationContext}
296
296
  # are available to use in functions.
297
297
  class EvaluationContext
298
+ include Functions
299
+
298
300
  # The options hash for the {Sass::Engine} that is processing the function call
299
301
  #
300
302
  # @return [{Symbol => Object}]
@@ -303,10 +305,6 @@ module Sass::Script
303
305
  # @param options [{Symbol => Object}] See \{#options}
304
306
  def initialize(options)
305
307
  @options = options
306
-
307
- # We need to include this individually in each instance
308
- # because of an icky Ruby restriction
309
- class << self; include Sass::Script::Functions; end
310
308
  end
311
309
 
312
310
  # Asserts that the type of a given SassScript value
@@ -330,8 +328,32 @@ module Sass::Script
330
328
  end
331
329
  end
332
330
 
333
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
331
+ class << self
332
+ FUNCTIONS = Set.new
334
333
 
334
+ # Returns whether user function with a given name exists.
335
+ #
336
+ # @param function_name [String]
337
+ # @return [Boolean]
338
+ def callable?(function_name)
339
+ FUNCTIONS.include?(function_name)
340
+ end
341
+
342
+ private
343
+ def include(*args)
344
+ r = super
345
+ update_callable_functions
346
+ # We have to re-include ourselves into EvaluationContext to work around
347
+ # an icky Ruby restriction.
348
+ EvaluationContext.send :include, self
349
+ r
350
+ end
351
+
352
+ def update_callable_functions
353
+ FUNCTIONS.clear
354
+ public_instance_methods.each {|function_name| FUNCTIONS << function_name.to_s}
355
+ end
356
+ end
335
357
 
336
358
  # Creates a {Color} object from red, green, and blue values.
337
359
  #
@@ -1325,5 +1347,15 @@ module Sass::Script
1325
1347
  color.with(attr => Sass::Util.restrict(
1326
1348
  color.send(attr).send(op, amount.value), range))
1327
1349
  end
1350
+
1351
+ class << self
1352
+ private
1353
+ def method_added(name)
1354
+ update_callable_functions
1355
+ super
1356
+ end
1357
+ end
1358
+
1359
+ update_callable_functions # generate the initial set
1328
1360
  end
1329
1361
  end
@@ -103,9 +103,7 @@ module Sass
103
103
  # @see Simple#to_a
104
104
  def to_a
105
105
  ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
106
- ary = Sass::Util.intersperse(ary, " ")
107
- ary = Sass::Util.substitute(ary, [" ", "\n", " "], ["\n"])
108
- ary.flatten.compact
106
+ Sass::Util.intersperse(ary, " ").flatten.compact
109
107
  end
110
108
 
111
109
  # Returns a string representation of the sequence.
@@ -41,23 +41,22 @@ module Sass::Tree
41
41
  self.else.options = options if self.else
42
42
  end
43
43
 
44
- # @see Node#_around_dump
45
- def _around_dump
46
- old_else = @else
47
- old_last_else = @last_else
48
- @else = Sass::Util.dump(@else)
49
- @last_else = (self == @last_else ? nil : Sass::Util.dump(@last_else))
50
- super
51
- ensure
52
- @else = old_else
53
- @last_else = old_last_else
44
+ def _dump(f)
45
+ Marshal.dump([self.expr, self.else])
54
46
  end
55
47
 
56
- # @see Node#_after_load
57
- def _after_load
58
- super
59
- @else = Sass::Util.load(@else)
60
- @last_else = (@last_else ? Sass::Util.load(@last_else) : self)
48
+ def self._load(data)
49
+ expr, else_ = Marshal.load(data)
50
+ node = IfNode.new(expr)
51
+ node.else = else_
52
+ node
53
+ end
54
+
55
+ # @see Node#deep_copy
56
+ def deep_copy
57
+ node = super
58
+ node.else = self.else.deep_copy if self.else
59
+ node
61
60
  end
62
61
  end
63
62
  end
@@ -180,29 +180,14 @@ module Sass
180
180
  Sass::Tree::Visitors::Convert.visit(self, options, :scss)
181
181
  end
182
182
 
183
- # Names of options that are saved when the node is serialized and cached.
184
- SAVED_OPTIONS = [:importer]
185
-
186
- # Ensures that only {SAVED_OPTIONS} get saved.
187
- def _around_dump
188
- old_options = @options
189
- old_children = @children
190
- @options = {}
191
- SAVED_OPTIONS.each do |opt|
192
- @options[opt] = old_options[opt]
193
- end
194
- @options = Sass::Util.dump(@options)
195
- @children = Sass::Util.dump(@children)
196
- yield
197
- ensure
198
- @options = old_options
199
- @children = old_children
200
- end
201
-
202
- # Ensures that only {SAVED_OPTIONS} get saved.
203
- def _after_load
204
- @options = Sass::Util.load(@options)
205
- @children = Sass::Util.load(@children)
183
+ # Return a deep clone of this node.
184
+ # The child nodes are cloned, but options are not.
185
+ #
186
+ # @return [Node]
187
+ def deep_copy
188
+ node = dup
189
+ node.children = children.map {|c| c.deep_copy}
190
+ node
206
191
  end
207
192
 
208
193
  protected
@@ -56,9 +56,22 @@ module Sass::Tree
56
56
  merged = Sass::Util.merge_adjacent_strings(rule)
57
57
  @rule = Sass::Util.strip_string_array(merged)
58
58
  @tabs = 0
59
+ try_to_parse_non_interpolated_rules
59
60
  super()
60
61
  end
61
62
 
63
+ # If we've precached the parsed selector, set the line on it, too.
64
+ def line=(line)
65
+ @parsed_rules.line = line if @parsed_rules
66
+ super
67
+ end
68
+
69
+ # If we've precached the parsed selector, set the filename on it, too.
70
+ def filename=(filename)
71
+ @parsed_rules.filename = filename if @parsed_rules
72
+ super
73
+ end
74
+
62
75
  # Compares the contents of two rules.
63
76
  #
64
77
  # @param other [Object] The object to compare with
@@ -74,6 +87,7 @@ module Sass::Tree
74
87
  def add_rules(node)
75
88
  @rule = Sass::Util.strip_string_array(
76
89
  Sass::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
90
+ try_to_parse_non_interpolated_rules
77
91
  end
78
92
 
79
93
  # @return [Boolean] Whether or not this rule is continued on the next line
@@ -100,5 +114,16 @@ module Sass::Tree
100
114
  {:filename => filename && ("file://" + URI.escape(File.expand_path(filename))),
101
115
  :line => self.line}
102
116
  end
117
+
118
+ private
119
+
120
+ def try_to_parse_non_interpolated_rules
121
+ if @rule.all? {|t| t.kind_of?(String)}
122
+ # We don't use real filename/line info because we don't have it yet.
123
+ # When we get it, we'll set it on the parsed rules if possible.
124
+ parser = Sass::SCSS::StaticParser.new(@rule.join.strip, 1)
125
+ @parsed_rules = parser.parse_selector('') rescue nil
126
+ end
127
+ end
103
128
  end
104
129
  end
@@ -228,7 +228,7 @@ END
228
228
  # and then parses the result into a {Sass::Selector::CommaSequence}.
229
229
  def visit_rule(node)
230
230
  parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), node.line)
231
- node.parsed_rules = parser.parse_selector(node.filename)
231
+ node.parsed_rules ||= parser.parse_selector(node.filename)
232
232
  yield
233
233
  end
234
234
 
@@ -128,11 +128,15 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
128
128
  rule_indent = ' ' * @tabs
129
129
  per_rule_indent, total_indent = [:nested, :expanded].include?(node.style) ? [rule_indent, ''] : ['', rule_indent]
130
130
 
131
- total_rule = total_indent + node.resolved_rules.members.
132
- map {|seq| seq.to_a.join.gsub(/([^,])\n/m, node.style == :compressed ? '\1 ' : "\\1\n")}.
133
- join(rule_separator).split("\n").map do |line|
134
- per_rule_indent + line.strip
135
- end.join(line_separator)
131
+ joined_rules = node.resolved_rules.members.map do |seq|
132
+ rule_part = seq.to_a.join
133
+ rule_part.gsub!(/\s*([^,])\s*\n\s*/m, '\1 ') if node.style == :compressed
134
+ rule_part
135
+ end.join(rule_separator)
136
+
137
+ joined_rules.sub!(/\A\s*/, per_rule_indent)
138
+ joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
139
+ total_rule = total_indent << joined_rules
136
140
 
137
141
  to_return = ''
138
142
  old_spaces = ' ' * @tabs
data/lib/sass/util.rb CHANGED
@@ -270,47 +270,6 @@ module Sass
270
270
  version_gt(v1, v2) || !version_gt(v2, v1)
271
271
  end
272
272
 
273
- # A wrapper for `Marshal.dump` that calls `#_before_dump` on the object
274
- # before dumping it, `#_after_dump` afterwards.
275
- # It also calls `#_around_dump` and passes it a block in which the object is dumped.
276
- #
277
- # If any of these methods are undefined, they are not called.
278
- #
279
- # This will recursively call itself on members of arrays and hashes,
280
- # but not of user-defined objects.
281
- # This means that user-defined objects that need their members' `#_before_dump` etc. methods called
282
- # must call `Haml::Util.dump` and `Haml::Util.load` manually on those members.
283
- #
284
- # @param obj [Object] The object to dump.
285
- # @return [String] The dumped data.
286
- def dump(obj)
287
- obj._before_dump if obj.respond_to?(:_before_dump)
288
- return convert_and_dump(obj) unless obj.respond_to?(:_around_dump)
289
- res = nil
290
- obj._around_dump {res = convert_and_dump(obj)}
291
- res
292
- ensure
293
- obj._after_dump if obj.respond_to?(:_after_dump)
294
- end
295
-
296
- # A wrapper for `Marshal.load` that calls `#_after_load` on the object
297
- # after loading it, if it's defined.
298
- #
299
- # @param data [String] The data to load.
300
- # @return [Object] The loaded object.
301
- def load(data)
302
- obj = Marshal.load(data)
303
-
304
- if obj.is_a?(Array)
305
- obj = obj.map {|e| Sass::Util.load(e)}
306
- elsif obj.is_a?(Hash)
307
- obj = map_hash(obj) {|k, v| [Sass::Util.load(k), Sass::Util.load(v)]}
308
- end
309
-
310
- obj._after_load if obj.respond_to?(:_after_load)
311
- obj
312
- end
313
-
314
273
  # Throws a NotImplementedError for an abstract method.
315
274
  #
316
275
  # @param obj [Object] `self`
@@ -706,14 +665,5 @@ MSG
706
665
  return lcs_backtrace(c, x, y, i, j-1, &block) if c[i][j-1] > c[i-1][j]
707
666
  return lcs_backtrace(c, x, y, i-1, j, &block)
708
667
  end
709
-
710
- def convert_and_dump(obj)
711
- if obj.is_a?(Array)
712
- obj = obj.map {|e| dump(e)}
713
- elsif obj.is_a?(Hash)
714
- obj = map_hash(obj) {|k, v| [dump(k), dump(v)]}
715
- end
716
- Marshal.dump(obj)
717
- end
718
668
  end
719
669
  end
@@ -5,8 +5,6 @@ require File.dirname(__FILE__) + '/test_helper'
5
5
  class ImporterTest < Test::Unit::TestCase
6
6
 
7
7
  class FruitImporter < Sass::Importers::Base
8
- attr_reader :cached
9
-
10
8
  def find(name, context = nil)
11
9
  if name =~ %r{fruits/(\w+)(\.s[ac]ss)?}
12
10
  fruit = $1
@@ -31,13 +29,6 @@ class ImporterTest < Test::Unit::TestCase
31
29
  def key(name, context)
32
30
  [self.class.name, name]
33
31
  end
34
-
35
- def _around_dump
36
- @cached = true
37
- yield
38
- ensure
39
- @cached = false
40
- end
41
32
  end
42
33
 
43
34
  # This class proves that you can override the extension scheme for importers
@@ -88,17 +79,4 @@ CSS
88
79
  ensure
89
80
  FileUtils.rm_rf(absolutize("tmp"))
90
81
  end
91
-
92
- def test_caching_importer
93
- source = "p\n foo: bar"
94
- importer = FruitImporter.new
95
- filename = filename_for_test
96
- engine = Sass::Engine.new(source, :filename => filename, :importer => importer)
97
- engine.to_tree # Trigger caching
98
-
99
- sha = Digest::SHA1.hexdigest(source)
100
- cache = engine.options[:cache_store]
101
- cached_tree = cache.retrieve(cache.key(*importer.key(filename, engine.options)), sha)
102
- assert cached_tree.options[:importer].cached, "Importer's _around_dump method should have been called"
103
- end
104
82
  end
@@ -198,6 +198,13 @@ SASS
198
198
  assert_equal "public_instance_methods()", resolve("public_instance_methods()")
199
199
  end
200
200
 
201
+ def test_adding_functions_directly_to_functions_module
202
+ assert !Functions.callable?('nonexistant')
203
+ Functions.class_eval { def nonexistant; end }
204
+ assert Functions.callable?('nonexistant')
205
+ Functions.send :remove_method, :nonexistant
206
+ end
207
+
201
208
  def test_default_functions
202
209
  assert_equal "url(12)", resolve("url(12)")
203
210
  assert_equal 'blam("foo")', resolve('blam("foo")')
@@ -5,19 +5,6 @@ require 'pathname'
5
5
  class UtilTest < Test::Unit::TestCase
6
6
  include Sass::Util
7
7
 
8
- class Dumpable
9
- attr_reader :arr
10
- def initialize; @arr = []; end
11
- def _before_dump; @arr << :before; end
12
- def _after_dump; @arr << :after; end
13
- def _around_dump
14
- @arr << :around_before
15
- yield
16
- @arr << :around_after
17
- end
18
- def _after_load; @arr << :loaded; end
19
- end
20
-
21
8
  def test_scope
22
9
  assert(File.exist?(scope("Rakefile")))
23
10
  end
@@ -253,14 +240,6 @@ class UtilTest < Test::Unit::TestCase
253
240
  assert(!version_gt(v2, v1), "Expected #{v2} = #{v1}")
254
241
  end
255
242
 
256
- def test_dump_and_load
257
- obj = Dumpable.new
258
- data = dump(obj)
259
- assert_equal([:before, :around_before, :around_after, :after], obj.arr)
260
- obj2 = load(data)
261
- assert_equal([:before, :around_before, :loaded], obj2.arr)
262
- end
263
-
264
243
  class FooBar
265
244
  def foo
266
245
  Sass::Util.abstract(self)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.alpha.214
4
+ version: 3.1.0.alpha.216
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Weizenbaum
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2010-12-31 00:00:00 -05:00
14
+ date: 2011-01-13 00:00:00 -05:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -60,6 +60,7 @@ files:
60
60
  - lib/sass/cache_stores.rb
61
61
  - lib/sass/cache_stores/active_support.rb
62
62
  - lib/sass/cache_stores/base.rb
63
+ - lib/sass/cache_stores/chain.rb
63
64
  - lib/sass/cache_stores/filesystem.rb
64
65
  - lib/sass/cache_stores/memory.rb
65
66
  - lib/sass/cache_stores/null.rb