twig_ruby 0.0.1 → 0.0.3
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/README.md +116 -0
- data/lib/tasks/twig_parity.rake +278 -0
- data/lib/twig/auto_hash.rb +7 -1
- data/lib/twig/callable.rb +28 -1
- data/lib/twig/compiler.rb +35 -3
- data/lib/twig/environment.rb +198 -41
- data/lib/twig/error/base.rb +81 -16
- data/lib/twig/error/loader.rb +8 -0
- data/lib/twig/error/logic.rb +8 -0
- data/lib/twig/error/runtime.rb +8 -0
- data/lib/twig/expression_parser/base.rb +30 -0
- data/lib/twig/expression_parser/expression_parsers.rb +57 -0
- data/lib/twig/expression_parser/infix/arrow.rb +31 -0
- data/lib/twig/expression_parser/infix/binary.rb +34 -0
- data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
- data/lib/twig/expression_parser/infix/dot.rb +72 -0
- data/lib/twig/expression_parser/infix/filter.rb +43 -0
- data/lib/twig/expression_parser/infix/function.rb +67 -0
- data/lib/twig/expression_parser/infix/is.rb +53 -0
- data/lib/twig/expression_parser/infix/is_not.rb +19 -0
- data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
- data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
- data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
- data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
- data/lib/twig/expression_parser/prefix/literal.rb +244 -0
- data/lib/twig/expression_parser/prefix/unary.rb +29 -0
- data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
- data/lib/twig/extension/base.rb +26 -4
- data/lib/twig/extension/core.rb +1076 -48
- data/lib/twig/extension/debug.rb +25 -0
- data/lib/twig/extension/escaper.rb +73 -0
- data/lib/twig/extension/rails.rb +10 -57
- data/lib/twig/extension/string_loader.rb +19 -0
- data/lib/twig/extension_set.rb +117 -20
- data/lib/twig/file_extension_escaping_strategy.rb +35 -0
- data/lib/twig/lexer.rb +225 -81
- data/lib/twig/loader/array.rb +25 -8
- data/lib/twig/loader/chain.rb +93 -0
- data/lib/twig/loader/filesystem.rb +106 -7
- data/lib/twig/node/auto_escape.rb +18 -0
- data/lib/twig/node/base.rb +58 -2
- data/lib/twig/node/block.rb +2 -0
- data/lib/twig/node/block_reference.rb +5 -1
- data/lib/twig/node/body.rb +7 -0
- data/lib/twig/node/cache.rb +50 -0
- data/lib/twig/node/capture.rb +22 -0
- data/lib/twig/node/deprecated.rb +53 -0
- data/lib/twig/node/do.rb +19 -0
- data/lib/twig/node/embed.rb +43 -0
- data/lib/twig/node/expression/array.rb +29 -20
- data/lib/twig/node/expression/arrow_function.rb +55 -0
- data/lib/twig/node/expression/assign_name.rb +1 -1
- data/lib/twig/node/expression/binary/and.rb +17 -0
- data/lib/twig/node/expression/binary/base.rb +6 -4
- data/lib/twig/node/expression/binary/boolean.rb +24 -0
- data/lib/twig/node/expression/binary/concat.rb +20 -0
- data/lib/twig/node/expression/binary/elvis.rb +35 -0
- data/lib/twig/node/expression/binary/ends_with.rb +24 -0
- data/lib/twig/node/expression/binary/floor_div.rb +21 -0
- data/lib/twig/node/expression/binary/has_every.rb +20 -0
- data/lib/twig/node/expression/binary/has_some.rb +20 -0
- data/lib/twig/node/expression/binary/in.rb +20 -0
- data/lib/twig/node/expression/binary/matches.rb +24 -0
- data/lib/twig/node/expression/binary/not_in.rb +20 -0
- data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
- data/lib/twig/node/expression/binary/or.rb +15 -0
- data/lib/twig/node/expression/binary/starts_with.rb +24 -0
- data/lib/twig/node/expression/binary/xor.rb +17 -0
- data/lib/twig/node/expression/block_reference.rb +62 -0
- data/lib/twig/node/expression/call.rb +126 -6
- data/lib/twig/node/expression/constant.rb +3 -1
- data/lib/twig/node/expression/filter/default.rb +37 -0
- data/lib/twig/node/expression/filter/raw.rb +31 -0
- data/lib/twig/node/expression/filter.rb +2 -2
- data/lib/twig/node/expression/function.rb +37 -0
- data/lib/twig/node/expression/get_attribute.rb +51 -7
- data/lib/twig/node/expression/hash.rb +75 -0
- data/lib/twig/node/expression/helper_method.rb +6 -18
- data/lib/twig/node/expression/macro_reference.rb +43 -0
- data/lib/twig/node/expression/name.rb +42 -8
- data/lib/twig/node/expression/operator_escape.rb +13 -0
- data/lib/twig/node/expression/parent.rb +28 -0
- data/lib/twig/node/expression/support_defined_test.rb +23 -0
- data/lib/twig/node/expression/ternary.rb +7 -1
- data/lib/twig/node/expression/test/base.rb +26 -0
- data/lib/twig/node/expression/test/constant.rb +35 -0
- data/lib/twig/node/expression/test/defined.rb +33 -0
- data/lib/twig/node/expression/test/divisible_by.rb +23 -0
- data/lib/twig/node/expression/test/even.rb +21 -0
- data/lib/twig/node/expression/test/iterable.rb +21 -0
- data/lib/twig/node/expression/test/mapping.rb +21 -0
- data/lib/twig/node/expression/test/null.rb +21 -0
- data/lib/twig/node/expression/test/odd.rb +21 -0
- data/lib/twig/node/expression/test/same_as.rb +23 -0
- data/lib/twig/node/expression/test/sequence.rb +21 -0
- data/lib/twig/node/expression/unary/base.rb +3 -1
- data/lib/twig/node/expression/unary/not.rb +18 -0
- data/lib/twig/node/expression/unary/spread.rb +18 -0
- data/lib/twig/node/expression/unary/string_cast.rb +18 -0
- data/lib/twig/node/expression/variable/assign_template.rb +35 -0
- data/lib/twig/node/expression/variable/local.rb +35 -0
- data/lib/twig/node/expression/variable/template.rb +54 -0
- data/lib/twig/node/for.rb +38 -8
- data/lib/twig/node/for_loop.rb +0 -22
- data/lib/twig/node/if.rb +4 -1
- data/lib/twig/node/import.rb +32 -0
- data/lib/twig/node/include.rb +38 -8
- data/lib/twig/node/macro.rb +79 -0
- data/lib/twig/node/module.rb +278 -23
- data/lib/twig/node/output.rb +7 -0
- data/lib/twig/node/print.rb +4 -1
- data/lib/twig/node/set.rb +72 -0
- data/lib/twig/node/text.rb +4 -1
- data/lib/twig/node/with.rb +50 -0
- data/lib/twig/node/yield.rb +6 -1
- data/lib/twig/node_traverser.rb +50 -0
- data/lib/twig/node_visitor/base.rb +30 -0
- data/lib/twig/node_visitor/escaper.rb +165 -0
- data/lib/twig/node_visitor/safe_analysis.rb +127 -0
- data/lib/twig/node_visitor/spreader.rb +39 -0
- data/lib/twig/output_buffer.rb +14 -12
- data/lib/twig/parser.rb +281 -8
- data/lib/twig/rails/config.rb +33 -0
- data/lib/twig/rails/engine.rb +44 -0
- data/lib/twig/rails/renderer.rb +41 -0
- data/lib/twig/runtime/argument_spreader.rb +46 -0
- data/lib/twig/runtime/context.rb +154 -0
- data/lib/twig/runtime/enumerable_hash.rb +51 -0
- data/lib/twig/runtime/escaper.rb +155 -0
- data/lib/twig/runtime/loop_context.rb +81 -0
- data/lib/twig/runtime/loop_iterator.rb +60 -0
- data/lib/twig/runtime/spread.rb +21 -0
- data/lib/twig/runtime_loader/base.rb +12 -0
- data/lib/twig/runtime_loader/factory.rb +23 -0
- data/lib/twig/template.rb +267 -14
- data/lib/twig/template_wrapper.rb +42 -0
- data/lib/twig/token.rb +28 -2
- data/lib/twig/token_parser/apply.rb +48 -0
- data/lib/twig/token_parser/auto_escape.rb +45 -0
- data/lib/twig/token_parser/base.rb +26 -0
- data/lib/twig/token_parser/block.rb +4 -4
- data/lib/twig/token_parser/cache.rb +31 -0
- data/lib/twig/token_parser/deprecated.rb +40 -0
- data/lib/twig/token_parser/do.rb +19 -0
- data/lib/twig/token_parser/embed.rb +62 -0
- data/lib/twig/token_parser/extends.rb +4 -3
- data/lib/twig/token_parser/for.rb +14 -9
- data/lib/twig/token_parser/from.rb +57 -0
- data/lib/twig/token_parser/guard.rb +65 -0
- data/lib/twig/token_parser/if.rb +9 -9
- data/lib/twig/token_parser/import.rb +29 -0
- data/lib/twig/token_parser/include.rb +2 -2
- data/lib/twig/token_parser/macro.rb +109 -0
- data/lib/twig/token_parser/set.rb +76 -0
- data/lib/twig/token_parser/use.rb +54 -0
- data/lib/twig/token_parser/with.rb +36 -0
- data/lib/twig/token_parser/yield.rb +7 -7
- data/lib/twig/token_stream.rb +23 -3
- data/lib/twig/twig_filter.rb +20 -0
- data/lib/twig/twig_function.rb +37 -0
- data/lib/twig/twig_test.rb +31 -0
- data/lib/twig/util/callable_arguments_extractor.rb +227 -0
- data/lib/twig_ruby.rb +21 -2
- metadata +148 -6
- data/lib/twig/context.rb +0 -64
- data/lib/twig/expression_parser.rb +0 -517
- data/lib/twig/railtie.rb +0 -60
data/lib/twig/lexer.rb
CHANGED
|
@@ -18,10 +18,16 @@ module Twig
|
|
|
18
18
|
REGEX_EXPONENT = /[eE][+-]?#{REGEX_LNUM}/
|
|
19
19
|
REGEX_DNUM = /#{REGEX_LNUM}(?:#{REGEX_FRAC})?/
|
|
20
20
|
|
|
21
|
-
REGEX_NAME = /[a-zA-Z_][a-zA-Z0-9_]*/
|
|
21
|
+
REGEX_NAME = /[a-zA-Z_\u{007f}-\u{00ff}][a-zA-Z0-9_\u{007f}-\u{00ff}]*/u
|
|
22
22
|
REGEX_SYMBOL = /:#{REGEX_NAME}/
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
REGEX_CVAR = /@#{REGEX_NAME}/
|
|
24
|
+
REGEX_STRING = /\G"([^#"\\]*(?:\\.[^#"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/mu
|
|
25
|
+
REGEX_DQ_STRING_PART = /\G[^#"\\]*(?:(?:\.|#(?!\{))[^#"\\]*)*/mu
|
|
26
|
+
REGEX_INLINE_COMMENT = /#[^\n]*/
|
|
27
|
+
REGEX_DQ_STRING_DELIM = /\G"/
|
|
28
|
+
REGEX_INTERP_START = /\G#\{[[:space:]]*/
|
|
29
|
+
REGEX_INTERP_END = /\G[[:space:]]*\}/
|
|
30
|
+
REGEX_NUMBER = /\G(?:#{REGEX_DNUM}(?:#{REGEX_EXPONENT})?)/x
|
|
25
31
|
|
|
26
32
|
STATE_DATA = 0
|
|
27
33
|
STATE_BLOCK = 1
|
|
@@ -29,6 +35,14 @@ module Twig
|
|
|
29
35
|
STATE_STRING = 3
|
|
30
36
|
STATE_INTERPOLATION = 4
|
|
31
37
|
|
|
38
|
+
SPECIAL_CHARS = {
|
|
39
|
+
'f' => "\f",
|
|
40
|
+
'n' => "\n",
|
|
41
|
+
'r' => "\r",
|
|
42
|
+
't' => "\t",
|
|
43
|
+
'v' => "\v",
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
32
46
|
# @param [Environment] environment
|
|
33
47
|
def initialize(environment)
|
|
34
48
|
@environment = environment
|
|
@@ -56,6 +70,10 @@ module Twig
|
|
|
56
70
|
lex_block
|
|
57
71
|
when STATE_VAR
|
|
58
72
|
lex_var
|
|
73
|
+
when STATE_STRING
|
|
74
|
+
lex_string
|
|
75
|
+
when STATE_INTERPOLATION
|
|
76
|
+
lex_interpolation
|
|
59
77
|
else
|
|
60
78
|
raise "Unknown state: #{@state}"
|
|
61
79
|
end
|
|
@@ -91,17 +109,26 @@ module Twig
|
|
|
91
109
|
# Push the template text first
|
|
92
110
|
text = text_content = @code[@cursor, (position.begin(0) - @cursor)]
|
|
93
111
|
|
|
94
|
-
#
|
|
112
|
+
# trim?
|
|
113
|
+
char = @positions[@position][2]
|
|
114
|
+
|
|
115
|
+
if char == WHITESPACE_TRIM
|
|
116
|
+
text = text.rstrip
|
|
117
|
+
elsif char == WHITESPACE_LINE_TRIM
|
|
118
|
+
text = text.gsub(/[ \t\0\x0B]*\z/, '')
|
|
119
|
+
end
|
|
95
120
|
|
|
96
121
|
push_token(Token::TEXT_TYPE, text)
|
|
97
122
|
move_cursor(text_content + position.to_s)
|
|
98
123
|
|
|
99
124
|
case @positions[@position][1]
|
|
125
|
+
when TAG_COMMENT[0]
|
|
126
|
+
lex_comment
|
|
100
127
|
when TAG_BLOCK[0]
|
|
101
|
-
if (match = @code
|
|
128
|
+
if (match = @code.match(lex_block_raw_regex, @cursor))
|
|
102
129
|
move_cursor(match.to_s)
|
|
103
130
|
lex_raw_data
|
|
104
|
-
elsif (match = @code
|
|
131
|
+
elsif (match = @code.match(lex_block_line_regex, @cursor))
|
|
105
132
|
move_cursor(match[0].to_s)
|
|
106
133
|
@lineno = match[1].to_i
|
|
107
134
|
else
|
|
@@ -114,24 +141,24 @@ module Twig
|
|
|
114
141
|
push_state(STATE_VAR)
|
|
115
142
|
@current_var_block_line = @lineno
|
|
116
143
|
else
|
|
117
|
-
raise "Invalid start token #{@positions[@position]}"
|
|
144
|
+
raise Error::Syntax.new("Invalid start token #{@positions[@position]}", @lineno)
|
|
118
145
|
end
|
|
119
146
|
end
|
|
120
147
|
|
|
121
148
|
def lex_raw_data
|
|
122
|
-
unless (match = @code
|
|
123
|
-
raise "Uexpected end of file. Unclosed 'verbatim' block"
|
|
149
|
+
unless (match = @code.match(lex_raw_data_regex, @cursor))
|
|
150
|
+
raise Error::Syntax.new("Uexpected end of file. Unclosed 'verbatim' block.", @lineno, @source)
|
|
124
151
|
end
|
|
125
152
|
|
|
126
|
-
text = @code[@cursor, match.begin(0)]
|
|
127
|
-
move_cursor(@code[@cursor, (
|
|
153
|
+
text = @code[@cursor, match.begin(0) - @cursor]
|
|
154
|
+
move_cursor(@code[@cursor, (text.length + match.to_s.length)])
|
|
128
155
|
|
|
129
156
|
# trim
|
|
130
157
|
if match[1]
|
|
131
158
|
text = if match[1] == WHITESPACE_TRIM
|
|
132
|
-
text.gsub(/ *$/, '') # space trim
|
|
133
|
-
else
|
|
134
159
|
text.rstrip # line trim
|
|
160
|
+
else
|
|
161
|
+
text.gsub(/[ \t\0\x0B]*\z/, '') # space trim
|
|
135
162
|
end
|
|
136
163
|
end
|
|
137
164
|
|
|
@@ -139,7 +166,7 @@ module Twig
|
|
|
139
166
|
end
|
|
140
167
|
|
|
141
168
|
def lex_block
|
|
142
|
-
if @brackets.empty? && (match = @code
|
|
169
|
+
if @brackets.empty? && (match = @code.match(lex_block_regex, @cursor))
|
|
143
170
|
push_token(Token::BLOCK_END_TYPE)
|
|
144
171
|
move_cursor(match.to_s)
|
|
145
172
|
pop_state
|
|
@@ -149,7 +176,7 @@ module Twig
|
|
|
149
176
|
end
|
|
150
177
|
|
|
151
178
|
def lex_var
|
|
152
|
-
match = @code
|
|
179
|
+
match = @code.match(lex_var_regex, @cursor)
|
|
153
180
|
|
|
154
181
|
if @brackets.empty? && match
|
|
155
182
|
push_token(Token::VAR_END_TYPE)
|
|
@@ -161,75 +188,144 @@ module Twig
|
|
|
161
188
|
end
|
|
162
189
|
|
|
163
190
|
def lex_expression
|
|
164
|
-
@code
|
|
191
|
+
@code.match(/\G[[:space:]]+/, @cursor) do |match|
|
|
165
192
|
move_cursor(match.to_s)
|
|
166
193
|
|
|
167
194
|
if @cursor >= @end
|
|
168
|
-
raise
|
|
195
|
+
raise Error::Syntax.new(
|
|
196
|
+
"Unclosed \"#{@state == STATE_BLOCK ? 'block' : 'variable'}\".",
|
|
197
|
+
@lineno,
|
|
198
|
+
@source
|
|
199
|
+
)
|
|
169
200
|
end
|
|
170
201
|
end
|
|
171
202
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
elsif (match = @code[@cursor..].match(operator_regex))
|
|
181
|
-
push_token(Token::OPERATOR_TYPE, match.to_s.gsub('/\s+/', ' '))
|
|
203
|
+
if (match = @code.match(operator_regex, @cursor))
|
|
204
|
+
operator = match.to_s.gsub(/[[:space:]]+/, ' ')
|
|
205
|
+
|
|
206
|
+
if OPENING_BRACKET.include?(operator)
|
|
207
|
+
check_brackets(operator)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
push_token(Token::OPERATOR_TYPE, operator)
|
|
182
211
|
move_cursor(match.to_s)
|
|
183
|
-
elsif (match = @code
|
|
212
|
+
elsif (match = @code.match(/\G#{REGEX_NAME}(?:\?(?!\?))?/, @cursor)) # Optional ? but not ??
|
|
184
213
|
push_token(Token::NAME_TYPE, match.to_s)
|
|
185
214
|
move_cursor(match.to_s)
|
|
186
|
-
elsif (match = @code
|
|
215
|
+
elsif (match = @code.match(/\G#{REGEX_SYMBOL}/, @cursor))
|
|
187
216
|
push_token(Token::SYMBOL_TYPE, match.to_s[1..])
|
|
188
217
|
move_cursor(match.to_s)
|
|
189
|
-
elsif (match = @code
|
|
218
|
+
elsif (match = @code.match(/\G#{REGEX_CVAR}/, @cursor))
|
|
219
|
+
push_token(Token::CLASS_VAR_TYPE, match.to_s)
|
|
220
|
+
move_cursor(match.to_s)
|
|
221
|
+
elsif (match = @code.match(REGEX_NUMBER, @cursor))
|
|
190
222
|
value = match.to_s.tr('_', '')
|
|
191
223
|
value = value.to_i.to_s == value ? value.to_i : value.to_f
|
|
192
224
|
push_token(Token::NUMBER_TYPE, value)
|
|
193
225
|
move_cursor(match.to_s)
|
|
194
226
|
elsif code_at?(0, PUNCTUATION)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
check_brackets(code_at)
|
|
228
|
+
push_token(Token::PUNCTUATION_TYPE, code_at)
|
|
229
|
+
@cursor += 1
|
|
230
|
+
elsif (match = @code.match(REGEX_STRING, @cursor))
|
|
231
|
+
push_token(Token::STRING_TYPE, stripcslashes(match.to_s[1...-1], match.to_s[0]))
|
|
232
|
+
move_cursor(match.to_s)
|
|
233
|
+
elsif (match = @code.match(REGEX_DQ_STRING_DELIM, @cursor))
|
|
234
|
+
@brackets << ['"', @lineno]
|
|
235
|
+
push_state(STATE_STRING)
|
|
236
|
+
move_cursor(match.to_s)
|
|
237
|
+
elsif (match = @code.match(REGEX_INLINE_COMMENT, @cursor))
|
|
238
|
+
move_cursor(match.to_s)
|
|
239
|
+
else
|
|
240
|
+
raise Error::Syntax.new("Unexpected character '#{code_at}'", @lineno, @source)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
202
243
|
|
|
203
|
-
|
|
244
|
+
def lex_comment
|
|
245
|
+
unless (match = @code.match(lex_comment_regex, @cursor))
|
|
246
|
+
raise Error::Syntax.new('Unclosed comment.', @lineno, @source)
|
|
247
|
+
end
|
|
204
248
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
249
|
+
move_cursor(@code[@cursor...match.offset(0)[1]])
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def lex_string
|
|
253
|
+
if (match = @code.match(REGEX_INTERP_START, @cursor))
|
|
254
|
+
@brackets << ['#{', @lineno]
|
|
255
|
+
push_token(Token::INTERPOLATION_START_TYPE)
|
|
256
|
+
move_cursor(match.to_s)
|
|
257
|
+
push_state(STATE_INTERPOLATION)
|
|
258
|
+
elsif (match = @code.match(REGEX_DQ_STRING_PART, @cursor)) && match.to_s != ''
|
|
259
|
+
push_token(Token::STRING_TYPE, stripcslashes(match.to_s, '"'))
|
|
260
|
+
move_cursor(match.to_s)
|
|
261
|
+
elsif @code.match(REGEX_DQ_STRING_DELIM, @cursor)
|
|
262
|
+
expect, lineno = @brackets.pop
|
|
263
|
+
|
|
264
|
+
unless code_at?(0, '"')
|
|
265
|
+
raise Error::Syntax.new("Unclosed '#{expect}'.", lineno, @source)
|
|
208
266
|
end
|
|
209
267
|
|
|
210
|
-
|
|
268
|
+
pop_state
|
|
211
269
|
@cursor += 1
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
270
|
+
else
|
|
271
|
+
Error::Syntax.new("Unexpected character '#{code_at}'.", @lineno, @source)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @return [String]
|
|
276
|
+
def stripcslashes(str, quote_type)
|
|
277
|
+
result = ''
|
|
278
|
+
length = str.length
|
|
279
|
+
|
|
280
|
+
i = 0
|
|
281
|
+
while i < length
|
|
282
|
+
pos = str.index('\\', i)
|
|
283
|
+
|
|
284
|
+
unless pos
|
|
285
|
+
result += str[i..]
|
|
286
|
+
break
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
result += str[i...pos]
|
|
290
|
+
i = pos + 1
|
|
291
|
+
|
|
292
|
+
if i >= length
|
|
293
|
+
result += '\\'
|
|
294
|
+
break
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
next_char = str[i]
|
|
298
|
+
|
|
299
|
+
if SPECIAL_CHARS.key?(next_char)
|
|
300
|
+
result += SPECIAL_CHARS[next_char]
|
|
301
|
+
elsif next_char == '\\' || next_char == quote_type
|
|
302
|
+
result += next_char
|
|
303
|
+
elsif next_char == '#' && i + 1 < length && str[i + 1] == '{'
|
|
304
|
+
result += '#{'
|
|
305
|
+
i += 1
|
|
306
|
+
elsif next_char == 'x' && i + 1 < length && str[i + 1].match?(/[0-9a-fA-F]/)
|
|
307
|
+
hex = str[i + 1]
|
|
308
|
+
i += 1
|
|
309
|
+
if i + 1 < length && str[i + 1].match?(/[0-9a-fA-F]/)
|
|
310
|
+
hex += str[i + 1]
|
|
311
|
+
i += 1
|
|
312
|
+
end
|
|
313
|
+
result += [hex.to_i(16)].pack('C')
|
|
314
|
+
elsif next_char.match?(/[0-7]/) # octal
|
|
315
|
+
octal = next_char
|
|
316
|
+
while i + 1 < length && str[i + 1].match?(/[0-7]/) && octal.length < 3
|
|
317
|
+
octal += str[i + 1]
|
|
318
|
+
i += 1
|
|
319
|
+
end
|
|
320
|
+
result += [octal.to_i(8)].pack('C')
|
|
321
|
+
else
|
|
322
|
+
result += "\\#{next_char}"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
i += 1
|
|
215
326
|
end
|
|
216
327
|
|
|
217
|
-
|
|
218
|
-
// opening double quoted string
|
|
219
|
-
elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
|
|
220
|
-
$this->brackets[] = ['"', $this->lineno];
|
|
221
|
-
$this->pushState(self::STATE_STRING);
|
|
222
|
-
$this->moveCursor($match[0]);
|
|
223
|
-
}
|
|
224
|
-
// inline comment
|
|
225
|
-
elseif (preg_match(self::REGEX_INLINE_COMMENT, $this->code, $match, 0, $this->cursor)) {
|
|
226
|
-
$this->moveCursor($match[0]);
|
|
227
|
-
}
|
|
228
|
-
// unlexable
|
|
229
|
-
else {
|
|
230
|
-
throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
|
231
|
-
}
|
|
232
|
-
TEMP
|
|
328
|
+
result
|
|
233
329
|
end
|
|
234
330
|
|
|
235
331
|
def push_token(type, value = '')
|
|
@@ -238,6 +334,18 @@ module Twig
|
|
|
238
334
|
@tokens << Token.new(type, value, @lineno)
|
|
239
335
|
end
|
|
240
336
|
|
|
337
|
+
def lex_interpolation
|
|
338
|
+
bracket = @brackets.last
|
|
339
|
+
if bracket[0] == '#{' && (match = @code.match(REGEX_INTERP_END, @cursor))
|
|
340
|
+
@brackets.pop
|
|
341
|
+
push_token(Token::INTERPOLATION_END_TYPE)
|
|
342
|
+
move_cursor(match.to_s)
|
|
343
|
+
pop_state
|
|
344
|
+
else
|
|
345
|
+
lex_expression
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
241
349
|
def push_state(state)
|
|
242
350
|
@states << @state
|
|
243
351
|
@state = state
|
|
@@ -287,36 +395,47 @@ module Twig
|
|
|
287
395
|
|
|
288
396
|
def lex_var_regex
|
|
289
397
|
@lex_var_regex ||=
|
|
290
|
-
/\
|
|
398
|
+
/\G[[:space:]]*(?:
|
|
291
399
|
#{Regexp.union(
|
|
292
|
-
|
|
293
|
-
WHITESPACE_LINE_TRIM
|
|
400
|
+
/#{WHITESPACE_TRIM}#{TAG_VARIABLE[1]}[[:space:]]*/,
|
|
401
|
+
/#{WHITESPACE_LINE_TRIM}#{TAG_VARIABLE[1]}[#{WHITESPACE_LINE_CHARS}]*/,
|
|
294
402
|
TAG_VARIABLE[1]
|
|
295
403
|
)}
|
|
296
404
|
)/x
|
|
297
405
|
end
|
|
298
406
|
|
|
407
|
+
def lex_comment_regex
|
|
408
|
+
@lex_comment_regex ||=
|
|
409
|
+
/
|
|
410
|
+
(?:#{Regexp.union(
|
|
411
|
+
/#{WHITESPACE_TRIM}#{TAG_COMMENT[1]}[[:space:]]*\n?/,
|
|
412
|
+
/#{WHITESPACE_LINE_TRIM}#{TAG_COMMENT[1]}[#{WHITESPACE_LINE_CHARS}]*/,
|
|
413
|
+
/#{TAG_COMMENT[1]}\n?/
|
|
414
|
+
)})
|
|
415
|
+
/mxu
|
|
416
|
+
end
|
|
417
|
+
|
|
299
418
|
def lex_block_raw_regex
|
|
300
419
|
@lex_block_raw_regex ||=
|
|
301
|
-
/\
|
|
420
|
+
/\G[[:space:]]*verbatim[[:space:]]*(?:
|
|
302
421
|
#{Regexp.union(
|
|
303
|
-
|
|
304
|
-
WHITESPACE_LINE_TRIM
|
|
422
|
+
/#{WHITESPACE_TRIM}#{TAG_BLOCK[1]}[[:space:]]*/,
|
|
423
|
+
/#{WHITESPACE_LINE_TRIM}#{TAG_BLOCK[1]}[#{WHITESPACE_LINE_CHARS}]*/,
|
|
305
424
|
TAG_BLOCK[1]
|
|
306
425
|
)}
|
|
307
|
-
)/
|
|
426
|
+
)/mxu
|
|
308
427
|
end
|
|
309
428
|
|
|
310
429
|
def lex_block_line_regex
|
|
311
|
-
@lex_block_line_regex ||= /\
|
|
430
|
+
@lex_block_line_regex ||= /\G[[:space:]]*line[[:space:]]+(\d+)[[:space:]]*#{Regexp.escape(TAG_BLOCK[1])}/mu
|
|
312
431
|
end
|
|
313
432
|
|
|
314
433
|
def lex_block_regex
|
|
315
434
|
@lex_block_regex ||=
|
|
316
|
-
/\
|
|
435
|
+
/\G[[:space:]]*(?:
|
|
317
436
|
#{Regexp.union(
|
|
318
|
-
/#{WHITESPACE_TRIM}#{TAG_BLOCK[1]}
|
|
319
|
-
WHITESPACE_LINE_TRIM
|
|
437
|
+
/#{WHITESPACE_TRIM}#{TAG_BLOCK[1]}[[:space:]]*\n?/,
|
|
438
|
+
/#{WHITESPACE_LINE_TRIM}#{TAG_BLOCK[1]}[#{WHITESPACE_LINE_CHARS}]*/,
|
|
320
439
|
/#{TAG_BLOCK[1]}\n?/
|
|
321
440
|
)}
|
|
322
441
|
)/x
|
|
@@ -326,20 +445,25 @@ module Twig
|
|
|
326
445
|
@lex_raw_data_regex ||=
|
|
327
446
|
/
|
|
328
447
|
#{TAG_BLOCK[0]}
|
|
329
|
-
(#{Regexp.union(WHITESPACE_TRIM, WHITESPACE_LINE_TRIM)})
|
|
448
|
+
(#{Regexp.union(WHITESPACE_TRIM, WHITESPACE_LINE_TRIM)})?[[:space:]]*endverbatim[[:space:]]*
|
|
330
449
|
(?:#{Regexp.union(
|
|
331
|
-
|
|
332
|
-
WHITESPACE_LINE_TRIM
|
|
450
|
+
/#{WHITESPACE_TRIM}#{TAG_BLOCK[1]}[[:space:]]*/,
|
|
451
|
+
/#{WHITESPACE_LINE_TRIM}#{TAG_BLOCK[1]}[#{WHITESPACE_LINE_CHARS}]*/,
|
|
333
452
|
TAG_BLOCK[1]
|
|
334
453
|
)})
|
|
335
|
-
/
|
|
454
|
+
/xu
|
|
336
455
|
end
|
|
337
456
|
|
|
338
457
|
def operator_regex
|
|
339
458
|
return @operator_regex if defined?(@operator_regex)
|
|
340
459
|
|
|
341
|
-
|
|
342
|
-
|
|
460
|
+
expression_parsers = ['=']
|
|
461
|
+
|
|
462
|
+
environment.expression_parsers.each do |expression_parser|
|
|
463
|
+
expression_parsers.concat([expression_parser.name], expression_parser.aliases)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
operators = expression_parsers.
|
|
343
467
|
to_h { |op| [op, op.length] }.
|
|
344
468
|
sort_by { |_, length| -length }.
|
|
345
469
|
to_h
|
|
@@ -352,7 +476,7 @@ module Twig
|
|
|
352
476
|
# an operator that ends with a character must be followed by
|
|
353
477
|
# a whitespace, a parenthesis, an opening map [ or sequence {
|
|
354
478
|
if operator[-1].match(/\w/)
|
|
355
|
-
regex << '(?=[
|
|
479
|
+
regex << '(?=[[[:space:]]()\[{])'
|
|
356
480
|
end
|
|
357
481
|
|
|
358
482
|
# an operator that begins with a character must not have a dot or pipe before
|
|
@@ -361,12 +485,32 @@ module Twig
|
|
|
361
485
|
end
|
|
362
486
|
|
|
363
487
|
# an operator with a space can be any amount of whitespaces
|
|
364
|
-
regex.gsub!(
|
|
488
|
+
regex.gsub!(/(\\\s)+/, '[[:space:]]+')
|
|
365
489
|
|
|
366
490
|
chain << regex
|
|
367
491
|
end
|
|
368
492
|
|
|
369
|
-
@operator_regex = Regexp.new("\\
|
|
493
|
+
@operator_regex = Regexp.new("\\G(?:#{chain.join('|')})")
|
|
370
494
|
end
|
|
495
|
+
|
|
496
|
+
# @param [String]
|
|
497
|
+
def check_brackets(code)
|
|
498
|
+
if OPENING_BRACKET.include?(code)
|
|
499
|
+
@brackets << [code, @lineno]
|
|
500
|
+
elsif CLOSING_BRACKET.include?(code)
|
|
501
|
+
if @brackets.empty?
|
|
502
|
+
raise Error::Syntax.new("Unexpected \"#{code}\".", @lineno, @source)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
expect, lineno = @brackets.pop
|
|
506
|
+
|
|
507
|
+
unless code_at?(0, expect.tr(OPENING_BRACKET.join, CLOSING_BRACKET.join))
|
|
508
|
+
raise Error::Syntax.new("Unclosed \"#{expect}\".", lineno, @source)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# @return [Environment]
|
|
514
|
+
attr_reader :environment
|
|
371
515
|
end
|
|
372
516
|
end
|
data/lib/twig/loader/array.rb
CHANGED
|
@@ -7,33 +7,50 @@ module Twig
|
|
|
7
7
|
def initialize(templates)
|
|
8
8
|
super()
|
|
9
9
|
|
|
10
|
-
@templates = templates.transform_keys(
|
|
10
|
+
@templates = templates.transform_keys { |name| normalize_name(name) }
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def get_source_context(name)
|
|
14
|
-
name = name
|
|
15
|
-
|
|
14
|
+
name = normalize_name(name)
|
|
15
|
+
|
|
16
|
+
unless @templates[name]
|
|
17
|
+
raise Error::Loader, "Template \"#{name}\" is not defined."
|
|
18
|
+
end
|
|
16
19
|
|
|
17
20
|
::Twig::Source.new(@templates[name], name)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def exists?(name)
|
|
21
|
-
|
|
24
|
+
name = normalize_name(name)
|
|
25
|
+
|
|
26
|
+
@templates.key?(name)
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
def get_cache_key(name)
|
|
25
|
-
name = name
|
|
26
|
-
|
|
30
|
+
name = normalize_name(name)
|
|
31
|
+
|
|
32
|
+
unless @templates[name]
|
|
33
|
+
raise Error::Loader, "Template \"#{name}\" is not defined."
|
|
34
|
+
end
|
|
27
35
|
|
|
28
36
|
"#{name}:#{@templates[name]}"
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
def fresh?(name, time)
|
|
32
|
-
name = name
|
|
33
|
-
|
|
40
|
+
name = normalize_name(name)
|
|
41
|
+
|
|
42
|
+
unless @templates[name]
|
|
43
|
+
raise Error::Loader, "Template \"#{name}\" is not defined."
|
|
44
|
+
end
|
|
34
45
|
|
|
35
46
|
true
|
|
36
47
|
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def normalize_name(name)
|
|
52
|
+
name.to_s
|
|
53
|
+
end
|
|
37
54
|
end
|
|
38
55
|
end
|
|
39
56
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module Loader
|
|
5
|
+
class Chain < Loader::Base
|
|
6
|
+
# @param [Array<Loader::Base>] loaders
|
|
7
|
+
def initialize(loaders)
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
@loaders = loaders
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get_source_context(name)
|
|
14
|
+
exceptions = []
|
|
15
|
+
|
|
16
|
+
loaders.each do |loader|
|
|
17
|
+
unless loader.exists?(name)
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
return loader.get_source_context(name)
|
|
23
|
+
rescue Twig::Error::Loader => e
|
|
24
|
+
exceptions << "#{loader.class.name}: #{e.message}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
exceptions = exceptions.any? ? "(#{exceptions.join(', ')})" : ''
|
|
29
|
+
|
|
30
|
+
raise Twig::Error::Loader, "Template \"#{name}\" is not defined#{exceptions}."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def exists?(name)
|
|
34
|
+
@has_source_cache ||= {}
|
|
35
|
+
|
|
36
|
+
return @has_source_cache[name] if @has_source_cache.key?(name)
|
|
37
|
+
|
|
38
|
+
loaders.each do |loader|
|
|
39
|
+
if loader.exists?(name)
|
|
40
|
+
return @has_source_cache[name] = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@has_source_cache[name] = false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_cache_key(name)
|
|
48
|
+
exceptions = []
|
|
49
|
+
|
|
50
|
+
loaders.each do |loader|
|
|
51
|
+
unless loader.exists?(name)
|
|
52
|
+
next
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
return loader.get_cache_key(name)
|
|
57
|
+
rescue Twig::Error::Loader => e
|
|
58
|
+
exceptions << "#{loader.class.name}: #{e.message}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
exceptions = exceptions.any? ? "(#{exceptions.join(', ')})" : ''
|
|
63
|
+
|
|
64
|
+
raise Twig::Error::Loader, "Template \"#{name}\" is not defined#{exceptions}."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def fresh?(name, time)
|
|
68
|
+
exceptions = []
|
|
69
|
+
|
|
70
|
+
loaders.each do |loader|
|
|
71
|
+
unless loader.exists?(name)
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
begin
|
|
76
|
+
return loader.fresh?(name, time)
|
|
77
|
+
rescue Twig::Error::Loader => e
|
|
78
|
+
exceptions << "#{loader.class.name}: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
exceptions = exceptions.any? ? "(#{exceptions.join(', ')})" : ''
|
|
83
|
+
|
|
84
|
+
raise Twig::Error::Loader, "Template \"#{name}\" is not defined#{exceptions}."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# @return [::Array<Loader::Base>]
|
|
90
|
+
attr_accessor :loaders
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|