haml 2.0.10 → 2.2.0

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 (107) hide show
  1. data/.yardopts +5 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +347 -0
  4. data/Rakefile +124 -19
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -0
  7. data/extra/haml-mode.el +397 -78
  8. data/extra/sass-mode.el +148 -36
  9. data/extra/update_watch.rb +13 -0
  10. data/lib/haml.rb +15 -993
  11. data/lib/haml/buffer.rb +131 -84
  12. data/lib/haml/engine.rb +129 -97
  13. data/lib/haml/error.rb +7 -7
  14. data/lib/haml/exec.rb +127 -42
  15. data/lib/haml/filters.rb +107 -42
  16. data/lib/haml/helpers.rb +210 -156
  17. data/lib/haml/helpers/action_view_extensions.rb +34 -39
  18. data/lib/haml/helpers/action_view_mods.rb +132 -139
  19. data/lib/haml/html.rb +77 -65
  20. data/lib/haml/precompiler.rb +404 -213
  21. data/lib/haml/shared.rb +78 -0
  22. data/lib/haml/template.rb +14 -14
  23. data/lib/haml/template/patch.rb +2 -2
  24. data/lib/haml/template/plugin.rb +2 -3
  25. data/lib/haml/util.rb +211 -6
  26. data/lib/haml/version.rb +30 -13
  27. data/lib/sass.rb +7 -856
  28. data/lib/sass/css.rb +169 -161
  29. data/lib/sass/engine.rb +344 -328
  30. data/lib/sass/environment.rb +79 -0
  31. data/lib/sass/error.rb +33 -11
  32. data/lib/sass/files.rb +139 -0
  33. data/lib/sass/plugin.rb +160 -117
  34. data/lib/sass/plugin/merb.rb +7 -6
  35. data/lib/sass/plugin/rails.rb +5 -6
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/script.rb +59 -0
  38. data/lib/sass/script/bool.rb +17 -0
  39. data/lib/sass/script/color.rb +183 -0
  40. data/lib/sass/script/funcall.rb +50 -0
  41. data/lib/sass/script/functions.rb +198 -0
  42. data/lib/sass/script/lexer.rb +178 -0
  43. data/lib/sass/script/literal.rb +177 -0
  44. data/lib/sass/script/node.rb +14 -0
  45. data/lib/sass/script/number.rb +381 -0
  46. data/lib/sass/script/operation.rb +45 -0
  47. data/lib/sass/script/parser.rb +172 -0
  48. data/lib/sass/script/string.rb +12 -0
  49. data/lib/sass/script/unary_operation.rb +34 -0
  50. data/lib/sass/script/variable.rb +31 -0
  51. data/lib/sass/tree/comment_node.rb +73 -10
  52. data/lib/sass/tree/debug_node.rb +30 -0
  53. data/lib/sass/tree/directive_node.rb +42 -17
  54. data/lib/sass/tree/file_node.rb +41 -0
  55. data/lib/sass/tree/for_node.rb +48 -0
  56. data/lib/sass/tree/if_node.rb +54 -0
  57. data/lib/sass/tree/mixin_def_node.rb +29 -0
  58. data/lib/sass/tree/mixin_node.rb +48 -0
  59. data/lib/sass/tree/node.rb +214 -11
  60. data/lib/sass/tree/prop_node.rb +109 -0
  61. data/lib/sass/tree/rule_node.rb +178 -51
  62. data/lib/sass/tree/variable_node.rb +34 -0
  63. data/lib/sass/tree/while_node.rb +31 -0
  64. data/test/haml/engine_test.rb +331 -36
  65. data/test/haml/helper_test.rb +12 -1
  66. data/test/haml/results/content_for_layout.xhtml +0 -3
  67. data/test/haml/results/filters.xhtml +2 -0
  68. data/test/haml/results/list.xhtml +1 -1
  69. data/test/haml/template_test.rb +7 -2
  70. data/test/haml/templates/content_for_layout.haml +0 -2
  71. data/test/haml/templates/list.haml +1 -1
  72. data/test/haml/util_test.rb +92 -0
  73. data/test/sass/css2sass_test.rb +69 -24
  74. data/test/sass/engine_test.rb +586 -64
  75. data/test/sass/functions_test.rb +125 -0
  76. data/test/sass/more_results/more1.css +9 -0
  77. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  78. data/test/sass/more_results/more_import.css +29 -0
  79. data/test/sass/more_templates/_more_partial.sass +2 -0
  80. data/test/sass/more_templates/more1.sass +23 -0
  81. data/test/sass/more_templates/more_import.sass +11 -0
  82. data/test/sass/plugin_test.rb +81 -28
  83. data/test/sass/results/line_numbers.css +49 -0
  84. data/test/sass/results/{constants.css → script.css} +4 -4
  85. data/test/sass/results/subdir/subdir.css +2 -0
  86. data/test/sass/results/units.css +11 -0
  87. data/test/sass/script_test.rb +258 -0
  88. data/test/sass/templates/import.sass +1 -1
  89. data/test/sass/templates/importee.sass +7 -2
  90. data/test/sass/templates/line_numbers.sass +13 -0
  91. data/test/sass/templates/{constants.sass → script.sass} +11 -10
  92. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  93. data/test/sass/templates/subdir/subdir.sass +2 -2
  94. data/test/sass/templates/units.sass +11 -0
  95. data/test/test_helper.rb +14 -0
  96. metadata +77 -19
  97. data/FAQ +0 -138
  98. data/README.rdoc +0 -319
  99. data/lib/sass/constant.rb +0 -216
  100. data/lib/sass/constant/color.rb +0 -101
  101. data/lib/sass/constant/literal.rb +0 -54
  102. data/lib/sass/constant/nil.rb +0 -9
  103. data/lib/sass/constant/number.rb +0 -87
  104. data/lib/sass/constant/operation.rb +0 -30
  105. data/lib/sass/constant/string.rb +0 -22
  106. data/lib/sass/tree/attr_node.rb +0 -57
  107. data/lib/sass/tree/value_node.rb +0 -20
@@ -3,29 +3,28 @@ require 'sass/tree/node'
3
3
  require 'strscan'
4
4
 
5
5
  module Sass
6
- # :stopdoc:
7
6
  module Tree
8
7
  class Node
9
- def to_sass(opts = {})
8
+ # Converts a node to Sass code that will generate it.
9
+ #
10
+ # @param tabs [Fixnum] The amount of tabulation to use for the Sass code
11
+ # @param opts [Hash<Symbol, Object>] An options hash (see {Sass::CSS#initialize})
12
+ # @return [String] The Sass code corresponding to the node
13
+ def to_sass(tabs = 0, opts = {})
10
14
  result = ''
11
15
 
12
16
  children.each do |child|
13
- result << "#{child.to_sass(0, opts)}\n"
17
+ result << "#{' ' * tabs}#{child.to_sass(0, opts)}\n"
14
18
  end
15
19
 
16
20
  result
17
21
  end
18
22
  end
19
23
 
20
- class ValueNode
21
- def to_sass(tabs, opts = {})
22
- "#{value}\n"
23
- end
24
- end
25
-
26
24
  class RuleNode
25
+ # @see Node#to_sass
27
26
  def to_sass(tabs, opts = {})
28
- str = "\n#{' ' * tabs}#{rule}#{children.any? { |c| c.is_a? AttrNode } ? "\n" : ''}"
27
+ str = "\n#{' ' * tabs}#{rules.first}#{children.any? { |c| c.is_a? PropNode } ? "\n" : ''}"
29
28
 
30
29
  children.each do |child|
31
30
  str << "#{child.to_sass(tabs + 1, opts)}"
@@ -35,96 +34,51 @@ module Sass
35
34
  end
36
35
  end
37
36
 
38
- class AttrNode
37
+ class PropNode
38
+ # @see Node#to_sass
39
39
  def to_sass(tabs, opts = {})
40
- "#{' ' * tabs}#{opts[:alternate] ? '' : ':'}#{name}#{opts[:alternate] ? ':' : ''} #{value}\n"
40
+ "#{' ' * tabs}#{opts[:old] ? ':' : ''}#{name}#{opts[:old] ? '' : ':'} #{value}\n"
41
41
  end
42
42
  end
43
43
 
44
44
  class DirectiveNode
45
+ # @see Node#to_sass
45
46
  def to_sass(tabs, opts = {})
46
47
  "#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
47
48
  end
48
49
  end
49
50
  end
50
51
 
51
- # This class is based on the Ruby 1.9 ordered hashes.
52
- # It keeps the semantics and most of the efficiency of normal hashes
53
- # while also keeping track of the order in which elements were set.
54
- class OrderedHash
55
- Node = Struct.new(:key, :value, :next, :prev)
56
- include Enumerable
57
-
58
- def initialize
59
- @hash = {}
60
- end
61
-
62
- def initialize_copy(other)
63
- @hash = other.instance_variable_get('@hash').clone
64
- end
65
-
66
- def [](key)
67
- @hash[key] && @hash[key].value
68
- end
69
-
70
- def []=(key, value)
71
- node = Node.new(key, value)
72
-
73
- if old = @hash[key]
74
- if old.prev
75
- old.prev.next = old.next
76
- else # old is @first and @last
77
- @first = @last = nil
78
- end
79
- end
80
-
81
- if @first.nil?
82
- @first = @last = node
83
- else
84
- node.prev = @last
85
- @last.next = node
86
- @last = node
87
- end
88
-
89
- @hash[key] = node
90
- value
91
- end
92
-
93
- def each
94
- return unless @first
95
- yield [@first.key, @first.value]
96
- node = @first
97
- yield [node.key, node.value] while node = node.next
98
- self
99
- end
100
-
101
- def values
102
- self.map { |k, v| v }
103
- end
104
- end
105
-
106
- # :startdoc:
107
-
108
- # This class contains the functionality used in the +css2sass+ utility,
109
- # namely converting CSS documents to Sass templates.
52
+ # This class converts CSS documents into Sass templates.
53
+ # It works by parsing the CSS document into a {Sass::Tree} structure,
54
+ # and then applying various transformations to the structure
55
+ # to produce more concise and idiomatic Sass.
56
+ #
57
+ # Example usage:
58
+ #
59
+ # Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
110
60
  class CSS
111
-
112
- # Creates a new instance of Sass::CSS that will compile the given document
113
- # to a Sass string when +render+ is called.
61
+ # @param template [String] The CSS code
62
+ # @option options :old [Boolean] (false)
63
+ # Whether or not to output old property syntax
64
+ # (`:color blue` as opposed to `color: blue`).
114
65
  def initialize(template, options = {})
115
66
  if template.is_a? IO
116
67
  template = template.read
117
68
  end
118
69
 
119
- @options = options
70
+ @options = options.dup
71
+ # Backwards compatibility
72
+ @options[:old] = true if @options[:alternate] == false
120
73
  @template = StringScanner.new(template)
121
74
  end
122
75
 
123
- # Processes the document and returns the result as a string
124
- # containing the CSS template.
76
+ # Converts the CSS template into Sass code.
77
+ #
78
+ # @return [String] The resulting Sass code
125
79
  def render
126
80
  begin
127
- build_tree.to_sass(@options).strip + "\n"
81
+ build_tree.to_sass(0, @options).strip + "\n"
128
82
  rescue Exception => err
129
83
  line = @template.string[0...@template.pos].split("\n").size
130
84
 
@@ -135,8 +89,11 @@ module Sass
135
89
 
136
90
  private
137
91
 
92
+ # Parses the CSS template and applies various transformations
93
+ #
94
+ # @return [Tree::Node] The root node of the parsed tree
138
95
  def build_tree
139
- root = Tree::Node.new(nil)
96
+ root = Tree::Node.new
140
97
  whitespace
141
98
  rules root
142
99
  expand_commas root
@@ -147,6 +104,9 @@ module Sass
147
104
  root
148
105
  end
149
106
 
107
+ # Parses a set of CSS rules.
108
+ #
109
+ # @param root [Tree::Node] The parent node of the rules
150
110
  def rules(root)
151
111
  while r = rule
152
112
  root << r
@@ -154,13 +114,28 @@ module Sass
154
114
  end
155
115
  end
156
116
 
117
+ # Parses a single CSS rule.
118
+ #
119
+ # @return [Tree::Node] The parsed rule
157
120
  def rule
158
- return unless rule = @template.scan(/[^\{\};]+/)
121
+ rule = ""
122
+ loop do
123
+ token = @template.scan(/(?:[^\{\};\/\s]|\/[^*])+/)
124
+ if token.nil?
125
+ return if rule.empty?
126
+ break
127
+ end
128
+ rule << token
129
+ break unless @template.match?(/\s|\/\*/)
130
+ whitespace
131
+ rule << " "
132
+ end
133
+
159
134
  rule.strip!
160
135
  directive = rule[0] == ?@
161
136
 
162
137
  if directive
163
- node = Tree::DirectiveNode.new(rule, nil)
138
+ node = Tree::DirectiveNode.new(rule)
164
139
  return node if @template.scan(/;/)
165
140
 
166
141
  assert_match /\{/
@@ -171,12 +146,15 @@ module Sass
171
146
  end
172
147
 
173
148
  assert_match /\{/
174
- node = Tree::RuleNode.new(rule, nil)
175
- attributes(node)
149
+ node = Tree::RuleNode.new(rule)
150
+ properties(node)
176
151
  return node
177
152
  end
178
153
 
179
- def attributes(rule)
154
+ # Parses a set of CSS properties within a rule.
155
+ #
156
+ # @param rule [Tree::RuleNode] The parent node of the properties
157
+ def properties(rule)
180
158
  while @template.scan(/[^:\}\s]+/)
181
159
  name = @template[0]
182
160
  whitespace
@@ -189,12 +167,15 @@ module Sass
189
167
  end
190
168
 
191
169
  assert_match /(;|(?=\}))/
192
- rule << Tree::AttrNode.new(name, value, nil)
170
+ rule << Tree::PropNode.new(name, value, nil)
193
171
  end
194
172
 
195
173
  assert_match /\}/
196
174
  end
197
175
 
176
+ # Moves the scanner over a section of whitespace or comments.
177
+ #
178
+ # @return [String] The ignored whitespace
198
179
  def whitespace
199
180
  space = @template.scan(/\s*/) || ''
200
181
 
@@ -207,37 +188,53 @@ module Sass
207
188
  return space
208
189
  end
209
190
 
191
+ # Moves the scanner over a regular expression,
192
+ # raising an exception if it doesn't match.
193
+ #
194
+ # @param re [Regexp] The regular expression to assert
210
195
  def assert_match(re)
211
- if !@template.scan(re)
212
- line = @template.string[0..@template.pos].count "\n"
213
- # Display basic regexps as plain old strings
214
- expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
215
- raise Exception.new("Invalid CSS on line #{line}: expected #{expected}")
196
+ if @template.scan(re)
197
+ whitespace
198
+ return
216
199
  end
217
- whitespace
200
+
201
+ line = @template.string[0..@template.pos].count "\n"
202
+ pos = @template.pos
203
+
204
+ after = @template.string[pos - 15...pos]
205
+ after = "..." + after if pos >= 15
206
+
207
+ # Display basic regexps as plain old strings
208
+ expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
209
+
210
+ was = @template.rest[0...15]
211
+ was += "..." if @template.rest.size >= 15
212
+ raise Exception.new(<<MESSAGE)
213
+ Invalid CSS on line #{line + 1} after #{after.inspect}:
214
+ expected #{expected}, was #{was.inspect}
215
+ MESSAGE
218
216
  end
219
217
 
220
218
  # Transform
221
219
  #
222
- # foo, bar, baz
223
- # color: blue
220
+ # foo, bar, baz
221
+ # color: blue
224
222
  #
225
223
  # into
226
224
  #
227
- # foo
228
- # color: blue
229
- # bar
230
- # color: blue
231
- # baz
232
- # color: blue
225
+ # foo
226
+ # color: blue
227
+ # bar
228
+ # color: blue
229
+ # baz
230
+ # color: blue
233
231
  #
234
- # Yes, this expands the amount of code,
235
- # but it's necessary to get nesting to work properly.
232
+ # @param root [Tree::Node] The parent node
236
233
  def expand_commas(root)
237
234
  root.children.map! do |child|
238
- next child unless Tree::RuleNode === child && child.rule.include?(',')
239
- child.rule.split(',').map do |rule|
240
- node = Tree::RuleNode.new(rule.strip, {})
235
+ next child unless Tree::RuleNode === child && child.rules.first.include?(',')
236
+ child.rules.first.split(',').map do |rule|
237
+ node = Tree::RuleNode.new(rule.strip)
241
238
  node.children = child.children
242
239
  node
243
240
  end
@@ -247,71 +244,77 @@ module Sass
247
244
 
248
245
  # Make rules use parent refs so that
249
246
  #
250
- # foo
251
- # color: green
252
- # foo.bar
253
- # color: blue
247
+ # foo
248
+ # color: green
249
+ # foo.bar
250
+ # color: blue
254
251
  #
255
252
  # becomes
256
253
  #
257
- # foo
258
- # color: green
259
- # &.bar
260
- # color: blue
254
+ # foo
255
+ # color: green
256
+ # &.bar
257
+ # color: blue
261
258
  #
262
259
  # This has the side effect of nesting rules,
263
260
  # so that
264
261
  #
265
- # foo
266
- # color: green
267
- # foo bar
268
- # color: red
269
- # foo baz
270
- # color: blue
262
+ # foo
263
+ # color: green
264
+ # foo bar
265
+ # color: red
266
+ # foo baz
267
+ # color: blue
271
268
  #
272
269
  # becomes
273
270
  #
274
- # foo
275
- # color: green
276
- # & bar
277
- # color: red
278
- # & baz
279
- # color: blue
271
+ # foo
272
+ # color: green
273
+ # & bar
274
+ # color: red
275
+ # & baz
276
+ # color: blue
280
277
  #
278
+ # @param root [Tree::Node] The parent node
281
279
  def parent_ref_rules(root)
282
- rules = OrderedHash.new
280
+ current_rule = nil
283
281
  root.children.select { |c| Tree::RuleNode === c }.each do |child|
284
282
  root.children.delete child
285
- first, rest = child.rule.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
286
- rules[first] ||= Tree::RuleNode.new(first, nil)
283
+ first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
284
+
285
+ if current_rule.nil? || current_rule.rules.first != first
286
+ current_rule = Tree::RuleNode.new(first)
287
+ root << current_rule
288
+ end
289
+
287
290
  if rest
288
- child.rule = "&" + rest
289
- rules[first] << child
291
+ child.rules = ["&" + rest]
292
+ current_rule << child
290
293
  else
291
- rules[first].children += child.children
294
+ current_rule.children += child.children
292
295
  end
293
296
  end
294
297
 
295
- rules.values.each { |v| parent_ref_rules(v) }
296
- root.children += rules.values
298
+ root.children.each { |v| parent_ref_rules(v) }
297
299
  end
298
300
 
299
301
  # Remove useless parent refs so that
300
302
  #
301
- # foo
302
- # & bar
303
- # color: blue
303
+ # foo
304
+ # & bar
305
+ # color: blue
304
306
  #
305
307
  # becomes
306
308
  #
307
- # foo
308
- # bar
309
- # color: blue
309
+ # foo
310
+ # bar
311
+ # color: blue
310
312
  #
313
+ # @param root [Tree::Node] The parent node
311
314
  def remove_parent_refs(root)
312
315
  root.children.each do |child|
313
316
  if child.is_a?(Tree::RuleNode)
314
- child.rule.gsub! /^& +/, ''
317
+ child.rules.first.gsub! /^& +/, ''
315
318
  remove_parent_refs child
316
319
  end
317
320
  end
@@ -319,39 +322,43 @@ module Sass
319
322
 
320
323
  # Flatten rules so that
321
324
  #
322
- # foo
323
- # bar
324
- # baz
325
+ # foo
326
+ # bar
325
327
  # color: red
326
328
  #
327
329
  # becomes
328
330
  #
329
- # foo bar baz
330
- # color: red
331
+ # foo bar
332
+ # color: red
331
333
  #
332
334
  # and
333
335
  #
334
- # foo
335
- # &.bar
336
- # color: blue
336
+ # foo
337
+ # &.bar
338
+ # color: blue
337
339
  #
338
340
  # becomes
339
341
  #
340
- # foo.bar
341
- # color: blue
342
+ # foo.bar
343
+ # color: blue
342
344
  #
345
+ # @param root [Tree::Node] The parent node
343
346
  def flatten_rules(root)
344
347
  root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
345
348
  end
346
349
 
350
+ # Flattens a single rule
351
+ #
352
+ # @param rule [Tree::RuleNode] The candidate for flattening
353
+ # @see #flatten_rules
347
354
  def flatten_rule(rule)
348
355
  while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
349
356
  child = rule.children.first
350
357
 
351
- if child.rule[0] == ?&
352
- rule.rule = child.rule.gsub /^&/, rule.rule
358
+ if child.rules.first[0] == ?&
359
+ rule.rules = [child.rules.first.gsub(/^&/, rule.rules.first)]
353
360
  else
354
- rule.rule = "#{rule.rule} #{child.rule}"
361
+ rule.rules = ["#{rule.rules.first} #{child.rules.first}"]
355
362
  end
356
363
 
357
364
  rule.children = child.children
@@ -362,25 +369,26 @@ module Sass
362
369
 
363
370
  # Transform
364
371
  #
365
- # foo
366
- # bar
367
- # color: blue
368
- # baz
369
- # color: blue
372
+ # foo
373
+ # bar
374
+ # color: blue
375
+ # baz
376
+ # color: blue
370
377
  #
371
378
  # into
372
379
  #
373
- # foo
374
- # bar, baz
375
- # color: blue
380
+ # foo
381
+ # bar, baz
382
+ # color: blue
376
383
  #
384
+ # @param rule [Tree::RuleNode] The candidate for flattening
377
385
  def fold_commas(root)
378
386
  prev_rule = nil
379
387
  root.children.map! do |child|
380
388
  next child unless child.is_a?(Tree::RuleNode)
381
389
 
382
390
  if prev_rule && prev_rule.children == child.children
383
- prev_rule.rule << ", #{child.rule}"
391
+ prev_rule.rules.first << ", #{child.rules.first}"
384
392
  next nil
385
393
  end
386
394