haml 5.2.2 → 6.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|