haml 3.0.0.beta.1 → 3.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (67) hide show
  1. data/README.md +7 -7
  2. data/REMEMBER +2 -1
  3. data/Rakefile +6 -4
  4. data/VERSION +1 -1
  5. data/lib/haml/exec.rb +119 -24
  6. data/lib/haml/filters.rb +5 -1
  7. data/lib/haml/helpers.rb +4 -2
  8. data/lib/haml/helpers/action_view_mods.rb +2 -3
  9. data/lib/haml/precompiler.rb +1 -0
  10. data/lib/sass.rb +1 -1
  11. data/lib/sass/css.rb +25 -6
  12. data/lib/sass/engine.rb +23 -7
  13. data/lib/sass/environment.rb +47 -1
  14. data/lib/sass/files.rb +9 -10
  15. data/lib/sass/plugin.rb +6 -27
  16. data/lib/sass/plugin/merb.rb +1 -0
  17. data/lib/sass/plugin/rails.rb +1 -0
  18. data/lib/sass/plugin/staleness_checker.rb +123 -0
  19. data/lib/sass/script/bool.rb +1 -1
  20. data/lib/sass/script/color.rb +1 -1
  21. data/lib/sass/script/css_lexer.rb +1 -4
  22. data/lib/sass/script/funcall.rb +2 -2
  23. data/lib/sass/script/functions.rb +102 -7
  24. data/lib/sass/script/interpolation.rb +5 -5
  25. data/lib/sass/script/lexer.rb +10 -8
  26. data/lib/sass/script/literal.rb +11 -1
  27. data/lib/sass/script/node.rb +10 -1
  28. data/lib/sass/script/number.rb +25 -10
  29. data/lib/sass/script/operation.rb +7 -6
  30. data/lib/sass/script/parser.rb +37 -28
  31. data/lib/sass/script/string.rb +12 -7
  32. data/lib/sass/script/string_interpolation.rb +70 -0
  33. data/lib/sass/script/unary_operation.rb +3 -3
  34. data/lib/sass/script/variable.rb +2 -2
  35. data/lib/sass/scss/css_parser.rb +1 -0
  36. data/lib/sass/scss/parser.rb +58 -44
  37. data/lib/sass/scss/rx.rb +1 -0
  38. data/lib/sass/tree/comment_node.rb +3 -2
  39. data/lib/sass/tree/debug_node.rb +1 -1
  40. data/lib/sass/tree/for_node.rb +1 -1
  41. data/lib/sass/tree/if_node.rb +1 -1
  42. data/lib/sass/tree/import_node.rb +3 -0
  43. data/lib/sass/tree/mixin_def_node.rb +3 -3
  44. data/lib/sass/tree/mixin_node.rb +7 -3
  45. data/lib/sass/tree/node.rb +8 -0
  46. data/lib/sass/tree/prop_node.rb +21 -16
  47. data/lib/sass/tree/rule_node.rb +3 -3
  48. data/lib/sass/tree/variable_node.rb +1 -1
  49. data/lib/sass/tree/warn_node.rb +41 -0
  50. data/lib/sass/tree/while_node.rb +1 -1
  51. data/test/haml/engine_test.rb +9 -0
  52. data/test/sass/conversion_test.rb +127 -15
  53. data/test/sass/css2sass_test.rb +34 -3
  54. data/test/sass/engine_test.rb +82 -5
  55. data/test/sass/functions_test.rb +31 -0
  56. data/test/sass/plugin_test.rb +25 -24
  57. data/test/sass/results/script.css +4 -4
  58. data/test/sass/results/warn.css +0 -0
  59. data/test/sass/results/warn_imported.css +0 -0
  60. data/test/sass/script_conversion_test.rb +43 -1
  61. data/test/sass/script_test.rb +3 -3
  62. data/test/sass/scss/css_test.rb +46 -5
  63. data/test/sass/scss/scss_test.rb +72 -0
  64. data/test/sass/templates/import.sass +1 -1
  65. data/test/sass/templates/warn.sass +3 -0
  66. data/test/sass/templates/warn_imported.sass +4 -0
  67. metadata +10 -3
@@ -9,6 +9,7 @@ module Sass
9
9
  def parent_selector; nil; end
10
10
  def interpolation; nil; end
11
11
  def interp_string; tok(STRING); end
12
+ def interp_ident(ident = IDENT); tok(ident); end
12
13
  def expected_property_separator; '":"'; end
13
14
  def use_css_import?; true; end
14
15
 
@@ -77,7 +77,7 @@ module Sass
77
77
  node << comment
78
78
  end
79
79
 
80
- DIRECTIVES = Set[:mixin, :include, :debug, :for, :while, :if, :import, :media]
80
+ DIRECTIVES = Set[:mixin, :include, :debug, :warn, :for, :while, :if, :import, :media]
81
81
 
82
82
  def directive
83
83
  return unless tok(/@/)
@@ -127,6 +127,10 @@ module Sass
127
127
  node(Sass::Tree::DebugNode.new(sass_script(:parse)))
128
128
  end
129
129
 
130
+ def warn
131
+ node(Sass::Tree::WarnNode.new(sass_script(:parse)))
132
+ end
133
+
130
134
  def for
131
135
  tok!(/\$/)
132
136
  var = tok! IDENT
@@ -259,18 +263,6 @@ module Sass
259
263
  tok(/[+-]/)
260
264
  end
261
265
 
262
- def property
263
- return unless e = (tok(IDENT) || interpolation)
264
- res = [e, str{ss}]
265
-
266
- while e = (interpolation || tok(IDENT))
267
- res << e
268
- end
269
-
270
- ss
271
- res
272
- end
273
-
274
266
  def ruleset
275
267
  rules = []
276
268
  return unless v = selector
@@ -366,12 +358,12 @@ module Sass
366
358
 
367
359
  def simple_selector_sequence
368
360
  # This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
369
- return expr unless e = element_name || tok(HASH) || class_expr ||
361
+ return expr unless e = element_name || id_selector || class_selector ||
370
362
  attrib || negation || pseudo || parent_selector || interpolation
371
363
  res = [e]
372
364
 
373
365
  # The tok(/\*/) allows the "E*" hack
374
- while v = element_name || tok(HASH) || class_expr ||
366
+ while v = element_name || id_selector || class_selector ||
375
367
  attrib || negation || pseudo || tok(/\*/) || interpolation
376
368
  res << v
377
369
  end
@@ -382,23 +374,30 @@ module Sass
382
374
  tok(/&/)
383
375
  end
384
376
 
385
- def class_expr
377
+ def class_selector
386
378
  return unless tok(/\./)
387
- '.' + tok!(IDENT)
379
+ @expected = "class name"
380
+ ['.', expr!(:interp_ident)]
381
+ end
382
+
383
+ def id_selector
384
+ return unless tok(/#(?!\{)/)
385
+ @expected = "id name"
386
+ ['#', expr!(:interp_name)]
388
387
  end
389
388
 
390
389
  def element_name
391
- return unless name = tok(IDENT) || tok(/\*/) || tok?(/\|/)
390
+ return unless name = interp_ident || tok(/\*/) || tok?(/\|/)
392
391
  if tok(/\|/)
393
392
  @expected = "element name or *"
394
- name << "|" << (tok(IDENT) || tok!(/\*/))
393
+ name << "|" << (interp_ident || tok!(/\*/))
395
394
  end
396
395
  name
397
396
  end
398
397
 
399
398
  def attrib
400
399
  return unless tok(/\[/)
401
- res = ['[', str{ss}, str{attrib_name!}, str{ss}]
400
+ res = ['[', str{ss}, attrib_name!, str{ss}]
402
401
 
403
402
  if m = tok(/=/) ||
404
403
  tok(INCLUDES) ||
@@ -413,31 +412,29 @@ module Sass
413
412
  end
414
413
 
415
414
  def attrib_name!
416
- if tok(IDENT)
417
- # E, E|E, or E|
418
- # The last is allowed so that E|="foo" will work
419
- tok(IDENT) if tok(/\|/)
420
- elsif tok(/\*/)
421
- # *|E
422
- tok!(/\|/)
423
- tok! IDENT
415
+ if name_or_ns = interp_ident
416
+ # E, E|E
417
+ if tok(/\|(?!=)/)
418
+ ns = name_or_ns
419
+ name = interp_ident
420
+ else
421
+ name = name_or_ns
422
+ end
424
423
  else
425
- # |E or E
426
- tok(/\|/)
427
- tok! IDENT
424
+ # *|E or |E
425
+ ns = tok(/\*/) || ""
426
+ tok!(/\|/)
427
+ name = expr!(:interp_ident)
428
428
  end
429
+ return [ns, ("|" if ns), name]
429
430
  end
430
431
 
431
432
  def pseudo
432
433
  return unless s = tok(/::?/)
433
-
434
434
  @expected = "pseudoclass or pseudoelement"
435
- [s, functional_pseudo || tok!(IDENT)]
436
- end
437
-
438
- def functional_pseudo
439
- return unless fn = tok(FUNCTION)
440
- [fn, str{ss}, expr!(:pseudo_expr), tok!(/\)/)]
435
+ res = [s, expr!(:interp_ident)]
436
+ return res unless tok(/\(/)
437
+ res << '(' << str{ss} << expr!(:pseudo_expr) << tok!(/\)/)
441
438
  end
442
439
 
443
440
  def pseudo_expr
@@ -455,18 +452,20 @@ module Sass
455
452
  return unless tok(NOT)
456
453
  res = [":not(", str{ss}]
457
454
  @expected = "selector"
458
- res << (element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo))
455
+ res << (element_name || id_selector || class_selector || attrib || expr!(:pseudo))
459
456
  res << tok!(/\)/)
460
457
  end
461
458
 
462
459
  def declaration
463
460
  # This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
464
- if s = tok(/[:\*\.]/)
465
- @use_property_exception = s != '.'
466
- name = [s, str{ss}] + expr!(:property)
461
+ if s = tok(/[:\*\.]|\#(?!\{)/)
462
+ @use_property_exception = s !~ /[\.\#]/
463
+ name = [s, str{ss}, *expr!(:interp_ident)]
467
464
  else
468
- return unless name = property
465
+ return unless name = interp_ident
466
+ name = [name] if name.is_a?(String)
469
467
  end
468
+ ss
470
469
 
471
470
  @expected = expected_property_separator
472
471
  space, value = expr!(:value)
@@ -553,7 +552,7 @@ MESSAGE
553
552
  end
554
553
 
555
554
  def interpolation
556
- return unless tok(/#\{/)
555
+ return unless tok(INTERP_START)
557
556
  sass_script(:parse_interpolated)
558
557
  end
559
558
 
@@ -571,6 +570,19 @@ MESSAGE
571
570
  res
572
571
  end
573
572
 
573
+ def interp_ident(start = IDENT)
574
+ return unless val = tok(start) || interpolation
575
+ res = [val]
576
+ while val = tok(NAME) || interpolation
577
+ res << val
578
+ end
579
+ res
580
+ end
581
+
582
+ def interp_name
583
+ interp_ident NAME
584
+ end
585
+
574
586
  def str
575
587
  @strs.push ""
576
588
  yield
@@ -608,6 +620,8 @@ MESSAGE
608
620
  :media_query => "media query (e.g. print, screen, print and screen)",
609
621
  :media_expr => "media expression (e.g. (min-device-width: 800px)))",
610
622
  :pseudo_expr => "expression (e.g. fr, 2n+1)",
623
+ :interp_ident => "identifier",
624
+ :interp_name => "identifier",
611
625
  :expr => "expression (e.g. 1px, bold)",
612
626
  }
613
627
 
@@ -107,6 +107,7 @@ module Sass
107
107
 
108
108
  # Custom
109
109
  HEXCOLOR = /\#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?/
110
+ INTERP_START = /#\{/
110
111
  end
111
112
  end
112
113
  end
@@ -59,10 +59,11 @@ module Sass::Tree
59
59
  else
60
60
  content.gsub!(/\n( \*|\/\/)/, "\n ")
61
61
  spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
62
+ sep = silent ? "\n//" : "\n *"
62
63
  if spaces >= 2
63
- content
64
+ content.gsub(/\n /, sep)
64
65
  else
65
- content.gsub(/\n#{' ' * spaces}/, "\n ")
66
+ content.gsub(/\n#{' ' * spaces}/, sep)
66
67
  end
67
68
  end
68
69
 
@@ -13,7 +13,7 @@ module Sass
13
13
  protected
14
14
 
15
15
  def to_src(tabs, opts, fmt)
16
- "#{' ' * tabs}@debug #{@expr.to_sass}#{semi fmt}\n"
16
+ "#{' ' * tabs}@debug #{@expr.to_sass(opts)}#{semi fmt}\n"
17
17
  end
18
18
 
19
19
  # Prints the expression to STDERR.
@@ -22,7 +22,7 @@ module Sass::Tree
22
22
 
23
23
  def to_src(tabs, opts, fmt)
24
24
  to = @exclusive ? "to" : "through"
25
- "#{' ' * tabs}@for $#{@var} from #{@from.to_sass} #{to} #{@to.to_sass}" +
25
+ "#{' ' * tabs}@for $#{dasherize(@var, opts)} from #{@from.to_sass(opts)} #{to} #{@to.to_sass(opts)}" +
26
26
  children_to_src(tabs, opts, fmt)
27
27
  end
28
28
 
@@ -44,7 +44,7 @@ module Sass::Tree
44
44
  else; "else"
45
45
  end
46
46
  str = "#{' ' * tabs}@#{name}"
47
- str << " #{@expr.to_sass}" if @expr
47
+ str << " #{@expr.to_sass(opts)}" if @expr
48
48
  str << children_to_src(tabs, opts, fmt)
49
49
  str << @else.send(:to_src, tabs, opts, fmt, true) if @else
50
50
  str
@@ -62,6 +62,7 @@ module Sass
62
62
  # @param environment [Sass::Environment] The lexical environment containing
63
63
  # variable and mixin values
64
64
  def perform!(environment)
65
+ environment.push_frame(:filename => @filename, :line => @line)
65
66
  root = Sass::Files.tree_for(full_filename, @options)
66
67
  @template = root.template
67
68
  self.children = root.children
@@ -70,6 +71,8 @@ module Sass
70
71
  e.modify_backtrace(:filename => full_filename)
71
72
  e.add_backtrace(:filename => @filename, :line => @line)
72
73
  raise e
74
+ ensure
75
+ environment.pop_frame
73
76
  end
74
77
 
75
78
  private
@@ -23,14 +23,14 @@ module Sass
23
23
  else
24
24
  '(' + @args.map do |v, d|
25
25
  if d
26
- "#{v.to_sass}: #{d.to_sass}"
26
+ "#{v.to_sass(opts)}: #{d.to_sass(opts)}"
27
27
  else
28
- v.to_sass
28
+ v.to_sass(opts)
29
29
  end
30
30
  end.join(", ") + ')'
31
31
  end
32
32
 
33
- "#{' ' * tabs}#{fmt == :sass ? '=' : '@mixin '}#{@name}#{args}" +
33
+ "#{' ' * tabs}#{fmt == :sass ? '=' : '@mixin '}#{dasherize(@name, opts)}#{args}" +
34
34
  children_to_src(tabs, opts, fmt)
35
35
  end
36
36
 
@@ -29,8 +29,8 @@ module Sass::Tree
29
29
  protected
30
30
 
31
31
  def to_src(tabs, opts, fmt)
32
- args = '(' + @args.map {|a| a.to_sass}.join(", ") + ')' unless @args.empty?
33
- "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{@name}#{args}#{semi fmt}\n"
32
+ args = '(' + @args.map {|a| a.to_sass(opts)}.join(", ") + ')' unless @args.empty?
33
+ "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{args}#{semi fmt}\n"
34
34
  end
35
35
 
36
36
  # @see Node#_cssize
@@ -50,13 +50,15 @@ module Sass::Tree
50
50
  # @raise [Sass::SyntaxError] if an incorrect number of arguments was passed
51
51
  # @see Sass::Tree
52
52
  def perform!(environment)
53
+ original_env = environment
54
+ original_env.push_frame(:filename => filename, :line => line)
55
+ original_env.prepare_frame(:mixin => @name)
53
56
  raise Sass::SyntaxError.new("Undefined mixin '#{@name}'.") unless mixin = environment.mixin(@name)
54
57
 
55
58
  raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < @args.size
56
59
  Mixin #{@name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
57
60
  but #{@args.size} #{@args.size == 1 ? 'was' : 'were'} passed.
58
61
  END
59
-
60
62
  environment = mixin.args.zip(@args).
61
63
  inject(Sass::Environment.new(mixin.environment)) do |env, ((var, default), value)|
62
64
  env.set_local_var(var.name,
@@ -78,6 +80,8 @@ END
78
80
  e.modify_backtrace(:mixin => @name, :line => @line)
79
81
  e.add_backtrace(:line => @line)
80
82
  raise e
83
+ ensure
84
+ original_env.pop_frame
81
85
  end
82
86
  end
83
87
  end
@@ -366,6 +366,14 @@ module Sass
366
366
  (fmt == :sass ? "\n" : " }\n")
367
367
  end
368
368
 
369
+ def dasherize(s, opts)
370
+ if opts[:dasherize]
371
+ s.gsub(/_/,'-')
372
+ else
373
+ s
374
+ end
375
+ end
376
+
369
377
  # Returns a semicolon if this is SCSS, or an empty string if this is Sass.
370
378
  #
371
379
  # @param fmt [Symbol] `:sass` or `:scss`
@@ -79,13 +79,17 @@ module Sass::Tree
79
79
  protected
80
80
 
81
81
  def to_src(tabs, opts, fmt)
82
- name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass}}"}.join
82
+ name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
83
+ if name[0] == ?:
84
+ raise Sass::SyntaxError.new("The \":#{name}: #{self.class.val_to_sass(value, opts)}\" hack is not allowed in the Sass indented syntax")
85
+ end
86
+
83
87
  old = opts[:old] && fmt == :sass
84
88
  initial = old ? ':' : ''
85
89
  mid = old ? '' : ':'
86
- res = "#{' ' * tabs}#{initial}#{name}#{mid} #{self.class.val_to_sass(value)}"
90
+ res = "#{' ' * tabs}#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}"
87
91
  return res + "#{semi fmt}\n" if children.empty?
88
- res.rstrip + children_to_src(tabs, opts, fmt)
92
+ res.rstrip + children_to_src(tabs, opts, fmt).rstrip + semi(fmt) + "\n"
89
93
  end
90
94
 
91
95
  # Computes the CSS for the property.
@@ -175,42 +179,43 @@ module Sass::Tree
175
179
 
176
180
  class << self
177
181
  # @private
178
- def val_to_sass(value)
179
- return value.to_sass unless value.context == :equals
180
- val_to_sass_comma(value).to_sass
182
+ def val_to_sass(value, opts)
183
+ return value.to_sass(opts) unless value.context == :equals
184
+ val_to_sass_comma(value, opts).to_sass(opts)
181
185
  end
182
186
 
183
187
  private
184
188
 
185
- def val_to_sass_comma(node)
189
+ def val_to_sass_comma(node, opts)
186
190
  return node unless node.is_a?(Sass::Script::Operation)
187
- return val_to_sass_concat(node) unless node.operator == :comma
191
+ return val_to_sass_concat(node, opts) unless node.operator == :comma
188
192
 
189
193
  Sass::Script::Operation.new(
190
- val_to_sass_concat(node.operand1),
191
- val_to_sass_comma(node.operand2),
194
+ val_to_sass_concat(node.operand1, opts),
195
+ val_to_sass_comma(node.operand2, opts),
192
196
  node.operator)
193
197
  end
194
198
 
195
- def val_to_sass_concat(node)
199
+ def val_to_sass_concat(node, opts)
196
200
  return node unless node.is_a?(Sass::Script::Operation)
197
- return val_to_sass_div(node) unless node.operator == :concat
201
+ return val_to_sass_div(node, opts) unless node.operator == :concat
198
202
 
199
203
  Sass::Script::Operation.new(
200
- val_to_sass_div(node.operand1),
201
- val_to_sass_concat(node.operand2),
204
+ val_to_sass_div(node.operand1, opts),
205
+ val_to_sass_concat(node.operand2, opts),
202
206
  node.operator)
203
207
  end
204
208
 
205
- def val_to_sass_div(node)
209
+ def val_to_sass_div(node, opts)
206
210
  unless node.is_a?(Sass::Script::Operation) && node.operator == :div &&
207
211
  node.operand1.is_a?(Sass::Script::Number) &&
208
212
  node.operand2.is_a?(Sass::Script::Number)
209
213
  return node
210
214
  end
211
215
 
212
- Sass::Script::String.new("(#{node.to_sass})")
216
+ Sass::Script::String.new("(#{node.to_sass(opts)})")
213
217
  end
218
+
214
219
  end
215
220
  end
216
221
  end
@@ -114,7 +114,7 @@ module Sass::Tree
114
114
  if r.is_a?(String)
115
115
  r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
116
116
  else
117
- "\#{#{r.to_sass}}"
117
+ "\#{#{r.to_sass(opts)}}"
118
118
  end
119
119
  end.join
120
120
  name = "\\" + name if name[0] == ?:
@@ -122,7 +122,7 @@ module Sass::Tree
122
122
  end
123
123
 
124
124
  def to_scss(tabs, opts = {})
125
- name = rule.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass}}"}.
125
+ name = rule.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(opts)}}"}.
126
126
  join.gsub(/^[ \t]*/, ' ' * tabs)
127
127
 
128
128
  res = name + children_to_src(tabs, opts, :scss)
@@ -316,7 +316,7 @@ module Sass::Tree
316
316
  debug_info.map {|k, v| [k.to_s, v.to_s]}.sort.each do |k, v|
317
317
  rule = RuleNode.new([""])
318
318
  rule.resolved_rules = [[k.to_s.gsub(/[^\w-]/, "\\\\\\0")]]
319
- prop = PropNode.new("", "", :new)
319
+ prop = PropNode.new([""], "", :new)
320
320
  prop.resolved_name = "font-family"
321
321
  prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
322
322
  rule << prop