rblade 3.1.1 → 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 +3 -0
- data/lib/rblade/compiler/compiles_comments.rb +17 -0
- 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
|
@@ -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
|
|
@@ -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
|