haml 4.0.7 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +36 -0
  3. data/.gitignore +19 -0
  4. data/.gitmodules +3 -0
  5. data/.yardopts +2 -3
  6. data/CHANGELOG.md +146 -4
  7. data/FAQ.md +4 -14
  8. data/Gemfile +16 -0
  9. data/MIT-LICENSE +2 -2
  10. data/README.md +90 -47
  11. data/REFERENCE.md +160 -74
  12. data/Rakefile +44 -63
  13. data/TODO +24 -0
  14. data/benchmark.rb +70 -0
  15. data/haml.gemspec +45 -0
  16. data/lib/haml/.gitattributes +1 -0
  17. data/lib/haml/attribute_builder.rb +219 -0
  18. data/lib/haml/attribute_compiler.rb +237 -0
  19. data/lib/haml/attribute_parser.rb +150 -0
  20. data/lib/haml/buffer.rb +12 -175
  21. data/lib/haml/compiler.rb +110 -320
  22. data/lib/haml/engine.rb +34 -41
  23. data/lib/haml/error.rb +28 -24
  24. data/lib/haml/escapable.rb +77 -0
  25. data/lib/haml/exec.rb +38 -20
  26. data/lib/haml/filters.rb +22 -27
  27. data/lib/haml/generator.rb +42 -0
  28. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  29. data/lib/haml/helpers/action_view_mods.rb +45 -60
  30. data/lib/haml/helpers/action_view_xss_mods.rb +2 -0
  31. data/lib/haml/helpers/safe_erubi_template.rb +20 -0
  32. data/lib/haml/helpers/safe_erubis_template.rb +5 -1
  33. data/lib/haml/helpers/xss_mods.rb +23 -13
  34. data/lib/haml/helpers.rb +134 -89
  35. data/lib/haml/options.rb +63 -69
  36. data/lib/haml/parser.rb +319 -227
  37. data/lib/haml/plugin.rb +54 -0
  38. data/lib/haml/railtie.rb +43 -12
  39. data/lib/haml/sass_rails_filter.rb +18 -4
  40. data/lib/haml/template/options.rb +13 -2
  41. data/lib/haml/template.rb +13 -6
  42. data/lib/haml/temple_engine.rb +124 -0
  43. data/lib/haml/temple_line_counter.rb +30 -0
  44. data/lib/haml/util.rb +83 -202
  45. data/lib/haml/version.rb +3 -1
  46. data/lib/haml.rb +2 -0
  47. data/yard/default/.gitignore +1 -0
  48. data/yard/default/fulldoc/html/css/common.sass +15 -0
  49. data/yard/default/layout/html/footer.erb +12 -0
  50. metadata +73 -115
  51. data/lib/haml/template/plugin.rb +0 -41
  52. data/test/engine_test.rb +0 -2013
  53. data/test/erb/_av_partial_1.erb +0 -12
  54. data/test/erb/_av_partial_2.erb +0 -8
  55. data/test/erb/action_view.erb +0 -62
  56. data/test/erb/standard.erb +0 -55
  57. data/test/filters_test.rb +0 -254
  58. data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
  59. data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
  60. data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
  61. data/test/gemfiles/Gemfile.rails-4.0.x +0 -5
  62. data/test/haml-spec/LICENSE +0 -14
  63. data/test/haml-spec/README.md +0 -106
  64. data/test/haml-spec/lua_haml_spec.lua +0 -38
  65. data/test/haml-spec/perl_haml_test.pl +0 -81
  66. data/test/haml-spec/ruby_haml_test.rb +0 -23
  67. data/test/haml-spec/tests.json +0 -660
  68. data/test/helper_test.rb +0 -583
  69. data/test/markaby/standard.mab +0 -52
  70. data/test/mocks/article.rb +0 -6
  71. data/test/parser_test.rb +0 -105
  72. data/test/results/content_for_layout.xhtml +0 -12
  73. data/test/results/eval_suppressed.xhtml +0 -9
  74. data/test/results/helpers.xhtml +0 -70
  75. data/test/results/helpful.xhtml +0 -10
  76. data/test/results/just_stuff.xhtml +0 -70
  77. data/test/results/list.xhtml +0 -12
  78. data/test/results/nuke_inner_whitespace.xhtml +0 -40
  79. data/test/results/nuke_outer_whitespace.xhtml +0 -148
  80. data/test/results/original_engine.xhtml +0 -20
  81. data/test/results/partial_layout.xhtml +0 -5
  82. data/test/results/partial_layout_erb.xhtml +0 -5
  83. data/test/results/partials.xhtml +0 -21
  84. data/test/results/render_layout.xhtml +0 -3
  85. data/test/results/silent_script.xhtml +0 -74
  86. data/test/results/standard.xhtml +0 -162
  87. data/test/results/tag_parsing.xhtml +0 -23
  88. data/test/results/very_basic.xhtml +0 -5
  89. data/test/results/whitespace_handling.xhtml +0 -90
  90. data/test/template_test.rb +0 -354
  91. data/test/templates/_av_partial_1.haml +0 -9
  92. data/test/templates/_av_partial_1_ugly.haml +0 -9
  93. data/test/templates/_av_partial_2.haml +0 -5
  94. data/test/templates/_av_partial_2_ugly.haml +0 -5
  95. data/test/templates/_layout.erb +0 -3
  96. data/test/templates/_layout_for_partial.haml +0 -3
  97. data/test/templates/_partial.haml +0 -8
  98. data/test/templates/_text_area.haml +0 -3
  99. data/test/templates/_text_area_helper.html.haml +0 -4
  100. data/test/templates/action_view.haml +0 -47
  101. data/test/templates/action_view_ugly.haml +0 -47
  102. data/test/templates/breakage.haml +0 -8
  103. data/test/templates/content_for_layout.haml +0 -8
  104. data/test/templates/eval_suppressed.haml +0 -11
  105. data/test/templates/helpers.haml +0 -55
  106. data/test/templates/helpful.haml +0 -11
  107. data/test/templates/just_stuff.haml +0 -85
  108. data/test/templates/list.haml +0 -12
  109. data/test/templates/nuke_inner_whitespace.haml +0 -32
  110. data/test/templates/nuke_outer_whitespace.haml +0 -144
  111. data/test/templates/original_engine.haml +0 -17
  112. data/test/templates/partial_layout.haml +0 -3
  113. data/test/templates/partial_layout_erb.erb +0 -4
  114. data/test/templates/partialize.haml +0 -1
  115. data/test/templates/partials.haml +0 -12
  116. data/test/templates/render_layout.haml +0 -2
  117. data/test/templates/silent_script.haml +0 -45
  118. data/test/templates/standard.haml +0 -43
  119. data/test/templates/standard_ugly.haml +0 -43
  120. data/test/templates/tag_parsing.haml +0 -21
  121. data/test/templates/very_basic.haml +0 -4
  122. data/test/templates/whitespace_handling.haml +0 -87
  123. data/test/test_helper.rb +0 -81
  124. data/test/util_test.rb +0 -63
data/lib/haml/compiler.rb CHANGED
@@ -1,4 +1,8 @@
1
- require 'cgi'
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml/attribute_builder'
4
+ require 'haml/attribute_compiler'
5
+ require 'haml/temple_line_counter'
2
6
 
3
7
  module Haml
4
8
  class Compiler
@@ -7,15 +11,21 @@ module Haml
7
11
  attr_accessor :options
8
12
 
9
13
  def initialize(options)
10
- @options = options
11
- @output_tabs = 0
14
+ @options = Options.wrap(options)
12
15
  @to_merge = []
13
- @precompiled = ''
16
+ @temple = [:multi]
17
+ @node = nil
18
+ @filters = Filters.defined.merge(options[:filters])
19
+ @attribute_compiler = AttributeCompiler.new(@options)
20
+ end
21
+
22
+ def call(node)
23
+ compile(node)
24
+ @temple
14
25
  end
15
26
 
16
27
  def compile(node)
17
- parent = instance_variable_defined?('@node') ? @node : nil
18
- @node = node
28
+ parent, @node = @node, node
19
29
  if node.children.empty?
20
30
  send(:"compile_#{node.type}")
21
31
  else
@@ -25,79 +35,16 @@ module Haml
25
35
  @node = parent
26
36
  end
27
37
 
28
- if RUBY_VERSION < "1.9"
29
- # The source code that is evaluated to produce the Haml document.
30
- #
31
- # In Ruby 1.9, this is automatically converted to the correct encoding
32
- # (see {file:REFERENCE.md#encodings the `:encoding` option}).
33
- #
34
- # @return [String]
35
- def precompiled
36
- @precompiled
37
- end
38
- else
39
- def precompiled
40
- encoding = Encoding.find(@options[:encoding])
41
- return @precompiled.force_encoding(encoding) if encoding == Encoding::BINARY
42
- return @precompiled.encode(encoding)
43
- end
44
- end
45
-
46
- def precompiled_with_return_value
47
- precompiled + ";" + precompiled_method_return_value
48
- end
49
-
50
- # Returns the precompiled string with the preamble and postamble.
51
- #
52
- # Initializes to ActionView::OutputBuffer when available; this is necessary
53
- # to avoid ordering issues with partial layouts in Rails. If not available,
54
- # initializes to nil.
55
- def precompiled_with_ambles(local_names)
56
- preamble = <<END.gsub("\n", ";")
57
- begin
58
- extend Haml::Helpers
59
- _hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, #{options.for_buffer.inspect})
60
- _erbout = _hamlout.buffer
61
- @output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil
62
- END
63
- postamble = <<END.gsub("\n", ";")
64
- #{precompiled_method_return_value}
65
- ensure
66
- @haml_buffer = @haml_buffer.upper if @haml_buffer
67
- end
68
- END
69
- preamble + locals_code(local_names) + precompiled + postamble
70
- end
71
-
72
38
  private
73
39
 
74
- # Returns the string used as the return value of the precompiled method.
75
- # This method exists so it can be monkeypatched to return modified values.
76
- def precompiled_method_return_value
77
- "_erbout"
78
- end
79
-
80
- def locals_code(names)
81
- names = names.keys if Hash == names
82
-
83
- names.map do |name|
84
- # Can't use || because someone might explicitly pass in false with a symbol
85
- sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
86
- str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
87
- "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
88
- end.join(';') + ';'
89
- end
90
-
91
40
  def compile_root
92
- @dont_indent_next_line = @dont_tab_up_next_text = false
93
41
  @output_line = 1
94
- @indentation = nil
95
- yield
42
+ yield if block_given?
96
43
  flush_merged_text
97
44
  end
98
45
 
99
46
  def compile_plain
100
- push_text @node.value[:text]
47
+ push_text("#{@node.value[:text]}\n")
101
48
  end
102
49
 
103
50
  def nuke_inner_whitespace?(node)
@@ -119,15 +66,11 @@ END
119
66
  end
120
67
 
121
68
  def compile_silent_script
122
- return if @options[:suppress_eval]
69
+ return if @options.suppress_eval
123
70
  push_silent(@node.value[:text])
124
71
  keyword = @node.value[:keyword]
125
72
 
126
73
  if block_given?
127
- # Store these values because for conditional statements,
128
- # we want to restore them for each branch
129
- @node.value[:dont_indent_next_line] = @dont_indent_next_line
130
- @node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
131
74
  yield
132
75
  push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
133
76
  elsif keyword == "end"
@@ -137,10 +80,6 @@ END
137
80
  @node.parent.value[:dont_push_end] = true
138
81
  end
139
82
  # Don't restore dont_* for end because it isn't a conditional branch.
140
- elsif Parser::MID_BLOCK_KEYWORDS.include?(keyword)
141
- # Restore dont_* for this conditional branch
142
- @dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
143
- @dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
144
83
  end
145
84
  end
146
85
 
@@ -152,117 +91,84 @@ END
152
91
  # Get rid of whitespace outside of the tag if we need to
153
92
  rstrip_buffer! if t[:nuke_outer_whitespace]
154
93
 
155
- dont_indent_next_line =
156
- (t[:nuke_outer_whitespace] && !block_given?) ||
157
- (t[:nuke_inner_whitespace] && block_given?)
158
-
159
- if @options[:suppress_eval]
160
- object_ref = "nil"
94
+ if @options.suppress_eval
95
+ object_ref = :nil
161
96
  parse = false
162
97
  value = t[:parse] ? nil : t[:value]
163
- attributes_hashes = {}
164
- preserve_script = false
98
+ dynamic_attributes = Haml::Parser::DynamicAttributes.new
165
99
  else
166
100
  object_ref = t[:object_ref]
167
101
  parse = t[:parse]
168
102
  value = t[:value]
169
- attributes_hashes = t[:attributes_hashes]
170
- preserve_script = t[:preserve_script]
103
+ dynamic_attributes = t[:dynamic_attributes]
171
104
  end
172
105
 
173
- # Check if we can render the tag directly to text and not process it in the buffer
174
- if object_ref == "nil" && attributes_hashes.empty? && !preserve_script
175
- tag_closed = !block_given? && !t[:self_closing] && !parse
176
-
177
- open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
178
- if tag_closed
179
- open_tag << "#{value}</#{t[:name]}>"
180
- open_tag << "\n" unless t[:nuke_outer_whitespace]
181
- elsif !(parse || t[:nuke_inner_whitespace] ||
182
- (t[:self_closing] && t[:nuke_outer_whitespace]))
183
- open_tag << "\n"
184
- end
185
-
186
- push_merged_text(open_tag,
187
- tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
188
- !t[:nuke_outer_whitespace])
106
+ if @options[:trace]
107
+ t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
108
+ end
189
109
 
190
- @dont_indent_next_line = dont_indent_next_line
191
- return if tag_closed
192
- else
193
- if attributes_hashes.empty?
194
- attributes_hashes = ''
195
- elsif attributes_hashes.size == 1
196
- attributes_hashes = ", #{attributes_hashes.first}"
110
+ push_text("<#{t[:name]}")
111
+ push_temple(@attribute_compiler.compile(t[:attributes], object_ref, dynamic_attributes))
112
+ push_text(
113
+ if t[:self_closing] && @options.xhtml?
114
+ " />#{"\n" unless t[:nuke_outer_whitespace]}"
197
115
  else
198
- attributes_hashes = ", (#{attributes_hashes.join(").merge(")})"
199
- end
200
-
201
- push_merged_text "<#{t[:name]}", 0, !t[:nuke_outer_whitespace]
202
- push_generated_script(
203
- "_hamlout.attributes(#{inspect_obj(t[:attributes])}, #{object_ref}#{attributes_hashes})")
204
- concat_merged_text(
205
- if t[:self_closing] && @options.xhtml?
206
- " />" + (t[:nuke_outer_whitespace] ? "" : "\n")
207
- else
208
- ">" + ((if t[:self_closing] && @options.html?
209
- t[:nuke_outer_whitespace]
210
- else
211
- !block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace]
212
- end) ? "" : "\n")
213
- end)
214
-
215
- if value && !parse
216
- concat_merged_text("#{value}</#{t[:name]}>#{t[:nuke_outer_whitespace] ? "" : "\n"}")
217
- elsif !t[:nuke_inner_whitespace] && !t[:self_closing]
218
- @to_merge << [:text, '', 1]
116
+ ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
219
117
  end
118
+ )
220
119
 
221
- @dont_indent_next_line = dont_indent_next_line
120
+ if value && !parse
121
+ push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
222
122
  end
223
123
 
224
124
  return if t[:self_closing]
225
125
 
226
126
  if value.nil?
227
- @output_tabs += 1 unless t[:nuke_inner_whitespace]
228
127
  yield if block_given?
229
- @output_tabs -= 1 unless t[:nuke_inner_whitespace]
230
128
  rstrip_buffer! if t[:nuke_inner_whitespace]
231
- push_merged_text("</#{t[:name]}>" + (t[:nuke_outer_whitespace] ? "" : "\n"),
232
- t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
233
- @dont_indent_next_line = t[:nuke_outer_whitespace]
129
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
234
130
  return
235
131
  end
236
132
 
237
133
  if parse
238
134
  push_script(value, t.merge(:in_tag => true))
239
- concat_merged_text("</#{t[:name]}>" + (t[:nuke_outer_whitespace] ? "" : "\n"))
135
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
240
136
  end
241
137
  end
242
138
 
243
139
  def compile_comment
244
- open = "<!--#{@node.value[:conditional]}"
140
+ condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
141
+ revealed = @node.value[:revealed]
142
+
143
+ open = "<!--#{condition}#{'<!-->' if revealed}"
144
+
145
+ close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
245
146
 
246
- # Render it statically if possible
247
147
  unless block_given?
248
- push_text("#{open} #{@node.value[:text]} #{@node.value[:conditional] ? "<![endif]-->" : "-->"}")
148
+ push_text("#{open} ")
149
+
150
+ if @node.value[:parse]
151
+ push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
152
+ else
153
+ push_text(@node.value[:text])
154
+ end
155
+
156
+ push_text(" #{close}\n")
249
157
  return
250
158
  end
251
159
 
252
- push_text(open, 1)
253
- @output_tabs += 1
160
+ push_text("#{open}\n")
254
161
  yield if block_given?
255
- @output_tabs -= 1
256
- push_text(@node.value[:conditional] ? "<![endif]-->" : "-->", -1)
162
+ push_text("#{close}\n")
257
163
  end
258
164
 
259
165
  def compile_doctype
260
166
  doctype = text_for_doctype
261
- push_text doctype if doctype
167
+ push_text("#{doctype}\n") if doctype
262
168
  end
263
169
 
264
170
  def compile_filter
265
- unless filter = Filters.defined[@node.value[:name]]
171
+ unless (filter = @filters[@node.value[:name]])
266
172
  name = @node.value[:name]
267
173
  if ["maruku", "textile"].include?(name)
268
174
  raise Error.new(Error.message(:install_haml_contrib, name), @node.line - 1)
@@ -276,36 +182,34 @@ END
276
182
  def text_for_doctype
277
183
  if @node.value[:type] == "xml"
278
184
  return nil if @options.html?
279
- wrapper = @options[:attr_wrapper]
185
+ wrapper = @options.attr_wrapper
280
186
  return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
281
187
  end
282
188
 
283
189
  if @options.html5?
284
190
  '<!DOCTYPE html>'
285
- else
286
- if @options.xhtml?
287
- if @node.value[:version] == "1.1"
288
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
289
- elsif @node.value[:version] == "5"
290
- '<!DOCTYPE html>'
291
- else
292
- case @node.value[:type]
293
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
294
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
295
- when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
296
- when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
297
- when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
298
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
299
- end
300
- end
301
-
302
- elsif @options.html4?
191
+ elsif @options.xhtml?
192
+ if @node.value[:version] == "1.1"
193
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
194
+ elsif @node.value[:version] == "5"
195
+ '<!DOCTYPE html>'
196
+ else
303
197
  case @node.value[:type]
304
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
305
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
306
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
198
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
199
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
200
+ when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
201
+ when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
202
+ when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
203
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
307
204
  end
308
205
  end
206
+
207
+ elsif @options.html4?
208
+ case @node.value[:type]
209
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
210
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
211
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
212
+ end
309
213
  end
310
214
  end
311
215
 
@@ -315,83 +219,52 @@ END
315
219
  flush_merged_text
316
220
  return if can_suppress && @options.suppress_eval?
317
221
  newline = (text == "end") ? ";" : "\n"
318
- @precompiled << "#{resolve_newlines}#{text}#{newline}"
319
- @output_line += (text + newline).count("\n")
320
- end
321
-
322
- # Adds `text` to `@buffer` with appropriate tabulation
323
- # without parsing it.
324
- def push_merged_text(text, tab_change = 0, indent = true)
325
- text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
326
- @to_merge << [:text, text, tab_change]
327
- @dont_indent_next_line = false
222
+ @temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
223
+ @output_line = @output_line + text.count("\n") + newline.count("\n")
328
224
  end
329
225
 
330
- # Concatenate `text` to `@buffer` without tabulation.
331
- def concat_merged_text(text)
332
- @to_merge << [:text, text, 0]
226
+ # Adds `text` to `@buffer`.
227
+ def push_text(text)
228
+ @to_merge << [:text, text]
333
229
  end
334
230
 
335
- def push_text(text, tab_change = 0)
336
- push_merged_text("#{text}\n", tab_change)
231
+ def push_temple(temple)
232
+ flush_merged_text
233
+ @temple.concat([[:newline]] * resolve_newlines.count("\n"))
234
+ @temple << temple
235
+ @output_line += TempleLineCounter.count_lines(temple)
337
236
  end
338
237
 
339
238
  def flush_merged_text
340
239
  return if @to_merge.empty?
341
240
 
342
- str = ""
343
- mtabs = 0
344
- @to_merge.each do |type, val, tabs|
241
+ @to_merge.each do |type, val|
345
242
  case type
346
243
  when :text
347
- str << inspect_obj(val)[1...-1]
348
- mtabs += tabs
244
+ @temple << [:static, val]
349
245
  when :script
350
- if mtabs != 0 && !@options[:ugly]
351
- val = "_hamlout.adjust_tabs(#{mtabs}); " + val
352
- end
353
- str << "\#{#{val}}"
354
- mtabs = 0
246
+ @temple << [:dynamic, val]
355
247
  else
356
248
  raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
357
249
  end
358
250
  end
359
251
 
360
- unless str.empty?
361
- @precompiled <<
362
- if @options[:ugly]
363
- "_hamlout.buffer << \"#{str}\";"
364
- else
365
- "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
366
- end
367
- end
368
252
  @to_merge = []
369
- @dont_tab_up_next_text = false
370
253
  end
371
254
 
372
255
  # Causes `text` to be evaluated in the context of
373
256
  # the scope object and the result to be added to `@buffer`.
374
257
  #
375
- # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
258
+ # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
376
259
  # the result before it is added to `@buffer`
377
260
  def push_script(text, opts = {})
378
261
  return if @options.suppress_eval?
379
262
 
380
- args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
381
- args.map! {|name| opts[name.to_sym]}
382
- args << !block_given? << @options[:ugly]
383
-
384
- no_format = @options[:ugly] &&
385
- !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
386
- output_expr = "(#{text}\n)"
387
- static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
388
-
389
- # Prerender tabulation unless we're in a tag
390
- push_merged_text '' unless opts[:in_tag]
263
+ no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
391
264
 
392
265
  unless block_given?
393
- push_generated_script(no_format ? "#{text}\n" : "#{static_method}(#{output_expr});")
394
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
266
+ push_generated_script(no_format ? "(#{text}\n).to_s" : build_script_formatter("(#{text}\n)", opts))
267
+ push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
395
268
  return
396
269
  end
397
270
 
@@ -399,117 +272,35 @@ END
399
272
  push_silent "haml_temp = #{text}"
400
273
  yield
401
274
  push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
402
- @precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}"
403
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly]
275
+ @temple << [:dynamic, no_format ? 'haml_temp.to_s;' : build_script_formatter('haml_temp', opts)]
404
276
  end
405
277
 
406
- def push_generated_script(text)
407
- @to_merge << [:script, resolve_newlines + text]
408
- @output_line += text.count("\n")
409
- end
410
-
411
- # This is a class method so it can be accessed from Buffer.
412
- def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
413
- # @TODO this is an absolutely ridiculous amount of arguments. At least
414
- # some of this needs to be moved into an instance method.
415
- quote_escape = attr_wrapper == '"' ? "&#x0022;" : "&#x0027;"
416
- other_quote_char = attr_wrapper == '"' ? "'" : '"'
417
- join_char = hyphenate_data_attrs ? '-' : '_'
418
-
419
- attributes.each do |key, value|
420
- if value.is_a?(Hash)
421
- data_attributes = attributes.delete(key)
422
- data_attributes = flatten_data_attributes(data_attributes, '', join_char)
423
- data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
424
- attributes = data_attributes.merge(attributes)
425
- end
278
+ def build_script_formatter(text, opts)
279
+ text = "(#{text}).to_s"
280
+ if opts[:escape_html]
281
+ text = "::Haml::Helpers.html_escape(#{text})"
426
282
  end
427
-
428
- result = attributes.collect do |attr, value|
429
- next if value.nil?
430
-
431
- value = filter_and_join(value, ' ') if attr == 'class'
432
- value = filter_and_join(value, '_') if attr == 'id'
433
-
434
- if value == true
435
- next " #{attr}" if is_html
436
- next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
437
- elsif value == false
438
- next
439
- end
440
-
441
- escaped =
442
- if escape_attrs == :once
443
- Haml::Helpers.escape_once(value.to_s)
444
- elsif escape_attrs
445
- Haml::Helpers.html_escape(value.to_s)
446
- else
447
- value.to_s
448
- end
449
- value = Haml::Helpers.preserve(escaped)
450
- if escape_attrs
451
- # We want to decide whether or not to escape quotes
452
- value.gsub!(/&quot;|&#x0022;/, '"')
453
- this_attr_wrapper = attr_wrapper
454
- if value.include? attr_wrapper
455
- if value.include? other_quote_char
456
- value.gsub!(attr_wrapper, quote_escape)
457
- else
458
- this_attr_wrapper = other_quote_char
459
- end
460
- end
461
- else
462
- this_attr_wrapper = attr_wrapper
463
- end
464
- " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
283
+ if opts[:nuke_inner_whitespace]
284
+ text = "(#{text}).strip"
465
285
  end
466
- result.compact.sort.join
467
- end
468
-
469
- def self.filter_and_join(value, separator)
470
- return "" if value == ""
471
- value = [value] unless value.is_a?(Array)
472
- value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
473
- return !value.empty? && value
474
- end
475
-
476
- def self.build_data_keys(data_hash, hyphenate, attr_name="data")
477
- Hash[data_hash.map do |name, value|
478
- if name == nil
479
- [attr_name, value]
480
- elsif hyphenate
481
- ["#{attr_name}-#{name.to_s.gsub(/_/, '-')}", value]
482
- else
483
- ["#{attr_name}-#{name}", value]
484
- end
485
- end]
486
- end
487
-
488
- def self.flatten_data_attributes(data, key, join_char, seen = [])
489
- return {key => data} unless data.is_a?(Hash)
490
-
491
- return {key => nil} if seen.include? data.object_id
492
- seen << data.object_id
493
-
494
- data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, array|
495
- k, v = array
496
- joined = key == '' ? k : [key, k].join(join_char)
497
- hash.merge! flatten_data_attributes(v, joined, join_char, seen)
286
+ if opts[:preserve_tag]
287
+ text = "_hamlout.fix_textareas!(::Haml::Helpers.preserve(#{text}))"
288
+ elsif opts[:preserve_script]
289
+ text = "_hamlout.fix_textareas!(::Haml::Helpers.find_and_preserve(#{text}, _hamlout.options[:preserve]))"
498
290
  end
291
+ "#{text};"
499
292
  end
500
293
 
501
- def prerender_tag(name, self_close, attributes)
502
- # TODO: consider just passing in the damn options here
503
- attributes_string = Compiler.build_attributes(
504
- @options.html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
505
- "<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
294
+ def push_generated_script(text)
295
+ @to_merge << [:script, resolve_newlines + text]
296
+ @output_line += text.count("\n")
506
297
  end
507
298
 
508
299
  def resolve_newlines
509
300
  diff = @node.line - @output_line
510
301
  return "" if diff <= 0
511
302
  @output_line = @node.line
512
- "\n" * [diff, 0].max
303
+ "\n" * diff
513
304
  end
514
305
 
515
306
  # Get rid of and whitespace at the end of the buffer
@@ -518,13 +309,12 @@ END
518
309
  last = @to_merge[index]
519
310
  if last.nil?
520
311
  push_silent("_hamlout.rstrip!", false)
521
- @dont_tab_up_next_text = true
522
312
  return
523
313
  end
524
314
 
525
315
  case last.first
526
316
  when :text
527
- last[1].rstrip!
317
+ last[1] = last[1].rstrip
528
318
  if last[1].empty?
529
319
  @to_merge.slice! index
530
320
  rstrip_buffer! index