hamlit 2.11.1 → 2.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +2 -1
- data/CHANGELOG.md +46 -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 +17 -9
- 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?
|