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