haml 5.2.2 → 6.0.0.beta.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/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +13 -9
- data/.gitignore +16 -16
- data/CHANGELOG.md +13 -3
- data/Gemfile +18 -11
- data/MIT-LICENSE +1 -1
- data/README.md +13 -19
- data/Rakefile +95 -93
- data/bin/bench +66 -0
- data/bin/console +11 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/bin/test +24 -0
- data/exe/haml +6 -0
- data/ext/haml/extconf.rb +10 -0
- data/ext/haml/haml.c +537 -0
- data/ext/haml/hescape.c +108 -0
- data/ext/haml/hescape.h +20 -0
- data/haml.gemspec +39 -37
- data/lib/haml/ambles.rb +20 -0
- data/lib/haml/attribute_builder.rb +135 -179
- data/lib/haml/attribute_compiler.rb +85 -194
- data/lib/haml/attribute_parser.rb +86 -126
- data/lib/haml/cli.rb +154 -0
- data/lib/haml/compiler/children_compiler.rb +126 -0
- data/lib/haml/compiler/comment_compiler.rb +39 -0
- data/lib/haml/compiler/doctype_compiler.rb +46 -0
- data/lib/haml/compiler/script_compiler.rb +116 -0
- data/lib/haml/compiler/silent_script_compiler.rb +24 -0
- data/lib/haml/compiler/tag_compiler.rb +76 -0
- data/lib/haml/compiler.rb +63 -296
- data/lib/haml/dynamic_merger.rb +67 -0
- data/lib/haml/engine.rb +42 -227
- data/lib/haml/error.rb +3 -52
- data/lib/haml/escapable.rb +6 -70
- data/lib/haml/filters/base.rb +12 -0
- data/lib/haml/filters/cdata.rb +20 -0
- data/lib/haml/filters/coffee.rb +17 -0
- data/lib/haml/filters/css.rb +33 -0
- data/lib/haml/filters/erb.rb +10 -0
- data/lib/haml/filters/escaped.rb +22 -0
- data/lib/haml/filters/javascript.rb +33 -0
- data/lib/haml/filters/less.rb +20 -0
- data/lib/haml/filters/markdown.rb +11 -0
- data/lib/haml/filters/plain.rb +29 -0
- data/lib/haml/filters/preserve.rb +22 -0
- data/lib/haml/filters/ruby.rb +10 -0
- data/lib/haml/filters/sass.rb +15 -0
- data/lib/haml/filters/scss.rb +15 -0
- data/lib/haml/filters/text_base.rb +25 -0
- data/lib/haml/filters/tilt_base.rb +49 -0
- data/lib/haml/filters.rb +54 -378
- data/lib/haml/force_escapable.rb +29 -0
- data/lib/haml/haml_error.rb +66 -0
- data/lib/haml/helpers.rb +3 -697
- data/lib/haml/html.rb +22 -0
- data/lib/haml/identity.rb +13 -0
- data/lib/haml/object_ref.rb +30 -0
- data/lib/haml/parser.rb +179 -49
- data/lib/haml/rails_helpers.rb +51 -0
- data/lib/haml/rails_template.rb +55 -0
- data/lib/haml/railtie.rb +7 -45
- data/lib/haml/ruby_expression.rb +32 -0
- data/lib/haml/string_splitter.rb +20 -0
- data/lib/haml/template.rb +15 -34
- data/lib/haml/temple_line_counter.rb +2 -1
- data/lib/haml/util.rb +17 -15
- data/lib/haml/version.rb +1 -2
- data/lib/haml.rb +8 -20
- metadata +211 -57
- data/.gitmodules +0 -3
- data/.yardopts +0 -22
- data/TODO +0 -24
- data/benchmark.rb +0 -70
- data/bin/haml +0 -9
- data/lib/haml/.gitattributes +0 -1
- data/lib/haml/buffer.rb +0 -182
- data/lib/haml/exec.rb +0 -347
- data/lib/haml/generator.rb +0 -42
- data/lib/haml/helpers/action_view_extensions.rb +0 -60
- data/lib/haml/helpers/action_view_mods.rb +0 -132
- data/lib/haml/helpers/action_view_xss_mods.rb +0 -60
- data/lib/haml/helpers/safe_erubi_template.rb +0 -20
- data/lib/haml/helpers/safe_erubis_template.rb +0 -33
- data/lib/haml/helpers/xss_mods.rb +0 -114
- data/lib/haml/options.rb +0 -273
- data/lib/haml/plugin.rb +0 -54
- data/lib/haml/sass_rails_filter.rb +0 -47
- data/lib/haml/template/options.rb +0 -27
- data/lib/haml/temple_engine.rb +0 -124
- data/yard/default/.gitignore +0 -1
- data/yard/default/fulldoc/html/css/common.sass +0 -15
- data/yard/default/layout/html/footer.erb +0 -12
data/lib/haml/html.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Haml
|
3
|
+
class HTML < Temple::HTML::Fast
|
4
|
+
DEPRECATED_FORMATS = %i[html4 html5].freeze
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
if DEPRECATED_FORMATS.include?(opts[:format])
|
8
|
+
opts = opts.dup
|
9
|
+
opts[:format] = :html
|
10
|
+
end
|
11
|
+
super(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
# This dispatcher supports Haml's "revealed" conditional comment.
|
15
|
+
def on_html_condcomment(condition, content, revealed = false)
|
16
|
+
on_html_comment [:multi,
|
17
|
+
[:static, "[#{condition}]>#{'<!-->' if revealed}"],
|
18
|
+
content,
|
19
|
+
[:static, "#{'<!--' if revealed}<![endif]"]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Haml
|
3
|
+
module ObjectRef
|
4
|
+
class << self
|
5
|
+
def parse(args)
|
6
|
+
object, prefix = args
|
7
|
+
return {} unless object
|
8
|
+
|
9
|
+
suffix = underscore(object.class)
|
10
|
+
{
|
11
|
+
'class' => [prefix, suffix].compact.join('_'),
|
12
|
+
'id' => [prefix, suffix, object.id || 'new'].compact.join('_'),
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Haml::Buffer.underscore
|
19
|
+
def underscore(camel_cased_word)
|
20
|
+
word = camel_cased_word.to_s.dup
|
21
|
+
word.gsub!(/::/, '_')
|
22
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
23
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
24
|
+
word.tr!('-', '_')
|
25
|
+
word.downcase!
|
26
|
+
word
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/haml/parser.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'ripper'
|
4
4
|
require 'strscan'
|
5
|
+
require 'haml/haml_error'
|
6
|
+
require 'haml/util'
|
5
7
|
|
6
8
|
module Haml
|
7
9
|
class Parser
|
@@ -95,7 +97,7 @@ module Haml
|
|
95
97
|
METHOD_CALL_PREFIX = 'a('
|
96
98
|
|
97
99
|
def initialize(options)
|
98
|
-
@options =
|
100
|
+
@options = ParserOptions.new(options)
|
99
101
|
# Record the indent levels of "if" statements to validate the subsequent
|
100
102
|
# elsif and else statements are indented at the appropriate level.
|
101
103
|
@script_level_stack = []
|
@@ -104,6 +106,10 @@ module Haml
|
|
104
106
|
end
|
105
107
|
|
106
108
|
def call(template)
|
109
|
+
template = Haml::Util.check_haml_encoding(template) do |msg, line|
|
110
|
+
raise Haml::Error.new(msg, line)
|
111
|
+
end
|
112
|
+
|
107
113
|
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
108
114
|
# discard the last match which is always blank
|
109
115
|
match.pop
|
@@ -119,7 +125,7 @@ module Haml
|
|
119
125
|
@indentation = nil
|
120
126
|
@line = next_line
|
121
127
|
|
122
|
-
raise
|
128
|
+
raise HamlSyntaxError.new(HamlError.message(:indenting_at_start), @line.index) if @line.tabs != 0
|
123
129
|
|
124
130
|
loop do
|
125
131
|
next_line
|
@@ -142,7 +148,7 @@ module Haml
|
|
142
148
|
end
|
143
149
|
|
144
150
|
if !flat? && @next_line.tabs - @line.tabs > 1
|
145
|
-
raise
|
151
|
+
raise HamlSyntaxError.new(HamlError.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
|
146
152
|
end
|
147
153
|
|
148
154
|
@line = @next_line
|
@@ -150,9 +156,9 @@ module Haml
|
|
150
156
|
# Close all the open tags
|
151
157
|
close until @parent.type == :root
|
152
158
|
@root
|
153
|
-
rescue Haml::
|
159
|
+
rescue Haml::HamlError => e
|
154
160
|
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
155
|
-
|
161
|
+
error_with_lineno(e)
|
156
162
|
end
|
157
163
|
|
158
164
|
def compute_tabs(line)
|
@@ -162,7 +168,7 @@ module Haml
|
|
162
168
|
@indentation = line.whitespace
|
163
169
|
|
164
170
|
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
165
|
-
raise
|
171
|
+
raise HamlSyntaxError.new(HamlError.message(:cant_use_tabs_and_spaces), line.index)
|
166
172
|
end
|
167
173
|
|
168
174
|
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
@@ -173,15 +179,25 @@ module Haml
|
|
173
179
|
return tabs if line.whitespace == @indentation * tabs
|
174
180
|
return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
|
175
181
|
|
176
|
-
message =
|
182
|
+
message = HamlError.message(:inconsistent_indentation,
|
177
183
|
human_indentation(line.whitespace),
|
178
184
|
human_indentation(@indentation)
|
179
185
|
)
|
180
|
-
raise
|
186
|
+
raise HamlSyntaxError.new(message, line.index)
|
181
187
|
end
|
182
188
|
|
183
189
|
private
|
184
190
|
|
191
|
+
def error_with_lineno(error)
|
192
|
+
return error if error.line
|
193
|
+
|
194
|
+
trace = error.backtrace.first
|
195
|
+
return error unless trace
|
196
|
+
|
197
|
+
line = trace.match(/\d+\z/).to_s.to_i
|
198
|
+
HamlSyntaxError.new(error.message, line)
|
199
|
+
end
|
200
|
+
|
185
201
|
# @private
|
186
202
|
Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
|
187
203
|
alias_method :eod?, :eod
|
@@ -221,7 +237,7 @@ module Haml
|
|
221
237
|
self[:old] = value
|
222
238
|
end
|
223
239
|
|
224
|
-
# This will be a literal for Haml::
|
240
|
+
# This will be a literal for Haml::HamlBuffer#attributes's last argument, `attributes_hashes`.
|
225
241
|
def to_literal
|
226
242
|
[new, stripped_old].compact.join(', ')
|
227
243
|
end
|
@@ -304,20 +320,20 @@ module Haml
|
|
304
320
|
|
305
321
|
def plain(line, escape_html = nil)
|
306
322
|
if block_opened?
|
307
|
-
raise
|
323
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_plain), @next_line.index)
|
308
324
|
end
|
309
325
|
|
310
|
-
unless contains_interpolation?(line.text)
|
326
|
+
unless Util.contains_interpolation?(line.text)
|
311
327
|
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
312
328
|
end
|
313
329
|
|
314
330
|
escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
|
315
|
-
line.text = unescape_interpolation(line.text
|
316
|
-
script(line, false)
|
331
|
+
line.text = Util.unescape_interpolation(line.text)
|
332
|
+
script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
|
317
333
|
end
|
318
334
|
|
319
335
|
def script(line, escape_html = nil, preserve = false)
|
320
|
-
raise
|
336
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '=')) if line.text.empty?
|
321
337
|
line = handle_ruby_multiline(line)
|
322
338
|
escape_html = @options.escape_html if escape_html.nil?
|
323
339
|
|
@@ -329,12 +345,12 @@ module Haml
|
|
329
345
|
end
|
330
346
|
|
331
347
|
def flat_script(line, escape_html = nil)
|
332
|
-
raise
|
348
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, '~')) if line.text.empty?
|
333
349
|
script(line, escape_html, :preserve)
|
334
350
|
end
|
335
351
|
|
336
352
|
def silent_script(line)
|
337
|
-
raise
|
353
|
+
raise HamlSyntaxError.new(HamlError.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
|
338
354
|
|
339
355
|
line = handle_ruby_multiline(line)
|
340
356
|
keyword = block_keyword(line.text)
|
@@ -343,7 +359,7 @@ module Haml
|
|
343
359
|
|
344
360
|
if ["else", "elsif", "when"].include?(keyword)
|
345
361
|
if @script_level_stack.empty?
|
346
|
-
raise Haml::
|
362
|
+
raise Haml::HamlSyntaxError.new(HamlError.message(:missing_if, keyword), @line.index)
|
347
363
|
end
|
348
364
|
|
349
365
|
if keyword == 'when' and !@script_level_stack.last[2]
|
@@ -354,8 +370,8 @@ module Haml
|
|
354
370
|
end
|
355
371
|
|
356
372
|
if @script_level_stack.last[1] != @line.tabs
|
357
|
-
message =
|
358
|
-
raise Haml::
|
373
|
+
message = HamlError.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
|
374
|
+
raise Haml::HamlSyntaxError.new(message, @line.index)
|
359
375
|
end
|
360
376
|
end
|
361
377
|
|
@@ -400,7 +416,8 @@ module Haml
|
|
400
416
|
when '='
|
401
417
|
parse = true
|
402
418
|
if value[0] == ?=
|
403
|
-
value = unescape_interpolation(value[1..-1].strip
|
419
|
+
value = Util.unescape_interpolation(value[1..-1].strip)
|
420
|
+
escape_interpolation = true if escape_html
|
404
421
|
escape_html = false
|
405
422
|
end
|
406
423
|
when '&', '!'
|
@@ -408,19 +425,21 @@ module Haml
|
|
408
425
|
parse = true
|
409
426
|
preserve_script = (value[0] == ?~)
|
410
427
|
if value[1] == ?=
|
411
|
-
value = unescape_interpolation(value[2..-1].strip
|
428
|
+
value = Util.unescape_interpolation(value[2..-1].strip)
|
429
|
+
escape_interpolation = true if escape_html
|
412
430
|
escape_html = false
|
413
431
|
else
|
414
432
|
value = value[1..-1].strip
|
415
433
|
end
|
416
|
-
elsif contains_interpolation?(value)
|
417
|
-
value = unescape_interpolation(value
|
434
|
+
elsif Util.contains_interpolation?(value)
|
435
|
+
value = Util.unescape_interpolation(value)
|
436
|
+
escape_interpolation = true if escape_html
|
418
437
|
parse = true
|
419
438
|
escape_html = false
|
420
439
|
end
|
421
440
|
else
|
422
|
-
if contains_interpolation?(value)
|
423
|
-
value = unescape_interpolation(value, escape_html)
|
441
|
+
if Util.contains_interpolation?(value)
|
442
|
+
value = Util.unescape_interpolation(value, escape_html)
|
424
443
|
parse = true
|
425
444
|
escape_html = false
|
426
445
|
end
|
@@ -431,22 +450,22 @@ module Haml
|
|
431
450
|
|
432
451
|
if attributes_hashes[:new]
|
433
452
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
434
|
-
|
453
|
+
AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
|
435
454
|
dynamic_attributes.new = attributes_hash
|
436
455
|
end
|
437
456
|
|
438
457
|
if attributes_hashes[:old]
|
439
458
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
440
|
-
|
459
|
+
AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
|
441
460
|
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
442
461
|
end
|
443
462
|
|
444
|
-
raise
|
445
|
-
raise
|
446
|
-
raise
|
463
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
464
|
+
raise HamlSyntaxError.new(HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
465
|
+
raise HamlSyntaxError.new(HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
447
466
|
|
448
467
|
if block_opened? && !value.empty? && !is_ruby_multiline?(value)
|
449
|
-
raise
|
468
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_line, tag_name), @next_line.index)
|
450
469
|
end
|
451
470
|
|
452
471
|
self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
|
@@ -459,7 +478,8 @@ module Haml
|
|
459
478
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
460
479
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
461
480
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
462
|
-
:preserve_script => preserve_script, :parse => parse, :value => line.text
|
481
|
+
:preserve_script => preserve_script, :parse => parse, :value => line.text,
|
482
|
+
:escape_interpolation => escape_interpolation)
|
463
483
|
end
|
464
484
|
|
465
485
|
# Renders a line that creates an XHTML tag and has an implicit div because of
|
@@ -481,15 +501,15 @@ module Haml
|
|
481
501
|
conditional, text = balance(text, ?[, ?]) if text[0] == ?[
|
482
502
|
text.strip!
|
483
503
|
|
484
|
-
if contains_interpolation?(text)
|
504
|
+
if Util.contains_interpolation?(text)
|
485
505
|
parse = true
|
486
|
-
text = unescape_interpolation(text)
|
506
|
+
text = Util.unescape_interpolation(text)
|
487
507
|
else
|
488
508
|
parse = false
|
489
509
|
end
|
490
510
|
|
491
511
|
if block_opened? && !text.empty?
|
492
|
-
raise
|
512
|
+
raise HamlSyntaxError.new(Haml::HamlError.message(:illegal_nesting_content), @next_line.index)
|
493
513
|
end
|
494
514
|
|
495
515
|
ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
|
@@ -497,13 +517,13 @@ module Haml
|
|
497
517
|
|
498
518
|
# Renders an XHTML doctype or XML shebang.
|
499
519
|
def doctype(text)
|
500
|
-
raise
|
520
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_nesting_header), @next_line.index) if block_opened?
|
501
521
|
version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
502
522
|
ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
|
503
523
|
end
|
504
524
|
|
505
525
|
def filter(name)
|
506
|
-
raise
|
526
|
+
raise HamlError.new(HamlError.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
|
507
527
|
|
508
528
|
if filter_opened?
|
509
529
|
@flat = true
|
@@ -552,7 +572,7 @@ module Haml
|
|
552
572
|
|
553
573
|
alias :close_script :close_silent_script
|
554
574
|
|
555
|
-
# This is a class method so it can be accessed from {Haml::
|
575
|
+
# This is a class method so it can be accessed from {Haml::HamlHelpers}.
|
556
576
|
#
|
557
577
|
# Iterates through the classes and ids supplied through `.`
|
558
578
|
# and `#` syntax, and returns a hash with them as attributes,
|
@@ -576,8 +596,8 @@ module Haml
|
|
576
596
|
attributes
|
577
597
|
end
|
578
598
|
|
579
|
-
# This method doesn't use Haml::
|
580
|
-
# Ideally this logic should be placed in Haml::
|
599
|
+
# This method doesn't use Haml::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
|
600
|
+
# Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
|
581
601
|
#
|
582
602
|
# @param [String] text - Hash literal or text inside old attributes
|
583
603
|
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
@@ -601,12 +621,12 @@ module Haml
|
|
601
621
|
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
602
622
|
def parse_tag(text)
|
603
623
|
match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0]
|
604
|
-
raise
|
624
|
+
raise HamlSyntaxError.new(HamlError.message(:invalid_tag, text)) unless match
|
605
625
|
|
606
626
|
tag_name, attributes, rest = match
|
607
627
|
|
608
628
|
if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
|
609
|
-
raise
|
629
|
+
raise HamlSyntaxError.new(HamlError.message(:illegal_element))
|
610
630
|
end
|
611
631
|
|
612
632
|
new_attributes_hash = old_attributes_hash = last_line = nil
|
@@ -665,8 +685,8 @@ module Haml
|
|
665
685
|
# 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
|
666
686
|
balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
|
667
687
|
attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
|
668
|
-
rescue
|
669
|
-
if e.message ==
|
688
|
+
rescue HamlSyntaxError => e
|
689
|
+
if e.message == HamlError.message(:unbalanced_brackets) && !@template.empty?
|
670
690
|
text << "\n#{@next_line.text}"
|
671
691
|
last_line += 1
|
672
692
|
next_line
|
@@ -695,7 +715,7 @@ module Haml
|
|
695
715
|
if name == false
|
696
716
|
scanned = Haml::Util.balance(text, ?(, ?))
|
697
717
|
text = scanned ? scanned.first : text
|
698
|
-
raise Haml::
|
718
|
+
raise Haml::HamlSyntaxError.new(HamlError.message(:invalid_attribute_list, text.inspect), last_line - 1)
|
699
719
|
end
|
700
720
|
attributes[name] = value
|
701
721
|
scanner.scan(/\s*/)
|
@@ -714,7 +734,7 @@ module Haml
|
|
714
734
|
if type == :static
|
715
735
|
static_attributes[name] = val
|
716
736
|
else
|
717
|
-
dynamic_attributes << "#{inspect_obj(name)} => #{val},"
|
737
|
+
dynamic_attributes << "#{Util.inspect_obj(name)} => #{val},"
|
718
738
|
end
|
719
739
|
end
|
720
740
|
dynamic_attributes << "}"
|
@@ -749,7 +769,7 @@ module Haml
|
|
749
769
|
|
750
770
|
return name, [:static, content.first[1]] if content.size == 1
|
751
771
|
return name, [:dynamic,
|
752
|
-
%!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
772
|
+
%!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? Util.inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
753
773
|
end
|
754
774
|
|
755
775
|
def next_line
|
@@ -817,7 +837,7 @@ module Haml
|
|
817
837
|
end
|
818
838
|
|
819
839
|
def balance(*args)
|
820
|
-
Haml::Util.balance(*args) or raise(
|
840
|
+
Haml::Util.balance(*args) or raise(HamlSyntaxError.new(HamlError.message(:unbalanced_brackets)))
|
821
841
|
end
|
822
842
|
|
823
843
|
# Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
|
@@ -836,7 +856,7 @@ module Haml
|
|
836
856
|
return text, buf.sub(text, '')
|
837
857
|
end
|
838
858
|
end
|
839
|
-
raise
|
859
|
+
raise HamlSyntaxError.new(HamlError.message(:unbalanced_brackets))
|
840
860
|
end
|
841
861
|
|
842
862
|
def block_opened?
|
@@ -852,5 +872,115 @@ module Haml
|
|
852
872
|
def flat?
|
853
873
|
@flat
|
854
874
|
end
|
875
|
+
|
876
|
+
class << AttributeMerger = Object.new
|
877
|
+
# Merges two attribute hashes.
|
878
|
+
# This is the same as `to.merge!(from)`,
|
879
|
+
# except that it merges id, class, and data attributes.
|
880
|
+
#
|
881
|
+
# ids are concatenated with `"_"`,
|
882
|
+
# and classes are concatenated with `" "`.
|
883
|
+
# data hashes are simply merged.
|
884
|
+
#
|
885
|
+
# Destructively modifies `to`.
|
886
|
+
#
|
887
|
+
# @param to [{String => String,Hash}] The attribute hash to merge into
|
888
|
+
# @param from [{String => Object}] The attribute hash to merge from
|
889
|
+
# @return [{String => String,Hash}] `to`, after being merged
|
890
|
+
def merge_attributes!(to, from)
|
891
|
+
from.keys.each do |key|
|
892
|
+
to[key] = merge_value(key, to[key], from[key])
|
893
|
+
end
|
894
|
+
to
|
895
|
+
end
|
896
|
+
|
897
|
+
private
|
898
|
+
|
899
|
+
# @return [String, nil]
|
900
|
+
def filter_and_join(value, separator)
|
901
|
+
return '' if (value.respond_to?(:empty?) && value.empty?)
|
902
|
+
|
903
|
+
if value.is_a?(Array)
|
904
|
+
value = value.flatten
|
905
|
+
value.map! {|item| item ? item.to_s : nil}
|
906
|
+
value.compact!
|
907
|
+
value = value.join(separator)
|
908
|
+
else
|
909
|
+
value = value ? value.to_s : nil
|
910
|
+
end
|
911
|
+
!value.nil? && !value.empty? && value
|
912
|
+
end
|
913
|
+
|
914
|
+
# Merge a couple of values to one attribute value. No destructive operation.
|
915
|
+
#
|
916
|
+
# @param to [String,Hash,nil]
|
917
|
+
# @param from [Object]
|
918
|
+
# @return [String,Hash]
|
919
|
+
def merge_value(key, to, from)
|
920
|
+
if from.kind_of?(Hash) || to.kind_of?(Hash)
|
921
|
+
from = { nil => from } if !from.is_a?(Hash)
|
922
|
+
to = { nil => to } if !to.is_a?(Hash)
|
923
|
+
to.merge(from)
|
924
|
+
elsif key == 'id'
|
925
|
+
merged_id = filter_and_join(from, '_')
|
926
|
+
if to && merged_id
|
927
|
+
merged_id = "#{to}_#{merged_id}"
|
928
|
+
elsif to || merged_id
|
929
|
+
merged_id ||= to
|
930
|
+
end
|
931
|
+
merged_id
|
932
|
+
elsif key == 'class'
|
933
|
+
merged_class = filter_and_join(from, ' ')
|
934
|
+
if to && merged_class
|
935
|
+
merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
|
936
|
+
elsif to || merged_class
|
937
|
+
merged_class ||= to
|
938
|
+
end
|
939
|
+
merged_class
|
940
|
+
else
|
941
|
+
from
|
942
|
+
end
|
943
|
+
end
|
944
|
+
end
|
945
|
+
private_constant :AttributeMerger
|
946
|
+
|
947
|
+
class ParserOptions
|
948
|
+
# A list of options that are actually used in the parser
|
949
|
+
AVAILABLE_OPTIONS = %i[
|
950
|
+
autoclose
|
951
|
+
escape_html
|
952
|
+
filename
|
953
|
+
line
|
954
|
+
mime_type
|
955
|
+
preserve
|
956
|
+
remove_whitespace
|
957
|
+
suppress_eval
|
958
|
+
].each do |option|
|
959
|
+
attr_reader option
|
960
|
+
end
|
961
|
+
|
962
|
+
DEFAULTS = {
|
963
|
+
autoclose: %w(area base basefont br col command embed frame
|
964
|
+
hr img input isindex keygen link menuitem meta
|
965
|
+
param source track wbr),
|
966
|
+
escape_html: false,
|
967
|
+
filename: '(haml)',
|
968
|
+
line: 1,
|
969
|
+
mime_type: 'text/html',
|
970
|
+
preserve: %w(textarea pre code),
|
971
|
+
remove_whitespace: false,
|
972
|
+
suppress_eval: false,
|
973
|
+
}
|
974
|
+
|
975
|
+
def initialize(values = {})
|
976
|
+
DEFAULTS.each {|k, v| instance_variable_set :"@#{k}", v}
|
977
|
+
AVAILABLE_OPTIONS.each do |key|
|
978
|
+
if values.key?(key)
|
979
|
+
instance_variable_set :"@#{key}", values[key]
|
980
|
+
end
|
981
|
+
end
|
982
|
+
end
|
983
|
+
end
|
984
|
+
private_constant :ParserOptions
|
855
985
|
end
|
856
986
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
require 'haml/helpers'
|
3
|
+
|
4
|
+
# Currently this Haml::Helpers depends on
|
5
|
+
# ActionView internal implementation. (not desired)
|
6
|
+
module Haml
|
7
|
+
module RailsHelpers
|
8
|
+
include Helpers
|
9
|
+
extend self
|
10
|
+
|
11
|
+
DEFAULT_PRESERVE_TAGS = %w[textarea pre code].freeze
|
12
|
+
|
13
|
+
def find_and_preserve(input = nil, tags = DEFAULT_PRESERVE_TAGS, &block)
|
14
|
+
return find_and_preserve(capture_haml(&block), input || tags) if block
|
15
|
+
|
16
|
+
tags = tags.each_with_object('') do |t, s|
|
17
|
+
s << '|' unless s.empty?
|
18
|
+
s << Regexp.escape(t)
|
19
|
+
end
|
20
|
+
|
21
|
+
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
22
|
+
input.to_s.gsub(re) do |s|
|
23
|
+
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
24
|
+
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def preserve(input = nil, &block)
|
29
|
+
return preserve(capture_haml(&block)) if block
|
30
|
+
super.html_safe
|
31
|
+
end
|
32
|
+
|
33
|
+
def surround(front, back = front, &block)
|
34
|
+
output = capture_haml(&block)
|
35
|
+
|
36
|
+
"#{escape_once(front)}#{output.chomp}#{escape_once(back)}\n".html_safe
|
37
|
+
end
|
38
|
+
|
39
|
+
def precede(str, &block)
|
40
|
+
"#{escape_once(str)}#{capture_haml(&block).chomp}\n".html_safe
|
41
|
+
end
|
42
|
+
|
43
|
+
def succeed(str, &block)
|
44
|
+
"#{capture_haml(&block).chomp}#{escape_once(str)}\n".html_safe
|
45
|
+
end
|
46
|
+
|
47
|
+
def capture_haml(*args, &block)
|
48
|
+
capture(*args, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'temple'
|
3
|
+
require 'haml/engine'
|
4
|
+
require 'haml/rails_helpers'
|
5
|
+
require 'haml/util'
|
6
|
+
|
7
|
+
module Haml
|
8
|
+
class RailsTemplate
|
9
|
+
# Compatible with: https://github.com/judofyr/temple/blob/v0.7.7/lib/temple/mixins/options.rb#L15-L24
|
10
|
+
class << self
|
11
|
+
def options
|
12
|
+
@options ||= {
|
13
|
+
generator: Temple::Generators::RailsOutputBuffer,
|
14
|
+
use_html_safe: true,
|
15
|
+
streaming: true,
|
16
|
+
buffer_class: 'ActionView::OutputBuffer',
|
17
|
+
disable_capture: true,
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_options(opts)
|
22
|
+
options.update(opts)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(template, source = nil)
|
27
|
+
source ||= template.source
|
28
|
+
options = RailsTemplate.options
|
29
|
+
|
30
|
+
# https://github.com/haml/haml/blob/4.0.7/lib/haml/template/plugin.rb#L19-L20
|
31
|
+
# https://github.com/haml/haml/blob/4.0.7/lib/haml/options.rb#L228
|
32
|
+
if template.respond_to?(:type) && template.type == 'text/xml'
|
33
|
+
options = options.merge(format: :xhtml)
|
34
|
+
end
|
35
|
+
|
36
|
+
if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html
|
37
|
+
options = options.merge(
|
38
|
+
preamble: "<!-- BEGIN #{template.short_identifier} -->\n",
|
39
|
+
postamble: "<!-- END #{template.short_identifier} -->\n",
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
Engine.new(options).call(source)
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_streaming?
|
47
|
+
RailsTemplate.options[:streaming]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
ActionView::Template.register_template_handler(:haml, RailsTemplate.new)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Haml extends Haml::Helpers in ActionView each time.
|
54
|
+
# It costs much, so Haml includes a compatible module at first.
|
55
|
+
ActionView::Base.send :include, Haml::RailsHelpers
|