haml 1.0.5 → 1.5.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 (92) hide show
  1. data/README +229 -0
  2. data/Rakefile +56 -60
  3. data/VERSION +1 -1
  4. data/bin/haml +4 -14
  5. data/bin/html2haml +89 -0
  6. data/bin/sass +8 -0
  7. data/init.rb +5 -1
  8. data/lib/haml.rb +643 -0
  9. data/lib/haml/buffer.rb +33 -30
  10. data/lib/haml/engine.rb +258 -75
  11. data/lib/haml/error.rb +43 -0
  12. data/lib/haml/exec.rb +181 -0
  13. data/lib/haml/filters.rb +89 -0
  14. data/lib/haml/helpers.rb +19 -5
  15. data/lib/haml/helpers/action_view_mods.rb +28 -4
  16. data/lib/haml/template.rb +13 -27
  17. data/lib/sass.rb +418 -0
  18. data/lib/sass/constant.rb +190 -0
  19. data/lib/sass/constant/color.rb +77 -0
  20. data/lib/sass/constant/literal.rb +51 -0
  21. data/lib/sass/constant/number.rb +87 -0
  22. data/lib/sass/constant/operation.rb +30 -0
  23. data/lib/sass/constant/string.rb +18 -0
  24. data/lib/sass/engine.rb +179 -0
  25. data/lib/sass/error.rb +35 -0
  26. data/lib/sass/plugin.rb +119 -0
  27. data/lib/sass/tree/attr_node.rb +44 -0
  28. data/lib/sass/tree/node.rb +29 -0
  29. data/lib/sass/tree/rule_node.rb +47 -0
  30. data/lib/sass/tree/value_node.rb +12 -0
  31. data/test/benchmark.rb +16 -19
  32. data/test/haml/engine_test.rb +220 -0
  33. data/test/{helper_test.rb → haml/helper_test.rb} +9 -8
  34. data/test/{mocks → haml/mocks}/article.rb +0 -0
  35. data/test/{results → haml/results}/content_for_layout.xhtml +0 -0
  36. data/test/{results → haml/results}/eval_suppressed.xhtml +0 -0
  37. data/test/haml/results/filters.xhtml +57 -0
  38. data/test/{results → haml/results}/helpers.xhtml +10 -0
  39. data/test/haml/results/helpful.xhtml +8 -0
  40. data/test/{results → haml/results}/just_stuff.xhtml +5 -0
  41. data/test/{results → haml/results}/list.xhtml +0 -0
  42. data/test/{results → haml/results}/original_engine.xhtml +1 -1
  43. data/test/{results → haml/results}/partials.xhtml +0 -0
  44. data/test/{results → haml/results}/silent_script.xhtml +0 -0
  45. data/test/{results → haml/results}/standard.xhtml +2 -1
  46. data/test/{results → haml/results}/tag_parsing.xhtml +0 -0
  47. data/test/{results → haml/results}/very_basic.xhtml +0 -0
  48. data/test/haml/results/whitespace_handling.xhtml +104 -0
  49. data/test/{rhtml → haml/rhtml}/standard.rhtml +4 -1
  50. data/test/{runner.rb → haml/runner.rb} +1 -1
  51. data/test/{template_test.rb → haml/template_test.rb} +28 -23
  52. data/test/{templates → haml/templates}/_partial.haml +0 -0
  53. data/test/{templates → haml/templates}/_text_area.haml +0 -0
  54. data/test/haml/templates/breakage.haml +8 -0
  55. data/test/{templates → haml/templates}/content_for_layout.haml +0 -0
  56. data/test/{templates → haml/templates}/eval_suppressed.haml +0 -0
  57. data/test/haml/templates/filters.haml +53 -0
  58. data/test/{templates → haml/templates}/helpers.haml +10 -1
  59. data/test/{templates → haml/templates}/helpful.haml +3 -0
  60. data/test/{templates → haml/templates}/just_stuff.haml +7 -0
  61. data/test/{templates → haml/templates}/list.haml +0 -0
  62. data/test/haml/templates/original_engine.haml +17 -0
  63. data/test/{templates → haml/templates}/partialize.haml +0 -0
  64. data/test/{templates → haml/templates}/partials.haml +0 -0
  65. data/test/{templates → haml/templates}/silent_script.haml +0 -0
  66. data/test/{templates → haml/templates}/standard.haml +3 -1
  67. data/test/{templates → haml/templates}/tag_parsing.haml +0 -0
  68. data/test/{templates → haml/templates}/very_basic.haml +0 -0
  69. data/test/haml/templates/whitespace_handling.haml +137 -0
  70. data/test/profile.rb +36 -18
  71. data/test/sass/engine_test.rb +87 -0
  72. data/test/sass/plugin_test.rb +103 -0
  73. data/test/sass/results/basic.css +9 -0
  74. data/test/sass/results/compact.css +5 -0
  75. data/test/sass/results/complex.css +86 -0
  76. data/test/sass/results/constants.css +12 -0
  77. data/test/sass/results/expanded.css +18 -0
  78. data/test/sass/results/nested.css +14 -0
  79. data/test/sass/templates/basic.sass +23 -0
  80. data/test/sass/templates/bork.sass +2 -0
  81. data/test/sass/templates/compact.sass +15 -0
  82. data/test/sass/templates/complex.sass +291 -0
  83. data/test/sass/templates/constants.sass +80 -0
  84. data/test/sass/templates/expanded.sass +15 -0
  85. data/test/sass/templates/nested.sass +15 -0
  86. metadata +98 -48
  87. data/REFERENCE +0 -662
  88. data/test/engine_test.rb +0 -93
  89. data/test/results/helpful.xhtml +0 -5
  90. data/test/results/whitespace_handling.xhtml +0 -51
  91. data/test/templates/original_engine.haml +0 -17
  92. data/test/templates/whitespace_handling.haml +0 -66
@@ -52,7 +52,7 @@ module Haml
52
52
  # instance_eval.
53
53
  def push_script(result, tabulation, flattened)
54
54
  if flattened
55
- result = find_and_flatten(result)
55
+ result = Haml::Helpers.find_and_preserve(result)
56
56
  end
57
57
  unless result.nil?
58
58
  result = result.to_s
@@ -71,8 +71,8 @@ module Haml
71
71
  # element, formats it, and adds it to the buffer.
72
72
  def open_tag(name, tabulation, atomic, try_one_line, class_id, attributes_hash, obj_ref, flattened)
73
73
  attributes = {}
74
- attributes.merge!(parse_object_ref(obj_ref)) if obj_ref
75
74
  attributes.merge!(parse_class_and_id(class_id)) unless class_id.nil? || class_id.empty?
75
+ attributes.merge!(parse_object_ref(obj_ref, attributes[:id], attributes[:class])) if obj_ref
76
76
  attributes.merge!(attributes_hash) if attributes_hash
77
77
 
78
78
  @one_liner_pending = false
@@ -157,20 +157,30 @@ module Haml
157
157
 
158
158
  # Takes an array of objects and uses the class and id of the first
159
159
  # one to create an attributes hash.
160
- def parse_object_ref(ref)
160
+ def parse_object_ref(ref, old_id, old_class)
161
161
  ref = ref[0]
162
162
  # Let's make sure the value isn't nil. If it is, return the default Hash.
163
163
  return {} if ref.nil?
164
164
  class_name = ref.class.to_s.underscore
165
- {:id => "#{class_name}_#{ref.id}", :class => class_name}
165
+ id = "#{class_name}_#{ref.id}"
166
+
167
+ if old_class
168
+ class_name += " #{old_class}"
169
+ end
170
+
171
+ if old_id
172
+ id = "#{old_id}_#{id}"
173
+ end
174
+
175
+ {:id => id, :class => class_name}
166
176
  end
167
177
 
168
178
  # Takes a hash and builds a list of XHTML attributes from it, returning
169
179
  # the result.
170
180
  def build_attributes(attributes = {})
171
181
  result = attributes.collect do |a,v|
172
- unless v.nil?
173
- v = v.to_s
182
+ v = v.to_s
183
+ unless v.nil? || v.empty?
174
184
  attr_wrapper = @options[:attr_wrapper]
175
185
  if v.include? attr_wrapper
176
186
  if v.include? @other_quote_char
@@ -190,35 +200,28 @@ module Haml
190
200
  def one_liner?(value)
191
201
  value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
192
202
  end
193
-
194
- # Isolates the whitespace-sensitive tags in the string and uses Haml::Helpers#flatten
195
- # to convert any endlines inside them into html entities.
196
- def find_and_flatten(input)
197
- input = input.to_s
198
- input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im) do |tag, contents|
199
- input = input.gsub(contents, Haml::Helpers.flatten(contents))
200
- end
201
- input
202
- end
203
203
  end
204
204
  end
205
205
 
206
- class String # :nodoc
207
- alias_method :old_comp, :<=>
208
- def <=>(other)
209
- if other.is_a? NilClass
210
- -1
211
- else
212
- old_comp(other)
206
+ unless String.methods.include? 'old_comp'
207
+ class String # :nodoc
208
+ alias_method :old_comp, :<=>
209
+
210
+ def <=>(other)
211
+ if other.is_a? NilClass
212
+ -1
213
+ else
214
+ old_comp(other)
215
+ end
213
216
  end
214
217
  end
215
- end
216
-
217
- class NilClass # :nodoc:
218
- include Comparable
219
-
220
- def <=>(other)
221
- other.nil? ? 0 : 1
218
+
219
+ class NilClass # :nodoc:
220
+ include Comparable
221
+
222
+ def <=>(other)
223
+ other.nil? ? 0 : 1
224
+ end
222
225
  end
223
226
  end
224
227
 
@@ -1,12 +1,14 @@
1
- require File.dirname(__FILE__) + '/helpers'
2
- require File.dirname(__FILE__) + '/buffer'
1
+ require 'haml/helpers'
2
+ require 'haml/buffer'
3
+ require 'haml/filters'
4
+ require 'haml/error'
3
5
 
4
6
  module Haml
5
- # This is the class where all the parsing and processing of the HAML
7
+ # This is the class where all the parsing and processing of the Haml
6
8
  # template is done. It can be directly used by the user by creating a
7
- # new instance and calling to_html to render the template. For example:
9
+ # new instance and calling <tt>to_html</tt> to render the template. For example:
8
10
  #
9
- # template = File.load('templates/really_cool_template.haml')
11
+ # template = File.read('templates/really_cool_template.haml')
10
12
  # haml_engine = Haml::Engine.new(template)
11
13
  # output = haml_engine.to_html
12
14
  # puts output
@@ -18,34 +20,37 @@ module Haml
18
20
  attr :options, true
19
21
 
20
22
  # Designates an XHTML/XML element.
21
- ELEMENT = '%'[0]
23
+ ELEMENT = ?%
22
24
 
23
25
  # Designates a <tt><div></tt> element with the given class.
24
- DIV_CLASS = '.'[0]
26
+ DIV_CLASS = ?.
25
27
 
26
28
  # Designates a <tt><div></tt> element with the given id.
27
- DIV_ID = '#'[0]
29
+ DIV_ID = ?#
28
30
 
29
31
  # Designates an XHTML/XML comment.
30
- COMMENT = '/'[0]
32
+ COMMENT = ?/
31
33
 
32
34
  # Designates an XHTML doctype.
33
- DOCTYPE = '!'[0]
35
+ DOCTYPE = ?!
34
36
 
35
37
  # Designates script, the result of which is output.
36
- SCRIPT = '='[0]
38
+ SCRIPT = ?=
37
39
 
38
40
  # Designates script, the result of which is flattened and output.
39
- FLAT_SCRIPT = '~'[0]
41
+ FLAT_SCRIPT = ?~
40
42
 
41
43
  # Designates script which is run but not output.
42
- SILENT_SCRIPT = '-'[0]
44
+ SILENT_SCRIPT = ?-
43
45
 
44
46
  # When following SILENT_SCRIPT, designates a comment that is not output.
45
- SILENT_COMMENT = '#'[0]
47
+ SILENT_COMMENT = ?#
46
48
 
47
49
  # Designates a non-parsed line.
48
- ESCAPE = '\\'[0]
50
+ ESCAPE = ?\\
51
+
52
+ # Designates a block of filtered text.
53
+ FILTER = ?:
49
54
 
50
55
  # Designates a non-parsed line. Not actually a character.
51
56
  PLAIN_TEXT = -1
@@ -61,16 +66,17 @@ module Haml
61
66
  SCRIPT,
62
67
  FLAT_SCRIPT,
63
68
  SILENT_SCRIPT,
64
- ESCAPE
69
+ ESCAPE,
70
+ FILTER
65
71
  ]
66
72
 
67
73
  # The value of the character that designates that a line is part
68
74
  # of a multiline string.
69
- MULTILINE_CHAR_VALUE = '|'[0]
75
+ MULTILINE_CHAR_VALUE = ?|
70
76
 
71
77
  # Characters that designate that a multiline string may be about
72
78
  # to begin.
73
- MULTILINE_STARTERS = SPECIAL_CHARACTERS - ["/"[0]]
79
+ MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/]
74
80
 
75
81
  # Keywords that appear in the middle of a Ruby block with lowered
76
82
  # indentation. If a block has been started using indentation,
@@ -86,39 +92,87 @@ module Haml
86
92
  # is a member of this array.
87
93
  MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
88
94
 
95
+ # The Regex that matches an HTML comment command.
96
+ COMMENT_REGEX = /\/(\[[a-zA-Z0-9 \.]*\])?(.*)/
97
+
98
+ # The Regex that matches a Doctype command.
99
+ DOCTYPE_REGEX = /([0-9]\.[0-9])?[\s]*([a-zA-Z]*)/
100
+
101
+ # The Regex that matches an HTML tag command.
102
+ TAG_REGEX = /[%]([-:_a-zA-Z0-9]+)([-_a-zA-Z0-9\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/
103
+
104
+ FLAT_WARNING = <<END
105
+ Haml deprecation warning:
106
+ The ~ command is deprecated and will be removed in future Haml versions.
107
+ Use the :preserve filter, the preserve helper, or the find_and_preserve
108
+ helper instead.
109
+ END
110
+
89
111
  # Creates a new instace of Haml::Engine that will compile the given
90
112
  # template string when <tt>to_html</tt> is called.
91
- # See REFERENCE for available options.
113
+ # See README for available options.
92
114
  #
93
115
  #--
94
116
  # When adding options, remember to add information about them
95
- # to REFERENCE!
117
+ # to README!
96
118
  #++
97
119
  #
98
120
  def initialize(template, options = {})
99
121
  @options = {
100
122
  :suppress_eval => false,
101
123
  :attr_wrapper => "'",
102
- :locals => {}
103
- }.merge options
124
+ :locals => {},
125
+ :filters => {
126
+ 'sass' => Sass::Engine,
127
+ 'plain' => Haml::Filters::Plain,
128
+ 'preserve' => Haml::Filters::Preserve
129
+ }
130
+ }
131
+
132
+ unless @options[:suppress_eval]
133
+ @options[:filters].merge!({
134
+ 'erb' => ERB,
135
+ 'ruby' => Haml::Filters::Ruby
136
+ })
137
+ end
138
+
139
+ if !NOT_LOADED.include? 'redcloth'
140
+ @options[:filters].merge!({
141
+ 'redcloth' => RedCloth,
142
+ 'textile' => Haml::Filters::Textile,
143
+ 'markdown' => Haml::Filters::Markdown
144
+ })
145
+ end
146
+ if !NOT_LOADED.include? 'bluecloth'
147
+ @options[:filters]['markdown'] = Haml::Filters::Markdown
148
+ end
149
+
150
+ @options.rec_merge! options
151
+
104
152
  @precompiled = @options[:precompiled]
105
153
 
106
154
  @template = template.strip #String
107
155
  @to_close_stack = []
108
156
  @output_tabs = 0
109
157
  @template_tabs = 0
158
+ @index = 0
110
159
 
111
160
  # This is the base tabulation of the currently active
112
161
  # flattened block. -1 signifies that there is no such block.
113
162
  @flat_spaces = -1
114
163
 
115
- # Only do the first round of pre-compiling if we really need to.
116
- # They might be passing in the precompiled string.
117
- do_precompile if @precompiled.nil? && (@precompiled = String.new)
164
+ begin
165
+ # Only do the first round of pre-compiling if we really need to.
166
+ # They might be passing in the precompiled string.
167
+ do_precompile if @precompiled.nil? && (@precompiled = String.new)
168
+ rescue Haml::Error => e
169
+ e.add_backtrace_entry(@index, @options[:filename])
170
+ raise e
171
+ end
118
172
  end
119
173
 
120
174
  # Processes the template and returns the result as a string.
121
- def to_html(scope = Object.new, &block)
175
+ def render(scope = Object.new, &block)
122
176
  @scope_object = scope
123
177
  @buffer = Haml::Buffer.new(@options)
124
178
 
@@ -139,6 +193,8 @@ module Haml
139
193
  @buffer.buffer
140
194
  end
141
195
 
196
+ alias_method :to_html, :render
197
+
142
198
  private
143
199
 
144
200
  #Precompile each line
@@ -153,26 +209,38 @@ module Haml
153
209
  old_index = nil
154
210
  old_spaces = nil
155
211
  old_tabs = nil
156
- (@template + "\n-#").each_with_index do |line, index|
212
+ old_uline = nil
213
+ (@template + "\n-#\n-#").each_with_index do |line, index|
157
214
  spaces, tabs = count_soft_tabs(line)
158
- line = line.strip
215
+ uline = line.lstrip[0...-1]
216
+ line = uline.rstrip
159
217
 
160
218
  if !line.empty?
161
219
  if old_line
162
220
  block_opened = tabs > old_tabs && !line.empty?
163
221
 
164
- suppress_render = handle_multiline(old_tabs, old_line, old_index)
222
+ suppress_render = handle_multiline(old_tabs, old_line, old_index) unless @flat_spaces != -1
165
223
 
166
224
  if !suppress_render
167
225
  line_empty = old_line.empty?
226
+
168
227
  process_indent(old_tabs, old_line) unless line_empty
169
228
  flat = @flat_spaces != -1
170
229
 
230
+
231
+ if !flat && old_spaces != old_tabs * 2
232
+ raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
233
+ end
234
+
171
235
  if flat
172
- push_flat(old_line, old_spaces)
236
+ push_flat(old_uline, old_spaces)
173
237
  elsif !line_empty
174
238
  process_line(old_line, old_index, block_opened)
175
239
  end
240
+
241
+ if @flat_spaces == -1 && tabs - old_tabs > 1
242
+ raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.")
243
+ end
176
244
  end
177
245
  end
178
246
 
@@ -180,10 +248,16 @@ module Haml
180
248
  old_index = index
181
249
  old_spaces = spaces
182
250
  old_tabs = tabs
251
+ old_uline = uline
183
252
  elsif @flat_spaces != -1
184
- push_flat(old_line, old_spaces)
185
- old_line = ''
186
- old_spaces = 0
253
+ process_indent(old_tabs, old_line) unless old_line.empty?
254
+
255
+ if @flat_spaces != -1
256
+ push_flat(old_line, old_spaces)
257
+ old_line = ''
258
+ old_uline = ''
259
+ old_spaces = 0
260
+ end
187
261
  end
188
262
  end
189
263
 
@@ -207,40 +281,52 @@ module Haml
207
281
  end
208
282
  end
209
283
 
210
- # Processes a single line of HAML.
284
+ # Processes a single line of Haml.
211
285
  #
212
286
  # This method doesn't return anything; it simply processes the line and
213
287
  # adds the appropriate code to <tt>@precompiled</tt>.
214
288
  def process_line(line, index, block_opened)
289
+ @index = index + 1
290
+ @block_opened = block_opened
291
+
215
292
  case line[0]
216
293
  when DIV_CLASS, DIV_ID
217
- render_div(line, index)
294
+ render_div(line)
218
295
  when ELEMENT
219
- render_tag(line, index)
296
+ render_tag(line)
220
297
  when COMMENT
221
298
  render_comment(line)
222
299
  when SCRIPT
223
- push_script(line[1..-1], false, block_opened, index)
300
+ sub_line = line[1..-1]
301
+ if sub_line[0] == SCRIPT
302
+ push_script(sub_line[1..-1].strip.dump.gsub('\\#', '#'), false)
303
+ else
304
+ push_script(sub_line, false)
305
+ end
224
306
  when FLAT_SCRIPT
225
- push_flat_script(line[1..-1], block_opened, index)
307
+ warn(FLAT_WARNING) unless defined?(Test::Unit)
308
+ push_flat_script(line[1..-1])
226
309
  when SILENT_SCRIPT
227
310
  sub_line = line[1..-1]
228
311
  unless sub_line[0] == SILENT_COMMENT
229
- push_silent(sub_line, index)
230
- if block_opened && !mid_block_keyword?(line)
312
+ push_silent(sub_line, true)
313
+ if @block_opened && !mid_block_keyword?(line)
231
314
  push_and_tabulate([:script])
232
315
  end
233
316
  end
317
+ when FILTER
318
+ name = line[1..-1].downcase
319
+ start_filtered(options[:filters][name] || name)
234
320
  when DOCTYPE
235
321
  if line[0...3] == '!!!'
236
322
  render_doctype(line)
237
323
  else
238
- push_text line
324
+ push_plain line
239
325
  end
240
326
  when ESCAPE
241
- push_text line[1..-1]
327
+ push_plain line[1..-1]
242
328
  else
243
- push_text line
329
+ push_plain line
244
330
  end
245
331
  end
246
332
 
@@ -307,20 +393,15 @@ module Haml
307
393
  @scope_object.instance_eval @precompiled
308
394
  @scope_object._haml_render &block
309
395
  rescue Exception => e
396
+ class << e
397
+ include Haml::Error
398
+ end
399
+
400
+ lineno = @scope_object.haml_lineno
401
+
310
402
  # Get information from the exception and format it so that
311
403
  # Rails can understand it.
312
404
  compile_error = e.message.scan(/\(eval\):([0-9]*):in `[-_a-zA-Z]*': compile error/)[0]
313
- filename = "(haml)"
314
- if @scope_object.methods.include? "haml_filename"
315
- # For some reason that I can't figure out,
316
- # @scope_object.methods.include? "haml_filename" && @scope_object.haml_filename
317
- # is false when it shouldn't be. Nested if statements work, though.
318
-
319
- if @scope_object.haml_filename
320
- filename = "#{@scope_object.haml_filename}.haml"
321
- end
322
- end
323
- lineno = @scope_object.haml_lineno
324
405
 
325
406
  if compile_error
326
407
  eval_line = compile_error[0].to_i
@@ -328,7 +409,7 @@ module Haml
328
409
  lineno = line_marker.scan(/[0-9]+/)[0].to_i if line_marker
329
410
  end
330
411
 
331
- e.backtrace.unshift "#{filename}:#{lineno}"
412
+ e.add_backtrace_entry(lineno, @options[:filename])
332
413
  raise e
333
414
  end
334
415
 
@@ -340,9 +421,9 @@ module Haml
340
421
 
341
422
  # Evaluates <tt>text</tt> in the context of <tt>@scope_object</tt>, but
342
423
  # does not output the result.
343
- def push_silent(text, index = nil)
344
- if index
345
- @precompiled << "@haml_lineno = #{index + 1}\n#{text}\n"
424
+ def push_silent(text, add_index = false)
425
+ if add_index
426
+ @precompiled << "@haml_lineno = #{@index}\n#{text}\n"
346
427
  else
347
428
  # Not really DRY, but probably faster
348
429
  @precompiled << "#{text}\n"
@@ -355,10 +436,24 @@ module Haml
355
436
  @precompiled << "_hamlout.push_text(#{text.dump}, #{@output_tabs})\n"
356
437
  end
357
438
 
439
+ # Renders a block of text as plain text.
440
+ # Also checks for an illegally opened block.
441
+ def push_plain(text)
442
+ if @block_opened
443
+ raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.")
444
+ end
445
+ push_text text
446
+ end
447
+
358
448
  # Adds +text+ to <tt>@buffer</tt> while flattening text.
359
449
  def push_flat(text, spaces)
360
450
  tabulation = spaces - @flat_spaces
361
- @precompiled << "_hamlout.push_text(#{text.dump}, #{tabulation > -1 ? tabulation : 0}, true)\n"
451
+ tabulation = tabulation > -1 ? tabulation : 0
452
+ if @filter_buffer
453
+ @filter_buffer << "#{' ' * tabulation}#{text}\n"
454
+ else
455
+ @precompiled << "_hamlout.push_text(#{text.dump}, #{tabulation}, true)\n"
456
+ end
362
457
  end
363
458
 
364
459
  # Causes <tt>text</tt> to be evaluated in the context of
@@ -366,11 +461,11 @@ module Haml
366
461
  #
367
462
  # If <tt>flattened</tt> is true, Haml::Helpers#find_and_flatten is run on
368
463
  # the result before it is added to <tt>@buffer</tt>
369
- def push_script(text, flattened, block_opened, index)
464
+ def push_script(text, flattened)
370
465
  unless options[:suppress_eval]
371
- push_silent("haml_temp = #{text}", index)
466
+ push_silent("haml_temp = #{text}", true)
372
467
  out = "haml_temp = _hamlout.push_script(haml_temp, #{@output_tabs}, #{flattened})\n"
373
- if block_opened
468
+ if @block_opened
374
469
  push_and_tabulate([:loud, out])
375
470
  else
376
471
  @precompiled << out
@@ -380,10 +475,13 @@ module Haml
380
475
 
381
476
  # Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
382
477
  # to be run on it afterwards.
383
- def push_flat_script(text, block_opened, index)
478
+ def push_flat_script(text)
384
479
  unless text.empty?
385
- push_script(text, true, block_opened, index)
480
+ push_script(text, true)
386
481
  else
482
+ unless @block_opened
483
+ raise SyntaxError.new('Filters must have nested text.')
484
+ end
387
485
  start_flat(false)
388
486
  end
389
487
  end
@@ -402,6 +500,8 @@ module Haml
402
500
  close_flat value
403
501
  when :loud
404
502
  close_loud value
503
+ when :filtered
504
+ close_filtered value
405
505
  end
406
506
  end
407
507
 
@@ -439,15 +539,40 @@ module Haml
439
539
 
440
540
  # Closes a loud Ruby block.
441
541
  def close_loud(command)
442
- push_silent "end"
542
+ push_silent 'end'
443
543
  @precompiled << command
444
544
  @template_tabs -= 1
445
545
  end
446
546
 
547
+ # Closes a filtered block.
548
+ def close_filtered(filter)
549
+ @flat_spaces = -1
550
+ if filter.is_a? String
551
+ if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
552
+ raise HamlError.new("You must have the RedCloth gem installed to use #{filter}")
553
+ else
554
+ raise HamlError.new("Filter \"#{filter}\" is not defined!")
555
+ end
556
+ else
557
+ filtered = filter.new(@filter_buffer).render
558
+
559
+ unless filter == Haml::Filters::Preserve
560
+ push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
561
+ else
562
+ push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\"\n")
563
+ end
564
+ end
565
+
566
+ @filter_buffer = nil
567
+ @template_tabs -= 1
568
+ end
569
+
447
570
  # Parses a line that will render as an XHTML tag, and adds the code that will
448
571
  # render that tag to <tt>@precompiled</tt>.
449
- def render_tag(line, index)
450
- line.scan(/[%]([-:_a-zA-Z0-9]+)([-_a-zA-Z0-9\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/) do |tag_name, attributes, attributes_hash, object_ref, action, value|
572
+ def render_tag(line)
573
+ matched = false
574
+ line.scan(TAG_REGEX) do |tag_name, attributes, attributes_hash, object_ref, action, value|
575
+ matched = true
451
576
  value = value.to_s
452
577
 
453
578
  case action
@@ -460,11 +585,24 @@ module Haml
460
585
  end
461
586
 
462
587
  flattened = (action == '~')
588
+
589
+ warn(FLAT_WARNING) if flattened && !defined?(Test::Unit)
590
+
463
591
  value_exists = !value.empty?
464
592
  attributes_hash = "nil" unless attributes_hash
465
593
  object_ref = "nil" unless object_ref
466
594
 
467
- push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{attributes_hash}, #{object_ref}, #{flattened.inspect})"
595
+ if @block_opened
596
+ if atomic
597
+ raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.")
598
+ elsif action == '=' || value_exists
599
+ raise SyntaxError.new("Illegal Nesting: Nesting within a tag that already has content is illegal.")
600
+ end
601
+ elsif parse && !value_exists
602
+ raise SyntaxError.new("No tag content to parse.")
603
+ end
604
+
605
+ push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{attributes_hash}, #{object_ref}, #{flattened.inspect})", true
468
606
 
469
607
  unless atomic
470
608
  push_and_tabulate([:element, tag_name])
@@ -472,7 +610,7 @@ module Haml
472
610
 
473
611
  if value_exists
474
612
  if parse
475
- push_script(value, flattened, false, index)
613
+ push_script(value, flattened)
476
614
  else
477
615
  push_text(value)
478
616
  end
@@ -482,18 +620,27 @@ module Haml
482
620
  end
483
621
  end
484
622
  end
623
+
624
+ unless matched
625
+ raise SyntaxError.new("Invalid tag: \"#{line}\"")
626
+ end
485
627
  end
486
628
 
487
629
  # Renders a line that creates an XHTML tag and has an implicit div because of
488
630
  # <tt>.</tt> or <tt>#</tt>.
489
- def render_div(line, index)
490
- render_tag('%div' + line, index)
631
+ def render_div(line)
632
+ render_tag('%div' + line)
491
633
  end
492
634
 
493
635
  # Renders an XHTML comment.
494
636
  def render_comment(line)
495
- conditional, content = line.scan(/\/(\[[a-zA-Z0-9 \.]*\])?(.*)/)[0]
496
- content = content.strip
637
+ conditional, content = line.scan(COMMENT_REGEX)[0]
638
+ content.strip!
639
+
640
+ if @block_opened && !content.empty?
641
+ raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.')
642
+ end
643
+
497
644
  try_one_line = !content.empty?
498
645
  push_silent "_hamlout.open_comment(#{try_one_line}, #{conditional.inspect}, #{@output_tabs})"
499
646
  @output_tabs += 1
@@ -506,13 +653,16 @@ module Haml
506
653
 
507
654
  # Renders an XHTML doctype or XML shebang.
508
655
  def render_doctype(line)
656
+ if @block_opened
657
+ raise SyntaxError.new("Illegal Nesting: Nesting within a header command is illegal.")
658
+ end
509
659
  line = line[3..-1].lstrip.downcase
510
660
  if line[0...3] == "xml"
511
661
  encoding = line.split[1] || "utf-8"
512
662
  wrapper = @options[:attr_wrapper]
513
663
  doctype = "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{encoding}#{wrapper} ?>"
514
664
  else
515
- version, type = line.scan(/([0-9]\.[0-9])?[\s]*([a-zA-Z]*)/)[0]
665
+ version, type = line.scan(DOCTYPE_REGEX)[0]
516
666
  if version == "1.1"
517
667
  doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
518
668
  else
@@ -541,10 +691,23 @@ module Haml
541
691
  @flat_spaces = @template_tabs * 2
542
692
  end
543
693
 
694
+ # Starts a filtered block.
695
+ def start_filtered(filter)
696
+ unless @block_opened
697
+ raise SyntaxError.new('Filters must have nested text.')
698
+ end
699
+ push_and_tabulate([:filtered, filter])
700
+ @flat_spaces = @template_tabs * 2
701
+ @filter_buffer = String.new
702
+ end
703
+
544
704
  # Counts the tabulation of a line.
545
705
  def count_soft_tabs(line)
546
706
  spaces = line.index(/[^ ]/)
547
- spaces ? [spaces, spaces/2] : []
707
+ if line[spaces] == ?\t
708
+ raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
709
+ end
710
+ [spaces, spaces/2]
548
711
  end
549
712
 
550
713
  # Pushes value onto <tt>@to_close_stack</tt> and increases
@@ -555,3 +718,23 @@ module Haml
555
718
  end
556
719
  end
557
720
  end
721
+
722
+ class Hash # :nodoc:
723
+ # Same as Hash#merge!, but recursively merges sub-hashes.
724
+ def rec_merge!(other)
725
+ other.each do |key, value|
726
+ myval = self[key]
727
+ if value.is_a?(Hash) && myval.is_a?(Hash)
728
+ myval.rec_merge!(value)
729
+ else
730
+ self[key] = value
731
+ end
732
+ end
733
+ self
734
+ end
735
+
736
+ def rec_merge(other)
737
+ toret = self.clone
738
+ toret.rec_merge! other
739
+ end
740
+ end