haml 7.2.2 → 7.3.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/CHANGELOG.md +7 -0
- data/haml.gemspec +4 -1
- data/lib/haml/attribute_compiler.rb +1 -3
- data/lib/haml/attribute_parser.rb +41 -78
- data/lib/haml/compiler/script_compiler.rb +6 -1
- data/lib/haml/compiler/tag_compiler.rb +6 -1
- data/lib/haml/filters/plain.rb +6 -1
- data/lib/haml/parser.rb +18 -15
- data/lib/haml/ruby_expression.rb +25 -18
- data/lib/haml/string_splitter.rb +43 -96
- data/lib/haml/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 381430279e5ca21c3d03f899c686f972609e54633c8e530609511e852597b7ef
|
|
4
|
+
data.tar.gz: a108268dccda6dc0e3ca014e484a7bcc0f50c973771a0057a086d75de2847495
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5f8d7bcc23e52be3fd4ed443ec3fdf171773f9c9aaacd1705d92ec09a3ef372adf13a8715ca4b4d039aa2a07fc2fdbb7e59eef30a22f55be2823bb0c7e23f0d
|
|
7
|
+
data.tar.gz: 954f3aa91fa3f49499d05dd1503056dd65a439519c5b2bcc300a6cf3dfbacf686717c8aa1e10ddb1ca7fa779504d92561a33e8a7d729b8013967cde102636fca
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Haml Changelog
|
|
2
2
|
|
|
3
|
+
## 7.3.0
|
|
4
|
+
|
|
5
|
+
* Replace Ripper with Prism https://github.com/haml/haml/pull/1214
|
|
6
|
+
* Interpolate `#@ivar`, `#$gvar` and `#@@cvar` in string literals instead of dropping them
|
|
7
|
+
* Keep an escaped delimiter of a percent literal, so `= %q{a\}b}` renders `a}b`
|
|
8
|
+
* Deprecate `Haml::AttributeParser.available?`, which is now always true and will be removed in the future
|
|
9
|
+
|
|
3
10
|
## 7.2.2
|
|
4
11
|
|
|
5
12
|
* Remove obsolete TruffleRuby compatibility skips https://github.com/haml/haml/pull/1210
|
data/haml.gemspec
CHANGED
|
@@ -21,11 +21,14 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
|
|
22
22
|
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
23
23
|
|
|
24
|
-
spec.metadata["
|
|
24
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/haml/haml/issues"
|
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/haml/haml/blob/main/CHANGELOG.md"
|
|
26
|
+
spec.metadata["homepage_uri"] = "https://haml.info/"
|
|
25
27
|
spec.metadata["source_code_uri"] = "https://github.com/haml/haml"
|
|
26
28
|
|
|
27
29
|
spec.required_ruby_version = '>= 3.2.0'
|
|
28
30
|
|
|
31
|
+
spec.add_dependency 'prism', '>= 1.1.0'
|
|
29
32
|
spec.add_dependency 'temple', '>= 0.8.2'
|
|
30
33
|
spec.add_dependency 'thor'
|
|
31
34
|
spec.add_dependency 'tilt'
|
|
@@ -22,9 +22,7 @@ module Haml
|
|
|
22
22
|
|
|
23
23
|
def compile(node)
|
|
24
24
|
hashes = []
|
|
25
|
-
if node.value[:object_ref] != :nil
|
|
26
|
-
return runtime_compile(node)
|
|
27
|
-
end
|
|
25
|
+
return runtime_compile(node) if node.value[:object_ref] != :nil
|
|
28
26
|
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
|
|
29
27
|
hash = AttributeParser.parse(attribute_str)
|
|
30
28
|
return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
|
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require '
|
|
2
|
+
require 'prism'
|
|
3
3
|
|
|
4
4
|
module Haml
|
|
5
5
|
class AttributeParser
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# @return [TrueClass, FalseClass] - return true if AttributeParser.parse can be used.
|
|
6
|
+
# @deprecated Prism is a hard dependency, so this is always true. Haml itself no longer
|
|
7
|
+
# asks, and this will be removed in the future.
|
|
8
|
+
# @return [TrueClass] - return true if AttributeParser.parse can be used.
|
|
10
9
|
def self.available?
|
|
11
|
-
|
|
10
|
+
true
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def self.parse(text)
|
|
15
14
|
self.new.parse(text)
|
|
16
15
|
end
|
|
17
16
|
|
|
17
|
+
# @return [Hash,nil] - keys and values are the attribute source as written, or nil if
|
|
18
|
+
# the text is not a Hash literal whose keys are all static.
|
|
18
19
|
def parse(text)
|
|
19
20
|
exp = wrap_bracket(text)
|
|
20
|
-
|
|
21
|
+
# A multi-line hash is left to the runtime, which keeps the [:newline] bookkeeping of
|
|
22
|
+
# the compiled code correct. Compiling it statically is a separate optimization.
|
|
23
|
+
return if exp.include?("\n")
|
|
24
|
+
|
|
25
|
+
node = hash_node(exp)
|
|
26
|
+
return if node.nil?
|
|
21
27
|
|
|
22
28
|
hash = {}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
node.elements.each do |element|
|
|
30
|
+
return unless element.is_a?(Prism::AssocNode)
|
|
31
|
+
|
|
32
|
+
key = static_key(element.key)
|
|
33
|
+
return if key.nil?
|
|
34
|
+
|
|
35
|
+
hash[key] = value_source(element.value)
|
|
27
36
|
end
|
|
28
37
|
hash
|
|
29
|
-
rescue ParseSkip
|
|
30
|
-
nil
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
private
|
|
@@ -38,78 +45,34 @@ module Haml
|
|
|
38
45
|
"{#{text}}"
|
|
39
46
|
end
|
|
40
47
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_, _, key = tokens.shift
|
|
50
|
-
assert_type!(tokens.shift, :on_tstring_end) if str != ':'
|
|
51
|
-
skip_until_hash_rocket!(tokens)
|
|
52
|
-
key
|
|
53
|
-
when :on_tstring_beg
|
|
54
|
-
_, _, key = tokens.shift
|
|
55
|
-
next_token = tokens.shift
|
|
56
|
-
unless next_token[1] == :on_label_end
|
|
57
|
-
assert_type!(next_token, :on_tstring_end)
|
|
58
|
-
skip_until_hash_rocket!(tokens)
|
|
59
|
-
end
|
|
60
|
-
key
|
|
61
|
-
else
|
|
62
|
-
raise ParseSkip
|
|
63
|
-
end
|
|
64
|
-
end
|
|
48
|
+
def hash_node(exp)
|
|
49
|
+
# partial_script, because an attribute may legitimately call `yield` and the like:
|
|
50
|
+
# a template is compiled into a method body.
|
|
51
|
+
result = Prism.parse(exp, partial_script: true)
|
|
52
|
+
return if result.failure?
|
|
53
|
+
|
|
54
|
+
statements = result.value.statements.body
|
|
55
|
+
return if statements.size != 1
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
node = statements.first
|
|
58
|
+
node if node.is_a?(Prism::HashNode)
|
|
68
59
|
end
|
|
69
60
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
# The key as written between its delimiters, not unescaped: an escape has to reach the
|
|
62
|
+
# attribute name as the source spelled it, like the `\0` of `{ "a\0b" => 1 }`.
|
|
63
|
+
def static_key(key)
|
|
64
|
+
case key
|
|
65
|
+
when Prism::SymbolNode then key.value_loc&.slice
|
|
66
|
+
when Prism::StringNode then key.content_loc&.slice
|
|
74
67
|
end
|
|
75
68
|
end
|
|
76
69
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
def value_source(value)
|
|
71
|
+
# Ruby 3.1 value omission (`{ foo: }`). An empty value tells AttributeCompiler to
|
|
72
|
+
# fall back to the runtime, which is what resolves it.
|
|
73
|
+
return '' if value.is_a?(Prism::ImplicitNode)
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
_, type, _ = token
|
|
83
|
-
case type
|
|
84
|
-
when :on_comma
|
|
85
|
-
if open_tokens.values.all?(&:zero?)
|
|
86
|
-
yield(attr_tokens)
|
|
87
|
-
attr_tokens = []
|
|
88
|
-
next
|
|
89
|
-
end
|
|
90
|
-
when :on_lbracket
|
|
91
|
-
open_tokens[:array] += 1
|
|
92
|
-
when :on_rbracket
|
|
93
|
-
open_tokens[:array] -= 1
|
|
94
|
-
when :on_lbrace
|
|
95
|
-
open_tokens[:block] += 1
|
|
96
|
-
when :on_rbrace
|
|
97
|
-
open_tokens[:block] -= 1
|
|
98
|
-
when :on_lparen
|
|
99
|
-
open_tokens[:paren] += 1
|
|
100
|
-
when :on_rparen
|
|
101
|
-
open_tokens[:paren] -= 1
|
|
102
|
-
when :on_embexpr_beg
|
|
103
|
-
open_tokens[:embexpr] += 1
|
|
104
|
-
when :on_embexpr_end
|
|
105
|
-
open_tokens[:embexpr] -= 1
|
|
106
|
-
when :on_sp
|
|
107
|
-
next if attr_tokens.empty?
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
attr_tokens << token
|
|
111
|
-
end
|
|
112
|
-
yield(attr_tokens) unless attr_tokens.empty?
|
|
75
|
+
value.slice
|
|
113
76
|
end
|
|
114
77
|
end
|
|
115
78
|
end
|
|
@@ -39,8 +39,13 @@ module Haml
|
|
|
39
39
|
# String-interpolated plain text must be compiled with this method
|
|
40
40
|
# because we have to escape only interpolated values.
|
|
41
41
|
def compile_interpolated_plain(node)
|
|
42
|
+
compiled = StringSplitter.try_compile(node.value[:text])
|
|
43
|
+
# Ruby that does not parse: let the generated code report it, rather than failing
|
|
44
|
+
# the whole compilation with an error that points at Haml instead of the template.
|
|
45
|
+
return delegate_optimization(node) if compiled.nil?
|
|
46
|
+
|
|
42
47
|
temple = [:multi]
|
|
43
|
-
|
|
48
|
+
compiled.each do |type, value|
|
|
44
49
|
case type
|
|
45
50
|
when :static
|
|
46
51
|
temple << [:static, value]
|
|
@@ -53,8 +53,13 @@ module Haml
|
|
|
53
53
|
|
|
54
54
|
# We should handle interpolation here to escape only interpolated values.
|
|
55
55
|
def compile_interpolated_plain(node)
|
|
56
|
+
compiled = StringSplitter.try_compile(node.value[:value])
|
|
57
|
+
# Ruby that does not parse: let the generated code report it, rather than failing
|
|
58
|
+
# the whole compilation with an error that points at Haml instead of the template.
|
|
59
|
+
return delegate_optimization(node) if compiled.nil?
|
|
60
|
+
|
|
56
61
|
temple = [:multi]
|
|
57
|
-
|
|
62
|
+
compiled.each do |type, value|
|
|
58
63
|
case type
|
|
59
64
|
when :static
|
|
60
65
|
temple << [:static, value]
|
data/lib/haml/filters/plain.rb
CHANGED
|
@@ -14,7 +14,12 @@ module Haml
|
|
|
14
14
|
|
|
15
15
|
def compile_plain(text)
|
|
16
16
|
string_literal = ::Haml::Util.unescape_interpolation(text)
|
|
17
|
-
StringSplitter.
|
|
17
|
+
compiled = StringSplitter.try_compile(string_literal)
|
|
18
|
+
# Ruby that does not parse: let the generated code report it against the template,
|
|
19
|
+
# rather than failing the whole compilation.
|
|
20
|
+
return [[:escape, false, [:dynamic, string_literal]]] if compiled.nil?
|
|
21
|
+
|
|
22
|
+
compiled.map do |temple|
|
|
18
23
|
type, str = temple
|
|
19
24
|
case type
|
|
20
25
|
when :dynamic
|
data/lib/haml/parser.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'prism'
|
|
4
4
|
require 'strscan'
|
|
5
5
|
require 'haml/error'
|
|
6
6
|
require 'haml/util'
|
|
@@ -601,8 +601,7 @@ module Haml
|
|
|
601
601
|
attributes
|
|
602
602
|
end
|
|
603
603
|
|
|
604
|
-
#
|
|
605
|
-
# Ideally this logic should be placed in Haml::HamlAttributeParser instead of here and this method should use it.
|
|
604
|
+
# Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
|
|
606
605
|
#
|
|
607
606
|
# @param [String] text - Hash literal or text inside old attributes
|
|
608
607
|
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
|
@@ -686,17 +685,17 @@ module Haml
|
|
|
686
685
|
# Old attributes often look like a valid Hash literal, but it sometimes allow code like
|
|
687
686
|
# `{ hash, foo: bar }`, which is compiled to `_hamlout.attributes({}, nil, hash, foo: bar)`.
|
|
688
687
|
#
|
|
689
|
-
# To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops
|
|
690
|
-
#
|
|
688
|
+
# To scan such code correctly, this scans `a( hash, foo: bar }` instead, stops on the '}'
|
|
689
|
+
# that closes one more brace than was opened, and resurrects '{' afterwards.
|
|
691
690
|
#
|
|
692
691
|
# Old attributes allow invalid Hash constructs (e.g., `{ hash, foo: bar }`).
|
|
693
692
|
# Replacing the opening `{` with `a(` makes the syntax look like a method call so
|
|
694
|
-
#
|
|
693
|
+
# it can be lexed reliably.
|
|
695
694
|
|
|
696
695
|
attributes_hash, rest = balance_tokens(
|
|
697
696
|
text.sub(?{, METHOD_CALL_PREFIX),
|
|
698
|
-
[:
|
|
699
|
-
[:
|
|
697
|
+
[:BRACE_LEFT, :LAMBDA_BEGIN, :EMBEXPR_BEGIN],
|
|
698
|
+
[:BRACE_RIGHT, :EMBEXPR_END],
|
|
700
699
|
count: 1)
|
|
701
700
|
attributes_hash = attributes_hash.sub(METHOD_CALL_PREFIX, ?{)
|
|
702
701
|
rescue SyntaxError => e
|
|
@@ -854,19 +853,23 @@ module Haml
|
|
|
854
853
|
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
|
855
854
|
end
|
|
856
855
|
|
|
857
|
-
# Unlike #balance, this balances
|
|
856
|
+
# Unlike #balance, this balances lexed tokens to balance something like `{ a: "}" }` correctly.
|
|
858
857
|
def balance_tokens(buf, start, finish, count: 0)
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
858
|
+
Prism.lex(buf).value.each do |token, _|
|
|
859
|
+
type = token.type
|
|
860
|
+
next if type == :EOF
|
|
861
|
+
|
|
862
|
+
if start.include?(type)
|
|
863
863
|
count += 1
|
|
864
|
-
elsif finish.include?(
|
|
864
|
+
elsif finish.include?(type)
|
|
865
865
|
count -= 1
|
|
866
866
|
end
|
|
867
867
|
|
|
868
868
|
if count == 0
|
|
869
|
-
|
|
869
|
+
# Splitting on the offset instead of the tokens seen so far: whitespace and comments
|
|
870
|
+
# are not tokens, so the text cannot be rebuilt by concatenating them.
|
|
871
|
+
offset = token.location.end_offset
|
|
872
|
+
return buf.byteslice(0, offset), buf.byteslice(offset..)
|
|
870
873
|
end
|
|
871
874
|
end
|
|
872
875
|
raise SyntaxError.new(Error.message(:unbalanced_brackets))
|
data/lib/haml/ruby_expression.rb
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require '
|
|
2
|
+
require 'prism'
|
|
3
3
|
|
|
4
4
|
module Haml
|
|
5
|
-
class RubyExpression
|
|
6
|
-
|
|
5
|
+
class RubyExpression
|
|
6
|
+
# A character literal (`?a`) is a StringNode for Prism, but it has no quotes
|
|
7
|
+
# to split on, so it must not be reported as a string literal.
|
|
8
|
+
CHAR_LITERAL_OPENING = '?'
|
|
9
|
+
|
|
10
|
+
# A template is compiled into a method body, so a jump like `yield` is valid here even
|
|
11
|
+
# though it would not be at the top level of a script.
|
|
12
|
+
PARSE_OPTIONS = { partial_script: true }.freeze
|
|
7
13
|
|
|
8
14
|
def self.syntax_error?(code)
|
|
9
|
-
|
|
10
|
-
false
|
|
11
|
-
rescue ParseError
|
|
12
|
-
true
|
|
15
|
+
Prism.parse_failure?(code, **PARSE_OPTIONS)
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
def self.string_literal?(code)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type, instructions = Ripper.sexp(code)
|
|
19
|
-
return false if type != :program
|
|
20
|
-
return false if instructions.size > 1
|
|
21
|
-
|
|
22
|
-
type, _ = instructions.first
|
|
23
|
-
type == :string_literal
|
|
19
|
+
!string_literal_node(code).nil?
|
|
24
20
|
end
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
# @return [Prism::Node, nil] - the node of a string literal StringSplitter can split, if any.
|
|
23
|
+
# Its locations are byte offsets into `code` as given, so `code` must not be stripped here.
|
|
24
|
+
def self.string_literal_node(code)
|
|
25
|
+
result = Prism.parse(code, **PARSE_OPTIONS)
|
|
26
|
+
return if result.failure?
|
|
27
|
+
|
|
28
|
+
statements = result.value.statements.body
|
|
29
|
+
return if statements.size > 1
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
case (node = statements.first)
|
|
32
|
+
when Prism::StringNode, Prism::InterpolatedStringNode
|
|
33
|
+
# Adjacent concatenation (`"a" "b"`) is one node without an opening
|
|
34
|
+
# delimiter of its own, and each of its parts keeps its own quotes.
|
|
35
|
+
node if !node.opening.nil? && node.opening != CHAR_LITERAL_OPENING
|
|
36
|
+
end
|
|
30
37
|
end
|
|
31
38
|
end
|
|
32
39
|
end
|
data/lib/haml/string_splitter.rb
CHANGED
|
@@ -1,92 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require '
|
|
2
|
+
require 'prism'
|
|
3
|
+
require 'haml/ruby_expression'
|
|
3
4
|
|
|
4
5
|
module Haml
|
|
5
6
|
# Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]
|
|
6
7
|
class StringSplitter < Temple::Filter
|
|
7
8
|
class << self
|
|
8
|
-
# `code` param must be
|
|
9
|
-
def compile(code)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
compile_tokens!(exps, tokens)
|
|
9
|
+
# `code` param must be a string literal, as RubyExpression.string_literal? defines it.
|
|
10
|
+
def compile(code, node: RubyExpression.string_literal_node(code))
|
|
11
|
+
case node
|
|
12
|
+
when Prism::StringNode
|
|
13
|
+
node.unescaped.empty? ? [] : [[:static, node.unescaped]]
|
|
14
|
+
when Prism::InterpolatedStringNode
|
|
15
|
+
compile_parts(node.parts, code)
|
|
16
|
+
else
|
|
17
|
+
raise(Haml::InternalError, "Expected a string literal but got: #{code}")
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
raise(Haml::InternalError, "Expected :on_tstring_beg but got: #{type}")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
_, type, end_str = tokens.pop
|
|
30
|
-
if type != :on_tstring_end
|
|
31
|
-
raise(Haml::InternalError, "Expected :on_tstring_end but got: #{type}")
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
[beg_str, end_str]
|
|
21
|
+
# Like compile, but nil instead of raising, for callers that built `code` themselves
|
|
22
|
+
# and would rather emit it untouched than fail the whole compilation.
|
|
23
|
+
def try_compile(code)
|
|
24
|
+
node = RubyExpression.string_literal_node(code)
|
|
25
|
+
compile(code, node: node) unless node.nil?
|
|
35
26
|
end
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
beg_str, end_str = strip_quotes!(tokens)
|
|
39
|
-
|
|
40
|
-
until tokens.empty?
|
|
41
|
-
_, type, str = tokens.shift
|
|
28
|
+
private
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
def compile_parts(parts, code)
|
|
31
|
+
[].tap do |exps|
|
|
32
|
+
parts.each do |part|
|
|
33
|
+
case part
|
|
34
|
+
when Prism::StringNode
|
|
35
|
+
content = part.unescaped
|
|
36
|
+
exps << [:static, content] unless content.empty?
|
|
37
|
+
when Prism::EmbeddedStatementsNode
|
|
38
|
+
embedded = embedded_source(part, code)
|
|
39
|
+
exps << [:dynamic, embedded] unless embedded.empty?
|
|
40
|
+
when Prism::EmbeddedVariableNode
|
|
41
|
+
exps << [:dynamic, part.variable.slice]
|
|
42
|
+
else
|
|
43
|
+
# Prism allows more part types than a string literal can currently hold. Dropping
|
|
44
|
+
# one would silently lose content, so fail instead of rendering something wrong.
|
|
45
|
+
raise(Haml::InternalError, "Unexpected #{part.class} in string literal: #{code}")
|
|
46
|
+
end
|
|
50
47
|
end
|
|
51
48
|
end
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
else
|
|
60
|
-
[beg_str, end_str]
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def shift_balanced_embexpr(tokens)
|
|
65
|
-
String.new.tap do |embedded|
|
|
66
|
-
embexpr_open = 1
|
|
67
|
-
|
|
68
|
-
until tokens.empty?
|
|
69
|
-
_, type, str = tokens.shift
|
|
70
|
-
case type
|
|
71
|
-
when :on_embexpr_beg
|
|
72
|
-
embexpr_open += 1
|
|
73
|
-
when :on_embexpr_end
|
|
74
|
-
embexpr_open -= 1
|
|
75
|
-
break if embexpr_open == 0
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
embedded << str
|
|
79
|
-
end
|
|
80
|
-
end
|
|
51
|
+
# The source between `#{` and `}`, kept verbatim. Slicing `code` rather than
|
|
52
|
+
# using the statements' own source preserves the whitespace around them.
|
|
53
|
+
def embedded_source(part, code)
|
|
54
|
+
from = part.opening_loc.end_offset
|
|
55
|
+
code.byteslice(from, part.closing_loc.start_offset - from)
|
|
81
56
|
end
|
|
82
57
|
end
|
|
83
58
|
|
|
84
59
|
def on_dynamic(code)
|
|
85
|
-
return [:dynamic, code] unless string_literal?(code)
|
|
86
60
|
return [:dynamic, code] if code.include?("\n")
|
|
87
61
|
|
|
62
|
+
node = RubyExpression.string_literal_node(code)
|
|
63
|
+
return [:dynamic, code] if node.nil?
|
|
64
|
+
|
|
88
65
|
temple = [:multi]
|
|
89
|
-
StringSplitter.compile(code).each do |type, content|
|
|
66
|
+
StringSplitter.compile(code, node: node).each do |type, content|
|
|
90
67
|
case type
|
|
91
68
|
when :static
|
|
92
69
|
temple << [:static, content]
|
|
@@ -96,35 +73,5 @@ module Haml
|
|
|
96
73
|
end
|
|
97
74
|
temple
|
|
98
75
|
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
def string_literal?(code)
|
|
103
|
-
return false if SyntaxChecker.syntax_error?(code)
|
|
104
|
-
|
|
105
|
-
type, instructions = Ripper.sexp(code)
|
|
106
|
-
return false if type != :program
|
|
107
|
-
return false if instructions.size > 1
|
|
108
|
-
|
|
109
|
-
type, _ = instructions.first
|
|
110
|
-
type == :string_literal
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
class SyntaxChecker < Ripper
|
|
114
|
-
class ParseError < StandardError; end
|
|
115
|
-
|
|
116
|
-
def self.syntax_error?(code)
|
|
117
|
-
self.new(code).parse
|
|
118
|
-
false
|
|
119
|
-
rescue ParseError
|
|
120
|
-
true
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
private
|
|
124
|
-
|
|
125
|
-
def on_parse_error(*)
|
|
126
|
-
raise ParseError
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
76
|
end
|
|
130
77
|
end
|
data/lib/haml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Natalie Weizenbaum
|
|
@@ -13,6 +13,20 @@ bindir: exe
|
|
|
13
13
|
cert_chain: []
|
|
14
14
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
|
+
- !ruby/object:Gem::Dependency
|
|
17
|
+
name: prism
|
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
|
19
|
+
requirements:
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.1.0
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 1.1.0
|
|
16
30
|
- !ruby/object:Gem::Dependency
|
|
17
31
|
name: temple
|
|
18
32
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -308,7 +322,9 @@ licenses:
|
|
|
308
322
|
- MIT
|
|
309
323
|
metadata:
|
|
310
324
|
rubygems_mfa_required: 'true'
|
|
311
|
-
|
|
325
|
+
bug_tracker_uri: https://github.com/haml/haml/issues
|
|
326
|
+
changelog_uri: https://github.com/haml/haml/blob/main/CHANGELOG.md
|
|
327
|
+
homepage_uri: https://haml.info/
|
|
312
328
|
source_code_uri: https://github.com/haml/haml
|
|
313
329
|
rdoc_options: []
|
|
314
330
|
require_paths:
|