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
@@ -6,7 +6,6 @@ require 'sass/tree/attr_node'
6
6
  require 'sass/tree/directive_node'
7
7
  require 'sass/constant'
8
8
  require 'sass/error'
9
- require 'haml/util'
10
9
 
11
10
  module Sass
12
11
  # This is the class where all the parsing and processing of the Sass
@@ -39,10 +38,16 @@ module Sass
39
38
 
40
39
  # The character used to denote a compiler directive.
41
40
  DIRECTIVE_CHAR = ?@
42
-
41
+
43
42
  # Designates a non-parsed rule.
44
43
  ESCAPE_CHAR = ?\\
45
44
 
45
+ # Designates block as mixin definition rather than CSS rules to output
46
+ MIXIN_DEFINITION_CHAR = ?=
47
+
48
+ # Includes named mixin declared using MIXIN_DEFINITION_CHAR
49
+ MIXIN_INCLUDE_CHAR = ?+
50
+
46
51
  # The regex that matches and extracts data from
47
52
  # attributes of the form <tt>:name attr</tt>.
48
53
  ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/
@@ -56,14 +61,14 @@ module Sass
56
61
 
57
62
  # Creates a new instace of Sass::Engine that will compile the given
58
63
  # template string when <tt>render</tt> is called.
59
- # See README for available options.
64
+ # See README.rdoc for available options.
60
65
  #
61
66
  #--
62
67
  #
63
68
  # TODO: Add current options to REFRENCE. Remember :filename!
64
69
  #
65
70
  # When adding options, remember to add information about them
66
- # to README!
71
+ # to README.rdoc!
67
72
  #++
68
73
  #
69
74
  def initialize(template, options={})
@@ -71,9 +76,10 @@ module Sass
71
76
  :style => :nested,
72
77
  :load_paths => ['.']
73
78
  }.merge! options
74
- @template = template.split(/\n?\r|\r?\n/)
79
+ @template = template.split(/\r\n|\r|\n/)
75
80
  @lines = []
76
81
  @constants = {"important" => "!important"}
82
+ @mixins = {}
77
83
  end
78
84
 
79
85
  # Processes the template and returns the result as a string.
@@ -96,6 +102,10 @@ module Sass
96
102
  @constants
97
103
  end
98
104
 
105
+ def mixins
106
+ @mixins
107
+ end
108
+
99
109
  def render_to_tree
100
110
  split_lines
101
111
 
@@ -113,7 +123,7 @@ module Sass
113
123
  end
114
124
  end
115
125
  end
116
- @line = nil
126
+ @lines.clear
117
127
 
118
128
  root
119
129
  end
@@ -136,7 +146,7 @@ module Sass
136
146
 
137
147
  if tabs # if line isn't blank
138
148
  if tabs - old_tabs > 1
139
- raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
149
+ raise SyntaxError.new("#{tabs * 2} spaces were used for indentation. Sass must be indented using two spaces.", @line)
140
150
  end
141
151
  @lines << [line.strip, tabs]
142
152
 
@@ -151,18 +161,20 @@ module Sass
151
161
 
152
162
  # Counts the tabulation of a line.
153
163
  def count_tabs(line)
154
- spaces = line.index(/[^ ]/)
155
- if spaces
156
- if spaces % 2 == 1 || line[spaces] == ?\t
157
- # Make sure a line with just tabs isn't an error
158
- return nil if line.strip.empty?
159
-
160
- raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
161
- end
162
- spaces / 2
163
- else
164
- nil
164
+ return nil if line.strip.empty?
165
+ return nil unless spaces = line.index(/[^ ]/)
166
+
167
+ if spaces % 2 == 1
168
+ raise SyntaxError.new(<<END.strip, @line)
169
+ #{spaces} space#{spaces == 1 ? ' was' : 's were'} used for indentation. Sass must be indented using two spaces.
170
+ END
171
+ elsif line[spaces] == ?\t
172
+ raise SyntaxError.new(<<END.strip, @line)
173
+ A tab character was used for indentation. Sass must be indented using two spaces.
174
+ Are you sure you have soft tabs enabled in your editor?
175
+ END
165
176
  end
177
+ spaces / 2
166
178
  end
167
179
 
168
180
  def build_tree(index)
@@ -177,15 +189,23 @@ module Sass
177
189
  unless node.is_a? Tree::Node
178
190
  if has_children
179
191
  if node == :constant
180
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line)
192
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line + 1)
181
193
  elsif node.is_a? Array
182
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line)
194
+ # arrays can either be full of import statements
195
+ # or attributes from mixin includes
196
+ # in either case they shouldn't have children.
197
+ # Need to peek into the array in order to give meaningful errors
198
+ directive_type = (node.first.is_a?(Tree::DirectiveNode) ? "import" : "mixin")
199
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath #{directive_type} directives.", @line + 1)
183
200
  end
184
201
  end
185
202
 
203
+ index = @line if node == :mixin
186
204
  return node, index
187
205
  end
188
206
 
207
+ node.line = @line
208
+
189
209
  if node.is_a? Tree::CommentNode
190
210
  while has_children
191
211
  line, index = raw_next_line(index)
@@ -193,26 +213,52 @@ module Sass
193
213
 
194
214
  has_children = has_children?(index, tabs)
195
215
  end
196
- else
197
- while has_children
198
- child, index = build_tree(index)
199
216
 
200
- if child == :constant
201
- raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
202
- elsif child.is_a? Array
203
- raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
204
- elsif child.is_a? Tree::Node
205
- child.line = @line
206
- node << child
217
+ return node, index
218
+ end
219
+
220
+ # Resolve multiline rules
221
+ if node.is_a?(Tree::RuleNode)
222
+ if node.continued?
223
+ child, index = build_tree(index) if @lines[old_index = index]
224
+ if @lines[old_index].nil? || has_children?(old_index, tabs) || !child.is_a?(Tree::RuleNode)
225
+ raise SyntaxError.new("Rules can't end in commas.", @line)
207
226
  end
208
227
 
209
- has_children = has_children?(index, tabs)
228
+ node.add_rules child
210
229
  end
230
+ node.children = child.children if child
231
+ end
232
+
233
+ while has_children
234
+ child, index = build_tree(index)
235
+
236
+ validate_and_append_child(node, child)
237
+
238
+ has_children = has_children?(index, tabs)
211
239
  end
212
240
 
213
241
  return node, index
214
242
  end
215
243
 
244
+ def validate_and_append_child(parent, child)
245
+ case child
246
+ when :constant
247
+ raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
248
+ when :mixin
249
+ raise SyntaxError.new("Mixins may only be defined at the root of a document.", @line)
250
+ when Array
251
+ child.each do |c|
252
+ if c.is_a?(Tree::DirectiveNode)
253
+ raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
254
+ end
255
+ parent << c
256
+ end
257
+ when Tree::Node
258
+ parent << child
259
+ end
260
+ end
261
+
216
262
  def has_children?(index, tabs)
217
263
  next_line = ['//', 0]
218
264
  while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0
@@ -238,6 +284,10 @@ module Sass
238
284
  parse_directive(line)
239
285
  when ESCAPE_CHAR
240
286
  Tree::RuleNode.new(line[1..-1], @options[:style])
287
+ when MIXIN_DEFINITION_CHAR
288
+ parse_mixin_definition(line)
289
+ when MIXIN_INCLUDE_CHAR
290
+ parse_mixin_include(line)
241
291
  else
242
292
  if line =~ ATTRIBUTE_ALTERNATE_MATCHER
243
293
  parse_attribute(line, ATTRIBUTE_ALTERNATE)
@@ -259,7 +309,7 @@ module Sass
259
309
  name, eq, value = line.scan(attribute_regx)[0]
260
310
 
261
311
  if name.nil? || value.nil?
262
- raise SyntaxError.new("Invalid attribute: \"#{line}\"", @line)
312
+ raise SyntaxError.new("Invalid attribute: \"#{line}\".", @line)
263
313
  end
264
314
 
265
315
  if eq.strip[0] == SCRIPT_CHAR
@@ -270,11 +320,18 @@ module Sass
270
320
  end
271
321
 
272
322
  def parse_constant(line)
273
- name, value = line.scan(Sass::Constant::MATCH)[0]
323
+ name, op, value = line.scan(Sass::Constant::MATCH)[0]
274
324
  unless name && value
275
- raise SyntaxError.new("Invalid constant: \"#{line}\"", @line)
325
+ raise SyntaxError.new("Invalid constant: \"#{line}\".", @line)
326
+ end
327
+
328
+ constant = Sass::Constant.parse(value, @constants, @line)
329
+ if op == '||='
330
+ @constants[name] ||= constant
331
+ else
332
+ @constants[name] = constant
276
333
  end
277
- @constants[name] = Sass::Constant.parse(value, @constants, @line)
334
+
278
335
  :constant
279
336
  end
280
337
 
@@ -291,14 +348,36 @@ module Sass
291
348
  def parse_directive(line)
292
349
  directive, value = line[1..-1].split(/\s+/, 2)
293
350
 
294
- case directive
295
- when "import"
351
+ # If value begins with url( or ",
352
+ # it's a CSS @import rule and we don't want to touch it.
353
+ if directive == "import" && value !~ /^(url\(|")/
296
354
  import(value)
297
355
  else
298
356
  Tree::DirectiveNode.new(line, @options[:style])
299
357
  end
300
358
  end
301
359
 
360
+ def parse_mixin_definition(line)
361
+ mixin_name = line[1..-1]
362
+ @mixins[mixin_name] = []
363
+ index = @line
364
+ line, tabs = @lines[index]
365
+ while !line.nil? && tabs > 0
366
+ child, index = build_tree(index)
367
+ validate_and_append_child(@mixins[mixin_name], child)
368
+ line, tabs = @lines[index]
369
+ end
370
+ :mixin
371
+ end
372
+
373
+ def parse_mixin_include(line)
374
+ mixin_name = line[1..-1]
375
+ unless @mixins.has_key?(mixin_name)
376
+ raise SyntaxError.new("Undefined mixin '#{mixin_name}'.", @line)
377
+ end
378
+ @mixins[mixin_name]
379
+ end
380
+
302
381
  def import(files)
303
382
  nodes = []
304
383
 
@@ -312,7 +391,7 @@ module Sass
312
391
  end
313
392
 
314
393
  if filename =~ /\.css$/
315
- nodes << Tree::ValueNode.new("@import url(#{filename});", @options[:style])
394
+ nodes << Tree::DirectiveNode.new("@import url(#{filename})", @options[:style])
316
395
  else
317
396
  File.open(filename) do |file|
318
397
  new_options = @options.dup
@@ -321,6 +400,7 @@ module Sass
321
400
  end
322
401
 
323
402
  engine.constants.merge! @constants
403
+ engine.mixins.merge! @mixins
324
404
 
325
405
  begin
326
406
  root = engine.render_to_tree
@@ -333,6 +413,7 @@ module Sass
333
413
  nodes << child
334
414
  end
335
415
  @constants = engine.constants
416
+ @mixins = engine.mixins
336
417
  end
337
418
  end
338
419
 
@@ -354,7 +435,7 @@ module Sass
354
435
 
355
436
  if new_filename.nil?
356
437
  if was_sass
357
- raise Exception.new("File to import not found or unreadable: #{original_filename}")
438
+ raise Exception.new("File to import not found or unreadable: #{original_filename}.")
358
439
  else
359
440
  return filename + '.css'
360
441
  end
@@ -4,7 +4,7 @@ module Sass
4
4
  # and the Sass file that was being parsed (if applicable).
5
5
  # It also provides a handy way to rescue only exceptions raised
6
6
  # because of a faulty template.
7
- class SyntaxError < StandardError
7
+ class SyntaxError < Exception
8
8
  # The line of the Sass template on which the exception was thrown.
9
9
  attr_accessor :sass_line
10
10
 
@@ -21,7 +21,7 @@ module Sass
21
21
  @@checked_for_updates
22
22
  end
23
23
 
24
- # Gets various options for Sass. See README for details.
24
+ # Gets various options for Sass. See README.rdoc for details.
25
25
  #--
26
26
  # TODO: *DOCUMENT OPTIONS*
27
27
  #++
@@ -104,7 +104,18 @@ module Sass
104
104
  end
105
105
  end
106
106
  end
107
- "/*\n#{e_string}\n\nBacktrace:\n#{e.backtrace.join("\n")}\n*/"
107
+ <<END
108
+ /*
109
+ #{e_string}
110
+
111
+ Backtrace:\n#{e.backtrace.join("\n")}
112
+ */
113
+ body:before {
114
+ white-space: pre;
115
+ font-family: monospace;
116
+ content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
117
+ END
118
+ # Fix an emacs syntax-highlighting hiccup: '
108
119
  else
109
120
  "/* Internal stylesheet error */"
110
121
  end
@@ -119,7 +130,7 @@ module Sass
119
130
  end
120
131
 
121
132
  def forbid_update?(name)
122
- name[0] == ?_
133
+ name.sub(/^.*\//, '')[0] == ?_
123
134
  end
124
135
 
125
136
  def stylesheet_needs_update?(name)
@@ -15,9 +15,13 @@ unless defined?(Sass::MERB_LOADED)
15
15
  :always_check => env != "production",
16
16
  :full_exception => env != "production")
17
17
  config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {}
18
- config.symbolize_keys!
18
+
19
+ if defined? config.symbolize_keys!
20
+ config.symbolize_keys!
21
+ end
22
+
19
23
  Sass::Plugin.options.merge!(config)
20
-
24
+
21
25
  if version[0] > 0 || version[1] >= 9
22
26
 
23
27
  class Merb::Rack::Application # :nodoc:
@@ -3,21 +3,21 @@ require 'sass/tree/node'
3
3
  module Sass::Tree
4
4
  class AttrNode < ValueNode
5
5
  attr_accessor :name
6
-
6
+
7
7
  def initialize(name, value, style)
8
8
  @name = name
9
9
  super(value, style)
10
10
  end
11
-
11
+
12
12
  def to_s(tabs, parent_name = nil)
13
13
  if value[-1] == ?;
14
- raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!)", @line)
14
+ raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!).", @line)
15
15
  end
16
16
  real_name = name
17
17
  real_name = "#{parent_name}-#{real_name}" if parent_name
18
18
 
19
19
  if value.empty? && children.empty?
20
- raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump}", @line)
20
+ raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump}.", @line)
21
21
  end
22
22
 
23
23
  join_string = case @style
@@ -34,7 +34,7 @@ module Sass::Tree
34
34
  children.each do |kid|
35
35
  to_return << "#{kid.to_s(tabs, real_name)}" << join_string
36
36
  end
37
-
37
+
38
38
  (@style == :compressed && parent_name) ? to_return : to_return[0...-1]
39
39
  end
40
40
 
@@ -15,11 +15,6 @@ module Sass::Tree
15
15
  was_attr = false
16
16
  first = true
17
17
  children.each do |child|
18
- if child.is_a?(RuleNode) && child.continued?
19
- check_multiline_rule(child)
20
- continued_rule = true
21
- end
22
-
23
18
  if @style == :compact
24
19
  if child.is_a?(AttrNode)
25
20
  result << "#{child.to_s(first || was_attr ? 1 : tabs + 1)} "
@@ -32,12 +27,12 @@ module Sass::Tree
32
27
  result << rendered
33
28
  end
34
29
  was_attr = child.is_a?(AttrNode)
35
- first = continued_rule
30
+ first = false
36
31
  elsif @style == :compressed
37
32
  result << (was_attr ? ";#{child.to_s(1)}" : child.to_s(1))
38
33
  was_attr = child.is_a?(AttrNode)
39
34
  else
40
- result << child.to_s(tabs + 1) + (continued_rule ? '' : "\n")
35
+ result << child.to_s(tabs + 1) + "\n"
41
36
  end
42
37
  end
43
38
  result.rstrip + if @style == :compressed
@@ -16,15 +16,12 @@ module Sass
16
16
  end
17
17
  @children << child
18
18
  end
19
-
19
+
20
20
  def to_s
21
21
  result = String.new
22
22
  children.each do |child|
23
23
  if child.is_a? AttrNode
24
24
  raise SyntaxError.new('Attributes aren\'t allowed at the root of a document.', child.line)
25
- elsif child.is_a?(RuleNode) && child.continued?
26
- check_multiline_rule(child)
27
- result << child.to_s(1)
28
25
  else
29
26
  result << "#{child.to_s(1)}" + (@style == :compressed ? '' : "\n")
30
27
  end
@@ -32,14 +29,6 @@ module Sass
32
29
  @style == :compressed ? result+"\n" : result[0...-1]
33
30
  end
34
31
 
35
- protected
36
-
37
- def check_multiline_rule(rule)
38
- unless rule.children.empty?
39
- raise SyntaxError.new('Rules can\'t end in commas.', rule.line)
40
- end
41
- end
42
-
43
32
  private
44
33
 
45
34
  # This method should be overridden by subclasses to return an error message