sass 3.2.0.alpha.35 → 3.2.0.alpha.49

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.md +1 -1
  2. data/REVISION +1 -1
  3. data/VERSION +1 -1
  4. data/lib/sass.rb +22 -0
  5. data/lib/sass/engine.rb +2 -2
  6. data/lib/sass/environment.rb +0 -1
  7. data/lib/sass/exec.rb +1 -1
  8. data/lib/sass/importers/filesystem.rb +1 -1
  9. data/lib/sass/plugin.rb +4 -8
  10. data/lib/sass/plugin/compiler.rb +42 -17
  11. data/lib/sass/plugin/configuration.rb +0 -2
  12. data/lib/sass/repl.rb +0 -1
  13. data/lib/sass/script/funcall.rb +6 -1
  14. data/lib/sass/script/functions.rb +2 -2
  15. data/lib/sass/script/number.rb +1 -1
  16. data/lib/sass/script/parser.rb +12 -5
  17. data/lib/sass/script/variable.rb +0 -1
  18. data/lib/sass/scss/css_parser.rb +1 -0
  19. data/lib/sass/scss/parser.rb +37 -12
  20. data/lib/sass/scss/rx.rb +8 -3
  21. data/lib/sass/selector.rb +21 -0
  22. data/lib/sass/selector/abstract_sequence.rb +7 -0
  23. data/lib/sass/tree/media_node.rb +4 -4
  24. data/lib/sass/tree/rule_node.rb +5 -0
  25. data/lib/sass/tree/visitors/check_nesting.rb +10 -10
  26. data/lib/sass/tree/visitors/convert.rb +3 -3
  27. data/lib/sass/tree/visitors/cssize.rb +5 -1
  28. data/lib/sass/tree/visitors/perform.rb +16 -2
  29. data/lib/sass/tree/visitors/to_css.rb +2 -1
  30. data/lib/sass/util.rb +4 -1
  31. data/test/sass/conversion_test.rb +32 -2
  32. data/test/sass/engine_test.rb +105 -1
  33. data/test/sass/extend_test.rb +65 -0
  34. data/test/sass/importer_test.rb +7 -0
  35. data/test/sass/plugin_test.rb +16 -13
  36. data/test/sass/script_conversion_test.rb +2 -0
  37. data/test/sass/script_test.rb +18 -0
  38. data/test/sass/scss/scss_test.rb +34 -0
  39. data/test/sass/test_helper.rb +1 -1
  40. data/test/test_helper.rb +1 -0
  41. metadata +3 -3
data/README.md CHANGED
@@ -54,7 +54,7 @@ To do so, just add
54
54
 
55
55
  to `config.ru`.
56
56
  Then any Sass files in `public/stylesheets/sass`
57
- will be compiled CSS files in `public/stylesheets` on every request.
57
+ will be compiled into CSS files in `public/stylesheets` on every request.
58
58
 
59
59
  To use Sass programatically,
60
60
  check out the [YARD documentation](http://sass-lang.com/docs/yardoc/).
data/REVISION CHANGED
@@ -1 +1 @@
1
- ac7e4266b54b32610016a4b8b651c9d4e18b1e62
1
+ 70a3b0e1abd689bf4f11749f3347e39e9d1c28fb
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.35
1
+ 3.2.0.alpha.49
@@ -17,6 +17,28 @@ require 'sass/version'
17
17
  #
18
18
  # Also see the {file:SASS_REFERENCE.md full Sass reference}.
19
19
  module Sass
20
+ # The global load paths for Sass files. This is meant for plugins and
21
+ # libraries to register the paths to their Sass stylesheets to that they may
22
+ # be `@imported`. This load path is used by every instance of [Sass::Engine].
23
+ # They are lower-precedence than any load paths passed in via the
24
+ # {file:SASS_REFERENCE.md#load_paths-option `:load_paths` option}.
25
+ #
26
+ # If the `SASS_PATH` environment variable is set,
27
+ # the initial value of `load_paths` will be initialized based on that.
28
+ # The variable should be a colon-separated list of path names
29
+ # (semicolon-separated on Windows).
30
+ #
31
+ # Note that files on the global load path are never compiled to CSS
32
+ # themselves, even if they aren't partials. They exist only to be imported.
33
+ #
34
+ # @example
35
+ # Sass.load_paths << File.dirname(__FILE__ + '/sass')
36
+ # @return [Array<String, Pathname, Sass::Importers::Base>]
37
+ def self.load_paths
38
+ @load_paths ||= ENV['SASS_PATH'] ?
39
+ ENV['SASS_PATH'].split(Sass::Util.windows? ? ';' : ':') : []
40
+ end
41
+
20
42
  # Compile a Sass or SCSS string to CSS.
21
43
  # Defaults to SCSS.
22
44
  #
@@ -173,7 +173,7 @@ module Sass
173
173
  # for quite a long time.
174
174
  options[:line_comments] ||= options[:line_numbers]
175
175
 
176
- options[:load_paths] = options[:load_paths].map do |p|
176
+ options[:load_paths] = (options[:load_paths] + Sass.load_paths).map do |p|
177
177
  next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
178
178
  options[:filesystem_importer].new(p.to_s)
179
179
  end
@@ -692,7 +692,7 @@ WARNING
692
692
  :line => @line + 1) unless line.children.empty?
693
693
  Tree::CharsetNode.new(name)
694
694
  elsif directive == "media"
695
- Tree::MediaNode.new(value)
695
+ Tree::MediaNode.new(value.split(',').map {|s| s.strip})
696
696
  else
697
697
  Tree::DirectiveNode.new(line.text)
698
698
  end
@@ -25,7 +25,6 @@ module Sass
25
25
  # @param parent [Environment] See \{#parent}
26
26
  def initialize(parent = nil)
27
27
  @parent = parent
28
- set_var("important", Script::String.new("!important")) unless parent
29
28
  end
30
29
 
31
30
  # The environment of the caller of this environment's mixin or function.
@@ -389,7 +389,7 @@ MSG
389
389
  dirs.map! {|from, to| [from, to || from]}
390
390
  ::Sass::Plugin.options[:template_location] = dirs
391
391
 
392
- ::Sass::Plugin.on_updating_stylesheet do |_, css|
392
+ ::Sass::Plugin.on_updated_stylesheet do |_, css|
393
393
  if File.exists? css
394
394
  puts_action :overwrite, :yellow, css
395
395
  else
@@ -108,7 +108,7 @@ module Sass
108
108
  # @return [(String, Symbol)] A filename-syntax pair.
109
109
  def find_real_file(dir, name)
110
110
  for (f,s) in possible_files(remove_root(name))
111
- path = (dir == ".") ? f : "#{dir}/#{f}"
111
+ path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{dir}/#{f}"
112
112
  if full_path = Dir[path].first
113
113
  full_path.gsub!(REDUNDANT_DIRECTORY,File::SEPARATOR)
114
114
  return full_path, s
@@ -92,14 +92,10 @@ module Sass
92
92
  # the second is the location of the CSS file that it should be compiled to.
93
93
  # @see #update_stylesheets
94
94
  def force_update_stylesheets(individual_files = [])
95
- old_options = options
96
- self.options = options.dup
97
- options[:never_update] = false
98
- options[:always_update] = true
99
- options[:cache] = false
100
- update_stylesheets(individual_files)
101
- ensure
102
- self.options = old_options
95
+ Compiler.new(options.dup.merge(
96
+ :never_update => false,
97
+ :always_update => true,
98
+ :cache => false)).update_stylesheets(individual_files)
103
99
  end
104
100
 
105
101
  # All other method invocations are proxied to the \{#compiler}.
@@ -38,7 +38,7 @@ module Sass::Plugin
38
38
  self.options.merge!(options)
39
39
  end
40
40
 
41
- # Register a callback to be run before stylesheets are mass-updated.
41
+ # Register a callback to be run after stylesheets are mass-updated.
42
42
  # This is run whenever \{#update\_stylesheets} is called,
43
43
  # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
44
44
  # is enabled.
@@ -51,6 +51,22 @@ module Sass::Plugin
51
51
  # the second is the target CSS file.
52
52
  define_callback :updating_stylesheets
53
53
 
54
+ # Register a callback to be run after a single stylesheet is updated.
55
+ # The callback is only run if the stylesheet is really updated;
56
+ # if the CSS file is fresh, this won't be run.
57
+ #
58
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
59
+ # is enabled, this callback won't be run
60
+ # when an exception CSS file is being written.
61
+ # To run an action for those files, use \{#on\_compilation\_error}.
62
+ #
63
+ # @yield [template, css]
64
+ # @yieldparam template [String]
65
+ # The location of the Sass/SCSS file being updated.
66
+ # @yieldparam css [String]
67
+ # The location of the CSS file being generated.
68
+ define_callback :updated_stylesheet
69
+
54
70
  # Register a callback to be run before a single stylesheet is updated.
55
71
  # The callback is only run if the stylesheet is guaranteed to be updated;
56
72
  # if the CSS file is fresh, this won't be run.
@@ -67,6 +83,13 @@ module Sass::Plugin
67
83
  # The location of the CSS file being generated.
68
84
  define_callback :updating_stylesheet
69
85
 
86
+ def on_updating_stylesheet_with_deprecation_warning(&block)
87
+ Sass::Util.sass_warn("Sass::Compiler#on_updating_stylesheet callback is deprecated and will be removed in a future release. Use Sass::Compiler#on_updated_stylesheet instead, which is run after stylesheet compilation.")
88
+ on_updating_stylesheet_without_deprecation_warning(&block)
89
+ end
90
+ alias_method :on_updating_stylesheet_without_deprecation_warning, :on_updating_stylesheet
91
+ alias_method :on_updating_stylesheet, :on_updating_stylesheet_with_deprecation_warning
92
+
70
93
  # Register a callback to be run when Sass decides not to update a stylesheet.
71
94
  # In particular, the callback is run when Sass finds that
72
95
  # the template file and none of its dependencies
@@ -160,28 +183,25 @@ module Sass::Plugin
160
183
  # The first string in each pair is the location of the Sass/SCSS file,
161
184
  # the second is the location of the CSS file that it should be compiled to.
162
185
  def update_stylesheets(individual_files = [])
163
- run_updating_stylesheets individual_files
164
186
  Sass::Plugin.checked_for_updates = true
165
187
  staleness_checker = StalenessChecker.new(engine_options)
166
188
 
167
- individual_files.each do |t, c|
168
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(c, t)
169
- update_stylesheet(t, c)
170
- end
171
- end
172
-
173
189
  template_location_array.each do |template_location, css_location|
174
-
175
190
  Dir.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
176
191
  # Get the relative path to the file
177
192
  name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
178
193
  css = css_filename(name, css_location)
194
+ individual_files << [file, css]
195
+ end
196
+ end
179
197
 
180
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
181
- update_stylesheet file, css
182
- else
183
- run_not_updating_stylesheet file, css
184
- end
198
+ run_updating_stylesheets individual_files
199
+
200
+ individual_files.each do |file, css|
201
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
202
+ update_stylesheet(file, css)
203
+ else
204
+ run_not_updating_stylesheet(file, css)
185
205
  end
186
206
  end
187
207
  end
@@ -318,18 +338,23 @@ module Sass::Plugin
318
338
  engine_opts = engine_options(:css_filename => css, :filename => filename)
319
339
  result = Sass::Engine.for_file(filename, engine_opts).render
320
340
  rescue Exception => e
341
+ compilation_error_occured = true
321
342
  run_compilation_error e, filename, css
322
343
  result = Sass::SyntaxError.exception_to_css(e, options)
323
344
  else
324
345
  run_updating_stylesheet filename, css
325
346
  end
326
347
 
327
- # Finally, write the file
348
+ write_file(css, result)
349
+ run_updated_stylesheet(filename, css) unless compilation_error_occured
350
+ end
351
+
352
+ def write_file(css, content)
328
353
  flag = 'w'
329
354
  flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
330
355
  File.open(css, flag) do |file|
331
- file.set_encoding(result.encoding) unless Sass::Util.ruby1_8?
332
- file.print(result)
356
+ file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
357
+ file.print(content)
333
358
  end
334
359
  end
335
360
 
@@ -31,8 +31,6 @@ module Sass
31
31
  # @return [{Symbol => Object}]
32
32
  def options
33
33
  @options ||= default_options.dup
34
- @options[:cache_store] ||= Sass::CacheStores::Filesystem.new(@options[:cache_location])
35
- @options
36
34
  end
37
35
 
38
36
  # Sets the options hash.
@@ -15,7 +15,6 @@ module Sass
15
15
  # Starts the read-eval-print loop.
16
16
  def run
17
17
  environment = Environment.new
18
- environment.set_var('important', Script::String.new('!important'))
19
18
  @line = 0
20
19
  loop do
21
20
  @line += 1
@@ -88,7 +88,12 @@ module Sass
88
88
  opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
89
89
  end
90
90
  rescue ArgumentError => e
91
- raise e unless e.backtrace.any? {|t| t =~ /:in `(block in )?(#{name}|perform)'$/}
91
+ # If this is a legitimate Ruby-raised argument error, re-raise it.
92
+ # Otherwise, it's an error in the user's stylesheet, so wrap it.
93
+ if e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
94
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
95
+ raise e
96
+ end
92
97
  raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
93
98
  end
94
99
 
@@ -93,7 +93,7 @@ module Sass::Script
93
93
  # \{#adjust_color 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\]}
96
+ # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\], \[$alpha\]}
97
97
  # : Fluidly scale one or more components of a color.
98
98
  #
99
99
  # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\]}
@@ -968,7 +968,7 @@ module Sass::Script
968
968
  #
969
969
  # Finally, the weight of color1 is renormalized to be within [0, 1]
970
970
  # and the weight of color2 is given by 1 minus the weight of color1.
971
- p = weight.value/100.0
971
+ p = (weight.value/100.0).to_f
972
972
  w = p*2 - 1
973
973
  a = color1.alpha - color2.alpha
974
974
 
@@ -360,7 +360,7 @@ module Sass::Script
360
360
  elsif num % 1 == 0.0
361
361
  num.to_i
362
362
  else
363
- (num * self.precision_factor).round / self.precision_factor
363
+ ((num * self.precision_factor).round / self.precision_factor).to_f
364
364
  end
365
365
  end
366
366
 
@@ -182,7 +182,11 @@ module Sass
182
182
  interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
183
183
  return unless e = #{sub}
184
184
  while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
185
- interp = try_op_before_interp(tok, e) and return interp
185
+ if interp = try_op_before_interp(tok, e)
186
+ return interp unless other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
187
+ return other_interp
188
+ end
189
+
186
190
  line = @lexer.line
187
191
  e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
188
192
  e.line = line
@@ -217,7 +221,10 @@ RUBY
217
221
  return unless e = interpolation
218
222
  arr = [e]
219
223
  while tok = try_tok(:comma)
220
- interp = try_op_before_interp(tok, e) and return interp
224
+ if interp = try_op_before_interp(tok, e)
225
+ return interp unless other_interp = try_ops_after_interp([:comma], :expr, interp)
226
+ return other_interp
227
+ end
221
228
  arr << assert_expr(:interpolation)
222
229
  end
223
230
  arr.size == 1 ? arr.first : node(List.new(arr, :comma), line)
@@ -235,15 +242,15 @@ RUBY
235
242
  interpolation(interp)
236
243
  end
237
244
 
238
- def try_ops_after_interp(ops, name)
245
+ def try_ops_after_interp(ops, name, prev = nil)
239
246
  return unless @lexer.after_interpolation?
240
247
  return unless op = try_tok(*ops)
241
- interp = try_op_before_interp(op) and return interp
248
+ interp = try_op_before_interp(op, prev) and return interp
242
249
 
243
250
  wa = @lexer.whitespace?
244
251
  str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
245
252
  str.line = @lexer.line
246
- interp = Script::Interpolation.new(nil, str, assert_expr(name), !:wb, wa, :originally_text)
253
+ interp = Script::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text)
247
254
  interp.line = @lexer.line
248
255
  return interp
249
256
  end
@@ -21,7 +21,6 @@ module Sass
21
21
 
22
22
  # @return [String] A string representation of the variable
23
23
  def inspect(opts = {})
24
- return "!important" if name == "important"
25
24
  "$#{dasherize(name, opts)}"
26
25
  end
27
26
  alias_method :to_sass, :inspect
@@ -18,6 +18,7 @@ module Sass
18
18
 
19
19
  private
20
20
 
21
+ def placeholder_selector; nil; end
21
22
  def parent_selector; nil; end
22
23
  def interpolation; nil; end
23
24
  def interp_string; tok(STRING); end
@@ -294,20 +294,23 @@ module Sass
294
294
  def use_css_import?; false; end
295
295
 
296
296
  def media_directive
297
- val = str {media_query_list}.strip
298
- block(node(Sass::Tree::MediaNode.new(val)), :directive)
297
+ block(node(Sass::Tree::MediaNode.new(media_query_list)), :directive)
299
298
  end
300
299
 
301
300
  # http://www.w3.org/TR/css3-mediaqueries/#syntax
302
301
  def media_query_list
303
- return unless media_query
302
+ has_q = false
303
+ q = str {has_q = media_query}
304
+
305
+ return unless has_q
306
+ queries = [q.strip]
304
307
 
305
308
  ss
306
309
  while tok(/,/)
307
- ss; expr!(:media_query); ss
310
+ ss; queries << str {expr!(:media_query)}.strip; ss
308
311
  end
309
312
 
310
- true
313
+ queries
311
314
  end
312
315
 
313
316
  def media_query
@@ -444,7 +447,7 @@ module Sass
444
447
  end
445
448
 
446
449
  def selector_sequence
447
- if sel = tok(STATIC_SELECTOR)
450
+ if sel = tok(STATIC_SELECTOR, true)
448
451
  return [sel]
449
452
  end
450
453
 
@@ -506,12 +509,14 @@ module Sass
506
509
  def simple_selector_sequence
507
510
  # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
508
511
  return expr unless e = element_name || id_selector || class_selector ||
509
- attrib || negation || pseudo || parent_selector || interpolation_selector
512
+ placeholder_selector || attrib || negation || pseudo || parent_selector ||
513
+ interpolation_selector
510
514
  res = [e]
511
515
 
512
516
  # The tok(/\*/) allows the "E*" hack
513
- while v = id_selector || class_selector || attrib || negation || pseudo ||
514
- interpolation_selector || (tok(/\*/) && Selector::Universal.new(nil))
517
+ while v = id_selector || class_selector || placeholder_selector || attrib ||
518
+ negation || pseudo || interpolation_selector ||
519
+ (tok(/\*/) && Selector::Universal.new(nil))
515
520
  res << v
516
521
  end
517
522
 
@@ -521,6 +526,9 @@ module Sass
521
526
  @scanner.pos = pos
522
527
  @line = line
523
528
  begin
529
+ # If we see "*E", don't force a throw because this could be the
530
+ # "*prop: val" hack.
531
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
524
532
  throw_error {expected('"{"')}
525
533
  rescue Sass::SyntaxError => e
526
534
  e.message << "\n\n\"#{sel}\" may only be used at the beginning of a selector."
@@ -548,6 +556,12 @@ module Sass
548
556
  Selector::Id.new(merge(expr!(:interp_name)))
549
557
  end
550
558
 
559
+ def placeholder_selector
560
+ return unless tok(/%/)
561
+ @expected = "placeholder name"
562
+ Selector::Placeholder.new(merge(expr!(:interp_ident)))
563
+ end
564
+
551
565
  def element_name
552
566
  return unless name = interp_ident || tok(/\*/) || (tok?(/\|/) && "")
553
567
  if tok(/\|/)
@@ -679,7 +693,7 @@ module Sass
679
693
  # we don't parse it at all, and instead return a plain old string
680
694
  # containing the value.
681
695
  # This results in a dramatic speed increase.
682
- if val = tok(STATIC_VALUE)
696
+ if val = tok(STATIC_VALUE, true)
683
697
  return space, Sass::Script::String.new(val.strip)
684
698
  end
685
699
  return space, sass_script(:parse)
@@ -768,7 +782,7 @@ MESSAGE
768
782
  end
769
783
 
770
784
  def interp_ident(start = IDENT)
771
- return unless val = tok(start) || interpolation
785
+ return unless val = tok(start) || interpolation || tok(IDENT_HYPHEN_INTERP, true)
772
786
  res = [val]
773
787
  while val = tok(NAME) || interpolation
774
788
  res << val
@@ -938,9 +952,20 @@ MESSAGE
938
952
  # This is important because `#tok` is called all the time.
939
953
  NEWLINE = "\n"
940
954
 
941
- def tok(rx)
955
+ def tok(rx, last_group_lookahead = false)
942
956
  res = @scanner.scan(rx)
943
957
  if res
958
+ # This fixes https://github.com/nex3/sass/issues/104, which affects
959
+ # Ruby 1.8.7 and REE. This fix is to replace the ?= zero-width
960
+ # positive lookahead operator in the Regexp (which matches without
961
+ # consuming the matched group), with a match that does consume the
962
+ # group, but then rewinds the scanner and removes the group from the
963
+ # end of the matched string. This fix makes the assumption that the
964
+ # matched group will always occur at the end of the match.
965
+ if last_group_lookahead && @scanner[-1]
966
+ @scanner.pos -= @scanner[-1].length
967
+ res.slice!(-@scanner[-1].length..-1)
968
+ end
944
969
  @line += res.count(NEWLINE)
945
970
  @expected = nil
946
971
  if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT