haml 4.1.0.beta.1 → 5.0.0.beta.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +36 -6
  4. data/FAQ.md +4 -14
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +81 -48
  7. data/REFERENCE.md +86 -50
  8. data/Rakefile +28 -41
  9. data/lib/haml/attribute_builder.rb +163 -0
  10. data/lib/haml/attribute_compiler.rb +214 -0
  11. data/lib/haml/attribute_parser.rb +112 -0
  12. data/lib/haml/buffer.rb +24 -126
  13. data/lib/haml/compiler.rb +62 -281
  14. data/lib/haml/engine.rb +16 -23
  15. data/lib/haml/error.rb +2 -0
  16. data/lib/haml/escapable.rb +48 -0
  17. data/lib/haml/exec.rb +23 -12
  18. data/lib/haml/filters.rb +3 -4
  19. data/lib/haml/generator.rb +36 -0
  20. data/lib/haml/helpers.rb +61 -48
  21. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  22. data/lib/haml/helpers/action_view_mods.rb +32 -50
  23. data/lib/haml/helpers/safe_erubi_template.rb +26 -0
  24. data/lib/haml/helpers/safe_erubis_template.rb +2 -0
  25. data/lib/haml/helpers/xss_mods.rb +17 -12
  26. data/lib/haml/options.rb +32 -36
  27. data/lib/haml/parser.rb +61 -38
  28. data/lib/haml/{template/plugin.rb → plugin.rb} +5 -2
  29. data/lib/haml/railtie.rb +14 -6
  30. data/lib/haml/template.rb +11 -6
  31. data/lib/haml/temple_engine.rb +119 -0
  32. data/lib/haml/temple_line_counter.rb +28 -0
  33. data/lib/haml/util.rb +17 -112
  34. data/lib/haml/version.rb +1 -1
  35. data/test/attribute_parser_test.rb +105 -0
  36. data/test/engine_test.rb +202 -106
  37. data/test/filters_test.rb +32 -19
  38. data/test/gemfiles/Gemfile.rails-4.0.x +7 -1
  39. data/test/gemfiles/Gemfile.rails-4.0.x.lock +57 -71
  40. data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
  41. data/test/gemfiles/Gemfile.rails-4.2.x +5 -0
  42. data/test/gemfiles/Gemfile.rails-5.0.x +4 -0
  43. data/test/helper_test.rb +156 -109
  44. data/test/options_test.rb +21 -0
  45. data/test/parser_test.rb +49 -4
  46. data/test/results/eval_suppressed.xhtml +4 -4
  47. data/test/results/helpers.xhtml +43 -41
  48. data/test/results/helpful.xhtml +6 -3
  49. data/test/results/just_stuff.xhtml +21 -20
  50. data/test/results/list.xhtml +9 -9
  51. data/test/results/nuke_inner_whitespace.xhtml +22 -22
  52. data/test/results/nuke_outer_whitespace.xhtml +84 -92
  53. data/test/results/original_engine.xhtml +17 -17
  54. data/test/results/partial_layout.xhtml +4 -3
  55. data/test/results/partial_layout_erb.xhtml +4 -3
  56. data/test/results/partials.xhtml +11 -10
  57. data/test/results/silent_script.xhtml +63 -63
  58. data/test/results/standard.xhtml +156 -159
  59. data/test/results/tag_parsing.xhtml +19 -19
  60. data/test/results/very_basic.xhtml +2 -2
  61. data/test/results/whitespace_handling.xhtml +77 -76
  62. data/test/template_test.rb +21 -48
  63. data/test/template_test_helper.rb +38 -0
  64. data/test/templates/just_stuff.haml +1 -0
  65. data/test/templates/standard_ugly.haml +1 -0
  66. data/test/temple_line_counter_test.rb +40 -0
  67. data/test/test_helper.rb +10 -10
  68. data/test/util_test.rb +1 -48
  69. metadata +49 -35
  70. data/lib/haml/temple.rb +0 -85
  71. data/test/gemfiles/Gemfile.rails-3.2.x +0 -4
  72. data/test/templates/_av_partial_1_ugly.haml +0 -9
  73. data/test/templates/_av_partial_2_ugly.haml +0 -5
  74. data/test/templates/action_view_ugly.haml +0 -47
  75. data/test/templates/standard_ugly.haml +0 -43
@@ -0,0 +1,112 @@
1
+ begin
2
+ require 'ripper'
3
+ rescue LoadError
4
+ end
5
+
6
+ module Haml
7
+ module AttributeParser
8
+ class UnexpectedTokenError < StandardError; end
9
+ class UnexpectedKeyError < StandardError; end
10
+
11
+ class << self
12
+ def available?
13
+ defined?(Ripper) && Temple::StaticAnalyzer.available?
14
+ end
15
+
16
+ def parse(text)
17
+ exp = wrap_bracket(text)
18
+ return if Temple::StaticAnalyzer.syntax_error?(exp)
19
+
20
+ hash = {}
21
+ tokens = Ripper.lex(exp)[1..-2] || []
22
+ each_attr(tokens) do |attr_tokens|
23
+ key = parse_key!(attr_tokens)
24
+ hash[key] = attr_tokens.map(&:last).join.strip
25
+ end
26
+ hash
27
+ rescue UnexpectedTokenError, UnexpectedKeyError
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def wrap_bracket(text)
34
+ text = text.strip
35
+ return text if text[0] == '{'
36
+ "{#{text}}"
37
+ end
38
+
39
+ def parse_key!(tokens)
40
+ _, type, str = tokens.shift
41
+ case type
42
+ when :on_sp
43
+ parse_key!(tokens)
44
+ when :on_label
45
+ str.tr(':', '')
46
+ when :on_symbeg
47
+ _, _, key = tokens.shift
48
+ assert_type!(tokens.shift, :on_tstring_end) if str != ':'
49
+ skip_until_hash_rocket!(tokens)
50
+ key
51
+ when :on_tstring_beg
52
+ _, _, key = tokens.shift
53
+ next_token = tokens.shift
54
+ unless next_token[1] == :on_label_end
55
+ assert_type!(next_token, :on_tstring_end)
56
+ skip_until_hash_rocket!(tokens)
57
+ end
58
+ key
59
+ else
60
+ raise UnexpectedKeyError
61
+ end
62
+ end
63
+
64
+ def assert_type!(token, type)
65
+ raise UnexpectedTokenError if token[1] != type
66
+ end
67
+
68
+ def skip_until_hash_rocket!(tokens)
69
+ until tokens.empty?
70
+ _, type, str = tokens.shift
71
+ break if type == :on_op && str == '=>'
72
+ end
73
+ end
74
+
75
+ def each_attr(tokens)
76
+ attr_tokens = []
77
+ array_open = 0
78
+ brace_open = 0
79
+ paren_open = 0
80
+
81
+ tokens.each do |token|
82
+ _, type, _ = token
83
+ case type
84
+ when :on_comma
85
+ if array_open == 0 && brace_open == 0 && paren_open == 0
86
+ yield(attr_tokens)
87
+ attr_tokens = []
88
+ next
89
+ end
90
+ when :on_lbracket
91
+ array_open += 1
92
+ when :on_rbracket
93
+ array_open -= 1
94
+ when :on_lbrace
95
+ brace_open += 1
96
+ when :on_rbrace
97
+ brace_open -= 1
98
+ when :on_lparen
99
+ paren_open += 1
100
+ when :on_rparen
101
+ paren_open -= 1
102
+ when :on_sp
103
+ next if attr_tokens.empty?
104
+ end
105
+
106
+ attr_tokens << token
107
+ end
108
+ yield(attr_tokens) unless attr_tokens.empty?
109
+ end
110
+ end
111
+ end
112
+ end
@@ -4,10 +4,6 @@ module Haml
4
4
  # It's called from within the precompiled code,
5
5
  # and helps reduce the amount of processing done within `instance_eval`ed code.
6
6
  class Buffer
7
- ID_KEY = 'id'.freeze
8
- CLASS_KEY = 'class'.freeze
9
- DATA_KEY = 'data'.freeze
10
-
11
7
  include Haml::Helpers
12
8
  include Haml::Util
13
9
 
@@ -94,7 +90,8 @@ module Haml
94
90
  def initialize(upper = nil, options = {})
95
91
  @active = true
96
92
  @upper = upper
97
- @options = options
93
+ @options = Options.buffer_defaults
94
+ @options = @options.merge(options) unless options.empty?
98
95
  @buffer = new_encoded_string
99
96
  @tabulation = 0
100
97
 
@@ -131,82 +128,24 @@ module Haml
131
128
  @real_tabs += tab_change
132
129
  end
133
130
 
134
- Haml::Util.def_static_method(self, :format_script, [:result],
135
- :preserve_script, :in_tag, :preserve_tag, :escape_html,
136
- :nuke_inner_whitespace, :interpolated, :ugly, <<RUBY)
137
- <% # Escape HTML here so that the safety of the string is preserved in Rails
138
- result_name = escape_html ? "html_escape(result.to_s)" : "result.to_s" %>
139
- <% unless ugly %>
140
- # If we're interpolated,
141
- # then the custom tabulation is handled in #push_text.
142
- # The easiest way to avoid it here is to reset @tabulation.
143
- <% if interpolated %>
144
- old_tabulation = @tabulation
145
- @tabulation = 0
146
- <% end %>
147
-
148
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
149
- tabulation = @real_tabs
150
- <% end %>
151
- result = <%= result_name %>.<% if nuke_inner_whitespace %>strip<% else %>rstrip<% end %>
152
- <% else %>
153
- result = <%= result_name %><% if nuke_inner_whitespace %>.strip<% end %>
154
- <% end %>
155
-
156
- <% if preserve_tag %>
157
- result = Haml::Helpers.preserve(result)
158
- <% elsif preserve_script %>
159
- result = Haml::Helpers.find_and_preserve(result, options[:preserve])
160
- <% end %>
161
-
162
- <% if ugly %>
163
- fix_textareas!(result) if toplevel? && result.include?('<textarea')
164
- return result
165
- <% else %>
166
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
167
- has_newline = result.include?("\\n")
168
- <% end %>
169
-
170
- <% if in_tag && !nuke_inner_whitespace %>
171
- <% unless preserve_tag %> if !has_newline <% end %>
172
- @real_tabs -= 1
173
- <% if interpolated %> @tabulation = old_tabulation <% end %>
174
- return result
175
- <% unless preserve_tag %> end <% end %>
176
- <% end %>
177
-
178
- <% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
179
- # Precompiled tabulation may be wrong
180
- <% if !interpolated && !in_tag %>
181
- result = tabs + result if @tabulation > 0
182
- <% end %>
183
-
184
- if has_newline
185
- result.gsub! "\\n", "\\n" + tabs(tabulation)
131
+ # the number of arguments here is insane, but passing in an options hash instead of named arguments
132
+ # causes a significant performance regression
133
+ def format_script(result, preserve_script, in_tag, preserve_tag, escape_html, nuke_inner_whitespace, interpolated)
134
+ result_name = escape_html ? html_escape(result.to_s) : result.to_s
186
135
 
187
- # Add tabulation if it wasn't precompiled
188
- <% if in_tag && !nuke_inner_whitespace %> result = tabs(tabulation) + result <% end %>
189
- end
190
-
191
- fix_textareas!(result) if toplevel? && result.include?('<textarea')
192
-
193
- <% if in_tag && !nuke_inner_whitespace %>
194
- result = "\\n\#{result}\\n\#{tabs(tabulation-1)}"
195
- @real_tabs -= 1
196
- <% end %>
197
- <% if interpolated %> @tabulation = old_tabulation <% end %>
198
- result
199
- <% end %>
200
- <% end %>
201
- RUBY
136
+ result = nuke_inner_whitespace ? result_name.strip : result_name
137
+ result = preserve(result, preserve_script, preserve_tag)
138
+ fix_textareas!(result) if toplevel? && result.include?('<textarea')
139
+ result
140
+ end
202
141
 
203
142
  def attributes(class_id, obj_ref, *attributes_hashes)
204
143
  attributes = class_id
205
144
  attributes_hashes.each do |old|
206
- self.class.merge_attrs(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
145
+ AttributeBuilder.merge_attributes!(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
207
146
  end
208
- self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
209
- Compiler.build_attributes(
147
+ AttributeBuilder.merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
148
+ AttributeBuilder.build_attributes(
210
149
  html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
211
150
  end
212
151
 
@@ -221,55 +160,14 @@ RUBY
221
160
  buffer << buffer.slice!(capture_position..-1).rstrip
222
161
  end
223
162
 
224
- # Merges two attribute hashes.
225
- # This is the same as `to.merge!(from)`,
226
- # except that it merges id, class, and data attributes.
227
- #
228
- # ids are concatenated with `"_"`,
229
- # and classes are concatenated with `" "`.
230
- # data hashes are simply merged.
231
- #
232
- # Destructively modifies both `to` and `from`.
233
- #
234
- # @param to [{String => String}] The attribute hash to merge into
235
- # @param from [{String => #to_s}] The attribute hash to merge from
236
- # @return [{String => String}] `to`, after being merged
237
- def self.merge_attrs(to, from)
238
- from[ID_KEY] = Compiler.filter_and_join(from[ID_KEY], '_') if from[ID_KEY]
239
- if to[ID_KEY] && from[ID_KEY]
240
- to[ID_KEY] << "_#{from.delete(ID_KEY)}"
241
- elsif to[ID_KEY] || from[ID_KEY]
242
- from[ID_KEY] ||= to[ID_KEY]
243
- end
244
-
245
- from[CLASS_KEY] = Compiler.filter_and_join(from[CLASS_KEY], ' ') if from[CLASS_KEY]
246
- if to[CLASS_KEY] && from[CLASS_KEY]
247
- # Make sure we don't duplicate class names
248
- from[CLASS_KEY] = (from[CLASS_KEY].to_s.split(' ') | to[CLASS_KEY].split(' ')).sort.join(' ')
249
- elsif to[CLASS_KEY] || from[CLASS_KEY]
250
- from[CLASS_KEY] ||= to[CLASS_KEY]
251
- end
252
-
253
- from.keys.each do |key|
254
- next unless from[key].kind_of?(Hash) || to[key].kind_of?(Hash)
255
-
256
- from_data = from.delete(key)
257
- # forces to_data & from_data into a hash
258
- from_data = { nil => from_data } if from_data && !from_data.is_a?(Hash)
259
- to[key] = { nil => to[key] } if to[key] && !to[key].is_a?(Hash)
260
-
261
- if from_data && !to[key]
262
- to[key] = from_data
263
- elsif from_data && to[key]
264
- to[key].merge! from_data
265
- end
266
- end
163
+ private
267
164
 
268
- to.merge!(from)
165
+ def preserve(result, preserve_script, preserve_tag)
166
+ return Haml::Helpers.preserve(result) if preserve_tag
167
+ return Haml::Helpers.find_and_preserve(result, options[:preserve]) if preserve_script
168
+ result
269
169
  end
270
170
 
271
- private
272
-
273
171
  # Works like #{find_and_preserve}, but allows the first newline after a
274
172
  # preserved opening tag to remain unencoded, and then outdents the content.
275
173
  # This change was motivated primarily by the change in Rails 3.2.3 to emit
@@ -279,16 +177,16 @@ RUBY
279
177
  # @since Haml 4.0.1
280
178
  # @private
281
179
  def fix_textareas!(input)
282
- pattern = /([ ]*)<(textarea)([^>]*)>(\n|&#x000A;)(.*?)(<\/\2>)/im
180
+ pattern = /<(textarea)([^>]*)>(\n|&#x000A;)(.*?)<\/textarea>/im
283
181
  input.gsub!(pattern) do |s|
284
182
  match = pattern.match(s)
285
- content = match[5]
286
- if match[4] == '&#x000A;'
183
+ content = match[4]
184
+ if match[3] == '&#x000A;'
287
185
  content.sub!(/\A /, '&#x0020;')
288
186
  else
289
187
  content.sub!(/\A[ ]*/, '')
290
188
  end
291
- "#{match[1]}<#{match[2]}#{match[3]}>\n#{content}</#{match[2]}>"
189
+ "<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
292
190
  end
293
191
  end
294
192
 
@@ -331,7 +229,7 @@ RUBY
331
229
  id = "#{ prefix }_#{ id }"
332
230
  end
333
231
 
334
- {ID_KEY => id, CLASS_KEY => class_name}
232
+ { 'id'.freeze => id, 'class'.freeze => class_name }
335
233
  end
336
234
 
337
235
  # Changes a word from camel case to underscores.
@@ -1,3 +1,7 @@
1
+ require 'haml/attribute_builder'
2
+ require 'haml/attribute_compiler'
3
+ require 'haml/temple_line_counter'
4
+
1
5
  module Haml
2
6
  class Compiler
3
7
  include Haml::Util
@@ -5,11 +9,16 @@ module Haml
5
9
  attr_accessor :options
6
10
 
7
11
  def initialize(options)
8
- @options = options
9
- @output_tabs = 0
12
+ @options = Options.wrap(options)
10
13
  @to_merge = []
11
- @precompiled = ''
14
+ @temple = [:multi]
12
15
  @node = nil
16
+ @attribute_compiler = AttributeCompiler.new(@options)
17
+ end
18
+
19
+ def call(node)
20
+ compile(node)
21
+ @temple
13
22
  end
14
23
 
15
24
  def compile(node)
@@ -23,73 +32,16 @@ module Haml
23
32
  @node = parent
24
33
  end
25
34
 
26
- # The source code that is evaluated to produce the Haml document.
27
- #
28
- # This is automatically converted to the correct encoding
29
- # (see {file:REFERENCE.md#encodings the `:encoding` option}).
30
- #
31
- # @return [String]
32
- def precompiled
33
- encoding = Encoding.find(@options.encoding)
34
- return @precompiled.force_encoding(encoding) if encoding == Encoding::ASCII_8BIT
35
- return @precompiled.encode(encoding)
36
- end
37
-
38
- def precompiled_with_return_value
39
- "#{precompiled};#{precompiled_method_return_value}"
40
- end
41
-
42
- # Returns the precompiled string with the preamble and postamble.
43
- #
44
- # Initializes to ActionView::OutputBuffer when available; this is necessary
45
- # to avoid ordering issues with partial layouts in Rails. If not available,
46
- # initializes to nil.
47
- def precompiled_with_ambles(local_names)
48
- preamble = <<END.tr("\n", ';')
49
- begin
50
- extend Haml::Helpers
51
- _hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, #{options.for_buffer.inspect})
52
- _erbout = _hamlout.buffer
53
- @output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil
54
- END
55
- postamble = <<END.tr("\n", ';')
56
- #{precompiled_method_return_value}
57
- ensure
58
- @haml_buffer = @haml_buffer.upper if @haml_buffer
59
- end
60
- END
61
- "#{preamble}#{locals_code(local_names)}#{precompiled}#{postamble}"
62
- end
63
-
64
35
  private
65
36
 
66
- # Returns the string used as the return value of the precompiled method.
67
- # This method exists so it can be monkeypatched to return modified values.
68
- def precompiled_method_return_value
69
- "_erbout"
70
- end
71
-
72
- def locals_code(names)
73
- names = names.keys if Hash === names
74
-
75
- names.map do |name|
76
- # Can't use || because someone might explicitly pass in false with a symbol
77
- sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
78
- str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
79
- "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local};"
80
- end.join
81
- end
82
-
83
37
  def compile_root
84
- @dont_indent_next_line = @dont_tab_up_next_text = false
85
38
  @output_line = 1
86
- @indentation = nil
87
39
  yield if block_given?
88
40
  flush_merged_text
89
41
  end
90
42
 
91
43
  def compile_plain
92
- push_text @node.value[:text]
44
+ push_text("#{@node.value[:text]}\n")
93
45
  end
94
46
 
95
47
  def nuke_inner_whitespace?(node)
@@ -116,10 +68,6 @@ END
116
68
  keyword = @node.value[:keyword]
117
69
 
118
70
  if block_given?
119
- # Store these values because for conditional statements,
120
- # we want to restore them for each branch
121
- @node.value[:dont_indent_next_line] = @dont_indent_next_line
122
- @node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
123
71
  yield
124
72
  push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
125
73
  elsif keyword == "end"
@@ -129,10 +77,6 @@ END
129
77
  @node.parent.value[:dont_push_end] = true
130
78
  end
131
79
  # Don't restore dont_* for end because it isn't a conditional branch.
132
- elsif Parser::MID_BLOCK_KEYWORDS.include?(keyword)
133
- # Restore dont_* for this conditional branch
134
- @dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
135
- @dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
136
80
  end
137
81
  end
138
82
 
@@ -144,15 +88,11 @@ END
144
88
  # Get rid of whitespace outside of the tag if we need to
145
89
  rstrip_buffer! if t[:nuke_outer_whitespace]
146
90
 
147
- dont_indent_next_line =
148
- (t[:nuke_outer_whitespace] && !block_given?) ||
149
- (t[:nuke_inner_whitespace] && block_given?)
150
-
151
91
  if @options.suppress_eval
152
92
  object_ref = :nil
153
93
  parse = false
154
94
  value = t[:parse] ? nil : t[:value]
155
- attributes_hashes = {}
95
+ attributes_hashes = []
156
96
  preserve_script = false
157
97
  else
158
98
  object_ref = t[:object_ref]
@@ -162,69 +102,36 @@ END
162
102
  preserve_script = t[:preserve_script]
163
103
  end
164
104
 
165
- # Check if we can render the tag directly to text and not process it in the buffer
166
- if (object_ref == :nil) && attributes_hashes.empty? && !preserve_script
167
- tag_closed = !block_given? && !t[:self_closing] && !parse
168
-
169
- open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
170
- if tag_closed
171
- open_tag << "#{value}</#{t[:name]}>"
172
- open_tag << "\n" unless t[:nuke_outer_whitespace]
173
- elsif !(parse || t[:nuke_inner_whitespace] ||
174
- (t[:self_closing] && t[:nuke_outer_whitespace]))
175
- open_tag << "\n"
176
- end
177
-
178
- push_merged_text(open_tag,
179
- tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
180
- !t[:nuke_outer_whitespace])
105
+ if @options[:trace]
106
+ t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
107
+ end
181
108
 
182
- @dont_indent_next_line = dont_indent_next_line
183
- return if tag_closed
184
- else
185
- if attributes_hashes.empty?
186
- attributes_hashes = ''
187
- elsif attributes_hashes.size == 1
188
- attributes_hashes = ", #{attributes_hashes.first}"
109
+ push_text("<#{t[:name]}")
110
+ push_temple(@attribute_compiler.compile(t[:attributes], object_ref, attributes_hashes))
111
+ push_text(
112
+ if t[:self_closing] && @options.xhtml?
113
+ " />#{"\n" unless t[:nuke_outer_whitespace]}"
189
114
  else
190
- attributes_hashes = ", #{attributes_hashes.join(", ")}"
191
- end
192
-
193
- push_merged_text "<#{t[:name]}", 0, !t[:nuke_outer_whitespace]
194
- push_generated_script(
195
- "_hamlout.attributes(#{inspect_obj(t[:attributes])}, #{object_ref}#{attributes_hashes})")
196
- concat_merged_text(
197
- if t[:self_closing] && @options.xhtml?
198
- " />#{"\n" unless t[:nuke_outer_whitespace]}"
199
- else
200
- ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
201
- end)
202
-
203
- if value && !parse
204
- concat_merged_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
205
- elsif !t[:nuke_inner_whitespace] && !t[:self_closing]
206
- @to_merge << [:text, '', 1]
115
+ ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
207
116
  end
117
+ )
208
118
 
209
- @dont_indent_next_line = dont_indent_next_line
119
+ if value && !parse
120
+ push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
210
121
  end
211
122
 
212
123
  return if t[:self_closing]
213
124
 
214
125
  if value.nil?
215
- @output_tabs += 1 unless t[:nuke_inner_whitespace]
216
126
  yield if block_given?
217
- @output_tabs -= 1 unless t[:nuke_inner_whitespace]
218
127
  rstrip_buffer! if t[:nuke_inner_whitespace]
219
- push_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}",
220
- t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
221
- @dont_indent_next_line = t[:nuke_outer_whitespace]
128
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
222
129
  return
223
130
  end
224
131
 
225
132
  if parse
226
133
  push_script(value, t.merge(:in_tag => true))
227
- concat_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
134
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
228
135
  end
229
136
  end
230
137
 
@@ -236,22 +143,27 @@ END
236
143
 
237
144
  close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
238
145
 
239
- # Render it statically if possible
240
146
  unless block_given?
241
- push_text("#{open} #{@node.value[:text]} #{close}")
147
+ push_text("#{open} ")
148
+
149
+ if @node.value[:parse]
150
+ push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
151
+ else
152
+ push_text(@node.value[:text])
153
+ end
154
+
155
+ push_text(" #{close}\n")
242
156
  return
243
157
  end
244
158
 
245
- push_text(open, 1)
246
- @output_tabs += 1
159
+ push_text("#{open}\n")
247
160
  yield if block_given?
248
- @output_tabs -= 1
249
- push_text(close, -1)
161
+ push_text("#{close}\n")
250
162
  end
251
163
 
252
164
  def compile_doctype
253
165
  doctype = text_for_doctype
254
- push_text doctype if doctype
166
+ push_text("#{doctype}\n") if doctype
255
167
  end
256
168
 
257
169
  def compile_filter
@@ -308,83 +220,58 @@ END
308
220
  flush_merged_text
309
221
  return if can_suppress && @options.suppress_eval?
310
222
  newline = (text == "end") ? ";" : "\n"
311
- @precompiled << "#{resolve_newlines}#{text}#{newline}"
223
+ @temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
312
224
  @output_line = @output_line + text.count("\n") + newline.count("\n")
313
225
  end
314
226
 
315
- # Adds `text` to `@buffer` with appropriate tabulation
316
- # without parsing it.
317
- def push_merged_text(text, tab_change = 0, indent = true)
318
- text = !indent || @dont_indent_next_line || @options.ugly ? text : "#{' ' * @output_tabs}#{text}"
319
- @to_merge << [:text, text, tab_change]
320
- @dont_indent_next_line = false
227
+ # Adds `text` to `@buffer`.
228
+ def push_text(text)
229
+ @to_merge << [:text, text]
321
230
  end
322
231
 
323
- # Concatenate `text` to `@buffer` without tabulation.
324
- def concat_merged_text(text)
325
- @to_merge << [:text, text, 0]
326
- end
327
-
328
- def push_text(text, tab_change = 0)
329
- push_merged_text("#{text}\n", tab_change)
232
+ def push_temple(temple)
233
+ flush_merged_text
234
+ newlines = resolve_newlines
235
+ @temple << [:code, newlines] unless newlines.empty?
236
+ @temple << temple
237
+ @output_line += TempleLineCounter.count_lines(temple)
330
238
  end
331
239
 
332
240
  def flush_merged_text
333
241
  return if @to_merge.empty?
334
242
 
335
- mtabs = 0
336
- @to_merge.map! do |type, val, tabs|
243
+ @to_merge.each do |type, val|
337
244
  case type
338
245
  when :text
339
- mtabs += tabs
340
- inspect_obj(val)[1...-1]
246
+ @temple << [:static, val]
341
247
  when :script
342
- if mtabs != 0 && !@options.ugly
343
- val = "_hamlout.adjust_tabs(#{mtabs}); " + val
344
- end
345
- mtabs = 0
346
- "\#{#{val}}"
248
+ @temple << [:dynamic, val]
347
249
  else
348
250
  raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
349
251
  end
350
252
  end
351
- str = @to_merge.join
352
253
 
353
- unless str.empty?
354
- @precompiled <<
355
- if @options.ugly
356
- "_hamlout.buffer << \"#{str}\";"
357
- else
358
- "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
359
- end
360
- end
361
254
  @to_merge = []
362
- @dont_tab_up_next_text = false
363
255
  end
364
256
 
365
257
  # Causes `text` to be evaluated in the context of
366
258
  # the scope object and the result to be added to `@buffer`.
367
259
  #
368
- # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
260
+ # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
369
261
  # the result before it is added to `@buffer`
370
262
  def push_script(text, opts = {})
371
263
  return if @options.suppress_eval?
372
264
 
373
265
  args = [:preserve_script, :in_tag, :preserve_tag, :escape_html, :nuke_inner_whitespace]
374
- args.map! {|name| opts[name]}
375
- args << !block_given? << @options.ugly
376
-
377
- no_format = @options.ugly &&
378
- !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
379
- output_expr = "(#{text}\n)"
380
- static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
266
+ args.map! {|name| !!opts[name]}
267
+ args << !block_given?
381
268
 
382
- # Prerender tabulation unless we're in a tag
383
- push_merged_text '' unless opts[:in_tag]
269
+ no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
384
270
 
385
271
  unless block_given?
386
- push_generated_script(no_format ? "#{text}\n" : "#{static_method}(#{output_expr});")
387
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
272
+ format_script_method = "_hamlout.format_script((#{text}\n),#{args.join(',')});"
273
+ push_generated_script(no_format ? "(#{text}\n).to_s" : format_script_method)
274
+ push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
388
275
  return
389
276
  end
390
277
 
@@ -392,8 +279,8 @@ END
392
279
  push_silent "haml_temp = #{text}"
393
280
  yield
394
281
  push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
395
- @precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}"
396
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options.ugly
282
+ format_script_method = "_hamlout.format_script(haml_temp,#{args.join(',')});"
283
+ @temple << [:dynamic, no_format ? "haml_temp.to_s;" : format_script_method]
397
284
  end
398
285
 
399
286
  def push_generated_script(text)
@@ -401,111 +288,6 @@ END
401
288
  @output_line += text.count("\n")
402
289
  end
403
290
 
404
- # This is a class method so it can be accessed from Buffer.
405
- def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
406
- # @TODO this is an absolutely ridiculous amount of arguments. At least
407
- # some of this needs to be moved into an instance method.
408
- quote_escape = attr_wrapper == '"' ? "&#x0022;" : "&#x0027;"
409
- other_quote_char = attr_wrapper == '"' ? "'" : '"'
410
- join_char = hyphenate_data_attrs ? '-' : '_'
411
-
412
- attributes.each do |key, value|
413
- if value.is_a?(Hash)
414
- data_attributes = attributes.delete(key)
415
- data_attributes = flatten_data_attributes(data_attributes, '', join_char)
416
- data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
417
- attributes = data_attributes.merge(attributes)
418
- end
419
- end
420
-
421
- result = attributes.collect do |attr, value|
422
- next if value.nil?
423
-
424
- value = filter_and_join(value, ' ') if attr == 'class'
425
- value = filter_and_join(value, '_') if attr == 'id'
426
-
427
- if value == true
428
- next " #{attr}" if is_html
429
- next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
430
- elsif value == false
431
- next
432
- end
433
-
434
- escaped =
435
- if escape_attrs == :once
436
- Haml::Helpers.escape_once(value.to_s)
437
- elsif escape_attrs
438
- Haml::Helpers.html_escape(value.to_s)
439
- else
440
- value.to_s
441
- end
442
- value = Haml::Helpers.preserve(escaped)
443
- if escape_attrs
444
- # We want to decide whether or not to escape quotes
445
- value.gsub!(/&quot;|&#x0022;/, '"')
446
- this_attr_wrapper = attr_wrapper
447
- if value.include? attr_wrapper
448
- if value.include? other_quote_char
449
- value.gsub!(attr_wrapper, quote_escape)
450
- else
451
- this_attr_wrapper = other_quote_char
452
- end
453
- end
454
- else
455
- this_attr_wrapper = attr_wrapper
456
- end
457
- " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
458
- end
459
- result.compact!
460
- result.sort!
461
- result.join
462
- end
463
-
464
- def self.filter_and_join(value, separator)
465
- return '' if (value.respond_to?(:empty?) && value.empty?)
466
-
467
- if value.is_a?(Array)
468
- value.flatten!
469
- value.map! {|item| item ? item.to_s : nil}
470
- value.compact!
471
- value = value.join(separator)
472
- else
473
- value = value ? value.to_s : nil
474
- end
475
- !value.nil? && !value.empty? && value
476
- end
477
-
478
- def self.build_data_keys(data_hash, hyphenate, attr_name="data")
479
- Hash[data_hash.map do |name, value|
480
- if name == nil
481
- [attr_name, value]
482
- elsif hyphenate
483
- ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
484
- else
485
- ["#{attr_name}-#{name}", value]
486
- end
487
- end]
488
- end
489
-
490
- def self.flatten_data_attributes(data, key, join_char, seen = [])
491
- return {key => data} unless data.is_a?(Hash)
492
-
493
- return {key => nil} if seen.include? data.object_id
494
- seen << data.object_id
495
-
496
- data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
497
- joined = key == '' ? k : [key, k].join(join_char)
498
- hash.merge! flatten_data_attributes(v, joined, join_char, seen)
499
- end
500
- end
501
-
502
- def prerender_tag(name, self_close, attributes)
503
- # TODO: consider just passing in the damn options here
504
- attributes_string = Compiler.build_attributes(
505
- @options.html?, @options.attr_wrapper, @options.escape_attrs, @options.hyphenate_data_attrs, attributes)
506
- "<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
507
- end
508
-
509
291
  def resolve_newlines
510
292
  diff = @node.line - @output_line
511
293
  return "" if diff <= 0
@@ -519,7 +301,6 @@ END
519
301
  last = @to_merge[index]
520
302
  if last.nil?
521
303
  push_silent("_hamlout.rstrip!", false)
522
- @dont_tab_up_next_text = true
523
304
  return
524
305
  end
525
306