rblade 3.1.0 → 3.2.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 +6 -0
- data/lib/rblade/compiler/compiles_comments.rb +17 -0
- data/lib/rblade/compiler/compiles_components.rb +2 -4
- data/lib/rblade/compiler/compiles_injections.rb +60 -37
- data/lib/rblade/compiler/compiles_verbatim.rb +25 -13
- data/lib/rblade/compiler/tokenizes_components.rb +95 -96
- data/lib/rblade/compiler/tokenizes_statements.rb +23 -24
- data/lib/rblade/compiler.rb +44 -3
- data/lib/rblade/helpers/source_map.rb +58 -0
- data/lib/rblade/helpers/string_utility.rb +10 -0
- data/lib/rblade/helpers/utility.rb +18 -0
- data/lib/rblade/rails_template.rb +43 -1
- data/rblade.gemspec +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2a97583c74dd9bff9b805c0eeedc59751909eea8bd3130e6947ca5e70355b30
|
4
|
+
data.tar.gz: fceb38fd16cbb4e569e8cdbf0082710862388d98e428bf99a81f6bc8e4f15e0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d8d277be52f54f2a524eacb9f7f5cf8095032f2d076bb893640ed7e86be6a49dcde5eb50e0309f592c2d69451b171f7d265eb198fd324eadcc41daf1df74b70
|
7
|
+
data.tar.gz: 5ac646bdcbc33e0183c981ac651cf8493beaf2494b1c07fb3aa13c933eb50115d8673bb6ff37ee2f17f3f8e367e6020ca3433c2d656b5d87d0be0bb9eeaf6b3f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 3.2.0 [UNRELEASED]
|
2
|
+
- Add translate_location method to improve error messages in RBlade templates
|
3
|
+
|
4
|
+
## 3.1.1 [2025-05-11]
|
5
|
+
- Fix issue with component attributes mutating unfrozen strings (#19)
|
6
|
+
|
1
7
|
## 3.1.0 [2025-04-02]
|
2
8
|
- Add ability to use slots in dynamic components and the component view helper method
|
3
9
|
- Change statement matching to use regular expressions to improve compile performance
|
@@ -10,5 +10,22 @@ module RBlade
|
|
10
10
|
token.value.gsub!(/<%#(?:[^%]++|%)*?%>/, "")
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
def self.comment_offsets(source)
|
15
|
+
current_match_id = nil
|
16
|
+
offsets = []
|
17
|
+
|
18
|
+
source.split(/(\{\{--(?:[^-]++|-)*?--}}|<%#(?:[^%]++|%)*?%>)/) do |before_match|
|
19
|
+
next if current_match_id == $~.object_id || $&.nil?
|
20
|
+
current_match_id = $~.object_id
|
21
|
+
|
22
|
+
offsets << {
|
23
|
+
source_position: (offsets.last&.[](:source_position) || 0) + before_match.length,
|
24
|
+
offset: $&.length,
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
offsets
|
29
|
+
end
|
13
30
|
end
|
14
31
|
end
|
@@ -107,15 +107,13 @@ module RBlade
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def process_string_attribute(string)
|
110
|
-
|
110
|
+
string.split(/((?<!@)\{\{(?:[^}]++|\})*?\}\})/).map do |substring|
|
111
111
|
if substring.start_with?("{{") && substring.end_with?("}}")
|
112
112
|
"(#{substring[2..-3]}).to_s"
|
113
113
|
elsif !substring.empty?
|
114
114
|
"'#{RBlade.escape_quotes(substring.gsub(/@\{\{/, "{{"))}'"
|
115
115
|
end
|
116
|
-
end.compact.join("<<")
|
117
|
-
|
118
|
-
result.empty? ? "+''" : result.prepend("+")
|
116
|
+
end.compact.unshift("+''").join("<<")
|
119
117
|
end
|
120
118
|
end
|
121
119
|
end
|
@@ -6,56 +6,79 @@ module RBlade
|
|
6
6
|
tokens.map! do |token|
|
7
7
|
next(token) if token.type != :unprocessed
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
current_match_id = nil
|
10
|
+
segments = []
|
11
|
+
token.value.split(/
|
12
|
+
(?<escape_unsafe_rblade>@?)\{!!(?<unsafe_rblade>(?:[^!}]++|[!}])+?)!!}
|
11
13
|
|
|
12
|
-
(
|
14
|
+
(?<escape_safe_rblade>@?) \{\{ (?<safe_rblade>(?:[^!}]++|[!}])+?) }}
|
13
15
|
|
|
14
|
-
|
16
|
+
\s?(?<![\w@])@ruby\s++(?<ruby>(?:[^@]++|[@])+?)(?<!\w)@end_?ruby(?!\w)\s?
|
15
17
|
|
|
16
|
-
(
|
17
|
-
|
18
|
+
(?<escaped_erb_start><%%)
|
19
|
+
|
|
20
|
+
(?<escaped_erb_end>%%>)
|
21
|
+
|
|
22
|
+
(?<erb_tag><%(?!%)=?=?)\s*+(?<erb_tag_content>(?:[^%]++|[%])+?)(?<!%)%?>
|
23
|
+
/xi) do |before_match|
|
24
|
+
next if current_match_id == $~.object_id
|
25
|
+
current_match_id = $~.object_id
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
case segments[i]
|
22
|
-
when "{{", "<%="
|
23
|
-
segments[i] = create_token(segments[i + 1].strip, true)
|
24
|
-
when "<%", /\A\s?@ruby\z/i
|
25
|
-
segments[i + 1].strip!
|
26
|
-
segments[i + 1] << ";" unless segments[i + 1].end_with?(";")
|
27
|
-
segments[i] = Token.new(type: :ruby, value: segments[i + 1])
|
28
|
-
when "{!!", "<%=="
|
29
|
-
segments[i] = create_token(segments[i + 1].strip, false)
|
30
|
-
when "@{!!", "@{{", /\A\s?@@ruby\z/i
|
31
|
-
segments[i].sub!("@", "")
|
32
|
-
segments[i] = Token.new(type: :raw_text, value: "#{segments[i]}#{segments[i + 1]}#{segments[i + 2]}")
|
33
|
-
when "<%%", "<%%=", "<%%=="
|
34
|
-
segments[i] = Token.new(type: :raw_text, value: "<#{segments[i].delete_prefix!("<%")}#{segments[i + 1]}%>")
|
35
|
-
when "", nil
|
36
|
-
segments.delete_at i
|
37
|
-
next
|
38
|
-
else
|
39
|
-
segments[i] = Token.new(type: :unprocessed, value: segments[i])
|
40
|
-
i += 1
|
41
|
-
next
|
27
|
+
unless before_match == ""
|
28
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, before_match)
|
42
29
|
end
|
30
|
+
next if $~.nil?
|
31
|
+
|
32
|
+
if $~[:unsafe_rblade].present? || $~[:erb_tag] == "<%=="
|
33
|
+
if $~[:escape_unsafe_rblade] == "@"
|
34
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, $&.delete_prefix("@"))
|
35
|
+
else
|
36
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
37
|
+
segments << create_token(
|
38
|
+
($~[:unsafe_rblade] || $~[:erb_tag_content]).strip,
|
39
|
+
false,
|
40
|
+
start_offset,
|
41
|
+
start_offset + $&.length,
|
42
|
+
)
|
43
|
+
end
|
44
|
+
elsif $~[:safe_rblade].present? || $~[:erb_tag] == "<%="
|
45
|
+
if $~[:escape_safe_rblade] == "@"
|
46
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, $&.delete_prefix("@"))
|
47
|
+
else
|
48
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
49
|
+
segments << create_token(
|
50
|
+
($~[:safe_rblade] || $~[:erb_tag_content]).strip,
|
51
|
+
true,
|
52
|
+
start_offset,
|
53
|
+
start_offset + $&.length,
|
54
|
+
)
|
55
|
+
end
|
56
|
+
elsif $~[:ruby].present? || $~[:erb_tag] == "<%"
|
57
|
+
value = ($~[:ruby] || $~[:erb_tag_content]).strip
|
58
|
+
value << ";" unless value.end_with?(";")
|
59
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
43
60
|
|
44
|
-
|
45
|
-
|
46
|
-
|
61
|
+
segments << Token.new(
|
62
|
+
type: :ruby,
|
63
|
+
value: value,
|
64
|
+
start_offset: start_offset,
|
65
|
+
end_offset: start_offset + $&.length,
|
66
|
+
)
|
67
|
+
elsif $~[:escaped_erb_start].present?
|
68
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, +"<%")
|
69
|
+
elsif $~[:escaped_erb_end].present?
|
70
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, +"%>")
|
71
|
+
end
|
47
72
|
end
|
48
73
|
|
49
74
|
segments
|
50
75
|
end.flatten!
|
51
76
|
end
|
52
77
|
|
53
|
-
private
|
54
|
-
|
55
|
-
def create_token(expression, escape_html)
|
78
|
+
private def create_token(expression, escape_html, start_offset, end_offset)
|
56
79
|
# Don't try to print ends
|
57
80
|
if expression.match?(/\A(?:}|end(?![[:alnum:]_]|[^\0-\177]))/i)
|
58
|
-
return Token.new(:ruby, "#{expression};")
|
81
|
+
return Token.new(:ruby, "#{expression};", start_offset, end_offset)
|
59
82
|
end
|
60
83
|
|
61
84
|
segment_value = if escape_html
|
@@ -75,7 +98,7 @@ module RBlade
|
|
75
98
|
"@output_buffer.raw_buffer<<(#{expression}).to_s;"
|
76
99
|
end
|
77
100
|
|
78
|
-
Token.new(:print, segment_value)
|
101
|
+
Token.new(:print, segment_value, start_offset, end_offset)
|
79
102
|
end
|
80
103
|
end
|
81
104
|
end
|
@@ -6,26 +6,38 @@ module RBlade
|
|
6
6
|
tokens.map! do |token|
|
7
7
|
next(token) if token.type != :unprocessed
|
8
8
|
|
9
|
-
|
9
|
+
current_match_id = nil
|
10
|
+
segments = []
|
11
|
+
token.value.split(/\s?(?<!\w)@verbatim(?!\w)\s?(?<contents>(?:[^@\s]++|[@\s])+?)\s?(?<!\w)@end_?verbatim(?!\w)\s?/i) do |before_match|
|
12
|
+
next if current_match_id == $~.object_id
|
13
|
+
current_match_id = $~.object_id
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
i += 1
|
18
|
-
elsif !segments[i].nil? && segments[i] != ""
|
19
|
-
segments[i] = Token.new(type: :unprocessed, value: segments[i])
|
15
|
+
# Add the current string to the segment list
|
16
|
+
unless before_match == ""
|
17
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, before_match)
|
18
|
+
end
|
19
|
+
next if $~.nil?
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
segments.
|
21
|
+
if $~[:contents].present?
|
22
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
23
|
+
segments << Token.new(
|
24
|
+
type: :raw_text,
|
25
|
+
value: $~[:contents],
|
26
|
+
start_offset: start_offset,
|
27
|
+
end_offset: start_offset + $&.length,
|
28
|
+
)
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
27
32
|
segments
|
28
33
|
end.flatten!
|
29
34
|
end
|
35
|
+
|
36
|
+
# Replaces verbatim statements with spaces of the same length. Used in comment offset calculation for source maps.
|
37
|
+
def self.nullify_verbatim(source)
|
38
|
+
source.gsub(/\s?(?<!\w)@verbatim(?!\w)\s?(?<contents>(?:[^@\s]++|[@\s])+?)\s?(?<!\w)@end_?verbatim(?!\w)\s?/i) do |match|
|
39
|
+
" " * match.length
|
40
|
+
end
|
41
|
+
end
|
30
42
|
end
|
31
43
|
end
|
@@ -8,53 +8,106 @@ module RBlade
|
|
8
8
|
tokens.map! do |token|
|
9
9
|
next(token) if token.type != :unprocessed
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
while i < segments.count
|
15
|
-
if segments[i] == "</" && segments[i + 1]&.match?(/x[-:]/)
|
16
|
-
segments[i] = Token.new(type: :component_end, value: {name: segments[i + 1][2..]})
|
17
|
-
|
18
|
-
segments.delete_at i + 1
|
19
|
-
i += 1
|
20
|
-
elsif segments[i] == "<//>"
|
21
|
-
segments[i] = Token.new(type: :component_unsafe_end)
|
22
|
-
i += 1
|
23
|
-
elsif segments[i] == "<" && segments[i + 1]&.match?(/x[-:]/)
|
24
|
-
name = segments[i + 1][2..]
|
25
|
-
raw_attributes = (segments[i + 2] != ">") ? tokenize_attributes(segments[i + 2]) : nil
|
26
|
-
|
27
|
-
attributes = process_attributes raw_attributes
|
28
|
-
|
29
|
-
if raw_attributes.nil?
|
30
|
-
segments.delete_at i + 1
|
31
|
-
else
|
32
|
-
while segments[i + 1] != ">" && segments[i + 1] != "/>"
|
33
|
-
segments.delete_at i + 1
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
token_type = (segments[i + 1] == "/>") ? :component : :component_start
|
38
|
-
segments[i] = Token.new(type: token_type, value: {name:, attributes:})
|
39
|
-
segments.delete_at i + 1
|
11
|
+
process_component_tags token
|
12
|
+
end.flatten!
|
13
|
+
end
|
40
14
|
|
41
|
-
|
42
|
-
|
43
|
-
|
15
|
+
private def process_component_tags(token)
|
16
|
+
current_match_id = nil
|
17
|
+
segments = []
|
44
18
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
19
|
+
token.value.split(%r/
|
20
|
+
# Opening and self-closing tags
|
21
|
+
(?<opening_tag>
|
22
|
+
<
|
23
|
+
\s*+
|
24
|
+
x[-\:](?<opening_tag_name>[\w\-\:\.]++)
|
25
|
+
(?<tag_attributes>
|
26
|
+
(?:
|
27
|
+
\s++
|
28
|
+
(?:
|
29
|
+
(?:
|
30
|
+
@class(?<nested_parentheses>\( (?: [^()]++ | \g<nested_parentheses> )*+ \))
|
31
|
+
)
|
32
|
+
|
|
33
|
+
(?:
|
34
|
+
@style\g<nested_parentheses>
|
35
|
+
)
|
36
|
+
|
|
37
|
+
\{\{ \s*+ attributes (?:[^}]++|\})*? \}\}
|
38
|
+
|
|
39
|
+
(?:
|
40
|
+
[\w\-:.@%]++
|
41
|
+
(?:
|
42
|
+
=
|
43
|
+
(?:
|
44
|
+
"(?> [^"{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )*+"
|
45
|
+
|
|
46
|
+
'(?> [^'{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )*+'
|
47
|
+
|
|
48
|
+
(?> [^'"=<>\s\/{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )++
|
49
|
+
)
|
50
|
+
)?
|
51
|
+
)
|
52
|
+
)
|
53
|
+
)*+
|
54
|
+
)
|
55
|
+
\s*+
|
56
|
+
(?<opening_tag_end>\/?>)
|
57
|
+
)
|
58
|
+
|
|
59
|
+
# Closing tags
|
60
|
+
(?<closing_tag>
|
61
|
+
<\/
|
62
|
+
\s*+
|
63
|
+
x[-\:](?<closing_tag_name>[\w\-\:\.]++)
|
64
|
+
\s*+
|
65
|
+
>
|
66
|
+
)
|
67
|
+
|
|
68
|
+
(?<unsafe_closing_tag><\/\/>)
|
69
|
+
/x) do |before_match|
|
70
|
+
next if current_match_id == $~.object_id
|
71
|
+
current_match_id = $~.object_id
|
72
|
+
|
73
|
+
# Add the current string to the segment list
|
74
|
+
unless before_match == ""
|
75
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, before_match)
|
49
76
|
end
|
77
|
+
next if $~.nil?
|
78
|
+
|
79
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
80
|
+
if $~[:unsafe_closing_tag].present?
|
81
|
+
segments << Token.new(
|
82
|
+
type: :component_unsafe_end,
|
83
|
+
start_offset: start_offset,
|
84
|
+
end_offset: start_offset + $&.length,
|
85
|
+
)
|
86
|
+
elsif $~[:closing_tag].present?
|
87
|
+
segments << Token.new(
|
88
|
+
type: :component_end,
|
89
|
+
value: {name: $~[:closing_tag_name]},
|
90
|
+
start_offset: start_offset,
|
91
|
+
end_offset: start_offset + $&.length,
|
92
|
+
)
|
93
|
+
elsif $~[:opening_tag].present?
|
94
|
+
raw_attributes = tokenize_attributes($~[:tag_attributes])
|
95
|
+
attributes = process_attributes raw_attributes
|
96
|
+
|
97
|
+
token_type = ($~[:opening_tag_end] == "/>") ? :component : :component_start
|
98
|
+
segments << Token.new(
|
99
|
+
type: token_type,
|
100
|
+
value: {name: $~[:opening_tag_name], attributes:},
|
101
|
+
start_offset: start_offset,
|
102
|
+
end_offset: start_offset + $&.length,
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
50
106
|
|
51
|
-
|
52
|
-
end.flatten!
|
107
|
+
segments
|
53
108
|
end
|
54
109
|
|
55
|
-
private
|
56
|
-
|
57
|
-
def process_attributes(raw_attributes)
|
110
|
+
private def process_attributes(raw_attributes)
|
58
111
|
attributes = []
|
59
112
|
i = 0
|
60
113
|
while i < raw_attributes.count
|
@@ -112,61 +165,7 @@ module RBlade
|
|
112
165
|
attributes
|
113
166
|
end
|
114
167
|
|
115
|
-
def
|
116
|
-
value.split(%r/
|
117
|
-
# Opening and self-closing tags
|
118
|
-
(?:
|
119
|
-
(<)
|
120
|
-
\s*+
|
121
|
-
(x[-\:][\w\-\:\.]++)
|
122
|
-
((?:
|
123
|
-
\s++
|
124
|
-
(?:
|
125
|
-
(?:
|
126
|
-
@class(\( (?: [^()]++ | \g<-1> )*+ \))
|
127
|
-
)
|
128
|
-
|
|
129
|
-
(?:
|
130
|
-
@style(\( (?: [^()]++ | \g<-1> )*+ \))
|
131
|
-
)
|
132
|
-
|
|
133
|
-
(
|
134
|
-
\{\{ \s*+ attributes (?:[^}]++|\})*? \}\}
|
135
|
-
)
|
136
|
-
|
|
137
|
-
(?:
|
138
|
-
[\w\-:.@%]++
|
139
|
-
(?:
|
140
|
-
=
|
141
|
-
(?:
|
142
|
-
"(?> [^"{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )*+"
|
143
|
-
|
|
144
|
-
'(?> [^'{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )*+'
|
145
|
-
|
|
146
|
-
(?> [^'"=<>\s\/{]++ | (?<!@)\{\{ (?:[^}]++|\})*? \}\} | \{ )++
|
147
|
-
)
|
148
|
-
)?
|
149
|
-
)
|
150
|
-
)
|
151
|
-
)*+)
|
152
|
-
\s*+
|
153
|
-
(\/?>)
|
154
|
-
)
|
155
|
-
|
|
156
|
-
# Closing tags
|
157
|
-
(?:
|
158
|
-
(<\/)
|
159
|
-
\s*+
|
160
|
-
(x[-\:][\w\-\:\.]++)
|
161
|
-
\s*+
|
162
|
-
>
|
163
|
-
)
|
164
|
-
|
|
165
|
-
(<\/\/>)
|
166
|
-
/x)
|
167
|
-
end
|
168
|
-
|
169
|
-
def tokenize_attributes(segment)
|
168
|
+
private def tokenize_attributes(segment)
|
170
169
|
segment.scan(%r/
|
171
170
|
(?<=\s|^)
|
172
171
|
(?:
|
@@ -16,7 +16,7 @@ module RBlade
|
|
16
16
|
(?:
|
17
17
|
(?:
|
18
18
|
(?<escaped_at>@@)
|
19
|
-
(
|
19
|
+
(?<escaped_statement_name>\w++[!\?]?(?!\w))
|
20
20
|
)
|
21
21
|
|
|
22
22
|
(?:
|
@@ -50,36 +50,29 @@ module RBlade
|
|
50
50
|
unless before_match == ""
|
51
51
|
# Skip output between case and when statements
|
52
52
|
unless segments.last&.type == :statement && segments.last&.value&.[](:name) == "case"
|
53
|
-
|
54
|
-
segments.last.value << before_match
|
55
|
-
else
|
56
|
-
segments << Token.new(type: :unprocessed, value: before_match)
|
57
|
-
end
|
53
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, before_match)
|
58
54
|
end
|
59
55
|
end
|
60
56
|
next if $~.nil?
|
61
57
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
else
|
70
|
-
segments << Token.new(type: :unprocessed, value: segment)
|
71
|
-
end
|
58
|
+
statement_handle = ($~[:statement_name] || $~[:escaped_statement_name])
|
59
|
+
&.downcase
|
60
|
+
&.tr("_", "")
|
61
|
+
next if statement_handle.nil?
|
62
|
+
|
63
|
+
unless CompilesStatements.has_handler(statement_handle)
|
64
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, $&)
|
72
65
|
|
73
66
|
next
|
74
67
|
end
|
75
68
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
69
|
+
# Skip escaped statements
|
70
|
+
if $~&.[](:escaped_at) == "@@"
|
71
|
+
segment = $&.dup
|
72
|
+
# Remove the first or second @, depending on whether there is whitespace
|
73
|
+
segment.slice!(1)
|
74
|
+
|
75
|
+
RBlade::Utility.append_unprocessed_string_segment!(token, segments, segment, 1)
|
83
76
|
|
84
77
|
next
|
85
78
|
end
|
@@ -95,7 +88,13 @@ module RBlade
|
|
95
88
|
|
96
89
|
end
|
97
90
|
|
98
|
-
|
91
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
92
|
+
segments << Token.new(
|
93
|
+
type: :statement,
|
94
|
+
value: statement_data,
|
95
|
+
start_offset: start_offset,
|
96
|
+
end_offset: start_offset + $&.length,
|
97
|
+
)
|
99
98
|
end
|
100
99
|
|
101
100
|
segments
|
data/lib/rblade/compiler.rb
CHANGED
@@ -7,9 +7,10 @@ require "rblade/compiler/compiles_verbatim"
|
|
7
7
|
require "rblade/compiler/compiles_statements"
|
8
8
|
require "rblade/compiler/tokenizes_components"
|
9
9
|
require "rblade/compiler/tokenizes_statements"
|
10
|
+
require "rblade/helpers/utility"
|
10
11
|
require "active_support/core_ext/string/output_safety"
|
11
12
|
|
12
|
-
Token = Struct.new(:type, :value)
|
13
|
+
Token = Struct.new(:type, :value, :start_offset, :end_offset)
|
13
14
|
|
14
15
|
module RBlade
|
15
16
|
def self.escape_quotes(string)
|
@@ -38,7 +39,46 @@ module RBlade
|
|
38
39
|
|
39
40
|
class Compiler
|
40
41
|
def self.compile_string(string_template, component_store)
|
41
|
-
tokens =
|
42
|
+
tokens = tokenize_string string_template, component_store
|
43
|
+
|
44
|
+
compile_tokens tokens
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.generate_source_map(string_template, component_store)
|
48
|
+
tokens = tokenize_string string_template, component_store
|
49
|
+
source_map = SourceMap.new(string_template)
|
50
|
+
|
51
|
+
i = 0
|
52
|
+
while i < tokens.count
|
53
|
+
token = tokens[i]
|
54
|
+
|
55
|
+
if token.type == :unprocessed || token.type == :raw_text
|
56
|
+
start_offset = token.start_offset
|
57
|
+
compiled_code = +"@output_buffer.raw_buffer<<-'"
|
58
|
+
|
59
|
+
# Merge together consecutive prints
|
60
|
+
while tokens[i + 1]&.type == :unprocessed || tokens[i + 1]&.type == :raw_text
|
61
|
+
compiled_code << RBlade.escape_quotes(token.value)
|
62
|
+
i += 1
|
63
|
+
token = tokens[i]
|
64
|
+
end
|
65
|
+
|
66
|
+
compiled_code << RBlade.escape_quotes(token.value)
|
67
|
+
compiled_code << "';"
|
68
|
+
|
69
|
+
source_map.add(start_offset, token.end_offset, compiled_code)
|
70
|
+
else
|
71
|
+
source_map.add(token.start_offset, token.end_offset, token.value)
|
72
|
+
end
|
73
|
+
|
74
|
+
i += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
source_map
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.tokenize_string(string_template, component_store)
|
81
|
+
tokens = [Token.new(:unprocessed, string_template, 0, string_template.length)]
|
42
82
|
|
43
83
|
CompilesVerbatim.new.compile! tokens
|
44
84
|
CompilesComments.new.compile! tokens
|
@@ -50,7 +90,8 @@ module RBlade
|
|
50
90
|
component_compiler = CompilesComponents.new(component_store)
|
51
91
|
component_compiler.compile! tokens
|
52
92
|
component_compiler.ensure_all_tags_closed
|
53
|
-
|
93
|
+
|
94
|
+
tokens
|
54
95
|
end
|
55
96
|
|
56
97
|
def self.compile_attribute_string(string_template)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RBlade
|
2
|
+
class SourceMap
|
3
|
+
def initialize(template)
|
4
|
+
@source_tokens = []
|
5
|
+
@compiled_column = 0
|
6
|
+
@compiled_offset = 0
|
7
|
+
|
8
|
+
calculate_comment_offsets(template)
|
9
|
+
end
|
10
|
+
|
11
|
+
private def calculate_comment_offsets(template)
|
12
|
+
template_without_verbatim = CompilesVerbatim.nullify_verbatim(template)
|
13
|
+
|
14
|
+
@comment_offsets = CompilesComments.comment_offsets(template_without_verbatim)
|
15
|
+
@current_comment_offset = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(start_offset, end_offset, compiled_code)
|
19
|
+
increment_comment_offset_to start_offset
|
20
|
+
start_offset += @current_comment_offset
|
21
|
+
|
22
|
+
# Prevent extending the end offset into a comment
|
23
|
+
increment_comment_offset_to end_offset - 1
|
24
|
+
end_offset += @current_comment_offset
|
25
|
+
|
26
|
+
lines = StringUtility.lines(compiled_code)
|
27
|
+
|
28
|
+
@source_tokens << {
|
29
|
+
start_offset: start_offset,
|
30
|
+
end_offset: end_offset,
|
31
|
+
compiled_start_line: @compiled_column,
|
32
|
+
compiled_start_offset: @compiled_offset,
|
33
|
+
}
|
34
|
+
|
35
|
+
@compiled_column += lines.length - 1
|
36
|
+
@compiled_offset = (lines.length == 1) ? @compiled_offset + compiled_code.length : lines.last.length
|
37
|
+
end
|
38
|
+
|
39
|
+
private def increment_comment_offset_to(source_position)
|
40
|
+
while @comment_offsets.any? && @comment_offsets.first[:source_position] <= source_position
|
41
|
+
@current_comment_offset += @comment_offsets.shift[:offset]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def source_location(first_lineno, first_column)
|
46
|
+
previous_token = nil
|
47
|
+
|
48
|
+
@source_tokens.each do |token|
|
49
|
+
break previous_token if token[:compiled_start_line] > first_lineno ||
|
50
|
+
(token[:compiled_start_line] == first_lineno && token[:compiled_start_offset] > first_column)
|
51
|
+
|
52
|
+
previous_token = token
|
53
|
+
end
|
54
|
+
|
55
|
+
previous_token
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RBlade
|
2
|
+
class Utility
|
3
|
+
def self.append_unprocessed_string_segment!(token, segments, string, offset = 0)
|
4
|
+
if segments.last&.type == :unprocessed
|
5
|
+
segments.last.value << string
|
6
|
+
segments.last.end_offset += string.length
|
7
|
+
else
|
8
|
+
start_offset = segments.last&.end_offset || token.start_offset
|
9
|
+
segments << Token.new(
|
10
|
+
type: :unprocessed,
|
11
|
+
value: string,
|
12
|
+
start_offset: start_offset,
|
13
|
+
end_offset: start_offset + string.length + offset,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -5,7 +5,9 @@ require "rblade/component_store"
|
|
5
5
|
require "rblade/helpers/attributes_manager"
|
6
6
|
require "rblade/helpers/class_manager"
|
7
7
|
require "rblade/helpers/slot_manager"
|
8
|
+
require "rblade/helpers/source_map"
|
8
9
|
require "rblade/helpers/stack_manager"
|
10
|
+
require "rblade/helpers/string_utility"
|
9
11
|
require "rblade/helpers/style_manager"
|
10
12
|
|
11
13
|
module RBlade
|
@@ -32,7 +34,47 @@ module RBlade
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
|
-
-"#{preamble}#{component_store.get}#{RBlade::Compiler.compile_string(source, component_store)}@output_buffer.raw_buffer.prepend(@_rblade_stack_manager.get(_stacks));@output_buffer;"
|
37
|
+
-"#{preamble}\n#{component_store.get}\n#{RBlade::Compiler.compile_string(source, component_store)}@output_buffer.raw_buffer.prepend(@_rblade_stack_manager.get(_stacks));@output_buffer;"
|
38
|
+
end
|
39
|
+
|
40
|
+
def translate_location(spot, backtrace_location, source)
|
41
|
+
view_name = backtrace_location.path
|
42
|
+
.sub(/^.*app\/views\/(.+?)(?:\.\w++)?\.rblade$/, "\\1")
|
43
|
+
.tr("/", ".")
|
44
|
+
|
45
|
+
# Let the component store know about the current view for relative components
|
46
|
+
component_store = RBlade::ComponentStore.new
|
47
|
+
component_store.view_name("view::#{view_name}")
|
48
|
+
|
49
|
+
source_map = RBlade::Compiler.generate_source_map(source, component_store)
|
50
|
+
|
51
|
+
# Account for the preamble and component store
|
52
|
+
offset = 2 + StringUtility.lines(component_store.get).length
|
53
|
+
offset += 1 if spot[:script_lines]&.first == "# frozen_string_literal: true\n"
|
54
|
+
|
55
|
+
location = source_map.source_location(spot[:first_lineno] - offset - 1, spot[:first_column])
|
56
|
+
|
57
|
+
before_lines = StringUtility.lines(source[0...(location[:start_offset])])
|
58
|
+
excerpt = source[(location[:start_offset])...(location[:end_offset])]
|
59
|
+
excerpt_lines = StringUtility.lines(excerpt)
|
60
|
+
|
61
|
+
first_lineno = before_lines.length
|
62
|
+
first_column = before_lines.last.length
|
63
|
+
|
64
|
+
last_lineno = first_lineno + excerpt_lines.length - 1
|
65
|
+
last_column = (excerpt_lines.length > 1) ? excerpt_lines.last.length : first_column + excerpt_lines.first.length
|
66
|
+
|
67
|
+
{
|
68
|
+
first_lineno: first_lineno,
|
69
|
+
first_column: first_column,
|
70
|
+
last_lineno: last_lineno,
|
71
|
+
last_column: last_column,
|
72
|
+
snippet: excerpt,
|
73
|
+
script_lines: source.lines,
|
74
|
+
}
|
75
|
+
rescue => e
|
76
|
+
Rails.logger&.debug "Unable to locate error position in template: #{e.message}"
|
77
|
+
spot
|
36
78
|
end
|
37
79
|
end
|
38
80
|
end
|
data/rblade.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rblade"
|
3
|
-
s.version = "3.
|
3
|
+
s.version = "3.2.0"
|
4
4
|
s.summary = "A component-first templating engine for Rails"
|
5
5
|
s.description = "RBlade is a simple, yet powerful templating engine for Ruby on Rails, inspired by Laravel Blade."
|
6
6
|
s.authors = ["Simon J"]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rblade
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon J
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -156,8 +156,11 @@ files:
|
|
156
156
|
- lib/rblade/helpers/class_manager.rb
|
157
157
|
- lib/rblade/helpers/regular_expressions.rb
|
158
158
|
- lib/rblade/helpers/slot_manager.rb
|
159
|
+
- lib/rblade/helpers/source_map.rb
|
159
160
|
- lib/rblade/helpers/stack_manager.rb
|
161
|
+
- lib/rblade/helpers/string_utility.rb
|
160
162
|
- lib/rblade/helpers/style_manager.rb
|
163
|
+
- lib/rblade/helpers/utility.rb
|
161
164
|
- lib/rblade/rails_template.rb
|
162
165
|
- lib/rblade/railtie.rb
|
163
166
|
- rblade.gemspec
|