haml 3.1.0.alpha.26 → 3.1.0.alpha.27

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.

@@ -1 +1 @@
1
- 3.1.0.alpha.26
1
+ 3.1.0.alpha.27
@@ -0,0 +1 @@
1
+ (release)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0.alpha.26
1
+ 3.1.0.alpha.27
@@ -209,7 +209,7 @@ RUBY
209
209
  end) ? "" : "\n")
210
210
  end
211
211
 
212
- attributes = Precompiler.build_attributes(
212
+ attributes = Compiler.build_attributes(
213
213
  html?, @options[:attr_wrapper], @options[:escape_attrs], attributes)
214
214
  @buffer << "#{nuke_outer_whitespace || @options[:ugly] ? '' : tabs(tabulation)}<#{name}#{attributes}#{str}"
215
215
 
@@ -246,14 +246,14 @@ RUBY
246
246
  # @param from [{String => #to_s}] The attribute hash to merge from
247
247
  # @return [{String => String}] `to`, after being merged
248
248
  def self.merge_attrs(to, from)
249
- from['id'] = Precompiler.filter_and_join(from['id'], '_') if from['id']
249
+ from['id'] = Compiler.filter_and_join(from['id'], '_') if from['id']
250
250
  if to['id'] && from['id']
251
251
  to['id'] << '_' << from.delete('id').to_s
252
252
  elsif to['id'] || from['id']
253
253
  from['id'] ||= to['id']
254
254
  end
255
255
 
256
- from['class'] = Precompiler.filter_and_join(from['class'], ' ') if from['class']
256
+ from['class'] = Compiler.filter_and_join(from['class'], ' ') if from['class']
257
257
  if to['class'] && from['class']
258
258
  # Make sure we don't duplicate class names
259
259
  from['class'] = (from['class'].to_s.split(' ') | to['class'].split(' ')).sort.join(' ')
@@ -0,0 +1,432 @@
1
+ module Haml
2
+ module Compiler
3
+ include Haml::Util
4
+
5
+ private
6
+
7
+ # Returns the precompiled string with the preamble and postamble
8
+ def precompiled_with_ambles(local_names)
9
+ preamble = <<END.gsub("\n", ";")
10
+ begin
11
+ extend Haml::Helpers
12
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
13
+ _erbout = _hamlout.buffer
14
+ __in_erb_template = true
15
+ END
16
+ postamble = <<END.gsub("\n", ";")
17
+ #{precompiled_method_return_value}
18
+ ensure
19
+ @haml_buffer = @haml_buffer.upper
20
+ end
21
+ END
22
+ preamble + locals_code(local_names) + precompiled + postamble
23
+ end
24
+
25
+ # Returns the string used as the return value of the precompiled method.
26
+ # This method exists so it can be monkeypatched to return modified values.
27
+ def precompiled_method_return_value
28
+ "_erbout"
29
+ end
30
+
31
+ def locals_code(names)
32
+ names = names.keys if Hash == names
33
+
34
+ names.map do |name|
35
+ # Can't use || because someone might explicitly pass in false with a symbol
36
+ sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
37
+ str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
38
+ "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
39
+ end.join(';') + ';'
40
+ end
41
+
42
+ def compile_root
43
+ @dont_indent_next_line = @dont_tab_up_next_text = false
44
+ @output_line = 1
45
+ @indentation = nil
46
+ yield
47
+ flush_merged_text
48
+ end
49
+
50
+ def compile_plain
51
+ push_text @node.value[:text]
52
+ end
53
+
54
+ def compile_script(&block)
55
+ push_script(@node.value[:text],
56
+ :preserve_script => @node.value[:preserve],
57
+ :escape_html => @node.value[:escape_html], &block)
58
+ end
59
+
60
+ def compile_silent_script
61
+ return if @options[:suppress_eval]
62
+ push_silent(@node.value[:text])
63
+ keyword = @node.value[:keyword]
64
+ ruby_block = block_given? && !keyword
65
+
66
+ if block_given?
67
+ # Store these values because for conditional statements,
68
+ # we want to restore them for each branch
69
+ @node.value[:dont_indent_next_line] = @dont_indent_next_line
70
+ @node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
71
+ yield
72
+ push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
73
+ elsif keyword == "end"
74
+ if @node.parent.children.last.equal?(@node)
75
+ # Since this "end" is ending the block,
76
+ # we don't need to generate an additional one
77
+ @node.parent.value[:dont_push_end] = true
78
+ end
79
+ # Don't restore dont_* for end because it isn't a conditional branch.
80
+ elsif Parser::MID_BLOCK_KEYWORDS.include?(keyword)
81
+ # Restore dont_* for this conditional branch
82
+ @dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
83
+ @dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
84
+ end
85
+ end
86
+
87
+ def compile_haml_comment; end
88
+
89
+ def compile_tag
90
+ t = @node.value
91
+
92
+ # Get rid of whitespace outside of the tag if we need to
93
+ rstrip_buffer! if t[:nuke_outer_whitespace]
94
+
95
+ dont_indent_next_line =
96
+ (t[:nuke_outer_whitespace] && !block_given?) ||
97
+ (t[:nuke_inner_whitespace] && block_given?)
98
+
99
+ if @options[:suppress_eval]
100
+ object_ref = "nil"
101
+ parse = false
102
+ value = t[:parse] ? nil : t[:value]
103
+ attributes_hashes = {}
104
+ preserve_script = false
105
+ else
106
+ object_ref = t[:object_ref]
107
+ parse = t[:parse]
108
+ value = t[:value]
109
+ attributes_hashes = t[:attributes_hashes]
110
+ preserve_script = t[:preserve_script]
111
+ end
112
+
113
+ # Check if we can render the tag directly to text and not process it in the buffer
114
+ if object_ref == "nil" && attributes_hashes.empty? && !preserve_script
115
+ tag_closed = !block_given? && !t[:self_closing] && !parse
116
+
117
+ open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
118
+ if tag_closed
119
+ open_tag << "#{value}</#{t[:name]}>"
120
+ open_tag << "\n" unless t[:nuke_outer_whitespace]
121
+ elsif !(parse || t[:nuke_inner_whitespace] ||
122
+ (t[:self_closing] && t[:nuke_outer_whitespace]))
123
+ open_tag << "\n"
124
+ end
125
+
126
+ push_merged_text(open_tag,
127
+ tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
128
+ !t[:nuke_outer_whitespace])
129
+
130
+ @dont_indent_next_line = dont_indent_next_line
131
+ return if tag_closed
132
+ else
133
+ content = parse ? 'nil' : inspect_obj(value)
134
+ if attributes_hashes.empty?
135
+ attributes_hashes = ''
136
+ elsif attributes_hashes.size == 1
137
+ attributes_hashes = ", #{attributes_hashes.first}"
138
+ else
139
+ attributes_hashes = ", (#{attributes_hashes.join(").merge(")})"
140
+ end
141
+
142
+ args = [t[:name], t[:self_closing], !block_given?, t[:preserve_tag],
143
+ t[:escape_html], t[:attributes], t[:nuke_outer_whitespace],
144
+ t[:nuke_inner_whitespace]].map {|v| inspect_obj(v)}.join(', ')
145
+ push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hashes})"
146
+ @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
147
+ end
148
+
149
+ return if t[:self_closing]
150
+
151
+ if value.nil?
152
+ @output_tabs += 1 unless t[:nuke_inner_whitespace]
153
+ yield if block_given?
154
+ @output_tabs -= 1 unless t[:nuke_inner_whitespace]
155
+ rstrip_buffer! if t[:nuke_inner_whitespace]
156
+ push_merged_text("</#{t[:name]}>" + (t[:nuke_outer_whitespace] ? "" : "\n"),
157
+ t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
158
+ @dont_indent_next_line = t[:nuke_outer_whitespace]
159
+ return
160
+ end
161
+
162
+ if parse
163
+ push_script(value, t.merge(:in_tag => true))
164
+ concat_merged_text("</#{t[:name]}>" + (t[:nuke_outer_whitespace] ? "" : "\n"))
165
+ end
166
+ end
167
+
168
+ def compile_comment
169
+ open = "<!--#{@node.value[:conditional]}"
170
+
171
+ # Render it statically if possible
172
+ unless block_given?
173
+ push_text("#{open} #{@node.value[:text]} #{@node.value[:conditional] ? "<![endif]-->" : "-->"}")
174
+ return
175
+ end
176
+
177
+ push_text(open, 1)
178
+ @output_tabs += 1
179
+ yield if block_given?
180
+ @output_tabs -= 1
181
+ push_text(@node.value[:conditional] ? "<![endif]-->" : "-->", -1)
182
+ end
183
+
184
+ def compile_doctype
185
+ doctype = text_for_doctype
186
+ push_text doctype if doctype
187
+ end
188
+
189
+ def compile_filter
190
+ unless filter = Filters.defined[@node.value[:name]]
191
+ raise Error.new("Filter \"#{@node.value[:name]}\" is not defined.", @node.line - 1)
192
+ end
193
+ filter.internal_compile(self, @node.value[:text])
194
+ end
195
+
196
+ def text_for_doctype
197
+ if @node.value[:type] == "xml"
198
+ return nil if html?
199
+ wrapper = @options[:attr_wrapper]
200
+ return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
201
+ end
202
+
203
+ if html5?
204
+ '<!DOCTYPE html>'
205
+ else
206
+ if xhtml?
207
+ if @node.value[:version] == "1.1"
208
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
209
+ elsif @node.value[:version] == "5"
210
+ '<!DOCTYPE html>'
211
+ else
212
+ case @node.value[:type]
213
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
214
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
215
+ when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
216
+ when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
217
+ when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
218
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
219
+ end
220
+ end
221
+
222
+ elsif html4?
223
+ case @node.value[:type]
224
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
225
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
226
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ # Evaluates `text` in the context of the scope object, but
233
+ # does not output the result.
234
+ def push_silent(text, can_suppress = false)
235
+ flush_merged_text
236
+ return if can_suppress && options[:suppress_eval]
237
+ @precompiled << "#{resolve_newlines}#{text}\n"
238
+ @output_line += text.count("\n") + 1
239
+ end
240
+
241
+ # Adds `text` to `@buffer` with appropriate tabulation
242
+ # without parsing it.
243
+ def push_merged_text(text, tab_change = 0, indent = true)
244
+ text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
245
+ @to_merge << [:text, text, tab_change]
246
+ @dont_indent_next_line = false
247
+ end
248
+
249
+ # Concatenate `text` to `@buffer` without tabulation.
250
+ def concat_merged_text(text)
251
+ @to_merge << [:text, text, 0]
252
+ end
253
+
254
+ def push_text(text, tab_change = 0)
255
+ push_merged_text("#{text}\n", tab_change)
256
+ end
257
+
258
+ def flush_merged_text
259
+ return if @to_merge.empty?
260
+
261
+ str = ""
262
+ mtabs = 0
263
+ @to_merge.each do |type, val, tabs|
264
+ case type
265
+ when :text
266
+ str << inspect_obj(val)[1...-1]
267
+ mtabs += tabs
268
+ when :script
269
+ if mtabs != 0 && !@options[:ugly]
270
+ val = "_hamlout.adjust_tabs(#{mtabs}); " + val
271
+ end
272
+ str << "\#{#{val}}"
273
+ mtabs = 0
274
+ else
275
+ raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
276
+ end
277
+ end
278
+
279
+ unless str.empty?
280
+ @precompiled <<
281
+ if @options[:ugly]
282
+ "_hamlout.buffer << \"#{str}\";"
283
+ else
284
+ "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
285
+ end
286
+ end
287
+ @to_merge = []
288
+ @dont_tab_up_next_text = false
289
+ end
290
+
291
+ # Causes `text` to be evaluated in the context of
292
+ # the scope object and the result to be added to `@buffer`.
293
+ #
294
+ # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
295
+ # the result before it is added to `@buffer`
296
+ def push_script(text, opts = {})
297
+ return if options[:suppress_eval]
298
+
299
+ args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
300
+ args.map! {|name| opts[name.to_sym]}
301
+ args << !block_given? << @options[:ugly]
302
+
303
+ no_format = @options[:ugly] &&
304
+ !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
305
+ output_expr = "(#{text}\n)"
306
+ static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
307
+
308
+ # Prerender tabulation unless we're in a tag
309
+ push_merged_text '' unless opts[:in_tag]
310
+
311
+ unless block_given?
312
+ script = no_format ? "#{text}\n" : "#{static_method}(#{output_expr});"
313
+ @to_merge << [:script, resolve_newlines + script]
314
+ @output_line += script.count("\n")
315
+ concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
316
+ return
317
+ end
318
+
319
+ flush_merged_text
320
+ push_silent "haml_temp = #{text}"
321
+ yield
322
+ push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
323
+ @precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}"
324
+ concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly]
325
+ end
326
+
327
+ # This is a class method so it can be accessed from Buffer.
328
+ def self.build_attributes(is_html, attr_wrapper, escape_attrs, attributes = {})
329
+ quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
330
+ other_quote_char = attr_wrapper == '"' ? "'" : '"'
331
+
332
+ if attributes['data'].is_a?(Hash)
333
+ attributes = attributes.dup
334
+ attributes =
335
+ Haml::Util.map_keys(attributes.delete('data')) {|name| "data-#{name}"}.merge(attributes)
336
+ end
337
+
338
+ result = attributes.collect do |attr, value|
339
+ next if value.nil?
340
+
341
+ value = filter_and_join(value, ' ') if attr == 'class'
342
+ value = filter_and_join(value, '_') if attr == 'id'
343
+
344
+ if value == true
345
+ next " #{attr}" if is_html
346
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
347
+ elsif value == false
348
+ next
349
+ end
350
+
351
+ escaped =
352
+ if escape_attrs == :once
353
+ Haml::Helpers.escape_once(value.to_s)
354
+ elsif escape_attrs
355
+ CGI.escapeHTML(value.to_s)
356
+ else
357
+ value.to_s
358
+ end
359
+ value = Haml::Helpers.preserve(escaped)
360
+ if escape_attrs
361
+ # We want to decide whether or not to escape quotes
362
+ value.gsub!('&quot;', '"')
363
+ this_attr_wrapper = attr_wrapper
364
+ if value.include? attr_wrapper
365
+ if value.include? other_quote_char
366
+ value = value.gsub(attr_wrapper, quote_escape)
367
+ else
368
+ this_attr_wrapper = other_quote_char
369
+ end
370
+ end
371
+ else
372
+ this_attr_wrapper = attr_wrapper
373
+ end
374
+ " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
375
+ end
376
+ result.compact.sort.join
377
+ end
378
+
379
+ def self.filter_and_join(value, separator)
380
+ return "" if value == ""
381
+ value = [value] unless value.is_a?(Array)
382
+ value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
383
+ return !value.empty? && value
384
+ end
385
+
386
+ def prerender_tag(name, self_close, attributes)
387
+ attributes_string = Compiler.build_attributes(
388
+ html?, @options[:attr_wrapper], @options[:escape_attrs], attributes)
389
+ "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
390
+ end
391
+
392
+ def resolve_newlines
393
+ diff = @node.line - @output_line
394
+ return "" if diff <= 0
395
+ @output_line = @node.line
396
+ "\n" * [diff, 0].max
397
+ end
398
+
399
+ # Get rid of and whitespace at the end of the buffer
400
+ # or the merged text
401
+ def rstrip_buffer!(index = -1)
402
+ last = @to_merge[index]
403
+ if last.nil?
404
+ push_silent("_hamlout.rstrip!", false)
405
+ @dont_tab_up_next_text = true
406
+ return
407
+ end
408
+
409
+ case last.first
410
+ when :text
411
+ last[1].rstrip!
412
+ if last[1].empty?
413
+ @to_merge.slice! index
414
+ rstrip_buffer! index
415
+ end
416
+ when :script
417
+ last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
418
+ rstrip_buffer! index - 1
419
+ else
420
+ raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
421
+ end
422
+ end
423
+
424
+ def compile(node)
425
+ parent, @node = @node, node
426
+ block = proc {node.children.each {|c| compile c}}
427
+ send("compile_#{node.type}", &(block unless node.children.empty?))
428
+ ensure
429
+ @node = parent
430
+ end
431
+ end
432
+ end