hamlit 2.11.1 → 2.14.1
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/.github/workflows/test.yml +36 -0
- data/.gitignore +2 -1
- data/CHANGELOG.md +53 -0
- data/Gemfile +0 -4
- data/LICENSE.txt +26 -23
- data/README.md +9 -8
- data/benchmark/graph/graph.key +0 -0
- data/benchmark/graph/graph.png +0 -0
- data/bin/update-haml +125 -0
- data/ext/hamlit/hamlit.c +0 -1
- data/hamlit.gemspec +1 -1
- data/lib/hamlit.rb +6 -4
- data/lib/hamlit/attribute_builder.rb +2 -2
- data/lib/hamlit/attribute_compiler.rb +3 -3
- data/lib/hamlit/cli.rb +34 -10
- data/lib/hamlit/compiler/children_compiler.rb +1 -1
- data/lib/hamlit/compiler/comment_compiler.rb +1 -0
- data/lib/hamlit/filters/escaped.rb +1 -1
- data/lib/hamlit/filters/markdown.rb +1 -0
- data/lib/hamlit/filters/preserve.rb +1 -1
- data/lib/hamlit/filters/text_base.rb +1 -1
- data/lib/hamlit/filters/tilt_base.rb +1 -1
- data/lib/hamlit/parser.rb +6 -2
- data/lib/hamlit/parser/haml_attribute_builder.rb +164 -0
- data/lib/hamlit/parser/haml_buffer.rb +20 -130
- data/lib/hamlit/parser/haml_compiler.rb +1 -553
- data/lib/hamlit/parser/haml_error.rb +29 -25
- data/lib/hamlit/parser/haml_escapable.rb +1 -0
- data/lib/hamlit/parser/haml_generator.rb +1 -0
- data/lib/hamlit/parser/haml_helpers.rb +41 -59
- data/lib/hamlit/parser/{haml_xss_mods.rb → haml_helpers/xss_mods.rb} +20 -15
- data/lib/hamlit/parser/haml_options.rb +53 -66
- data/lib/hamlit/parser/haml_parser.rb +133 -73
- data/lib/hamlit/parser/haml_temple_engine.rb +123 -0
- data/lib/hamlit/parser/haml_util.rb +10 -40
- data/lib/hamlit/rails_template.rb +1 -1
- data/lib/hamlit/string_splitter.rb +1 -0
- data/lib/hamlit/version.rb +1 -1
- metadata +17 -12
- data/.travis.yml +0 -49
- data/lib/hamlit/parser/MIT-LICENSE +0 -20
- data/lib/hamlit/parser/README.md +0 -30
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
1
4
|
require 'strscan'
|
2
|
-
require 'hamlit/parser/haml_util'
|
3
|
-
require 'hamlit/parser/haml_error'
|
4
5
|
|
5
6
|
module Hamlit
|
6
7
|
class HamlParser
|
7
|
-
include
|
8
|
+
include Hamlit::HamlUtil
|
8
9
|
|
9
10
|
attr_reader :root
|
10
11
|
|
@@ -61,7 +62,7 @@ module Hamlit
|
|
61
62
|
SILENT_SCRIPT,
|
62
63
|
ESCAPE,
|
63
64
|
FILTER
|
64
|
-
]
|
65
|
+
].freeze
|
65
66
|
|
66
67
|
# The value of the character that designates that a line is part
|
67
68
|
# of a multiline string.
|
@@ -75,8 +76,8 @@ module Hamlit
|
|
75
76
|
#
|
76
77
|
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
77
78
|
|
78
|
-
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
79
|
-
START_BLOCK_KEYWORDS = %w[if begin case unless]
|
79
|
+
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when].freeze
|
80
|
+
START_BLOCK_KEYWORDS = %w[if begin case unless].freeze
|
80
81
|
# Try to parse assignments to block starters as best as possible
|
81
82
|
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
82
83
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
@@ -90,14 +91,19 @@ module Hamlit
|
|
90
91
|
ID_KEY = 'id'.freeze
|
91
92
|
CLASS_KEY = 'class'.freeze
|
92
93
|
|
93
|
-
|
94
|
-
|
94
|
+
# Used for scanning old attributes, substituting the first '{'
|
95
|
+
METHOD_CALL_PREFIX = 'a('
|
96
|
+
|
97
|
+
def initialize(options)
|
98
|
+
@options = HamlOptions.wrap(options)
|
95
99
|
# Record the indent levels of "if" statements to validate the subsequent
|
96
100
|
# elsif and else statements are indented at the appropriate level.
|
97
101
|
@script_level_stack = []
|
98
102
|
@template_index = 0
|
99
103
|
@template_tabs = 0
|
104
|
+
end
|
100
105
|
|
106
|
+
def call(template)
|
101
107
|
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
102
108
|
# discard the last match which is always blank
|
103
109
|
match.pop
|
@@ -106,16 +112,14 @@ module Hamlit
|
|
106
112
|
end
|
107
113
|
# Append special end-of-document marker
|
108
114
|
@template << Line.new(nil, '-#', '-#', @template.size, self, true)
|
109
|
-
end
|
110
115
|
|
111
|
-
def parse
|
112
116
|
@root = @parent = ParseNode.new(:root)
|
113
117
|
@flat = false
|
114
118
|
@filter_buffer = nil
|
115
119
|
@indentation = nil
|
116
120
|
@line = next_line
|
117
121
|
|
118
|
-
raise
|
122
|
+
raise HamlSyntaxError.new(HamlError.message(:indenting_at_start), @line.index) if @line.tabs != 0
|
119
123
|
|
120
124
|
loop do
|
121
125
|
next_line
|
@@ -138,7 +142,7 @@ module Hamlit
|
|
138
142
|
end
|
139
143
|
|
140
144
|
if !flat? && @next_line.tabs - @line.tabs > 1
|
141
|
-
raise
|
145
|
+
raise HamlSyntaxError.new(HamlError.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
|
142
146
|
end
|
143
147
|
|
144
148
|
@line = @next_line
|
@@ -146,7 +150,7 @@ module Hamlit
|
|
146
150
|
# Close all the open tags
|
147
151
|
close until @parent.type == :root
|
148
152
|
@root
|
149
|
-
rescue
|
153
|
+
rescue Hamlit::HamlError => e
|
150
154
|
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
151
155
|
raise
|
152
156
|
end
|
@@ -158,7 +162,7 @@ module Hamlit
|
|
158
162
|
@indentation = line.whitespace
|
159
163
|
|
160
164
|
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
161
|
-
raise
|
165
|
+
raise HamlSyntaxError.new(HamlError.message(:cant_use_tabs_and_spaces), line.index)
|
162
166
|
end
|
163
167
|
|
164
168
|
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
@@ -169,17 +173,17 @@ module Hamlit
|
|
169
173
|
return tabs if line.whitespace == @indentation * tabs
|
170
174
|
return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
|
171
175
|
|
172
|
-
message =
|
176
|
+
message = HamlError.message(:inconsistent_indentation,
|
173
177
|
human_indentation(line.whitespace),
|
174
178
|
human_indentation(@indentation)
|
175
179
|
)
|
176
|
-
raise
|
180
|
+
raise HamlSyntaxError.new(message, line.index)
|
177
181
|
end
|
178
182
|
|
179
183
|
private
|
180
184
|
|
181
185
|
# @private
|
182
|
-
|
186
|
+
Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
|
183
187
|
alias_method :eod?, :eod
|
184
188
|
|
185
189
|
# @private
|
@@ -195,14 +199,39 @@ module Hamlit
|
|
195
199
|
end
|
196
200
|
|
197
201
|
# @private
|
198
|
-
|
202
|
+
ParseNode = Struct.new(:type, :line, :value, :parent, :children) do
|
199
203
|
def initialize(*args)
|
200
204
|
super
|
201
205
|
self.children ||= []
|
202
206
|
end
|
203
207
|
|
204
208
|
def inspect
|
205
|
-
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
209
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object(''.dup) {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})].dup
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# @param [String] new - Hash literal including dynamic values.
|
214
|
+
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
215
|
+
DynamicAttributes = Struct.new(:new, :old) do
|
216
|
+
undef :old=
|
217
|
+
def old=(value)
|
218
|
+
unless value =~ /\A{.*}\z/m
|
219
|
+
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
220
|
+
end
|
221
|
+
self[:old] = value
|
222
|
+
end
|
223
|
+
|
224
|
+
# This will be a literal for Hamlit::HamlBuffer#attributes's last argument, `attributes_hashes`.
|
225
|
+
def to_literal
|
226
|
+
[new, stripped_old].compact.join(', ')
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
# For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
|
232
|
+
def stripped_old
|
233
|
+
return nil if old.nil?
|
234
|
+
old.sub!(/\A{/, '').sub!(/}\z/m, '')
|
206
235
|
end
|
207
236
|
end
|
208
237
|
|
@@ -264,7 +293,7 @@ module Hamlit
|
|
264
293
|
end
|
265
294
|
|
266
295
|
def block_keyword(text)
|
267
|
-
return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
|
296
|
+
return unless (keyword = text.scan(BLOCK_KEYWORD_REGEX)[0])
|
268
297
|
keyword[0] || keyword[1]
|
269
298
|
end
|
270
299
|
|
@@ -275,20 +304,20 @@ module Hamlit
|
|
275
304
|
|
276
305
|
def plain(line, escape_html = nil)
|
277
306
|
if block_opened?
|
278
|
-
raise
|
307
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_plain), @next_line.index)
|
279
308
|
end
|
280
309
|
|
281
310
|
unless contains_interpolation?(line.text)
|
282
311
|
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
283
312
|
end
|
284
313
|
|
285
|
-
escape_html = @options.escape_html if escape_html.nil?
|
286
|
-
line.text =
|
314
|
+
escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
|
315
|
+
line.text = unescape_interpolation(line.text)
|
287
316
|
script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
|
288
317
|
end
|
289
318
|
|
290
319
|
def script(line, escape_html = nil, preserve = false)
|
291
|
-
raise
|
320
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '=')) if line.text.empty?
|
292
321
|
line = handle_ruby_multiline(line)
|
293
322
|
escape_html = @options.escape_html if escape_html.nil?
|
294
323
|
|
@@ -300,12 +329,12 @@ module Hamlit
|
|
300
329
|
end
|
301
330
|
|
302
331
|
def flat_script(line, escape_html = nil)
|
303
|
-
raise
|
332
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '~')) if line.text.empty?
|
304
333
|
script(line, escape_html, :preserve)
|
305
334
|
end
|
306
335
|
|
307
336
|
def silent_script(line)
|
308
|
-
raise
|
337
|
+
raise HamlSyntaxError.new(HamlError.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
|
309
338
|
|
310
339
|
line = handle_ruby_multiline(line)
|
311
340
|
keyword = block_keyword(line.text)
|
@@ -314,7 +343,7 @@ module Hamlit
|
|
314
343
|
|
315
344
|
if ["else", "elsif", "when"].include?(keyword)
|
316
345
|
if @script_level_stack.empty?
|
317
|
-
raise
|
346
|
+
raise Hamlit::HamlSyntaxError.new(HamlError.message(:missing_if, keyword), @line.index)
|
318
347
|
end
|
319
348
|
|
320
349
|
if keyword == 'when' and !@script_level_stack.last[2]
|
@@ -325,8 +354,8 @@ module Hamlit
|
|
325
354
|
end
|
326
355
|
|
327
356
|
if @script_level_stack.last[1] != @line.tabs
|
328
|
-
message =
|
329
|
-
raise
|
357
|
+
message = HamlError.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
|
358
|
+
raise Hamlit::HamlSyntaxError.new(message, @line.index)
|
330
359
|
end
|
331
360
|
end
|
332
361
|
|
@@ -363,7 +392,6 @@ module Hamlit
|
|
363
392
|
|
364
393
|
preserve_tag = @options.preserve.include?(tag_name)
|
365
394
|
nuke_inner_whitespace ||= preserve_tag
|
366
|
-
preserve_tag = false if @options.ugly
|
367
395
|
escape_html = (action == '&' || (action != '!' && @options.escape_html))
|
368
396
|
|
369
397
|
case action
|
@@ -372,7 +400,7 @@ module Hamlit
|
|
372
400
|
when '='
|
373
401
|
parse = true
|
374
402
|
if value[0] == ?=
|
375
|
-
value =
|
403
|
+
value = unescape_interpolation(value[1..-1].strip)
|
376
404
|
escape_interpolation = true if escape_html
|
377
405
|
escape_html = false
|
378
406
|
end
|
@@ -381,50 +409,47 @@ module Hamlit
|
|
381
409
|
parse = true
|
382
410
|
preserve_script = (value[0] == ?~)
|
383
411
|
if value[1] == ?=
|
384
|
-
value =
|
412
|
+
value = unescape_interpolation(value[2..-1].strip)
|
385
413
|
escape_interpolation = true if escape_html
|
386
414
|
escape_html = false
|
387
415
|
else
|
388
416
|
value = value[1..-1].strip
|
389
417
|
end
|
390
418
|
elsif contains_interpolation?(value)
|
391
|
-
value =
|
419
|
+
value = unescape_interpolation(value)
|
392
420
|
escape_interpolation = true if escape_html
|
393
421
|
parse = true
|
394
422
|
escape_html = false
|
395
423
|
end
|
396
424
|
else
|
397
425
|
if contains_interpolation?(value)
|
398
|
-
value =
|
399
|
-
escape_interpolation = true if escape_html
|
426
|
+
value = unescape_interpolation(value, escape_html)
|
400
427
|
parse = true
|
401
428
|
escape_html = false
|
402
429
|
end
|
403
430
|
end
|
404
431
|
|
405
|
-
attributes =
|
406
|
-
|
432
|
+
attributes = HamlParser.parse_class_and_id(attributes)
|
433
|
+
dynamic_attributes = DynamicAttributes.new
|
407
434
|
|
408
435
|
if attributes_hashes[:new]
|
409
436
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
410
|
-
|
411
|
-
|
437
|
+
HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
438
|
+
dynamic_attributes.new = attributes_hash
|
412
439
|
end
|
413
440
|
|
414
441
|
if attributes_hashes[:old]
|
415
442
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
416
|
-
|
417
|
-
|
443
|
+
HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
444
|
+
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
418
445
|
end
|
419
446
|
|
420
|
-
|
421
|
-
|
422
|
-
raise
|
423
|
-
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
424
|
-
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
447
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
448
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
449
|
+
raise HamlSyntaxError.new(HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
425
450
|
|
426
451
|
if block_opened? && !value.empty? && !is_ruby_multiline?(value)
|
427
|
-
raise
|
452
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_line, tag_name), @next_line.index)
|
428
453
|
end
|
429
454
|
|
430
455
|
self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
|
@@ -433,7 +458,7 @@ module Hamlit
|
|
433
458
|
line = handle_ruby_multiline(line) if parse
|
434
459
|
|
435
460
|
ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
|
436
|
-
:
|
461
|
+
:dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
|
437
462
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
438
463
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
439
464
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
@@ -462,13 +487,13 @@ module Hamlit
|
|
462
487
|
|
463
488
|
if contains_interpolation?(text)
|
464
489
|
parse = true
|
465
|
-
text =
|
490
|
+
text = unescape_interpolation(text)
|
466
491
|
else
|
467
492
|
parse = false
|
468
493
|
end
|
469
494
|
|
470
495
|
if block_opened? && !text.empty?
|
471
|
-
raise
|
496
|
+
raise HamlSyntaxError.new(Hamlit::HamlError.message(:illegal_nesting_content), @next_line.index)
|
472
497
|
end
|
473
498
|
|
474
499
|
ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
|
@@ -476,13 +501,13 @@ module Hamlit
|
|
476
501
|
|
477
502
|
# Renders an XHTML doctype or XML shebang.
|
478
503
|
def doctype(text)
|
479
|
-
raise
|
504
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_header), @next_line.index) if block_opened?
|
480
505
|
version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
481
506
|
ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
|
482
507
|
end
|
483
508
|
|
484
509
|
def filter(name)
|
485
|
-
raise
|
510
|
+
raise HamlError.new(HamlError.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
|
486
511
|
|
487
512
|
if filter_opened?
|
488
513
|
@flat = true
|
@@ -519,7 +544,7 @@ module Hamlit
|
|
519
544
|
|
520
545
|
# Post-process case statements to normalize the nesting of "when" clauses
|
521
546
|
return unless node.value[:keyword] == "case"
|
522
|
-
return unless first = node.children.first
|
547
|
+
return unless (first = node.children.first)
|
523
548
|
return unless first.type == :silent_script && first.value[:keyword] == "when"
|
524
549
|
return if first.children.empty?
|
525
550
|
# If the case node has a "when" child with children, it's the
|
@@ -531,7 +556,7 @@ module Hamlit
|
|
531
556
|
|
532
557
|
alias :close_script :close_silent_script
|
533
558
|
|
534
|
-
# This is a class method so it can be accessed from {
|
559
|
+
# This is a class method so it can be accessed from {Hamlit::HamlHelpers}.
|
535
560
|
#
|
536
561
|
# Iterates through the classes and ids supplied through `.`
|
537
562
|
# and `#` syntax, and returns a hash with them as attributes,
|
@@ -540,7 +565,7 @@ module Hamlit
|
|
540
565
|
attributes = {}
|
541
566
|
return attributes if list.empty?
|
542
567
|
|
543
|
-
list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
|
568
|
+
list.scan(/([#.])([-:_a-zA-Z0-9\@]+)/) do |type, property|
|
544
569
|
case type
|
545
570
|
when '.'
|
546
571
|
if attributes[CLASS_KEY]
|
@@ -555,16 +580,22 @@ module Hamlit
|
|
555
580
|
attributes
|
556
581
|
end
|
557
582
|
|
583
|
+
# This method doesn't use Hamlit::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
|
584
|
+
# Ideally this logic should be placed in Hamlit::HamlAttributeParser instead of here and this method should use it.
|
585
|
+
#
|
586
|
+
# @param [String] text - Hash literal or text inside old attributes
|
587
|
+
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
558
588
|
def parse_static_hash(text)
|
559
589
|
attributes = {}
|
560
590
|
return attributes if text.empty?
|
561
591
|
|
592
|
+
text = text[1...-1] # strip brackets
|
562
593
|
scanner = StringScanner.new(text)
|
563
594
|
scanner.scan(/\s+/)
|
564
595
|
until scanner.eos?
|
565
|
-
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
596
|
+
return unless (key = scanner.scan(LITERAL_VALUE_REGEX))
|
566
597
|
return unless scanner.scan(/\s*=>\s*/)
|
567
|
-
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
598
|
+
return unless (value = scanner.scan(LITERAL_VALUE_REGEX))
|
568
599
|
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
569
600
|
attributes[eval(key).to_s] = eval(value).to_s
|
570
601
|
end
|
@@ -573,13 +604,13 @@ module Hamlit
|
|
573
604
|
|
574
605
|
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
575
606
|
def parse_tag(text)
|
576
|
-
match = text.scan(/%([-:\w]+)([-:\w
|
577
|
-
raise
|
607
|
+
match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0]
|
608
|
+
raise HamlSyntaxError.new(HamlError.message(:invalid_tag, text)) unless match
|
578
609
|
|
579
610
|
tag_name, attributes, rest = match
|
580
611
|
|
581
612
|
if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
|
582
|
-
raise
|
613
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_element))
|
583
614
|
end
|
584
615
|
|
585
616
|
new_attributes_hash = old_attributes_hash = last_line = nil
|
@@ -624,14 +655,22 @@ module Hamlit
|
|
624
655
|
nuke_inner_whitespace, action, value, last_line || @line.index + 1]
|
625
656
|
end
|
626
657
|
|
658
|
+
# @return [String] attributes_hash - Hash literal starting with `{` and ending with `}`
|
659
|
+
# @return [String] rest
|
660
|
+
# @return [Integer] last_line
|
627
661
|
def parse_old_attributes(text)
|
628
|
-
text = text.dup
|
629
662
|
last_line = @line.index + 1
|
630
663
|
|
631
664
|
begin
|
632
|
-
|
633
|
-
|
634
|
-
|
665
|
+
# Old attributes often look like a valid Hash literal, but it sometimes allow code like
|
666
|
+
# `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
|
667
|
+
#
|
668
|
+
# To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops when there is
|
669
|
+
# 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
|
670
|
+
balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
|
671
|
+
attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
|
672
|
+
rescue HamlSyntaxError => e
|
673
|
+
if e.message == HamlError.message(:unbalanced_brackets) && !@template.empty?
|
635
674
|
text << "\n#{@next_line.text}"
|
636
675
|
last_line += 1
|
637
676
|
next_line
|
@@ -641,10 +680,12 @@ module Hamlit
|
|
641
680
|
raise e
|
642
681
|
end
|
643
682
|
|
644
|
-
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
645
683
|
return attributes_hash, rest, last_line
|
646
684
|
end
|
647
685
|
|
686
|
+
# @return [Array<Hash,String,nil>] - [static_attributes (Hash), dynamic_attributes (nil or String starting with `{` and ending with `}`)]
|
687
|
+
# @return [String] rest
|
688
|
+
# @return [Integer] last_line
|
648
689
|
def parse_new_attributes(text)
|
649
690
|
scanner = StringScanner.new(text)
|
650
691
|
last_line = @line.index + 1
|
@@ -656,9 +697,9 @@ module Hamlit
|
|
656
697
|
break if name.nil?
|
657
698
|
|
658
699
|
if name == false
|
659
|
-
scanned =
|
700
|
+
scanned = Hamlit::HamlUtil.balance(text, ?(, ?))
|
660
701
|
text = scanned ? scanned.first : text
|
661
|
-
raise
|
702
|
+
raise Hamlit::HamlSyntaxError.new(HamlError.message(:invalid_attribute_list, text.inspect), last_line - 1)
|
662
703
|
end
|
663
704
|
attributes[name] = value
|
664
705
|
scanner.scan(/\s*/)
|
@@ -672,7 +713,7 @@ module Hamlit
|
|
672
713
|
end
|
673
714
|
|
674
715
|
static_attributes = {}
|
675
|
-
dynamic_attributes = "{"
|
716
|
+
dynamic_attributes = "{".dup
|
676
717
|
attributes.each do |name, (type, val)|
|
677
718
|
if type == :static
|
678
719
|
static_attributes[name] = val
|
@@ -687,7 +728,7 @@ module Hamlit
|
|
687
728
|
end
|
688
729
|
|
689
730
|
def parse_new_attribute(scanner)
|
690
|
-
unless name = scanner.scan(/[-:\w]+/)
|
731
|
+
unless (name = scanner.scan(/[-:\w]+/))
|
691
732
|
return if scanner.scan(/\)/)
|
692
733
|
return false
|
693
734
|
end
|
@@ -696,8 +737,8 @@ module Hamlit
|
|
696
737
|
return name, [:static, true] unless scanner.scan(/=/) #/end
|
697
738
|
|
698
739
|
scanner.scan(/\s*/)
|
699
|
-
unless quote = scanner.scan(/["']/)
|
700
|
-
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
740
|
+
unless (quote = scanner.scan(/["']/))
|
741
|
+
return false unless (var = scanner.scan(/(@@?|\$)?\w+/))
|
701
742
|
return name, [:dynamic, var]
|
702
743
|
end
|
703
744
|
|
@@ -712,7 +753,7 @@ module Hamlit
|
|
712
753
|
|
713
754
|
return name, [:static, content.first[1]] if content.size == 1
|
714
755
|
return name, [:dynamic,
|
715
|
-
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
756
|
+
%!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
716
757
|
end
|
717
758
|
|
718
759
|
def next_line
|
@@ -780,7 +821,26 @@ module Hamlit
|
|
780
821
|
end
|
781
822
|
|
782
823
|
def balance(*args)
|
783
|
-
|
824
|
+
Hamlit::HamlUtil.balance(*args) or raise(HamlSyntaxError.new(HamlError.message(:unbalanced_brackets)))
|
825
|
+
end
|
826
|
+
|
827
|
+
# Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
|
828
|
+
def balance_tokens(buf, start, finish, count: 0)
|
829
|
+
text = ''.dup
|
830
|
+
Ripper.lex(buf).each do |_, token, str|
|
831
|
+
text << str
|
832
|
+
case token
|
833
|
+
when start
|
834
|
+
count += 1
|
835
|
+
when finish
|
836
|
+
count -= 1
|
837
|
+
end
|
838
|
+
|
839
|
+
if count == 0
|
840
|
+
return text, buf.sub(text, '')
|
841
|
+
end
|
842
|
+
end
|
843
|
+
raise HamlSyntaxError.new(HamlError.message(:unbalanced_brackets))
|
784
844
|
end
|
785
845
|
|
786
846
|
def block_opened?
|