haml 1.8.2 → 2.0.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 (77) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +1 -1
  3. data/{README → README.rdoc} +66 -3
  4. data/Rakefile +111 -147
  5. data/VERSION +1 -1
  6. data/bin/css2sass +0 -0
  7. data/bin/haml +0 -0
  8. data/bin/html2haml +0 -0
  9. data/bin/sass +0 -0
  10. data/init.rb +6 -1
  11. data/lib/haml.rb +464 -201
  12. data/lib/haml/buffer.rb +117 -63
  13. data/lib/haml/engine.rb +63 -44
  14. data/lib/haml/error.rb +16 -6
  15. data/lib/haml/exec.rb +37 -7
  16. data/lib/haml/filters.rb +213 -68
  17. data/lib/haml/helpers.rb +95 -60
  18. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  19. data/lib/haml/helpers/action_view_mods.rb +54 -6
  20. data/lib/haml/html.rb +6 -6
  21. data/lib/haml/precompiler.rb +254 -133
  22. data/lib/haml/template.rb +3 -6
  23. data/lib/haml/template/patch.rb +9 -2
  24. data/lib/haml/template/plugin.rb +52 -23
  25. data/lib/sass.rb +157 -12
  26. data/lib/sass/constant.rb +22 -22
  27. data/lib/sass/constant/color.rb +13 -13
  28. data/lib/sass/constant/literal.rb +7 -7
  29. data/lib/sass/constant/number.rb +9 -9
  30. data/lib/sass/constant/operation.rb +4 -4
  31. data/lib/sass/constant/string.rb +3 -3
  32. data/lib/sass/css.rb +104 -31
  33. data/lib/sass/engine.rb +120 -39
  34. data/lib/sass/error.rb +1 -1
  35. data/lib/sass/plugin.rb +14 -3
  36. data/lib/sass/plugin/merb.rb +6 -2
  37. data/lib/sass/tree/attr_node.rb +5 -5
  38. data/lib/sass/tree/directive_node.rb +2 -7
  39. data/lib/sass/tree/node.rb +1 -12
  40. data/lib/sass/tree/rule_node.rb +39 -31
  41. data/lib/sass/tree/value_node.rb +1 -1
  42. data/test/benchmark.rb +67 -80
  43. data/test/haml/engine_test.rb +284 -84
  44. data/test/haml/helper_test.rb +51 -15
  45. data/test/haml/results/content_for_layout.xhtml +1 -2
  46. data/test/haml/results/eval_suppressed.xhtml +2 -4
  47. data/test/haml/results/filters.xhtml +44 -15
  48. data/test/haml/results/helpers.xhtml +2 -3
  49. data/test/haml/results/just_stuff.xhtml +2 -6
  50. data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
  51. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  52. data/test/haml/results/original_engine.xhtml +3 -7
  53. data/test/haml/results/partials.xhtml +1 -0
  54. data/test/haml/results/tag_parsing.xhtml +1 -6
  55. data/test/haml/results/very_basic.xhtml +2 -4
  56. data/test/haml/results/whitespace_handling.xhtml +13 -21
  57. data/test/haml/template_test.rb +8 -15
  58. data/test/haml/templates/_partial.haml +1 -0
  59. data/test/haml/templates/filters.haml +48 -7
  60. data/test/haml/templates/just_stuff.haml +1 -2
  61. data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
  62. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  63. data/test/haml/templates/tag_parsing.haml +0 -3
  64. data/test/haml/test_helper.rb +15 -0
  65. data/test/sass/engine_test.rb +80 -34
  66. data/test/sass/plugin_test.rb +1 -1
  67. data/test/sass/results/import.css +2 -2
  68. data/test/sass/results/mixins.css +95 -0
  69. data/test/sass/results/multiline.css +24 -0
  70. data/test/sass/templates/import.sass +4 -1
  71. data/test/sass/templates/importee.sass +4 -0
  72. data/test/sass/templates/mixins.sass +76 -0
  73. data/test/sass/templates/multiline.sass +20 -0
  74. metadata +65 -51
  75. data/lib/haml/util.rb +0 -18
  76. data/test/haml/runner.rb +0 -16
  77. data/test/profile.rb +0 -65
@@ -21,9 +21,9 @@ module Sass::Constant # :nodoc:
21
21
  'teal' => 0x008080,
22
22
  'aqua' => 0x00ffff
23
23
  }
24
-
24
+
25
25
  REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/
26
-
26
+
27
27
  def parse(value)
28
28
  if (value =~ REGEXP)
29
29
  @value = value.scan(REGEXP)[0].map { |num| num.ljust(2, num).to_i(16) }
@@ -32,7 +32,7 @@ module Sass::Constant # :nodoc:
32
32
  @value = (0..2).map{ |n| color >> (n << 3) & 0xff }.reverse
33
33
  end
34
34
  end
35
-
35
+
36
36
  def plus(other)
37
37
  if other.is_a? Sass::Constant::String
38
38
  Sass::Constant::String.from_value(self.to_s + other.to_s)
@@ -40,7 +40,7 @@ module Sass::Constant # :nodoc:
40
40
  piecewise(other, :+)
41
41
  end
42
42
  end
43
-
43
+
44
44
  def minus(other)
45
45
  if other.is_a? Sass::Constant::String
46
46
  raise NoMethodError.new(nil, :minus)
@@ -48,7 +48,7 @@ module Sass::Constant # :nodoc:
48
48
  piecewise(other, :-)
49
49
  end
50
50
  end
51
-
51
+
52
52
  def times(other)
53
53
  if other.is_a? Sass::Constant::String
54
54
  raise NoMethodError.new(nil, :times)
@@ -56,7 +56,7 @@ module Sass::Constant # :nodoc:
56
56
  piecewise(other, :*)
57
57
  end
58
58
  end
59
-
59
+
60
60
  def div(other)
61
61
  if other.is_a? Sass::Constant::String
62
62
  raise NoMethodError.new(nil, :div)
@@ -64,7 +64,7 @@ module Sass::Constant # :nodoc:
64
64
  piecewise(other, :/)
65
65
  end
66
66
  end
67
-
67
+
68
68
  def mod(other)
69
69
  if other.is_a? Sass::Constant::String
70
70
  raise NoMethodError.new(nil, :mod)
@@ -72,24 +72,24 @@ module Sass::Constant # :nodoc:
72
72
  piecewise(other, :%)
73
73
  end
74
74
  end
75
-
75
+
76
76
  def to_s
77
77
  red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
78
78
  "##{red}#{green}#{blue}"
79
79
  end
80
-
80
+
81
81
  protected
82
-
82
+
83
83
  def self.filter_value(value)
84
84
  value.map { |num| num.to_i }
85
85
  end
86
-
86
+
87
87
  private
88
-
88
+
89
89
  def piecewise(other, operation)
90
90
  other_num = other.is_a? Number
91
91
  other_val = other.value
92
-
92
+
93
93
  rgb = []
94
94
  for i in (0...3)
95
95
  res = @value[i].send(operation, other_num ? other_val : other_val[i])
@@ -13,7 +13,7 @@ class Sass::Constant::Literal # :nodoc:
13
13
 
14
14
  # The regular expression matching colors.
15
15
  COLOR = /^\# (?: [\da-f]{3} | [\da-f]{6} ) | #{html_color_matcher}/ix
16
-
16
+
17
17
  def self.parse(value)
18
18
  case value
19
19
  when NUMBER
@@ -24,11 +24,11 @@ class Sass::Constant::Literal # :nodoc:
24
24
  Sass::Constant::String.new(value)
25
25
  end
26
26
  end
27
-
27
+
28
28
  def initialize(value = nil)
29
29
  self.parse(value) if value
30
30
  end
31
-
31
+
32
32
  def perform
33
33
  self
34
34
  end
@@ -36,15 +36,15 @@ class Sass::Constant::Literal # :nodoc:
36
36
  def concat(other)
37
37
  Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}")
38
38
  end
39
-
39
+
40
40
  attr_reader :value
41
-
41
+
42
42
  protected
43
-
43
+
44
44
  def self.filter_value(value)
45
45
  value
46
46
  end
47
-
47
+
48
48
  def self.from_value(value)
49
49
  instance = self.new
50
50
  instance.instance_variable_set('@value', self.filter_value(value))
@@ -10,7 +10,7 @@ module Sass::Constant # :nodoc:
10
10
  @value = first.empty? ? second.to_i : "#{first}#{second}".to_f
11
11
  @unit = unit unless unit.empty?
12
12
  end
13
-
13
+
14
14
  def plus(other)
15
15
  if other.is_a? Number
16
16
  operate(other, :+)
@@ -20,7 +20,7 @@ module Sass::Constant # :nodoc:
20
20
  Sass::Constant::String.from_value(self.to_s + other.to_s)
21
21
  end
22
22
  end
23
-
23
+
24
24
  def minus(other)
25
25
  if other.is_a? Number
26
26
  operate(other, :-)
@@ -28,7 +28,7 @@ module Sass::Constant # :nodoc:
28
28
  raise NoMethodError.new(nil, :minus)
29
29
  end
30
30
  end
31
-
31
+
32
32
  def times(other)
33
33
  if other.is_a? Number
34
34
  operate(other, :*)
@@ -38,7 +38,7 @@ module Sass::Constant # :nodoc:
38
38
  raise NoMethodError.new(nil, :times)
39
39
  end
40
40
  end
41
-
41
+
42
42
  def div(other)
43
43
  if other.is_a? Number
44
44
  operate(other, :/)
@@ -46,7 +46,7 @@ module Sass::Constant # :nodoc:
46
46
  raise NoMethodError.new(nil, :div)
47
47
  end
48
48
  end
49
-
49
+
50
50
  def mod(other)
51
51
  if other.is_a? Number
52
52
  operate(other, :%)
@@ -54,13 +54,13 @@ module Sass::Constant # :nodoc:
54
54
  raise NoMethodError.new(nil, :mod)
55
55
  end
56
56
  end
57
-
57
+
58
58
  def to_s
59
59
  value = @value
60
60
  value = value.to_i if value % 1 == 0.0
61
61
  "#{value}#{@unit}"
62
62
  end
63
-
63
+
64
64
  protected
65
65
 
66
66
  def self.from_value(value, unit=nil)
@@ -68,7 +68,7 @@ module Sass::Constant # :nodoc:
68
68
  instance.instance_variable_set('@unit', unit)
69
69
  instance
70
70
  end
71
-
71
+
72
72
  def operate(other, operation)
73
73
  unit = nil
74
74
  if other.unit.nil?
@@ -78,7 +78,7 @@ module Sass::Constant # :nodoc:
78
78
  elsif other.unit == self.unit
79
79
  unit = self.unit
80
80
  else
81
- raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}")
81
+ raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}.")
82
82
  end
83
83
 
84
84
  Number.from_value(self.value.send(operation, other.value), unit)
@@ -9,13 +9,13 @@ module Sass::Constant # :nodoc:
9
9
  @operand2 = operand2
10
10
  @operator = operator
11
11
  end
12
-
12
+
13
13
  def to_s
14
14
  self.perform.to_s
15
15
  end
16
-
16
+
17
17
  protected
18
-
18
+
19
19
  def perform
20
20
  literal1 = @operand1.perform
21
21
  literal2 = @operand2.perform
@@ -23,7 +23,7 @@ module Sass::Constant # :nodoc:
23
23
  literal1.send(@operator, literal2)
24
24
  rescue NoMethodError => e
25
25
  raise e unless e.name.to_s == @operator.to_s
26
- raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\"")
26
+ raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
27
27
  end
28
28
  end
29
29
  end
@@ -2,11 +2,11 @@ require 'sass/constant/literal'
2
2
 
3
3
  module Sass::Constant # :nodoc:
4
4
  class String < Literal # :nodoc:
5
-
5
+
6
6
  def parse(value)
7
7
  @value = value
8
8
  end
9
-
9
+
10
10
  def plus(other)
11
11
  Sass::Constant::String.from_value(self.to_s + other.to_s)
12
12
  end
@@ -14,7 +14,7 @@ module Sass::Constant # :nodoc:
14
14
  def funcall(other)
15
15
  Sass::Constant::String.from_value("#{self.to_s}(#{other.to_s})")
16
16
  end
17
-
17
+
18
18
  def to_s
19
19
  @value
20
20
  end
@@ -6,11 +6,11 @@ module Sass
6
6
  # :stopdoc:
7
7
  module Tree
8
8
  class Node
9
- def to_sass
9
+ def to_sass(opts = {})
10
10
  result = ''
11
11
 
12
12
  children.each do |child|
13
- result << "#{child.to_sass(0)}\n"
13
+ result << "#{child.to_sass(0, opts)}\n"
14
14
  end
15
15
 
16
16
  result
@@ -24,11 +24,11 @@ module Sass
24
24
  end
25
25
 
26
26
  class RuleNode
27
- def to_sass(tabs)
28
- str = "#{' ' * tabs}#{rule}\n"
27
+ def to_sass(tabs, opts = {})
28
+ str = "\n#{' ' * tabs}#{rule}#{children.any? { |c| c.is_a? AttrNode } ? "\n" : ''}"
29
29
 
30
30
  children.each do |child|
31
- str << "#{child.to_sass(tabs + 1)}"
31
+ str << "#{child.to_sass(tabs + 1, opts)}"
32
32
  end
33
33
 
34
34
  str
@@ -36,8 +36,8 @@ module Sass
36
36
  end
37
37
 
38
38
  class AttrNode
39
- def to_sass(tabs)
40
- "#{' ' * tabs}:#{name} #{value}\n"
39
+ def to_sass(tabs, opts = {})
40
+ "#{' ' * tabs}#{opts[:alternate] ? '' : ':'}#{name}#{opts[:alternate] ? ':' : ''} #{value}\n"
41
41
  end
42
42
  end
43
43
  end
@@ -46,25 +46,40 @@ module Sass
46
46
  # It keeps the semantics and most of the efficiency of normal hashes
47
47
  # while also keeping track of the order in which elements were set.
48
48
  class OrderedHash
49
- Node = Struct.new('Node', :key, :value, :next)
49
+ Node = Struct.new(:key, :value, :next, :prev)
50
50
  include Enumerable
51
51
 
52
52
  def initialize
53
53
  @hash = {}
54
54
  end
55
55
 
56
+ def initialize_copy(other)
57
+ @hash = other.instance_variable_get('@hash').clone
58
+ end
59
+
56
60
  def [](key)
57
61
  @hash[key] && @hash[key].value
58
62
  end
59
63
 
60
64
  def []=(key, value)
61
- node = Node.new(key, value, nil)
65
+ node = Node.new(key, value)
66
+
67
+ if old = @hash[key]
68
+ if old.prev
69
+ old.prev.next = old.next
70
+ else # old is @first and @last
71
+ @first = @last = nil
72
+ end
73
+ end
74
+
62
75
  if @first.nil?
63
76
  @first = @last = node
64
77
  else
78
+ node.prev = @last
65
79
  @last.next = node
66
80
  @last = node
67
81
  end
82
+
68
83
  @hash[key] = node
69
84
  value
70
85
  end
@@ -90,11 +105,12 @@ module Sass
90
105
 
91
106
  # Creates a new instance of Sass::CSS that will compile the given document
92
107
  # to a Sass string when +render+ is called.
93
- def initialize(template)
108
+ def initialize(template, options = {})
94
109
  if template.is_a? IO
95
110
  template = template.read
96
111
  end
97
112
 
113
+ @options = options
98
114
  @template = StringScanner.new(template)
99
115
  end
100
116
 
@@ -102,10 +118,10 @@ module Sass
102
118
  # containing the CSS template.
103
119
  def render
104
120
  begin
105
- build_tree.to_sass
121
+ build_tree.to_sass(@options).lstrip
106
122
  rescue Exception => err
107
123
  line = @template.string[0...@template.pos].split("\n").size
108
-
124
+
109
125
  err.backtrace.unshift "(css):#{line}"
110
126
  raise err
111
127
  end
@@ -116,12 +132,13 @@ module Sass
116
132
  def build_tree
117
133
  root = Tree::Node.new(nil)
118
134
  whitespace
119
- directives root
120
- rules root
121
- expand_commas root
122
- nest_rules root
123
- flatten_rules root
124
- fold_commas root
135
+ directives root
136
+ rules root
137
+ expand_commas root
138
+ parent_ref_rules root
139
+ remove_parent_refs root
140
+ flatten_rules root
141
+ fold_commas root
125
142
  root
126
143
  end
127
144
 
@@ -164,12 +181,12 @@ module Sass
164
181
  whitespace
165
182
 
166
183
  assert_match /:/
167
-
184
+
168
185
  value = ''
169
186
  while @template.scan(/[^;\s\}]+/)
170
187
  value << @template[0] << whitespace
171
188
  end
172
-
189
+
173
190
  assert_match /(;|(?=\}))/
174
191
  rule << Tree::AttrNode.new(name, value, nil)
175
192
  end
@@ -191,7 +208,10 @@ module Sass
191
208
 
192
209
  def assert_match(re)
193
210
  if !@template.scan(re)
194
- raise Exception.new("Invalid CSS!")
211
+ line = @template.string[0..@template.pos].count "\n"
212
+ # Display basic regexps as plain old strings
213
+ expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
214
+ raise Exception.new("Invalid CSS on line #{line}: expected #{expected}")
195
215
  end
196
216
  whitespace
197
217
  end
@@ -224,7 +244,22 @@ module Sass
224
244
  root.children.flatten!
225
245
  end
226
246
 
227
- # Nest rules so that
247
+ # Make rules use parent refs so that
248
+ #
249
+ # foo
250
+ # color: green
251
+ # foo.bar
252
+ # color: blue
253
+ #
254
+ # becomes
255
+ #
256
+ # foo
257
+ # color: green
258
+ # &.bar
259
+ # color: blue
260
+ #
261
+ # This has the side effect of nesting rules,
262
+ # so that
228
263
  #
229
264
  # foo
230
265
  # color: green
@@ -237,29 +272,50 @@ module Sass
237
272
  #
238
273
  # foo
239
274
  # color: green
240
- # bar
275
+ # & bar
241
276
  # color: red
242
- # baz
277
+ # & baz
243
278
  # color: blue
244
- #
245
- def nest_rules(root)
279
+ #
280
+ def parent_ref_rules(root)
246
281
  rules = OrderedHash.new
247
282
  root.children.select { |c| Tree::RuleNode === c }.each do |child|
248
283
  root.children.delete child
249
- first, rest = child.rule.split(' ', 2)
284
+ first, rest = child.rule.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
250
285
  rules[first] ||= Tree::RuleNode.new(first, nil)
251
286
  if rest
252
- child.rule = rest
287
+ child.rule = "&" + rest
253
288
  rules[first] << child
254
289
  else
255
290
  rules[first].children += child.children
256
291
  end
257
292
  end
258
293
 
259
- rules.values.each { |v| nest_rules(v) }
294
+ rules.values.each { |v| parent_ref_rules(v) }
260
295
  root.children += rules.values
261
296
  end
262
297
 
298
+ # Remove useless parent refs so that
299
+ #
300
+ # foo
301
+ # & bar
302
+ # color: blue
303
+ #
304
+ # becomes
305
+ #
306
+ # foo
307
+ # bar
308
+ # color: blue
309
+ #
310
+ def remove_parent_refs(root)
311
+ root.children.each do |child|
312
+ if child.is_a?(Tree::RuleNode)
313
+ child.rule.gsub! /^& /, ''
314
+ remove_parent_refs child
315
+ end
316
+ end
317
+ end
318
+
263
319
  # Flatten rules so that
264
320
  #
265
321
  # foo
@@ -271,7 +327,18 @@ module Sass
271
327
  #
272
328
  # foo bar baz
273
329
  # color: red
274
- #
330
+ #
331
+ # and
332
+ #
333
+ # foo
334
+ # &.bar
335
+ # color: blue
336
+ #
337
+ # becomes
338
+ #
339
+ # foo.bar
340
+ # color: blue
341
+ #
275
342
  def flatten_rules(root)
276
343
  root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
277
344
  end
@@ -279,7 +346,13 @@ module Sass
279
346
  def flatten_rule(rule)
280
347
  while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
281
348
  child = rule.children.first
282
- rule.rule = "#{rule.rule} #{child.rule}"
349
+
350
+ if child.rule[0] == ?&
351
+ rule.rule = child.rule.gsub /^&/, rule.rule
352
+ else
353
+ rule.rule = "#{rule.rule} #{child.rule}"
354
+ end
355
+
283
356
  rule.children = child.children
284
357
  end
285
358