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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +36 -6
- data/FAQ.md +4 -14
- data/MIT-LICENSE +1 -1
- data/README.md +81 -48
- data/REFERENCE.md +86 -50
- data/Rakefile +28 -41
- data/lib/haml/attribute_builder.rb +163 -0
- data/lib/haml/attribute_compiler.rb +214 -0
- data/lib/haml/attribute_parser.rb +112 -0
- data/lib/haml/buffer.rb +24 -126
- data/lib/haml/compiler.rb +62 -281
- data/lib/haml/engine.rb +16 -23
- data/lib/haml/error.rb +2 -0
- data/lib/haml/escapable.rb +48 -0
- data/lib/haml/exec.rb +23 -12
- data/lib/haml/filters.rb +3 -4
- data/lib/haml/generator.rb +36 -0
- data/lib/haml/helpers.rb +61 -48
- data/lib/haml/helpers/action_view_extensions.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +32 -50
- data/lib/haml/helpers/safe_erubi_template.rb +26 -0
- data/lib/haml/helpers/safe_erubis_template.rb +2 -0
- data/lib/haml/helpers/xss_mods.rb +17 -12
- data/lib/haml/options.rb +32 -36
- data/lib/haml/parser.rb +61 -38
- data/lib/haml/{template/plugin.rb → plugin.rb} +5 -2
- data/lib/haml/railtie.rb +14 -6
- data/lib/haml/template.rb +11 -6
- data/lib/haml/temple_engine.rb +119 -0
- data/lib/haml/temple_line_counter.rb +28 -0
- data/lib/haml/util.rb +17 -112
- data/lib/haml/version.rb +1 -1
- data/test/attribute_parser_test.rb +105 -0
- data/test/engine_test.rb +202 -106
- data/test/filters_test.rb +32 -19
- data/test/gemfiles/Gemfile.rails-4.0.x +7 -1
- data/test/gemfiles/Gemfile.rails-4.0.x.lock +57 -71
- data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
- data/test/gemfiles/Gemfile.rails-4.2.x +5 -0
- data/test/gemfiles/Gemfile.rails-5.0.x +4 -0
- data/test/helper_test.rb +156 -109
- data/test/options_test.rb +21 -0
- data/test/parser_test.rb +49 -4
- data/test/results/eval_suppressed.xhtml +4 -4
- data/test/results/helpers.xhtml +43 -41
- data/test/results/helpful.xhtml +6 -3
- data/test/results/just_stuff.xhtml +21 -20
- data/test/results/list.xhtml +9 -9
- data/test/results/nuke_inner_whitespace.xhtml +22 -22
- data/test/results/nuke_outer_whitespace.xhtml +84 -92
- data/test/results/original_engine.xhtml +17 -17
- data/test/results/partial_layout.xhtml +4 -3
- data/test/results/partial_layout_erb.xhtml +4 -3
- data/test/results/partials.xhtml +11 -10
- data/test/results/silent_script.xhtml +63 -63
- data/test/results/standard.xhtml +156 -159
- data/test/results/tag_parsing.xhtml +19 -19
- data/test/results/very_basic.xhtml +2 -2
- data/test/results/whitespace_handling.xhtml +77 -76
- data/test/template_test.rb +21 -48
- data/test/template_test_helper.rb +38 -0
- data/test/templates/just_stuff.haml +1 -0
- data/test/templates/standard_ugly.haml +1 -0
- data/test/temple_line_counter_test.rb +40 -0
- data/test/test_helper.rb +10 -10
- data/test/util_test.rb +1 -48
- metadata +49 -35
- data/lib/haml/temple.rb +0 -85
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -4
- data/test/templates/_av_partial_1_ugly.haml +0 -9
- data/test/templates/_av_partial_2_ugly.haml +0 -5
- data/test/templates/action_view_ugly.haml +0 -47
- 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
|
data/lib/haml/buffer.rb
CHANGED
@@ -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 =
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
145
|
+
AttributeBuilder.merge_attributes!(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
|
207
146
|
end
|
208
|
-
|
209
|
-
|
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
|
-
|
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
|
-
|
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 =
|
180
|
+
pattern = /<(textarea)([^>]*)>(\n|
)(.*?)<\/textarea>/im
|
283
181
|
input.gsub!(pattern) do |s|
|
284
182
|
match = pattern.match(s)
|
285
|
-
content = match[
|
286
|
-
if match[
|
183
|
+
content = match[4]
|
184
|
+
if match[3] == '
'
|
287
185
|
content.sub!(/\A /, ' ')
|
288
186
|
else
|
289
187
|
content.sub!(/\A[ ]*/, '')
|
290
188
|
end
|
291
|
-
"
|
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
|
-
{
|
232
|
+
{ 'id'.freeze => id, 'class'.freeze => class_name }
|
335
233
|
end
|
336
234
|
|
337
235
|
# Changes a word from camel case to underscores.
|
data/lib/haml/compiler.rb
CHANGED
@@ -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
|
-
@
|
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
|
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
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
if
|
186
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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}
|
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
|
246
|
-
@output_tabs += 1
|
159
|
+
push_text("#{open}\n")
|
247
160
|
yield if block_given?
|
248
|
-
|
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
|
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
|
-
@
|
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
|
316
|
-
|
317
|
-
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
336
|
-
@to_merge.map! do |type, val, tabs|
|
243
|
+
@to_merge.each do |type, val|
|
337
244
|
case type
|
338
245
|
when :text
|
339
|
-
|
340
|
-
inspect_obj(val)[1...-1]
|
246
|
+
@temple << [:static, val]
|
341
247
|
when :script
|
342
|
-
|
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#
|
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?
|
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
|
-
|
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
|
-
|
387
|
-
|
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
|
-
|
396
|
-
|
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 == '"' ? """ : "'"
|
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!(/"|"/, '"')
|
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
|
|