haml 5.0.4 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +40 -0
- data/.gitignore +16 -15
- data/CHANGELOG.md +62 -1
- data/Gemfile +18 -14
- data/MIT-LICENSE +2 -2
- data/README.md +4 -5
- data/REFERENCE.md +46 -12
- data/Rakefile +93 -103
- 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 -30
- data/lib/haml/ambles.rb +20 -0
- data/lib/haml/attribute_builder.rb +140 -128
- data/lib/haml/attribute_compiler.rb +86 -181
- data/lib/haml/attribute_parser.rb +86 -124
- 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 +64 -298
- data/lib/haml/dynamic_merger.rb +67 -0
- data/lib/haml/engine.rb +43 -219
- data/lib/haml/error.rb +29 -27
- data/lib/haml/escapable.rb +6 -42
- 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 +55 -378
- data/lib/haml/force_escapable.rb +29 -0
- data/lib/haml/helpers.rb +4 -696
- 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 +208 -43
- data/lib/haml/rails_helpers.rb +51 -0
- data/lib/haml/rails_template.rb +55 -0
- data/lib/haml/railtie.rb +7 -40
- data/lib/haml/ruby_expression.rb +32 -0
- data/lib/haml/string_splitter.rb +20 -0
- data/lib/haml/template.rb +15 -33
- data/lib/haml/temple_line_counter.rb +2 -0
- data/lib/haml/util.rb +23 -21
- data/lib/haml/version.rb +1 -1
- data/lib/haml.rb +8 -19
- metadata +222 -50
- data/.gitmodules +0 -3
- data/.travis.yml +0 -54
- data/.yardopts +0 -23
- data/TODO +0 -24
- data/benchmark.rb +0 -66
- data/bin/haml +0 -9
- data/lib/haml/.gitattributes +0 -1
- data/lib/haml/buffer.rb +0 -235
- data/lib/haml/exec.rb +0 -348
- data/lib/haml/generator.rb +0 -41
- data/lib/haml/helpers/action_view_extensions.rb +0 -59
- data/lib/haml/helpers/action_view_mods.rb +0 -129
- data/lib/haml/helpers/action_view_xss_mods.rb +0 -59
- data/lib/haml/helpers/safe_erubi_template.rb +0 -19
- data/lib/haml/helpers/safe_erubis_template.rb +0 -32
- data/lib/haml/helpers/xss_mods.rb +0 -110
- data/lib/haml/options.rb +0 -273
- data/lib/haml/plugin.rb +0 -34
- data/lib/haml/sass_rails_filter.rb +0 -46
- data/lib/haml/template/options.rb +0 -26
- data/lib/haml/temple_engine.rb +0 -121
- 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
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
# frozen_string_literal:
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ripper'
|
|
2
4
|
require 'strscan'
|
|
5
|
+
require 'haml/error'
|
|
6
|
+
require 'haml/util'
|
|
3
7
|
|
|
4
8
|
module Haml
|
|
5
9
|
class Parser
|
|
@@ -60,7 +64,7 @@ module Haml
|
|
|
60
64
|
SILENT_SCRIPT,
|
|
61
65
|
ESCAPE,
|
|
62
66
|
FILTER
|
|
63
|
-
]
|
|
67
|
+
].freeze
|
|
64
68
|
|
|
65
69
|
# The value of the character that designates that a line is part
|
|
66
70
|
# of a multiline string.
|
|
@@ -74,8 +78,8 @@ module Haml
|
|
|
74
78
|
#
|
|
75
79
|
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
|
76
80
|
|
|
77
|
-
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
|
78
|
-
START_BLOCK_KEYWORDS = %w[if begin case unless]
|
|
81
|
+
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when].freeze
|
|
82
|
+
START_BLOCK_KEYWORDS = %w[if begin case unless].freeze
|
|
79
83
|
# Try to parse assignments to block starters as best as possible
|
|
80
84
|
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
|
81
85
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
|
@@ -89,16 +93,27 @@ module Haml
|
|
|
89
93
|
ID_KEY = 'id'.freeze
|
|
90
94
|
CLASS_KEY = 'class'.freeze
|
|
91
95
|
|
|
96
|
+
# Used for scanning old attributes, substituting the first '{'
|
|
97
|
+
METHOD_CALL_PREFIX = 'a('
|
|
98
|
+
|
|
92
99
|
def initialize(options)
|
|
93
|
-
@options =
|
|
100
|
+
@options = ParserOptions.new(options)
|
|
94
101
|
# Record the indent levels of "if" statements to validate the subsequent
|
|
95
102
|
# elsif and else statements are indented at the appropriate level.
|
|
96
103
|
@script_level_stack = []
|
|
97
104
|
@template_index = 0
|
|
98
105
|
@template_tabs = 0
|
|
106
|
+
# When used in Haml::Engine, which gives options[:generator] to every filter
|
|
107
|
+
# in the engine, including Haml::Parser, we don't want to throw exceptions.
|
|
108
|
+
# However, when Haml::Parser is used as a library, we want to throw exceptions.
|
|
109
|
+
@raise_error = !options.key?(:generator)
|
|
99
110
|
end
|
|
100
111
|
|
|
101
112
|
def call(template)
|
|
113
|
+
template = Haml::Util.check_haml_encoding(template) do |msg, line|
|
|
114
|
+
raise Haml::Error.new(msg, line)
|
|
115
|
+
end
|
|
116
|
+
|
|
102
117
|
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
|
103
118
|
# discard the last match which is always blank
|
|
104
119
|
match.pop
|
|
@@ -147,7 +162,8 @@ module Haml
|
|
|
147
162
|
@root
|
|
148
163
|
rescue Haml::Error => e
|
|
149
164
|
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
|
150
|
-
raise
|
|
165
|
+
raise if @raise_error
|
|
166
|
+
error_with_lineno(e)
|
|
151
167
|
end
|
|
152
168
|
|
|
153
169
|
def compute_tabs(line)
|
|
@@ -177,8 +193,18 @@ module Haml
|
|
|
177
193
|
|
|
178
194
|
private
|
|
179
195
|
|
|
196
|
+
def error_with_lineno(error)
|
|
197
|
+
return error if error.line
|
|
198
|
+
|
|
199
|
+
trace = error.backtrace.first
|
|
200
|
+
return error unless trace
|
|
201
|
+
|
|
202
|
+
line = trace.match(/\d+\z/).to_s.to_i
|
|
203
|
+
SyntaxError.new(error.message, line)
|
|
204
|
+
end
|
|
205
|
+
|
|
180
206
|
# @private
|
|
181
|
-
|
|
207
|
+
Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
|
|
182
208
|
alias_method :eod?, :eod
|
|
183
209
|
|
|
184
210
|
# @private
|
|
@@ -194,28 +220,29 @@ module Haml
|
|
|
194
220
|
end
|
|
195
221
|
|
|
196
222
|
# @private
|
|
197
|
-
|
|
223
|
+
ParseNode = Struct.new(:type, :line, :value, :parent, :children) do
|
|
198
224
|
def initialize(*args)
|
|
199
225
|
super
|
|
200
226
|
self.children ||= []
|
|
201
227
|
end
|
|
202
228
|
|
|
203
229
|
def inspect
|
|
204
|
-
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
|
230
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object(''.dup) {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})].dup
|
|
205
231
|
end
|
|
206
232
|
end
|
|
207
233
|
|
|
208
234
|
# @param [String] new - Hash literal including dynamic values.
|
|
209
235
|
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
|
210
|
-
|
|
236
|
+
DynamicAttributes = Struct.new(:new, :old) do
|
|
237
|
+
undef :old=
|
|
211
238
|
def old=(value)
|
|
212
239
|
unless value =~ /\A{.*}\z/m
|
|
213
240
|
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
|
214
241
|
end
|
|
215
|
-
|
|
242
|
+
self[:old] = value
|
|
216
243
|
end
|
|
217
244
|
|
|
218
|
-
# This will be a literal for Haml::
|
|
245
|
+
# This will be a literal for Haml::HamlBuffer#attributes's last argument, `attributes_hashes`.
|
|
219
246
|
def to_literal
|
|
220
247
|
[new, stripped_old].compact.join(', ')
|
|
221
248
|
end
|
|
@@ -287,7 +314,7 @@ module Haml
|
|
|
287
314
|
end
|
|
288
315
|
|
|
289
316
|
def block_keyword(text)
|
|
290
|
-
return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
|
|
317
|
+
return unless (keyword = text.scan(BLOCK_KEYWORD_REGEX)[0])
|
|
291
318
|
keyword[0] || keyword[1]
|
|
292
319
|
end
|
|
293
320
|
|
|
@@ -301,13 +328,13 @@ module Haml
|
|
|
301
328
|
raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
|
|
302
329
|
end
|
|
303
330
|
|
|
304
|
-
unless contains_interpolation?(line.text)
|
|
331
|
+
unless Util.contains_interpolation?(line.text)
|
|
305
332
|
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
|
306
333
|
end
|
|
307
334
|
|
|
308
|
-
escape_html = @options.escape_html if escape_html.nil?
|
|
309
|
-
line.text = unescape_interpolation(line.text
|
|
310
|
-
script(line, false)
|
|
335
|
+
escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
|
|
336
|
+
line.text = Util.unescape_interpolation(line.text)
|
|
337
|
+
script(line, false).tap { |n| n.value[:escape_interpolation] = true if escape_html }
|
|
311
338
|
end
|
|
312
339
|
|
|
313
340
|
def script(line, escape_html = nil, preserve = false)
|
|
@@ -394,7 +421,8 @@ module Haml
|
|
|
394
421
|
when '='
|
|
395
422
|
parse = true
|
|
396
423
|
if value[0] == ?=
|
|
397
|
-
value = unescape_interpolation(value[1..-1].strip
|
|
424
|
+
value = Util.unescape_interpolation(value[1..-1].strip)
|
|
425
|
+
escape_interpolation = true if escape_html
|
|
398
426
|
escape_html = false
|
|
399
427
|
end
|
|
400
428
|
when '&', '!'
|
|
@@ -402,19 +430,21 @@ module Haml
|
|
|
402
430
|
parse = true
|
|
403
431
|
preserve_script = (value[0] == ?~)
|
|
404
432
|
if value[1] == ?=
|
|
405
|
-
value = unescape_interpolation(value[2..-1].strip
|
|
433
|
+
value = Util.unescape_interpolation(value[2..-1].strip)
|
|
434
|
+
escape_interpolation = true if escape_html
|
|
406
435
|
escape_html = false
|
|
407
436
|
else
|
|
408
437
|
value = value[1..-1].strip
|
|
409
438
|
end
|
|
410
|
-
elsif contains_interpolation?(value)
|
|
411
|
-
value = unescape_interpolation(value
|
|
439
|
+
elsif Util.contains_interpolation?(value)
|
|
440
|
+
value = Util.unescape_interpolation(value)
|
|
441
|
+
escape_interpolation = true if escape_html
|
|
412
442
|
parse = true
|
|
413
443
|
escape_html = false
|
|
414
444
|
end
|
|
415
445
|
else
|
|
416
|
-
if contains_interpolation?(value)
|
|
417
|
-
value = unescape_interpolation(value, escape_html)
|
|
446
|
+
if Util.contains_interpolation?(value)
|
|
447
|
+
value = Util.unescape_interpolation(value, escape_html)
|
|
418
448
|
parse = true
|
|
419
449
|
escape_html = false
|
|
420
450
|
end
|
|
@@ -425,13 +455,13 @@ module Haml
|
|
|
425
455
|
|
|
426
456
|
if attributes_hashes[:new]
|
|
427
457
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
|
428
|
-
|
|
458
|
+
AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
|
|
429
459
|
dynamic_attributes.new = attributes_hash
|
|
430
460
|
end
|
|
431
461
|
|
|
432
462
|
if attributes_hashes[:old]
|
|
433
463
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
|
434
|
-
|
|
464
|
+
AttributeMerger.merge_attributes!(attributes, static_attributes) if static_attributes
|
|
435
465
|
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
|
436
466
|
end
|
|
437
467
|
|
|
@@ -453,7 +483,8 @@ module Haml
|
|
|
453
483
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
|
454
484
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
|
455
485
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
|
456
|
-
:preserve_script => preserve_script, :parse => parse, :value => line.text
|
|
486
|
+
:preserve_script => preserve_script, :parse => parse, :value => line.text,
|
|
487
|
+
:escape_interpolation => escape_interpolation)
|
|
457
488
|
end
|
|
458
489
|
|
|
459
490
|
# Renders a line that creates an XHTML tag and has an implicit div because of
|
|
@@ -475,9 +506,9 @@ module Haml
|
|
|
475
506
|
conditional, text = balance(text, ?[, ?]) if text[0] == ?[
|
|
476
507
|
text.strip!
|
|
477
508
|
|
|
478
|
-
if contains_interpolation?(text)
|
|
509
|
+
if Util.contains_interpolation?(text)
|
|
479
510
|
parse = true
|
|
480
|
-
text = unescape_interpolation(text)
|
|
511
|
+
text = Util.unescape_interpolation(text)
|
|
481
512
|
else
|
|
482
513
|
parse = false
|
|
483
514
|
end
|
|
@@ -534,7 +565,7 @@ module Haml
|
|
|
534
565
|
|
|
535
566
|
# Post-process case statements to normalize the nesting of "when" clauses
|
|
536
567
|
return unless node.value[:keyword] == "case"
|
|
537
|
-
return unless first = node.children.first
|
|
568
|
+
return unless (first = node.children.first)
|
|
538
569
|
return unless first.type == :silent_script && first.value[:keyword] == "when"
|
|
539
570
|
return if first.children.empty?
|
|
540
571
|
# If the case node has a "when" child with children, it's the
|
|
@@ -546,7 +577,7 @@ module Haml
|
|
|
546
577
|
|
|
547
578
|
alias :close_script :close_silent_script
|
|
548
579
|
|
|
549
|
-
# This is a class method so it can be accessed from {Haml::
|
|
580
|
+
# This is a class method so it can be accessed from {Haml::HamlHelpers}.
|
|
550
581
|
#
|
|
551
582
|
# Iterates through the classes and ids supplied through `.`
|
|
552
583
|
# and `#` syntax, and returns a hash with them as attributes,
|
|
@@ -570,8 +601,8 @@ module Haml
|
|
|
570
601
|
attributes
|
|
571
602
|
end
|
|
572
603
|
|
|
573
|
-
# This method doesn't use Haml::
|
|
574
|
-
# Ideally this logic should be placed in Haml::
|
|
604
|
+
# This method doesn't use Haml::HamlAttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
|
|
605
|
+
# Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
|
|
575
606
|
#
|
|
576
607
|
# @param [String] text - Hash literal or text inside old attributes
|
|
577
608
|
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
|
@@ -583,9 +614,9 @@ module Haml
|
|
|
583
614
|
scanner = StringScanner.new(text)
|
|
584
615
|
scanner.scan(/\s+/)
|
|
585
616
|
until scanner.eos?
|
|
586
|
-
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
|
617
|
+
return unless (key = scanner.scan(LITERAL_VALUE_REGEX))
|
|
587
618
|
return unless scanner.scan(/\s*=>\s*/)
|
|
588
|
-
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
|
619
|
+
return unless (value = scanner.scan(LITERAL_VALUE_REGEX))
|
|
589
620
|
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
|
590
621
|
attributes[eval(key).to_s] = eval(value).to_s
|
|
591
622
|
end
|
|
@@ -649,13 +680,18 @@ module Haml
|
|
|
649
680
|
# @return [String] rest
|
|
650
681
|
# @return [Integer] last_line
|
|
651
682
|
def parse_old_attributes(text)
|
|
652
|
-
text = text.dup
|
|
653
683
|
last_line = @line.index + 1
|
|
654
684
|
|
|
655
685
|
begin
|
|
656
|
-
|
|
686
|
+
# Old attributes often look like a valid Hash literal, but it sometimes allow code like
|
|
687
|
+
# `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
|
|
688
|
+
#
|
|
689
|
+
# To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops when there is
|
|
690
|
+
# 1 more :on_embexpr_end (the last '}') than :on_embexpr_beg, and resurrects '{' afterwards.
|
|
691
|
+
balanced, rest = balance_tokens(text.sub(?{, METHOD_CALL_PREFIX), :on_embexpr_beg, :on_embexpr_end, count: 1)
|
|
692
|
+
attributes_hash = balanced.sub(METHOD_CALL_PREFIX, ?{)
|
|
657
693
|
rescue SyntaxError => e
|
|
658
|
-
if
|
|
694
|
+
if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
|
|
659
695
|
text << "\n#{@next_line.text}"
|
|
660
696
|
last_line += 1
|
|
661
697
|
next_line
|
|
@@ -698,12 +734,12 @@ module Haml
|
|
|
698
734
|
end
|
|
699
735
|
|
|
700
736
|
static_attributes = {}
|
|
701
|
-
dynamic_attributes = "{"
|
|
737
|
+
dynamic_attributes = "{".dup
|
|
702
738
|
attributes.each do |name, (type, val)|
|
|
703
739
|
if type == :static
|
|
704
740
|
static_attributes[name] = val
|
|
705
741
|
else
|
|
706
|
-
dynamic_attributes << "#{inspect_obj(name)} => #{val},"
|
|
742
|
+
dynamic_attributes << "#{Util.inspect_obj(name)} => #{val},"
|
|
707
743
|
end
|
|
708
744
|
end
|
|
709
745
|
dynamic_attributes << "}"
|
|
@@ -713,7 +749,7 @@ module Haml
|
|
|
713
749
|
end
|
|
714
750
|
|
|
715
751
|
def parse_new_attribute(scanner)
|
|
716
|
-
unless name = scanner.scan(/[-:\w]+/)
|
|
752
|
+
unless (name = scanner.scan(/[-:\w]+/))
|
|
717
753
|
return if scanner.scan(/\)/)
|
|
718
754
|
return false
|
|
719
755
|
end
|
|
@@ -722,8 +758,8 @@ module Haml
|
|
|
722
758
|
return name, [:static, true] unless scanner.scan(/=/) #/end
|
|
723
759
|
|
|
724
760
|
scanner.scan(/\s*/)
|
|
725
|
-
unless quote = scanner.scan(/["']/)
|
|
726
|
-
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
|
761
|
+
unless (quote = scanner.scan(/["']/))
|
|
762
|
+
return false unless (var = scanner.scan(/(@@?|\$)?\w+/))
|
|
727
763
|
return name, [:dynamic, var]
|
|
728
764
|
end
|
|
729
765
|
|
|
@@ -738,7 +774,7 @@ module Haml
|
|
|
738
774
|
|
|
739
775
|
return name, [:static, content.first[1]] if content.size == 1
|
|
740
776
|
return name, [:dynamic,
|
|
741
|
-
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
|
777
|
+
%!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? Util.inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
|
742
778
|
end
|
|
743
779
|
|
|
744
780
|
def next_line
|
|
@@ -809,6 +845,25 @@ module Haml
|
|
|
809
845
|
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
|
810
846
|
end
|
|
811
847
|
|
|
848
|
+
# Unlike #balance, this balances Ripper tokens to balance something like `{ a: "}" }` correctly.
|
|
849
|
+
def balance_tokens(buf, start, finish, count: 0)
|
|
850
|
+
text = ''.dup
|
|
851
|
+
Ripper.lex(buf).each do |_, token, str|
|
|
852
|
+
text << str
|
|
853
|
+
case token
|
|
854
|
+
when start
|
|
855
|
+
count += 1
|
|
856
|
+
when finish
|
|
857
|
+
count -= 1
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
if count == 0
|
|
861
|
+
return text, buf.sub(text, '')
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
raise SyntaxError.new(Error.message(:unbalanced_brackets))
|
|
865
|
+
end
|
|
866
|
+
|
|
812
867
|
def block_opened?
|
|
813
868
|
@next_line.tabs > @line.tabs
|
|
814
869
|
end
|
|
@@ -822,5 +877,115 @@ module Haml
|
|
|
822
877
|
def flat?
|
|
823
878
|
@flat
|
|
824
879
|
end
|
|
880
|
+
|
|
881
|
+
class << AttributeMerger = Object.new
|
|
882
|
+
# Merges two attribute hashes.
|
|
883
|
+
# This is the same as `to.merge!(from)`,
|
|
884
|
+
# except that it merges id, class, and data attributes.
|
|
885
|
+
#
|
|
886
|
+
# ids are concatenated with `"_"`,
|
|
887
|
+
# and classes are concatenated with `" "`.
|
|
888
|
+
# data hashes are simply merged.
|
|
889
|
+
#
|
|
890
|
+
# Destructively modifies `to`.
|
|
891
|
+
#
|
|
892
|
+
# @param to [{String => String,Hash}] The attribute hash to merge into
|
|
893
|
+
# @param from [{String => Object}] The attribute hash to merge from
|
|
894
|
+
# @return [{String => String,Hash}] `to`, after being merged
|
|
895
|
+
def merge_attributes!(to, from)
|
|
896
|
+
from.keys.each do |key|
|
|
897
|
+
to[key] = merge_value(key, to[key], from[key])
|
|
898
|
+
end
|
|
899
|
+
to
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
private
|
|
903
|
+
|
|
904
|
+
# @return [String, nil]
|
|
905
|
+
def filter_and_join(value, separator)
|
|
906
|
+
return '' if (value.respond_to?(:empty?) && value.empty?)
|
|
907
|
+
|
|
908
|
+
if value.is_a?(Array)
|
|
909
|
+
value = value.flatten
|
|
910
|
+
value.map! {|item| item ? item.to_s : nil}
|
|
911
|
+
value.compact!
|
|
912
|
+
value = value.join(separator)
|
|
913
|
+
else
|
|
914
|
+
value = value ? value.to_s : nil
|
|
915
|
+
end
|
|
916
|
+
!value.nil? && !value.empty? && value
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# Merge a couple of values to one attribute value. No destructive operation.
|
|
920
|
+
#
|
|
921
|
+
# @param to [String,Hash,nil]
|
|
922
|
+
# @param from [Object]
|
|
923
|
+
# @return [String,Hash]
|
|
924
|
+
def merge_value(key, to, from)
|
|
925
|
+
if from.kind_of?(Hash) || to.kind_of?(Hash)
|
|
926
|
+
from = { nil => from } if !from.is_a?(Hash)
|
|
927
|
+
to = { nil => to } if !to.is_a?(Hash)
|
|
928
|
+
to.merge(from)
|
|
929
|
+
elsif key == 'id'
|
|
930
|
+
merged_id = filter_and_join(from, '_')
|
|
931
|
+
if to && merged_id
|
|
932
|
+
merged_id = "#{to}_#{merged_id}"
|
|
933
|
+
elsif to || merged_id
|
|
934
|
+
merged_id ||= to
|
|
935
|
+
end
|
|
936
|
+
merged_id
|
|
937
|
+
elsif key == 'class'
|
|
938
|
+
merged_class = filter_and_join(from, ' ')
|
|
939
|
+
if to && merged_class
|
|
940
|
+
merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
|
|
941
|
+
elsif to || merged_class
|
|
942
|
+
merged_class ||= to
|
|
943
|
+
end
|
|
944
|
+
merged_class
|
|
945
|
+
else
|
|
946
|
+
from
|
|
947
|
+
end
|
|
948
|
+
end
|
|
949
|
+
end
|
|
950
|
+
private_constant :AttributeMerger
|
|
951
|
+
|
|
952
|
+
class ParserOptions
|
|
953
|
+
# A list of options that are actually used in the parser
|
|
954
|
+
AVAILABLE_OPTIONS = %i[
|
|
955
|
+
autoclose
|
|
956
|
+
escape_html
|
|
957
|
+
filename
|
|
958
|
+
line
|
|
959
|
+
mime_type
|
|
960
|
+
preserve
|
|
961
|
+
remove_whitespace
|
|
962
|
+
suppress_eval
|
|
963
|
+
].each do |option|
|
|
964
|
+
attr_reader option
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
DEFAULTS = {
|
|
968
|
+
autoclose: %w(area base basefont br col command embed frame
|
|
969
|
+
hr img input isindex keygen link menuitem meta
|
|
970
|
+
param source track wbr),
|
|
971
|
+
escape_html: false,
|
|
972
|
+
filename: '(haml)',
|
|
973
|
+
line: 1,
|
|
974
|
+
mime_type: 'text/html',
|
|
975
|
+
preserve: %w(textarea pre code),
|
|
976
|
+
remove_whitespace: false,
|
|
977
|
+
suppress_eval: false,
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
def initialize(values = {})
|
|
981
|
+
DEFAULTS.each {|k, v| instance_variable_set :"@#{k}", v}
|
|
982
|
+
AVAILABLE_OPTIONS.each do |key|
|
|
983
|
+
if values.key?(key)
|
|
984
|
+
instance_variable_set :"@#{key}", values[key]
|
|
985
|
+
end
|
|
986
|
+
end
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
private_constant :ParserOptions
|
|
825
990
|
end
|
|
826
991
|
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
|