haml 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +117 -5
- data/FAQ.md +7 -17
- data/MIT-LICENSE +1 -1
- data/README.md +85 -42
- data/REFERENCE.md +181 -86
- data/Rakefile +47 -51
- data/lib/haml/attribute_builder.rb +163 -0
- data/lib/haml/attribute_compiler.rb +215 -0
- data/lib/haml/attribute_parser.rb +144 -0
- data/lib/haml/buffer.rb +38 -128
- data/lib/haml/compiler.rb +88 -295
- data/lib/haml/engine.rb +25 -41
- data/lib/haml/error.rb +3 -0
- data/lib/haml/escapable.rb +49 -0
- data/lib/haml/exec.rb +33 -19
- data/lib/haml/filters.rb +20 -24
- data/lib/haml/generator.rb +41 -0
- data/lib/haml/helpers/action_view_extensions.rb +3 -2
- data/lib/haml/helpers/action_view_mods.rb +44 -66
- data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
- data/lib/haml/helpers/safe_erubi_template.rb +27 -0
- data/lib/haml/helpers/safe_erubis_template.rb +16 -4
- data/lib/haml/helpers/xss_mods.rb +18 -12
- data/lib/haml/helpers.rb +122 -58
- data/lib/haml/options.rb +39 -46
- data/lib/haml/parser.rb +278 -217
- data/lib/haml/{template/plugin.rb → plugin.rb} +8 -15
- data/lib/haml/railtie.rb +21 -11
- data/lib/haml/sass_rails_filter.rb +17 -4
- data/lib/haml/template/options.rb +12 -2
- data/lib/haml/template.rb +12 -6
- data/lib/haml/temple_engine.rb +120 -0
- data/lib/haml/temple_line_counter.rb +29 -0
- data/lib/haml/util.rb +80 -199
- data/lib/haml/version.rb +2 -1
- data/lib/haml.rb +2 -1
- data/test/attribute_parser_test.rb +101 -0
- data/test/engine_test.rb +306 -176
- data/test/filters_test.rb +32 -19
- data/test/gemfiles/Gemfile.rails-4.0.x +11 -0
- data/test/gemfiles/Gemfile.rails-4.0.x.lock +87 -0
- 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 +282 -96
- data/test/options_test.rb +22 -0
- data/test/parser_test.rb +71 -4
- data/test/results/bemit.xhtml +4 -0
- 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 +56 -50
- data/test/template_test.rb +44 -53
- data/test/template_test_helper.rb +38 -0
- data/test/templates/_text_area_helper.html.haml +4 -0
- data/test/templates/bemit.haml +3 -0
- data/test/templates/just_stuff.haml +1 -0
- data/test/templates/partial_layout_erb.erb +1 -1
- data/test/templates/standard_ugly.haml +1 -0
- data/test/templates/with_bom.haml +1 -0
- data/test/temple_line_counter_test.rb +40 -0
- data/test/test_helper.rb +26 -12
- data/test/util_test.rb +6 -47
- metadata +88 -106
- data/lib/haml/helpers/rails_323_textarea_fix.rb +0 -24
- data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
- data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
- data/test/gemfiles/Gemfile.rails-master +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
data/lib/haml/parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
require 'strscan'
|
2
3
|
|
3
4
|
module Haml
|
@@ -71,7 +72,7 @@ module Haml
|
|
71
72
|
# foo.each do | bar |
|
72
73
|
# = bar
|
73
74
|
#
|
74
|
-
BLOCK_WITH_SPACES = /do
|
75
|
+
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
75
76
|
|
76
77
|
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
77
78
|
START_BLOCK_KEYWORDS = %w[if begin case unless]
|
@@ -80,18 +81,16 @@ module Haml
|
|
80
81
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
81
82
|
|
82
83
|
# The Regex that matches a Doctype command.
|
83
|
-
DOCTYPE_REGEX = /(\d(?:\.\d)?)
|
84
|
+
DOCTYPE_REGEX = /(\d(?:\.\d)?)?\s*([a-z]*)\s*([^ ]+)?/i
|
84
85
|
|
85
86
|
# The Regex that matches a literal string or symbol value
|
86
|
-
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((
|
87
|
+
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?!\\|\#\{|\#@|\#\$|\2).|\\.)*\2/
|
87
88
|
|
89
|
+
ID_KEY = 'id'.freeze
|
90
|
+
CLASS_KEY = 'class'.freeze
|
88
91
|
|
89
|
-
def initialize(
|
90
|
-
|
91
|
-
@template = (template.rstrip).split(/\r\n|\r|\n/) + [:eod, :eod]
|
92
|
-
@options = options
|
93
|
-
@flat = false
|
94
|
-
@index = 0
|
92
|
+
def initialize(options)
|
93
|
+
@options = Options.wrap(options)
|
95
94
|
# Record the indent levels of "if" statements to validate the subsequent
|
96
95
|
# elsif and else statements are indented at the appropriate level.
|
97
96
|
@script_level_stack = []
|
@@ -99,15 +98,27 @@ module Haml
|
|
99
98
|
@template_tabs = 0
|
100
99
|
end
|
101
100
|
|
102
|
-
def
|
101
|
+
def call(template)
|
102
|
+
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
103
|
+
# discard the last match which is always blank
|
104
|
+
match.pop
|
105
|
+
@template = match.each_with_index.map do |(full, whitespace, text), index|
|
106
|
+
Line.new(whitespace, text.rstrip, full, index, self, false)
|
107
|
+
end
|
108
|
+
# Append special end-of-document marker
|
109
|
+
@template << Line.new(nil, '-#', '-#', @template.size, self, true)
|
110
|
+
|
103
111
|
@root = @parent = ParseNode.new(:root)
|
104
|
-
@
|
112
|
+
@flat = false
|
113
|
+
@filter_buffer = nil
|
105
114
|
@indentation = nil
|
106
115
|
@line = next_line
|
107
116
|
|
108
117
|
raise SyntaxError.new(Error.message(:indenting_at_start), @line.index) if @line.tabs != 0
|
109
118
|
|
110
|
-
|
119
|
+
loop do
|
120
|
+
next_line
|
121
|
+
|
111
122
|
process_indent(@line) unless @line.text.empty?
|
112
123
|
|
113
124
|
if flat?
|
@@ -119,61 +130,66 @@ module Haml
|
|
119
130
|
end
|
120
131
|
|
121
132
|
@tab_up = nil
|
122
|
-
process_line(@line
|
123
|
-
if
|
133
|
+
process_line(@line) unless @line.text.empty?
|
134
|
+
if block_opened? || @tab_up
|
124
135
|
@template_tabs += 1
|
125
136
|
@parent = @parent.children.last
|
126
137
|
end
|
127
138
|
|
128
|
-
if
|
139
|
+
if !flat? && @next_line.tabs - @line.tabs > 1
|
129
140
|
raise SyntaxError.new(Error.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
|
130
141
|
end
|
131
142
|
|
132
143
|
@line = @next_line
|
133
144
|
end
|
134
|
-
|
135
145
|
# Close all the open tags
|
136
146
|
close until @parent.type == :root
|
137
147
|
@root
|
138
148
|
rescue Haml::Error => e
|
139
|
-
e.backtrace.unshift "#{@options
|
149
|
+
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
140
150
|
raise
|
141
151
|
end
|
142
152
|
|
153
|
+
def compute_tabs(line)
|
154
|
+
return 0 if line.text.empty? || !line.whitespace
|
155
|
+
|
156
|
+
if @indentation.nil?
|
157
|
+
@indentation = line.whitespace
|
158
|
+
|
159
|
+
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
160
|
+
raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
|
161
|
+
end
|
162
|
+
|
163
|
+
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
164
|
+
return 1
|
165
|
+
end
|
166
|
+
|
167
|
+
tabs = line.whitespace.length / @indentation.length
|
168
|
+
return tabs if line.whitespace == @indentation * tabs
|
169
|
+
return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
|
170
|
+
|
171
|
+
message = Error.message(:inconsistent_indentation,
|
172
|
+
human_indentation(line.whitespace),
|
173
|
+
human_indentation(@indentation)
|
174
|
+
)
|
175
|
+
raise SyntaxError.new(message, line.index)
|
176
|
+
end
|
143
177
|
|
144
178
|
private
|
145
179
|
|
146
180
|
# @private
|
147
|
-
class Line < Struct.new(:
|
181
|
+
class Line < Struct.new(:whitespace, :text, :full, :index, :parser, :eod)
|
148
182
|
alias_method :eod?, :eod
|
149
183
|
|
150
184
|
# @private
|
151
185
|
def tabs
|
152
|
-
|
153
|
-
|
154
|
-
break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
|
155
|
-
|
156
|
-
if @indentation.nil?
|
157
|
-
@indentation = whitespace
|
158
|
-
|
159
|
-
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
160
|
-
raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
|
161
|
-
end
|
162
|
-
|
163
|
-
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
164
|
-
break 1
|
165
|
-
end
|
166
|
-
|
167
|
-
tabs = whitespace.length / @indentation.length
|
168
|
-
break tabs if whitespace == @indentation * tabs
|
169
|
-
break @template_tabs + 1 if flat? && whitespace =~ /^#{@flat_spaces}/
|
186
|
+
@tabs ||= parser.compute_tabs(self)
|
187
|
+
end
|
170
188
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
raise SyntaxError.new(message, line.index)
|
176
|
-
end
|
189
|
+
def strip!(from)
|
190
|
+
self.text = text[from..-1]
|
191
|
+
self.text.lstrip!
|
192
|
+
self
|
177
193
|
end
|
178
194
|
end
|
179
195
|
|
@@ -185,9 +201,31 @@ module Haml
|
|
185
201
|
end
|
186
202
|
|
187
203
|
def inspect
|
188
|
-
|
189
|
-
|
190
|
-
|
204
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [String] new - Hash literal including dynamic values.
|
209
|
+
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
210
|
+
class DynamicAttributes < Struct.new(:new, :old)
|
211
|
+
def old=(value)
|
212
|
+
unless value =~ /\A{.*}\z/m
|
213
|
+
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
214
|
+
end
|
215
|
+
super
|
216
|
+
end
|
217
|
+
|
218
|
+
# This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
|
219
|
+
def to_literal
|
220
|
+
[new, stripped_old].compact.join(', ')
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
# For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
|
226
|
+
def stripped_old
|
227
|
+
return nil if old.nil?
|
228
|
+
old.sub!(/\A{/, '').sub!(/}\z/m, '')
|
191
229
|
end
|
192
230
|
end
|
193
231
|
|
@@ -196,44 +234,55 @@ module Haml
|
|
196
234
|
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
197
235
|
|
198
236
|
to_close = @template_tabs - line.tabs
|
199
|
-
to_close.times {|i| close unless to_close - 1 - i == 0 &&
|
237
|
+
to_close.times {|i| close unless to_close - 1 - i == 0 && continuation_script?(line.text)}
|
238
|
+
end
|
239
|
+
|
240
|
+
def continuation_script?(text)
|
241
|
+
text[0] == SILENT_SCRIPT && mid_block_keyword?(text)
|
242
|
+
end
|
243
|
+
|
244
|
+
def mid_block_keyword?(text)
|
245
|
+
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
200
246
|
end
|
201
247
|
|
202
248
|
# Processes a single line of Haml.
|
203
249
|
#
|
204
250
|
# This method doesn't return anything; it simply processes the line and
|
205
251
|
# adds the appropriate code to `@precompiled`.
|
206
|
-
def process_line(
|
207
|
-
|
208
|
-
|
209
|
-
case text[0]
|
210
|
-
when DIV_CLASS; push div(text)
|
252
|
+
def process_line(line)
|
253
|
+
case line.text[0]
|
254
|
+
when DIV_CLASS; push div(line)
|
211
255
|
when DIV_ID
|
212
|
-
return push plain(
|
213
|
-
push div(
|
214
|
-
when ELEMENT; push tag(
|
215
|
-
when COMMENT; push comment(text[1..-1].
|
256
|
+
return push plain(line) if %w[{ @ $].include?(line.text[1])
|
257
|
+
push div(line)
|
258
|
+
when ELEMENT; push tag(line)
|
259
|
+
when COMMENT; push comment(line.text[1..-1].lstrip)
|
216
260
|
when SANITIZE
|
217
|
-
return push plain(
|
218
|
-
return push script(
|
219
|
-
return push flat_script(
|
220
|
-
return push plain(
|
221
|
-
push plain(
|
261
|
+
return push plain(line.strip!(3), :escape_html) if line.text[1, 2] == '=='
|
262
|
+
return push script(line.strip!(2), :escape_html) if line.text[1] == SCRIPT
|
263
|
+
return push flat_script(line.strip!(2), :escape_html) if line.text[1] == FLAT_SCRIPT
|
264
|
+
return push plain(line.strip!(1), :escape_html) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
265
|
+
push plain(line)
|
222
266
|
when SCRIPT
|
223
|
-
return push plain(
|
224
|
-
|
225
|
-
|
226
|
-
when
|
227
|
-
when
|
267
|
+
return push plain(line.strip!(2)) if line.text[1] == SCRIPT
|
268
|
+
line.text = line.text[1..-1]
|
269
|
+
push script(line)
|
270
|
+
when FLAT_SCRIPT; push flat_script(line.strip!(1))
|
271
|
+
when SILENT_SCRIPT
|
272
|
+
return push haml_comment(line.text[2..-1]) if line.text[1] == SILENT_COMMENT
|
273
|
+
push silent_script(line)
|
274
|
+
when FILTER; push filter(line.text[1..-1].downcase)
|
228
275
|
when DOCTYPE
|
229
|
-
return push doctype(text) if text[0
|
230
|
-
return push plain(
|
231
|
-
return push script(
|
232
|
-
return push flat_script(
|
233
|
-
return push plain(
|
234
|
-
push plain(
|
235
|
-
when ESCAPE
|
236
|
-
|
276
|
+
return push doctype(line.text) if line.text[0, 3] == '!!!'
|
277
|
+
return push plain(line.strip!(3), false) if line.text[1, 2] == '=='
|
278
|
+
return push script(line.strip!(2), false) if line.text[1] == SCRIPT
|
279
|
+
return push flat_script(line.strip!(2), false) if line.text[1] == FLAT_SCRIPT
|
280
|
+
return push plain(line.strip!(1), false) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
281
|
+
push plain(line)
|
282
|
+
when ESCAPE
|
283
|
+
line.text = line.text[1..-1]
|
284
|
+
push plain(line)
|
285
|
+
else; push plain(line)
|
237
286
|
end
|
238
287
|
end
|
239
288
|
|
@@ -242,52 +291,47 @@ module Haml
|
|
242
291
|
keyword[0] || keyword[1]
|
243
292
|
end
|
244
293
|
|
245
|
-
def mid_block_keyword?(text)
|
246
|
-
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
247
|
-
end
|
248
|
-
|
249
294
|
def push(node)
|
250
295
|
@parent.children << node
|
251
296
|
node.parent = @parent
|
252
297
|
end
|
253
298
|
|
254
|
-
def plain(
|
299
|
+
def plain(line, escape_html = nil)
|
255
300
|
if block_opened?
|
256
301
|
raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
|
257
302
|
end
|
258
303
|
|
259
|
-
unless contains_interpolation?(text)
|
260
|
-
return ParseNode.new(:plain,
|
304
|
+
unless contains_interpolation?(line.text)
|
305
|
+
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
261
306
|
end
|
262
307
|
|
263
|
-
escape_html = @options
|
264
|
-
|
308
|
+
escape_html = @options.escape_html if escape_html.nil?
|
309
|
+
line.text = unescape_interpolation(line.text, escape_html)
|
310
|
+
script(line, false)
|
265
311
|
end
|
266
312
|
|
267
|
-
def script(
|
268
|
-
raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if text.empty?
|
269
|
-
|
270
|
-
escape_html = @options
|
313
|
+
def script(line, escape_html = nil, preserve = false)
|
314
|
+
raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if line.text.empty?
|
315
|
+
line = handle_ruby_multiline(line)
|
316
|
+
escape_html = @options.escape_html if escape_html.nil?
|
271
317
|
|
272
|
-
keyword = block_keyword(text)
|
318
|
+
keyword = block_keyword(line.text)
|
273
319
|
check_push_script_stack(keyword)
|
274
320
|
|
275
|
-
ParseNode.new(:script,
|
321
|
+
ParseNode.new(:script, line.index + 1, :text => line.text, :escape_html => escape_html,
|
276
322
|
:preserve => preserve, :keyword => keyword)
|
277
323
|
end
|
278
324
|
|
279
|
-
def flat_script(
|
280
|
-
raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if text.empty?
|
281
|
-
script(
|
325
|
+
def flat_script(line, escape_html = nil)
|
326
|
+
raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if line.text.empty?
|
327
|
+
script(line, escape_html, :preserve)
|
282
328
|
end
|
283
329
|
|
284
|
-
def silent_script(
|
285
|
-
|
330
|
+
def silent_script(line)
|
331
|
+
raise SyntaxError.new(Error.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
|
286
332
|
|
287
|
-
|
288
|
-
|
289
|
-
text = handle_ruby_multiline(text)
|
290
|
-
keyword = block_keyword(text)
|
333
|
+
line = handle_ruby_multiline(line)
|
334
|
+
keyword = block_keyword(line.text)
|
291
335
|
|
292
336
|
check_push_script_stack(keyword)
|
293
337
|
|
@@ -309,8 +353,8 @@ module Haml
|
|
309
353
|
end
|
310
354
|
end
|
311
355
|
|
312
|
-
ParseNode.new(:silent_script, @index,
|
313
|
-
:text => text[1..-1], :keyword => keyword)
|
356
|
+
ParseNode.new(:silent_script, @line.index + 1,
|
357
|
+
:text => line.text[1..-1], :keyword => keyword)
|
314
358
|
end
|
315
359
|
|
316
360
|
def check_push_script_stack(keyword)
|
@@ -324,18 +368,25 @@ module Haml
|
|
324
368
|
end
|
325
369
|
|
326
370
|
def haml_comment(text)
|
327
|
-
|
328
|
-
|
371
|
+
if filter_opened?
|
372
|
+
@flat = true
|
373
|
+
@filter_buffer = String.new
|
374
|
+
@filter_buffer << "#{text}\n" unless text.empty?
|
375
|
+
text = @filter_buffer
|
376
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
377
|
+
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
378
|
+
end
|
379
|
+
|
380
|
+
ParseNode.new(:haml_comment, @line.index + 1, :text => text)
|
329
381
|
end
|
330
382
|
|
331
383
|
def tag(line)
|
332
384
|
tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
333
|
-
nuke_inner_whitespace, action, value, last_line = parse_tag(line)
|
385
|
+
nuke_inner_whitespace, action, value, last_line = parse_tag(line.text)
|
334
386
|
|
335
|
-
preserve_tag = @options
|
387
|
+
preserve_tag = @options.preserve.include?(tag_name)
|
336
388
|
nuke_inner_whitespace ||= preserve_tag
|
337
|
-
|
338
|
-
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
389
|
+
escape_html = (action == '&' || (action != '!' && @options.escape_html))
|
339
390
|
|
340
391
|
case action
|
341
392
|
when '/'; self_closing = true
|
@@ -370,22 +421,20 @@ module Haml
|
|
370
421
|
end
|
371
422
|
|
372
423
|
attributes = Parser.parse_class_and_id(attributes)
|
373
|
-
|
424
|
+
dynamic_attributes = DynamicAttributes.new
|
374
425
|
|
375
426
|
if attributes_hashes[:new]
|
376
427
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
377
|
-
|
378
|
-
|
428
|
+
AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
429
|
+
dynamic_attributes.new = attributes_hash
|
379
430
|
end
|
380
431
|
|
381
432
|
if attributes_hashes[:old]
|
382
433
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
383
|
-
|
384
|
-
|
434
|
+
AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
435
|
+
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
385
436
|
end
|
386
437
|
|
387
|
-
attributes_list.compact!
|
388
|
-
|
389
438
|
raise SyntaxError.new(Error.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
390
439
|
raise SyntaxError.new(Error.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
391
440
|
raise SyntaxError.new(Error.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
@@ -394,56 +443,70 @@ module Haml
|
|
394
443
|
raise SyntaxError.new(Error.message(:illegal_nesting_line, tag_name), @next_line.index)
|
395
444
|
end
|
396
445
|
|
397
|
-
self_closing ||= !!(!block_opened? && value.empty? && @options
|
446
|
+
self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
|
398
447
|
value = nil if value.empty? && (block_opened? || self_closing)
|
399
|
-
|
448
|
+
line.text = value
|
449
|
+
line = handle_ruby_multiline(line) if parse
|
400
450
|
|
401
|
-
ParseNode.new(:tag,
|
402
|
-
:
|
451
|
+
ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
|
452
|
+
:dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
|
403
453
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
404
454
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
405
455
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
406
|
-
:preserve_script => preserve_script, :parse => parse, :value =>
|
456
|
+
:preserve_script => preserve_script, :parse => parse, :value => line.text)
|
407
457
|
end
|
408
458
|
|
409
459
|
# Renders a line that creates an XHTML tag and has an implicit div because of
|
410
460
|
# `.` or `#`.
|
411
461
|
def div(line)
|
412
|
-
|
462
|
+
line.text = "%div#{line.text}"
|
463
|
+
tag(line)
|
413
464
|
end
|
414
465
|
|
415
466
|
# Renders an XHTML comment.
|
416
|
-
def comment(
|
417
|
-
|
418
|
-
|
419
|
-
|
467
|
+
def comment(text)
|
468
|
+
if text[0..1] == '!['
|
469
|
+
revealed = true
|
470
|
+
text = text[1..-1]
|
471
|
+
else
|
472
|
+
revealed = false
|
473
|
+
end
|
474
|
+
|
475
|
+
conditional, text = balance(text, ?[, ?]) if text[0] == ?[
|
476
|
+
text.strip!
|
420
477
|
|
421
|
-
if
|
478
|
+
if contains_interpolation?(text)
|
479
|
+
parse = true
|
480
|
+
text = unescape_interpolation(text)
|
481
|
+
else
|
482
|
+
parse = false
|
483
|
+
end
|
484
|
+
|
485
|
+
if block_opened? && !text.empty?
|
422
486
|
raise SyntaxError.new(Haml::Error.message(:illegal_nesting_content), @next_line.index)
|
423
487
|
end
|
424
488
|
|
425
|
-
ParseNode.new(:comment, @index, :conditional => conditional, :text =>
|
489
|
+
ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
|
426
490
|
end
|
427
491
|
|
428
492
|
# Renders an XHTML doctype or XML shebang.
|
429
|
-
def doctype(
|
493
|
+
def doctype(text)
|
430
494
|
raise SyntaxError.new(Error.message(:illegal_nesting_header), @next_line.index) if block_opened?
|
431
|
-
version, type, encoding =
|
432
|
-
ParseNode.new(:doctype, @index, :version => version, :type => type, :encoding => encoding)
|
495
|
+
version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
496
|
+
ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
|
433
497
|
end
|
434
498
|
|
435
499
|
def filter(name)
|
436
500
|
raise Error.new(Error.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
|
437
501
|
|
438
|
-
@filter_buffer = String.new
|
439
|
-
|
440
502
|
if filter_opened?
|
441
503
|
@flat = true
|
504
|
+
@filter_buffer = String.new
|
442
505
|
# If we don't know the indentation by now, it'll be set in Line#tabs
|
443
506
|
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
444
507
|
end
|
445
508
|
|
446
|
-
ParseNode.new(:filter, @index, :name => name, :text => @filter_buffer)
|
509
|
+
ParseNode.new(:filter, @line.index + 1, :name => name, :text => @filter_buffer)
|
447
510
|
end
|
448
511
|
|
449
512
|
def close
|
@@ -453,13 +516,17 @@ module Haml
|
|
453
516
|
end
|
454
517
|
|
455
518
|
def close_filter(_)
|
456
|
-
|
457
|
-
@flat_spaces = nil
|
458
|
-
@filter_buffer = nil
|
519
|
+
close_flat_section
|
459
520
|
end
|
460
521
|
|
461
522
|
def close_haml_comment(_)
|
462
|
-
|
523
|
+
close_flat_section
|
524
|
+
end
|
525
|
+
|
526
|
+
def close_flat_section
|
527
|
+
@flat = false
|
528
|
+
@flat_spaces = nil
|
529
|
+
@filter_buffer = nil
|
463
530
|
end
|
464
531
|
|
465
532
|
def close_silent_script(node)
|
@@ -486,23 +553,33 @@ module Haml
|
|
486
553
|
# that can then be merged with another attributes hash.
|
487
554
|
def self.parse_class_and_id(list)
|
488
555
|
attributes = {}
|
489
|
-
|
556
|
+
return attributes if list.empty?
|
557
|
+
|
558
|
+
list.scan(/([#.])([-:_a-zA-Z0-9\@]+)/) do |type, property|
|
490
559
|
case type
|
491
560
|
when '.'
|
492
|
-
if attributes[
|
493
|
-
attributes[
|
561
|
+
if attributes[CLASS_KEY]
|
562
|
+
attributes[CLASS_KEY] += " "
|
494
563
|
else
|
495
|
-
attributes[
|
564
|
+
attributes[CLASS_KEY] = ""
|
496
565
|
end
|
497
|
-
attributes[
|
498
|
-
when '#'; attributes[
|
566
|
+
attributes[CLASS_KEY] += property
|
567
|
+
when '#'; attributes[ID_KEY] = property
|
499
568
|
end
|
500
569
|
end
|
501
570
|
attributes
|
502
571
|
end
|
503
572
|
|
573
|
+
# This method doesn't use Haml::AttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
|
574
|
+
# Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
|
575
|
+
#
|
576
|
+
# @param [String] text - Hash literal or text inside old attributes
|
577
|
+
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
504
578
|
def parse_static_hash(text)
|
505
579
|
attributes = {}
|
580
|
+
return attributes if text.empty?
|
581
|
+
|
582
|
+
text = text[1...-1] # strip brackets
|
506
583
|
scanner = StringScanner.new(text)
|
507
584
|
scanner.scan(/\s+/)
|
508
585
|
until scanner.eos?
|
@@ -516,20 +593,20 @@ module Haml
|
|
516
593
|
end
|
517
594
|
|
518
595
|
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
519
|
-
def parse_tag(
|
520
|
-
match =
|
521
|
-
raise SyntaxError.new(Error.message(:invalid_tag,
|
596
|
+
def parse_tag(text)
|
597
|
+
match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0]
|
598
|
+
raise SyntaxError.new(Error.message(:invalid_tag, text)) unless match
|
522
599
|
|
523
600
|
tag_name, attributes, rest = match
|
524
601
|
|
525
|
-
if attributes =~ /[
|
602
|
+
if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
|
526
603
|
raise SyntaxError.new(Error.message(:illegal_element))
|
527
604
|
end
|
528
605
|
|
529
606
|
new_attributes_hash = old_attributes_hash = last_line = nil
|
530
|
-
object_ref =
|
607
|
+
object_ref = :nil
|
531
608
|
attributes_hashes = {}
|
532
|
-
while rest
|
609
|
+
while rest && !rest.empty?
|
533
610
|
case rest[0]
|
534
611
|
when ?{
|
535
612
|
break if old_attributes_hash
|
@@ -540,38 +617,46 @@ module Haml
|
|
540
617
|
new_attributes_hash, rest, last_line = parse_new_attributes(rest)
|
541
618
|
attributes_hashes[:new] = new_attributes_hash
|
542
619
|
when ?[
|
543
|
-
break unless object_ref ==
|
620
|
+
break unless object_ref == :nil
|
544
621
|
object_ref, rest = balance(rest, ?[, ?])
|
545
622
|
else; break
|
546
623
|
end
|
547
624
|
end
|
548
625
|
|
549
|
-
if rest
|
626
|
+
if rest && !rest.empty?
|
550
627
|
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
551
|
-
nuke_whitespace
|
552
|
-
|
553
|
-
|
628
|
+
if nuke_whitespace
|
629
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
630
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
631
|
+
end
|
554
632
|
end
|
555
633
|
|
556
|
-
if @options
|
634
|
+
if @options.remove_whitespace
|
557
635
|
nuke_outer_whitespace = true
|
558
636
|
nuke_inner_whitespace = true
|
559
637
|
end
|
560
638
|
|
561
|
-
|
639
|
+
if value.nil?
|
640
|
+
value = ''
|
641
|
+
else
|
642
|
+
value.strip!
|
643
|
+
end
|
562
644
|
[tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
563
|
-
nuke_inner_whitespace, action, value, last_line || @index]
|
645
|
+
nuke_inner_whitespace, action, value, last_line || @line.index + 1]
|
564
646
|
end
|
565
647
|
|
566
|
-
|
567
|
-
|
568
|
-
|
648
|
+
# @return [String] attributes_hash - Hash literal starting with `{` and ending with `}`
|
649
|
+
# @return [String] rest
|
650
|
+
# @return [Integer] last_line
|
651
|
+
def parse_old_attributes(text)
|
652
|
+
text = text.dup
|
653
|
+
last_line = @line.index + 1
|
569
654
|
|
570
655
|
begin
|
571
|
-
attributes_hash, rest = balance(
|
656
|
+
attributes_hash, rest = balance(text, ?{, ?})
|
572
657
|
rescue SyntaxError => e
|
573
|
-
if
|
574
|
-
|
658
|
+
if text.strip[-1] == ?, && e.message == Error.message(:unbalanced_brackets)
|
659
|
+
text << "\n#{@next_line.text}"
|
575
660
|
last_line += 1
|
576
661
|
next_line
|
577
662
|
retry
|
@@ -580,14 +665,15 @@ module Haml
|
|
580
665
|
raise e
|
581
666
|
end
|
582
667
|
|
583
|
-
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
584
668
|
return attributes_hash, rest, last_line
|
585
669
|
end
|
586
670
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
671
|
+
# @return [Array<Hash,String,nil>] - [static_attributs (Hash), dynamic_attributes (nil or String starting with `{` and ending with `}`)]
|
672
|
+
# @return [String] rest
|
673
|
+
# @return [Integer] last_line
|
674
|
+
def parse_new_attributes(text)
|
675
|
+
scanner = StringScanner.new(text)
|
676
|
+
last_line = @line.index + 1
|
591
677
|
attributes = {}
|
592
678
|
|
593
679
|
scanner.scan(/\(\s*/)
|
@@ -596,14 +682,15 @@ module Haml
|
|
596
682
|
break if name.nil?
|
597
683
|
|
598
684
|
if name == false
|
599
|
-
|
685
|
+
scanned = Haml::Util.balance(text, ?(, ?))
|
686
|
+
text = scanned ? scanned.first : text
|
600
687
|
raise Haml::SyntaxError.new(Error.message(:invalid_attribute_list, text.inspect), last_line - 1)
|
601
688
|
end
|
602
689
|
attributes[name] = value
|
603
690
|
scanner.scan(/\s*/)
|
604
691
|
|
605
692
|
if scanner.eos?
|
606
|
-
|
693
|
+
text << " #{@next_line.text}"
|
607
694
|
last_line += 1
|
608
695
|
next_line
|
609
696
|
scanner.scan(/\s*/)
|
@@ -616,7 +703,7 @@ module Haml
|
|
616
703
|
if type == :static
|
617
704
|
static_attributes[name] = val
|
618
705
|
else
|
619
|
-
dynamic_attributes << inspect_obj(name)
|
706
|
+
dynamic_attributes << "#{inspect_obj(name)} => #{val},"
|
620
707
|
end
|
621
708
|
end
|
622
709
|
dynamic_attributes << "}"
|
@@ -651,35 +738,16 @@ module Haml
|
|
651
738
|
|
652
739
|
return name, [:static, content.first[1]] if content.size == 1
|
653
740
|
return name, [:dynamic,
|
654
|
-
|
655
|
-
end
|
656
|
-
|
657
|
-
def raw_next_line
|
658
|
-
text = @template.shift
|
659
|
-
return unless text
|
660
|
-
|
661
|
-
index = @template_index
|
662
|
-
@template_index += 1
|
663
|
-
|
664
|
-
return text, index
|
741
|
+
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
665
742
|
end
|
666
743
|
|
667
744
|
def next_line
|
668
|
-
|
669
|
-
return unless text
|
670
|
-
|
671
|
-
# :eod is a special end-of-document marker
|
672
|
-
line =
|
673
|
-
if text == :eod
|
674
|
-
Line.new '-#', '-#', '-#', index, self, true
|
675
|
-
else
|
676
|
-
Line.new text.strip, text.lstrip.chomp, text, index, self, false
|
677
|
-
end
|
745
|
+
line = @template.shift || raise(StopIteration)
|
678
746
|
|
679
747
|
# `flat?' here is a little outdated,
|
680
748
|
# so we have to manually check if either the previous or current line
|
681
749
|
# closes the flat block, as well as whether a new block is opened.
|
682
|
-
line_defined = instance_variable_defined?(
|
750
|
+
line_defined = instance_variable_defined?(:@line)
|
683
751
|
@line.tabs if line_defined
|
684
752
|
unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
|
685
753
|
(line_defined && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
|
@@ -695,21 +763,17 @@ module Haml
|
|
695
763
|
line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
|
696
764
|
end
|
697
765
|
|
698
|
-
def un_next_line(line)
|
699
|
-
@template.unshift line
|
700
|
-
@template_index -= 1
|
701
|
-
end
|
702
|
-
|
703
766
|
def handle_multiline(line)
|
704
767
|
return unless is_multiline?(line.text)
|
705
768
|
line.text.slice!(-1)
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
769
|
+
loop do
|
770
|
+
new_line = @template.first
|
771
|
+
break if new_line.eod?
|
772
|
+
next @template.shift if new_line.text.strip.empty?
|
773
|
+
break unless is_multiline?(new_line.text.strip)
|
774
|
+
line.text << new_line.text.strip[0...-1]
|
775
|
+
@template.shift
|
711
776
|
end
|
712
|
-
un_next_line new_line
|
713
777
|
end
|
714
778
|
|
715
779
|
# Checks whether or not `line` is in a multiline sequence.
|
@@ -717,18 +781,18 @@ module Haml
|
|
717
781
|
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s && text !~ BLOCK_WITH_SPACES
|
718
782
|
end
|
719
783
|
|
720
|
-
def handle_ruby_multiline(
|
721
|
-
text
|
722
|
-
return
|
723
|
-
un_next_line @next_line.full
|
784
|
+
def handle_ruby_multiline(line)
|
785
|
+
line.text.rstrip!
|
786
|
+
return line unless is_ruby_multiline?(line.text)
|
724
787
|
begin
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
788
|
+
# Use already fetched @next_line in the first loop. Otherwise, fetch next
|
789
|
+
new_line = new_line.nil? ? @next_line : @template.shift
|
790
|
+
break if new_line.eod?
|
791
|
+
next if new_line.text.empty?
|
792
|
+
line.text << " #{new_line.text.rstrip}"
|
793
|
+
end while is_ruby_multiline?(new_line.text)
|
730
794
|
next_line
|
731
|
-
|
795
|
+
line
|
732
796
|
end
|
733
797
|
|
734
798
|
# `text' is a Ruby multiline block if it:
|
@@ -736,16 +800,13 @@ module Haml
|
|
736
800
|
# - but not "?," which is a character literal
|
737
801
|
# (however, "x?," is a method call and not a literal)
|
738
802
|
# - and not "?\," which is a character literal
|
739
|
-
#
|
740
803
|
def is_ruby_multiline?(text)
|
741
804
|
text && text.length > 1 && text[-1] == ?, &&
|
742
|
-
!((text[-3
|
805
|
+
!((text[-3, 2] =~ /\W\?/) || text[-3, 2] == "?\\")
|
743
806
|
end
|
744
807
|
|
745
808
|
def balance(*args)
|
746
|
-
|
747
|
-
return res if res
|
748
|
-
raise SyntaxError.new(Error.message(:unbalanced_brackets))
|
809
|
+
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
749
810
|
end
|
750
811
|
|
751
812
|
def block_opened?
|
@@ -755,7 +816,7 @@ module Haml
|
|
755
816
|
# Same semantics as block_opened?, except that block_opened? uses Line#tabs,
|
756
817
|
# which doesn't interact well with filter lines
|
757
818
|
def filter_opened?
|
758
|
-
@next_line.full =~ (@indentation ? /^#{@indentation * @template_tabs}/ : /^\s/)
|
819
|
+
@next_line.full =~ (@indentation ? /^#{@indentation * (@template_tabs + 1)}/ : /^\s/)
|
759
820
|
end
|
760
821
|
|
761
822
|
def flat?
|