haml 3.0.0.beta.3 → 3.0.0.rc.1

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 (82) hide show
  1. data/.yardopts +2 -0
  2. data/REMEMBER +4 -11
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml.rb +5 -2
  6. data/lib/haml/exec.rb +11 -4
  7. data/lib/haml/filters.rb +3 -0
  8. data/lib/haml/helpers.rb +2 -10
  9. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  10. data/lib/haml/helpers/action_view_mods.rb +6 -4
  11. data/lib/haml/html.rb +0 -1
  12. data/lib/haml/precompiler.rb +37 -30
  13. data/lib/haml/railtie.rb +6 -2
  14. data/lib/haml/root.rb +4 -0
  15. data/lib/haml/template.rb +2 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml/util/subset_map.rb +101 -0
  18. data/lib/sass.rb +1 -0
  19. data/lib/sass/engine.rb +36 -31
  20. data/lib/sass/files.rb +1 -1
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/plugin/staleness_checker.rb +9 -9
  23. data/lib/sass/script.rb +1 -2
  24. data/lib/sass/script/color.rb +4 -3
  25. data/lib/sass/script/css_lexer.rb +11 -1
  26. data/lib/sass/script/css_parser.rb +4 -1
  27. data/lib/sass/script/funcall.rb +9 -0
  28. data/lib/sass/script/interpolation.rb +21 -0
  29. data/lib/sass/script/lexer.rb +30 -13
  30. data/lib/sass/script/node.rb +1 -1
  31. data/lib/sass/script/number.rb +4 -5
  32. data/lib/sass/script/parser.rb +13 -14
  33. data/lib/sass/script/string.rb +8 -2
  34. data/lib/sass/script/string_interpolation.rb +27 -4
  35. data/lib/sass/scss.rb +3 -0
  36. data/lib/sass/scss/css_parser.rb +5 -3
  37. data/lib/sass/scss/parser.rb +146 -64
  38. data/lib/sass/scss/rx.rb +9 -1
  39. data/lib/sass/scss/sass_parser.rb +11 -0
  40. data/lib/sass/scss/script_lexer.rb +2 -0
  41. data/lib/sass/scss/static_parser.rb +48 -0
  42. data/lib/sass/selector.rb +353 -0
  43. data/lib/sass/selector/abstract_sequence.rb +40 -0
  44. data/lib/sass/selector/comma_sequence.rb +80 -0
  45. data/lib/sass/selector/sequence.rb +194 -0
  46. data/lib/sass/selector/simple.rb +107 -0
  47. data/lib/sass/selector/simple_sequence.rb +161 -0
  48. data/lib/sass/tree/comment_node.rb +1 -0
  49. data/lib/sass/tree/debug_node.rb +1 -0
  50. data/lib/sass/tree/directive_node.rb +1 -0
  51. data/lib/sass/tree/extend_node.rb +60 -0
  52. data/lib/sass/tree/for_node.rb +1 -0
  53. data/lib/sass/tree/if_node.rb +2 -0
  54. data/lib/sass/tree/import_node.rb +2 -0
  55. data/lib/sass/tree/mixin_def_node.rb +1 -0
  56. data/lib/sass/tree/mixin_node.rb +21 -5
  57. data/lib/sass/tree/node.rb +59 -12
  58. data/lib/sass/tree/prop_node.rb +20 -21
  59. data/lib/sass/tree/root_node.rb +8 -17
  60. data/lib/sass/tree/rule_node.rb +49 -100
  61. data/lib/sass/tree/variable_node.rb +1 -0
  62. data/lib/sass/tree/warn_node.rb +1 -0
  63. data/lib/sass/tree/while_node.rb +1 -0
  64. data/test/haml/engine_test.rb +185 -3
  65. data/test/haml/helper_test.rb +25 -2
  66. data/test/haml/template_test.rb +2 -2
  67. data/test/haml/templates/helpers.haml +13 -0
  68. data/test/haml/util/subset_map_test.rb +91 -0
  69. data/test/haml/util_test.rb +25 -0
  70. data/test/sass/conversion_test.rb +23 -3
  71. data/test/sass/engine_test.rb +50 -7
  72. data/test/sass/extend_test.rb +1045 -0
  73. data/test/sass/results/complex.css +0 -1
  74. data/test/sass/results/script.css +1 -1
  75. data/test/sass/script_conversion_test.rb +16 -0
  76. data/test/sass/script_test.rb +37 -4
  77. data/test/sass/scss/css_test.rb +17 -3
  78. data/test/sass/scss/rx_test.rb +1 -1
  79. data/test/sass/scss/scss_test.rb +30 -0
  80. data/test/sass/templates/complex.sass +0 -2
  81. data/test/test_helper.rb +5 -0
  82. metadata +18 -4
@@ -72,6 +72,7 @@ module Sass::Tree
72
72
  content.rstrip + "\n"
73
73
  end
74
74
 
75
+ # @see Node#to_scss
75
76
  def to_scss(tabs, opts = {})
76
77
  spaces = (' ' * [tabs - value[/^ */].size, 0].max)
77
78
  if silent
@@ -12,6 +12,7 @@ module Sass
12
12
 
13
13
  protected
14
14
 
15
+ # @see Node#to_src
15
16
  def to_src(tabs, opts, fmt)
16
17
  "#{' ' * tabs}@debug #{@expr.to_sass(opts)}#{semi fmt}\n"
17
18
  end
@@ -22,6 +22,7 @@ module Sass::Tree
22
22
 
23
23
  protected
24
24
 
25
+ # @see Node#to_src
25
26
  def to_src(tabs, opts, fmt)
26
27
  res = "#{' ' * tabs}#{value}"
27
28
  return res + "#{semi fmt}\n" if children.empty?
@@ -0,0 +1,60 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A static node reprenting an `@extend` directive.
5
+ #
6
+ # @see Sass::Tree
7
+ class ExtendNode < Node
8
+ # @param selector [Array<String, Sass::Script::Node>]
9
+ # The CSS selector to extend,
10
+ # interspersed with {Sass::Script::Node}s
11
+ # representing `#{}`-interpolation.
12
+ def initialize(selector)
13
+ @selector = selector
14
+ super()
15
+ end
16
+
17
+ # Registers this extension in the `extends` subset map.
18
+ #
19
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
20
+ # The extensions defined for this tree
21
+ # @param parent [RuleNode] The parent node of this node
22
+ # @see Node#cssize
23
+ def cssize(extends, parent)
24
+ @resolved_selector.members.each do |seq|
25
+ if seq.members.size > 1
26
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
27
+ end
28
+
29
+ sseq = seq.members.first
30
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
31
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
32
+ end
33
+
34
+ sel = sseq.members
35
+ parent.resolved_rules.members.each do |seq|
36
+ if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
37
+ raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
38
+ end
39
+
40
+ extends[sel] = seq
41
+ end
42
+ end
43
+
44
+ []
45
+ end
46
+
47
+ protected
48
+
49
+ # Runs SassScript interpolation in the selector,
50
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
51
+ #
52
+ # @param environment [Sass::Environment] The lexical environment containing
53
+ # variable and mixin values
54
+ def perform!(environment)
55
+ @resolved_selector = Sass::SCSS::CssParser.new(run_interp(@selector, environment)).
56
+ parse_selector(self.line, self.filename)
57
+ super
58
+ end
59
+ end
60
+ end
@@ -20,6 +20,7 @@ module Sass::Tree
20
20
 
21
21
  protected
22
22
 
23
+ # @see Node#to_src
23
24
  def to_src(tabs, opts, fmt)
24
25
  to = @exclusive ? "to" : "through"
25
26
  "#{' ' * tabs}@for $#{dasherize(@var, opts)} from #{@from.to_sass(opts)} #{to} #{@to.to_sass(opts)}" +
@@ -30,6 +30,7 @@ module Sass::Tree
30
30
  @last_else = node
31
31
  end
32
32
 
33
+ # @see Node#options=
33
34
  def options=(options)
34
35
  super
35
36
  self.else.options = options if self.else
@@ -37,6 +38,7 @@ module Sass::Tree
37
38
 
38
39
  protected
39
40
 
41
+ # @see Node#to_src
40
42
  def to_src(tabs, opts, fmt, is_else = false)
41
43
  name =
42
44
  if !is_else; "if"
@@ -28,10 +28,12 @@ module Sass
28
28
  @full_filename ||= import
29
29
  end
30
30
 
31
+ # @see Node#to_sass
31
32
  def to_sass(tabs = 0, opts = {})
32
33
  "#{' ' * tabs}@import #{@imported_filename}\n"
33
34
  end
34
35
 
36
+ # @see Node#to_scss
35
37
  def to_scss(tabs = 0, opts = {})
36
38
  "#{' ' * tabs}@import \"#{@imported_filename}\";\n"
37
39
  end
@@ -16,6 +16,7 @@ module Sass
16
16
 
17
17
  protected
18
18
 
19
+ # @see Node#to_src
19
20
  def to_src(tabs, opts, fmt)
20
21
  args =
21
22
  if @args.empty?
@@ -22,22 +22,38 @@ module Sass::Tree
22
22
  end
23
23
 
24
24
  # @see Node#cssize
25
- def cssize(parent = nil)
26
- _cssize(parent) # Pass on the parent even if it's not a MixinNode
25
+ def cssize(extends, parent = nil)
26
+ _cssize(extends, parent) # Pass on the parent even if it's not a MixinNode
27
27
  end
28
28
 
29
29
  protected
30
30
 
31
+ # Returns an error message if the given child node is invalid,
32
+ # and false otherwise.
33
+ #
34
+ # {ExtendNode}s are valid within {MixinNode}s.
35
+ #
36
+ # @param child [Tree::Node] A potential child node
37
+ # @return [Boolean, String] Whether or not the child node is valid,
38
+ # as well as the error message to display if it is invalid
39
+ def invalid_child?(child)
40
+ super unless child.is_a?(ExtendNode)
41
+ end
42
+
43
+ # @see Node#to_src
31
44
  def to_src(tabs, opts, fmt)
32
45
  args = '(' + @args.map {|a| a.to_sass(opts)}.join(", ") + ')' unless @args.empty?
33
46
  "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{args}#{semi fmt}\n"
34
47
  end
35
48
 
36
49
  # @see Node#_cssize
37
- def _cssize(parent)
38
- children.map {|c| c.cssize(parent)}.flatten
50
+ def _cssize(extends, parent)
51
+ children.map do |c|
52
+ parent.check_child! c
53
+ c.cssize(extends, parent)
54
+ end.flatten
39
55
  rescue Sass::SyntaxError => e
40
- e.modify_backtrace(:mixin => @name, :line => line)
56
+ e.modify_backtrace(:mixin => @name, :filename => filename, :line => line)
41
57
  e.add_backtrace(:filename => filename, :line => line)
42
58
  raise e
43
59
  end
@@ -89,11 +89,20 @@ module Sass
89
89
  # @see #invalid_child?
90
90
  def <<(child)
91
91
  return if child.nil?
92
+ check_child! child
93
+ self.has_children = true
94
+ @children << child
95
+ end
96
+
97
+ # Raises an error if the given child node is invalid.
98
+ #
99
+ # @param child [Tree::Node] The child node
100
+ # @raise [Sass::SyntaxError] if `child` is invalid
101
+ # @see #invalid_child?
102
+ def check_child!(child)
92
103
  if msg = invalid_child?(child)
93
104
  raise Sass::SyntaxError.new(msg, :line => child.line)
94
105
  end
95
- self.has_children = true
96
- @children << child
97
106
  end
98
107
 
99
108
  # Compares this node and another object (only other {Tree::Node}s will be equal).
@@ -116,7 +125,10 @@ module Sass
116
125
  # @see #perform
117
126
  # @see #to_s
118
127
  def render
119
- perform(Environment.new).cssize.to_s
128
+ extends = Haml::Util::SubsetMap.new
129
+ result = perform(Environment.new).cssize(extends)
130
+ result = result.do_extend(extends) unless extends.empty?
131
+ result.to_s
120
132
  end
121
133
 
122
134
  # True if \{#to\_s} will return `nil`;
@@ -150,19 +162,42 @@ module Sass
150
162
  raise e
151
163
  end
152
164
 
165
+ # Converts a static CSS tree (e.g. the output of \{#cssize})
166
+ # into another static CSS tree,
167
+ # with the given extensions applied to all relevant {RuleNode}s.
168
+ #
169
+ # @todo Link this to the reference documentation on `@extend`
170
+ # when such a thing exists.
171
+ #
172
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
173
+ # The extensions to perform on this tree
174
+ # @return [Tree::Node] The resulting tree of static CSS nodes.
175
+ # @raise [Sass::SyntaxError] Only if there's a programmer error
176
+ # and this is not a static CSS tree
177
+ def do_extend(extends)
178
+ node = dup
179
+ node.children = children.map {|c| c.do_extend(extends)}
180
+ node
181
+ rescue Sass::SyntaxError => e
182
+ e.modify_backtrace(:filename => filename, :line => line)
183
+ raise e
184
+ end
185
+
153
186
  # Converts a static Sass tree (e.g. the output of \{#perform})
154
187
  # into a static CSS tree.
155
188
  #
156
189
  # \{#cssize} shouldn't be overridden directly;
157
190
  # instead, override \{#\_cssize} or \{#cssize!}.
158
191
  #
192
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
193
+ # The extensions defined for this tree
159
194
  # @param parent [Node, nil] The parent node of this node.
160
195
  # This should only be non-nil if the parent is the same class as this node
161
196
  # @return [Tree::Node] The resulting tree of static nodes
162
197
  # @raise [Sass::SyntaxError] if some element of the tree is invalid
163
198
  # @see Sass::Tree
164
- def cssize(parent = nil)
165
- _cssize((parent if parent.class == self.class))
199
+ def cssize(extends, parent = nil)
200
+ _cssize(extends, (parent if parent.class == self.class))
166
201
  rescue Sass::SyntaxError => e
167
202
  e.modify_backtrace(:filename => filename, :line => line)
168
203
  raise e
@@ -237,15 +272,17 @@ module Sass
237
272
  # returning the new node.
238
273
  # This doesn't modify this node or any of its children.
239
274
  #
275
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
276
+ # The extensions defined for this tree
240
277
  # @param parent [Node, nil] The parent node of this node.
241
278
  # This should only be non-nil if the parent is the same class as this node
242
279
  # @return [Tree::Node, Array<Tree::Node>] The resulting static CSS nodes
243
280
  # @raise [Sass::SyntaxError] if some element of the tree is invalid
244
281
  # @see #cssize
245
282
  # @see Sass::Tree
246
- def _cssize(parent)
283
+ def _cssize(extends, parent)
247
284
  node = dup
248
- node.cssize!(parent)
285
+ node.cssize!(extends, parent)
249
286
  node
250
287
  end
251
288
 
@@ -253,11 +290,13 @@ module Sass
253
290
  # This *does* modify this node,
254
291
  # but will be run non-destructively by \{#\_cssize\}.
255
292
  #
293
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
294
+ # The extensions defined for this tree
256
295
  # @param parent [Node, nil] The parent node of this node.
257
296
  # This should only be non-nil if the parent is the same class as this node
258
297
  # @see #cssize
259
- def cssize!(parent)
260
- self.children = children.map {|c| c.cssize(self)}.flatten
298
+ def cssize!(extends, parent)
299
+ self.children = children.map {|c| c.cssize(extends, self)}.flatten
261
300
  end
262
301
 
263
302
  # Runs any dynamic Sass code in this particular node.
@@ -322,8 +361,8 @@ module Sass
322
361
  # Returns an error message if the given child node is invalid,
323
362
  # and false otherwise.
324
363
  #
325
- # By default, all child nodes except those only allowed at root level
326
- # ({Tree::MixinDefNode}, {Tree::ImportNode}) are valid.
364
+ # By default, all child nodes except those only allowed under specific nodes
365
+ # ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid.
327
366
  # This is expected to be overriden by subclasses
328
367
  # for which some children are invalid.
329
368
  #
@@ -336,6 +375,8 @@ module Sass
336
375
  "Mixins may only be defined at the root of a document."
337
376
  when Tree::ImportNode
338
377
  "Import directives may only be used at the root of a document."
378
+ when Tree::ExtendNode
379
+ "Extend directives may only be used within rules."
339
380
  end
340
381
  end
341
382
 
@@ -366,9 +407,15 @@ module Sass
366
407
  (fmt == :sass ? "\n" : " }\n")
367
408
  end
368
409
 
410
+ # Convert any underscores in a string into hyphens,
411
+ # but only if the `:dasherize` option is set.
412
+ #
413
+ # @param s [String] The string to convert
414
+ # @param opts [{Symbol => Object}] The options hash
415
+ # @return [String] The converted string
369
416
  def dasherize(s, opts)
370
417
  if opts[:dasherize]
371
- s.gsub(/_/,'-')
418
+ s.gsub('_', '-')
372
419
  else
373
420
  s
374
421
  end
@@ -68,28 +68,19 @@ module Sass::Tree
68
68
  # This only applies for old-style properties with no value,
69
69
  # so returns the empty string if this is new-style.
70
70
  #
71
- # This should only be called once \{#perform} has been called.
72
- #
73
71
  # @return [String] The message
74
72
  def pseudo_class_selector_message
75
- return "" if @prop_syntax == :new || !resolved_value.empty?
73
+ return "" if @prop_syntax == :new || !value.is_a?(Sass::Script::String) || !value.value.empty?
76
74
  "\nIf #{declaration.dump} should be a selector, use \"\\#{declaration}\" instead."
77
75
  end
78
76
 
79
77
  protected
80
78
 
79
+ # @see Node#to_src
81
80
  def to_src(tabs, opts, fmt)
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
-
87
- old = opts[:old] && fmt == :sass
88
- initial = old ? ':' : ''
89
- mid = old ? '' : ':'
90
- res = "#{' ' * tabs}#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}"
81
+ res = declaration(tabs, opts, fmt)
91
82
  return res + "#{semi fmt}\n" if children.empty?
92
- res.rstrip + children_to_src(tabs, opts, fmt).rstrip + semi(fmt) + "\n"
83
+ res + children_to_src(tabs, opts, fmt).rstrip + semi(fmt) + "\n"
93
84
  end
94
85
 
95
86
  # Computes the CSS for the property.
@@ -103,10 +94,12 @@ module Sass::Tree
103
94
 
104
95
  # Converts nested properties into flat properties.
105
96
  #
97
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
98
+ # The extensions defined for this tree
106
99
  # @param parent [PropNode, nil] The parent node of this node,
107
100
  # or nil if the parent isn't a {PropNode}
108
101
  # @raise [Sass::SyntaxError] if the property uses invalid syntax
109
- def _cssize(parent)
102
+ def _cssize(extends, parent)
110
103
  node = super
111
104
  result = node.children.dup
112
105
  if !node.resolved_value.empty? || node.children.empty?
@@ -119,9 +112,11 @@ module Sass::Tree
119
112
  # Updates the name and indentation of this node based on the parent name
120
113
  # and nesting level.
121
114
  #
115
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
116
+ # The extensions defined for this tree
122
117
  # @param parent [PropNode, nil] The parent node of this node,
123
118
  # or nil if the parent isn't a {PropNode}
124
- def cssize!(parent)
119
+ def cssize!(extends, parent)
125
120
  self.resolved_name = "#{parent.resolved_name}-#{resolved_name}" if parent
126
121
  self.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if parent && style == :nested
127
122
  super
@@ -169,12 +164,16 @@ module Sass::Tree
169
164
  end
170
165
  end
171
166
 
172
- def declaration
173
- if @prop_syntax == :new
174
- "#{resolved_name}: #{resolved_value}"
175
- else
176
- ":#{resolved_name} #{resolved_value}"
177
- end.strip
167
+ def declaration(tabs = 0, opts = {:old => @prop_syntax == :old}, fmt = :sass)
168
+ name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
169
+ if name[0] == ?:
170
+ raise Sass::SyntaxError.new("The \":#{name}: #{self.class.val_to_sass(value, opts)}\" hack is not allowed in the Sass indented syntax")
171
+ end
172
+
173
+ old = opts[:old] && fmt == :sass
174
+ initial = old ? ':' : ''
175
+ mid = old ? '' : ':'
176
+ "#{' ' * tabs}#{initial}#{name}#{mid} #{self.class.val_to_sass(value, opts)}".rstrip
178
177
  end
179
178
 
180
179
  class << self
@@ -62,6 +62,7 @@ module Sass
62
62
 
63
63
  protected
64
64
 
65
+ # @see Node#to_src
65
66
  def to_src(opts, fmt)
66
67
  Haml::Util.enum_cons(children + [nil], 2).map do |child, nxt|
67
68
  child.send("to_#{fmt}", 0, opts) +
@@ -75,20 +76,6 @@ module Sass
75
76
  end.join.rstrip + "\n"
76
77
  end
77
78
 
78
- # Destructively converts this static Sass node into a static CSS node,
79
- # and checks that there are no properties at root level.
80
- #
81
- # @param parent [Node, nil] The parent node of this node.
82
- # This should only be non-nil if the parent is the same class as this node
83
- # @see Node#cssize!
84
- def cssize!(parent)
85
- super
86
- return unless child = children.find {|c| c.is_a?(PropNode)}
87
- message = "Properties aren't allowed at the root of a document." +
88
- child.pseudo_class_selector_message
89
- raise Sass::SyntaxError.new(message, :line => child.line)
90
- end
91
-
92
79
  # Computes the CSS corresponding to this Sass tree.
93
80
  #
94
81
  # @param args [Array] ignored
@@ -106,12 +93,16 @@ module Sass
106
93
  return result + "\n"
107
94
  end
108
95
 
109
- # Returns false, because all nodes are allowed at the root of the document
110
- # (properties are detected elsewhere post-mixin-resolution).
96
+ # Returns an error message if the given child node is invalid,
97
+ # and false otherwise.
98
+ #
99
+ # Only property nodes are invalid at root level.
111
100
  #
112
101
  # @see Node#invalid_child?
113
102
  def invalid_child?(child)
114
- false
103
+ return unless child.is_a?(Tree::PropNode)
104
+ "Properties aren't allowed at the root of a document." +
105
+ child.pseudo_class_selector_message
115
106
  end
116
107
  end
117
108
  end