twig_ruby 0.0.1

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/lib/twig/auto_hash.rb +17 -0
  3. data/lib/twig/cache/base.rb +31 -0
  4. data/lib/twig/cache/filesystem.rb +47 -0
  5. data/lib/twig/cache/nil.rb +19 -0
  6. data/lib/twig/callable.rb +21 -0
  7. data/lib/twig/compiler.rb +123 -0
  8. data/lib/twig/context.rb +64 -0
  9. data/lib/twig/environment.rb +161 -0
  10. data/lib/twig/error/base.rb +37 -0
  11. data/lib/twig/error/syntax.rb +8 -0
  12. data/lib/twig/expression_parser.rb +517 -0
  13. data/lib/twig/extension/base.rb +23 -0
  14. data/lib/twig/extension/core.rb +89 -0
  15. data/lib/twig/extension/rails.rb +70 -0
  16. data/lib/twig/extension_set.rb +69 -0
  17. data/lib/twig/lexer.rb +372 -0
  18. data/lib/twig/loader/array.rb +39 -0
  19. data/lib/twig/loader/base.rb +32 -0
  20. data/lib/twig/loader/filesystem.rb +45 -0
  21. data/lib/twig/node/base.rb +61 -0
  22. data/lib/twig/node/block.rb +20 -0
  23. data/lib/twig/node/block_reference.rb +17 -0
  24. data/lib/twig/node/empty.rb +11 -0
  25. data/lib/twig/node/expression/array.rb +50 -0
  26. data/lib/twig/node/expression/assign_name.rb +28 -0
  27. data/lib/twig/node/expression/base.rb +20 -0
  28. data/lib/twig/node/expression/binary/base.rb +63 -0
  29. data/lib/twig/node/expression/call.rb +28 -0
  30. data/lib/twig/node/expression/constant.rb +17 -0
  31. data/lib/twig/node/expression/filter.rb +52 -0
  32. data/lib/twig/node/expression/get_attribute.rb +30 -0
  33. data/lib/twig/node/expression/helper_method.rb +31 -0
  34. data/lib/twig/node/expression/name.rb +37 -0
  35. data/lib/twig/node/expression/ternary.rb +28 -0
  36. data/lib/twig/node/expression/unary/base.rb +52 -0
  37. data/lib/twig/node/expression/variable/assign_context.rb +11 -0
  38. data/lib/twig/node/expression/variable/context.rb +11 -0
  39. data/lib/twig/node/for.rb +64 -0
  40. data/lib/twig/node/for_loop.rb +39 -0
  41. data/lib/twig/node/if.rb +50 -0
  42. data/lib/twig/node/include.rb +71 -0
  43. data/lib/twig/node/module.rb +74 -0
  44. data/lib/twig/node/nodes.rb +13 -0
  45. data/lib/twig/node/print.rb +18 -0
  46. data/lib/twig/node/text.rb +20 -0
  47. data/lib/twig/node/yield.rb +54 -0
  48. data/lib/twig/output_buffer.rb +29 -0
  49. data/lib/twig/parser.rb +131 -0
  50. data/lib/twig/railtie.rb +60 -0
  51. data/lib/twig/source.rb +13 -0
  52. data/lib/twig/template.rb +50 -0
  53. data/lib/twig/token.rb +48 -0
  54. data/lib/twig/token_parser/base.rb +20 -0
  55. data/lib/twig/token_parser/block.rb +54 -0
  56. data/lib/twig/token_parser/extends.rb +25 -0
  57. data/lib/twig/token_parser/for.rb +64 -0
  58. data/lib/twig/token_parser/if.rb +64 -0
  59. data/lib/twig/token_parser/include.rb +51 -0
  60. data/lib/twig/token_parser/yield.rb +44 -0
  61. data/lib/twig/token_stream.rb +73 -0
  62. data/lib/twig/twig_filter.rb +21 -0
  63. data/lib/twig_ruby.rb +36 -0
  64. metadata +103 -0
@@ -0,0 +1,517 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ # @!attribute [r] environment
5
+ # @return [Environment]
6
+ class ExpressionParser
7
+ attr_reader :environment
8
+
9
+ OPERATOR_LEFT = 1
10
+ OPERATOR_RIGHT = 2
11
+
12
+ # @param [Parser] parser
13
+ # @param [Environment] environment
14
+ def initialize(parser, environment)
15
+ @parser = parser
16
+ @environment = environment
17
+ end
18
+
19
+ # @return [Node::Expression::Base]
20
+ def parse_expression(precedence = 0)
21
+ # @todo parse arrow
22
+
23
+ expr = primary
24
+ token = parser.current_token
25
+
26
+ while binary?(token) && binary_operators[token.value.to_sym][:precedence] >= precedence
27
+ operator = binary_operators[token.value.to_sym]
28
+ parser.stream.next
29
+
30
+ next_precedence = operator[:precedence]
31
+ next_precedence += 1 if operator[:associativity] == OPERATOR_LEFT
32
+
33
+ expr1 = parse_expression(next_precedence)
34
+
35
+ # @type [Node::Expression::Binary::Base]
36
+ expr = operator[:class].new(expr, expr1, token.lineno)
37
+ expr.attributes[:operator] = "binary_#{token.value}"
38
+
39
+ token = parser.current_token
40
+ end
41
+
42
+ return parse_ternary_expression(expr) if precedence.zero?
43
+
44
+ expr
45
+ end
46
+
47
+ # @return [Node::Expression::Base]
48
+ def parse_primary_expression
49
+ token = parser.current_token
50
+
51
+ case token.type
52
+ when Token::SYMBOL_TYPE
53
+ parser.stream.next
54
+
55
+ node = Node::Expression::Constant.new(token.value.to_sym, token.lineno)
56
+ when Token::NAME_TYPE
57
+ parser.stream.next
58
+
59
+ node = case token.value
60
+ when 'true', 'TRUE'
61
+ Node::Expression::Constant.new(true, token.lineno)
62
+ when 'false', 'FALSE'
63
+ Node::Expression::Constant.new(false, token.lineno)
64
+ when 'null', 'NULL', 'nil'
65
+ Node::Expression::Constant.new(nil, token.lineno)
66
+ else
67
+ if parser.current_token.value == '('
68
+ get_function_node(token.value, token.lineno)
69
+ else
70
+ # @todo should be a context variable
71
+ Node::Expression::Name.new(token.value, token.lineno)
72
+ end
73
+ end
74
+ when Token::NUMBER_TYPE
75
+ parser.stream.next
76
+
77
+ node = Node::Expression::Constant.new(token.value, token.lineno)
78
+ when Token::STRING_TYPE, Token::INTERPOLATION_START_TYPE
79
+ node = parse_string_expression
80
+ when Token::PUNCTUATION_TYPE
81
+ case token.value
82
+ when '['
83
+ node = parse_sequence_expression
84
+ when '{'
85
+ node = parse_mapping_expression
86
+ else
87
+ Error::Syntax.new(
88
+ "Unexpected token #{token.type} of value #{token.value}",
89
+ token.lineno,
90
+ parser.stream.source
91
+ )
92
+ end
93
+ else
94
+ raise Error::Syntax.new(
95
+ "Unexpected token '#{token.type}' of value '#{token.value}'",
96
+ token.lineno,
97
+ parser.stream.source
98
+ )
99
+ end
100
+
101
+ parse_post_fix_expression(node)
102
+ end
103
+
104
+ # @param [Node::Expression::Base] node
105
+ # @return [Node::Expression::Base]
106
+ def parse_post_fix_expression(node)
107
+ loop do
108
+ token = parser.current_token
109
+
110
+ unless token.type == Token::PUNCTUATION_TYPE
111
+ break
112
+ end
113
+
114
+ case token.value
115
+ when '.', '['
116
+ node = parse_subscript_expression(node)
117
+ when '|'
118
+ node = parse_filter_expression(node)
119
+ else
120
+ break
121
+ end
122
+ end
123
+
124
+ node
125
+ end
126
+
127
+ def parse_subscript_expression(node)
128
+ if parser.stream.next.value == '.'
129
+ return parse_subscript_expression_dot(node)
130
+ end
131
+
132
+ parse_subscript_expression_array(node)
133
+ end
134
+
135
+ def parse_subscript_expression_dot(node)
136
+ stream = parser.stream
137
+ token = stream.current
138
+ lineno = token.lineno
139
+ arguments = Node::Expression::Array.new({}, lineno)
140
+ token = stream.next
141
+
142
+ if token.type == Token::NAME_TYPE
143
+ attribute = Node::Expression::Constant.new(token.value, token.lineno)
144
+ end
145
+
146
+ Node::Expression::GetAttribute.new(node, attribute, arguments, nil, token.lineno)
147
+ end
148
+
149
+ def parse_sequence_expression
150
+ stream = parser.stream
151
+ stream.expect(Token::PUNCTUATION_TYPE, '[', 'A sequence element was expected')
152
+
153
+ node = Node::Expression::Array.new({}, stream.current.lineno)
154
+ first = true
155
+
156
+ # raise stream.debug
157
+
158
+ until stream.test(Token::PUNCTUATION_TYPE, ']')
159
+ unless first
160
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma')
161
+
162
+ # trailing comma
163
+ break if stream.test(Token::PUNCTUATION_TYPE, ']')
164
+ end
165
+
166
+ first = false
167
+
168
+ if stream.next_if(Token::SPREAD_TYPE)
169
+ expr = parse_expression
170
+ expr.attributes[:spread] = true
171
+ node.add_element(expr)
172
+ else
173
+ node.add_element(parse_expression)
174
+ end
175
+ end
176
+
177
+ stream.expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed')
178
+
179
+ node
180
+ end
181
+
182
+ def parse_mapping_expression
183
+ stream = parser.stream
184
+ stream.expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected')
185
+
186
+ node = Node::Expression::Array.new({}, stream.current.lineno)
187
+ first = true
188
+
189
+ until stream.test(Token::PUNCTUATION_TYPE, '}')
190
+ unless first
191
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma')
192
+
193
+ # trailing comma
194
+ if stream.test(Token::PUNCTUATION_TYPE, '}')
195
+ break
196
+ end
197
+ end
198
+
199
+ first = false
200
+
201
+ if stream.next_if(Token::SPREAD_TYPE)
202
+ value = parse_expression
203
+ value.attributes[:spread] = true
204
+ node.add_element(value)
205
+
206
+ next
207
+ end
208
+
209
+ # a mapping key can be:
210
+ #
211
+ # * a number -- 12
212
+ # * a string -- 'a'
213
+ # * a name, which is equivalent to a symbol -- a
214
+ # * an expression, which must be enclosed in parentheses -- (1 + 2)
215
+ if (token = stream.next_if(Token::NAME_TYPE))
216
+ key = Node::Expression::Constant.new(token.value.to_sym, token.lineno)
217
+
218
+ # {a} is a shortcut for {a: a}
219
+ if stream.test(Token::PUNCTUATION_TYPE, %w[, }])
220
+ value = Node::Expression::Variable::Context.new(key.attributes[:value], key.lineno)
221
+ node.add_element(value, key.to_sym)
222
+
223
+ next
224
+ end
225
+ elsif (token = stream.next_if(Token::STRING_TYPE)) || (token = stream.next_if(Token::NUMBER_TYPE))
226
+ key = Node::Expression::Constant.new(token.value, token.lineno)
227
+ elsif stream.test(Token::PUNCTUATION_TYPE, '(')
228
+ key = parse_expression
229
+ else
230
+ current = stream.current
231
+
232
+ raise Error::Syntax.new(
233
+ 'A mapping key must be a quoted string, number, name, or expression in parentheses ' \
234
+ "expected token '#{current.type}' of value '#{current.value}'",
235
+ current.lineno,
236
+ stream.source
237
+ )
238
+ end
239
+
240
+ stream.expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)')
241
+ value = parse_expression
242
+
243
+ node.add_element(value, key)
244
+ end
245
+
246
+ stream.expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed')
247
+
248
+ node
249
+ end
250
+
251
+ def get_function_node(name, line)
252
+ # @todo lots of stuff in this method
253
+ args = parse_only_arguments
254
+
255
+ if environment.helper_method?(name.to_sym)
256
+ Node::Expression::HelperMethod.new(name, args, line)
257
+ else
258
+ raise Error::Syntax.new("Unknown function '#{name}'", line, parser.stream.source)
259
+ end
260
+ end
261
+
262
+ def parse_only_arguments
263
+ args = AutoHash.new
264
+ stream = parser.stream
265
+ stream.expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis')
266
+ has_spread = false
267
+
268
+ until stream.test(Token::PUNCTUATION_TYPE, ')')
269
+ unless args.empty?
270
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma')
271
+
272
+ # if above was trailing comma, exit early
273
+ break if stream.test(Token::PUNCTUATION_TYPE, ')')
274
+ end
275
+
276
+ if stream.next_if(Token::SPREAD_TYPE)
277
+ has_spread = true
278
+ value = Node::Expression::Unary::Spread.new(parse_expression, stream.current.lineno)
279
+ elsif has_spread
280
+ raise Error::Syntax.new(
281
+ 'Normal arguments must be placed before argument unpacking.',
282
+ stream.current.lineno,
283
+ stream.source
284
+ )
285
+ else
286
+ value = parse_expression
287
+ end
288
+
289
+ name = nil
290
+ if (token = stream.next_if(Token::OPERATOR_TYPE, '=')) ||
291
+ (token = stream.next_if(Token::PUNCTUATION_TYPE, ':'))
292
+ unless value.class <= Node::Expression::Name
293
+ raise Error::Syntax.new(
294
+ "A parameter name must be a string, #{value.class.name} given.",
295
+ token.lineno,
296
+ stream.source
297
+ )
298
+ end
299
+
300
+ name = value.attributes[:name]
301
+ value = parse_expression
302
+ end
303
+
304
+ if name.nil?
305
+ args.add(value)
306
+ else
307
+ args[name] = value
308
+ end
309
+ end
310
+
311
+ stream.expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis')
312
+
313
+ Node::Nodes.new(args)
314
+ end
315
+
316
+ # @return [Parser]
317
+ attr_reader :parser
318
+
319
+ # @return [Node::Expression::Base]
320
+ def primary
321
+ token = parser.current_token
322
+
323
+ if unary?(token)
324
+ operator = unary_operators[token.value.to_sym]
325
+ parser.stream.next
326
+
327
+ expr = parse_expression(operator[:precedence])
328
+ expr = operator[:class].new(expr, token.lineno)
329
+ expr.attributes[:operator] = "unary_#{token.value}"
330
+
331
+ return parse_post_fix_expression(expr)
332
+ elsif token.test(Token::PUNCTUATION_TYPE, '(')
333
+ parser.stream.next
334
+ expr = parse_expression.set_explicit_parentheses
335
+
336
+ parser.stream.expect(Token::PUNCTUATION_TYPE, ')', 'Open parenthesis not closed')
337
+
338
+ return parse_post_fix_expression(expr)
339
+ end
340
+
341
+ parse_primary_expression
342
+ end
343
+
344
+ # @param [Node::Expression] expr
345
+ # @return [Node::Expression]
346
+ def parse_ternary_expression(expr)
347
+ while parser.stream.next_if(Token::PUNCTUATION_TYPE, '?')
348
+ expr2 = parse_expression
349
+ expr3 = if parser.stream.next_if(Token::PUNCTUATION_TYPE, ':')
350
+ parse_expression
351
+ else
352
+ Node::Expression::Constant.new('', parser.current_token.lineno)
353
+ end
354
+
355
+ expr = Node::Expression::Ternary.new(expr, expr2, expr3, parser.current_token.lineno)
356
+ end
357
+
358
+ expr
359
+ end
360
+
361
+ def parse_filter_expression(node)
362
+ parser.stream.next
363
+
364
+ parse_filter_expression_raw(node)
365
+ end
366
+
367
+ def parse_filter_expression_raw(node)
368
+ loop do
369
+ token = parser.stream.expect(Token::NAME_TYPE)
370
+
371
+ arguments = if parser.stream.test(Token::PUNCTUATION_TYPE, '(')
372
+ parse_only_arguments
373
+ else
374
+ Node::Empty.new
375
+ end
376
+
377
+ filter = get_filter(token.value, token.lineno)
378
+ node = filter.node_class.new(node, filter, arguments, token.lineno)
379
+
380
+ unless parser.stream.test(Token::PUNCTUATION_TYPE, '|')
381
+ break
382
+ end
383
+
384
+ parser.stream.next
385
+ end
386
+
387
+ node
388
+ end
389
+
390
+ def parse_arguments
391
+ raise NotImplementedError
392
+ end
393
+
394
+ # @return [Node::Nodes]
395
+ def parse_assignment_expression
396
+ stream = parser.stream
397
+ targets = AutoHash.new
398
+
399
+ loop do
400
+ token = parser.current_token
401
+
402
+ if stream.test(Token::OPERATOR_TYPE) && token.value.match(Lexer::REGEX_NAME)
403
+ # in this context, string operators are variables names
404
+ parser.stream.next
405
+ else
406
+ stream.expect(Token::NAME_TYPE, nil, 'Only variables can be assigned to')
407
+ end
408
+
409
+ targets << Node::Expression::Variable::AssignContext.new(token.value, token.lineno)
410
+
411
+ unless stream.next_if(Token::PUNCTUATION_TYPE, ',')
412
+ break
413
+ end
414
+ end
415
+
416
+ Node::Nodes.new(targets)
417
+ end
418
+
419
+ def parse_string_expression
420
+ stream = parser.stream
421
+ nodes = []
422
+
423
+ # a string cannot be followed by another string in a single expression
424
+ next_can_be_string = true
425
+
426
+ loop do
427
+ if next_can_be_string && (token = stream.next_if(Token::STRING_TYPE))
428
+ nodes << Node::Expression::Constant.new(token.value, token.lineno)
429
+ next_can_be_string = false
430
+ elsif stream.next_if(Token::INTERPOLATION_START_TYPE)
431
+ nodes << parse_expression
432
+ stream.expect(Token::INTERPOLATION_END_TYPE)
433
+ next_can_be_string = true
434
+ else
435
+ break
436
+ end
437
+ end
438
+
439
+ expr = nodes.shift
440
+ nodes.each do |node|
441
+ expr = Node::Expression::Binary::Concat(expr, node, node.lineno)
442
+ end
443
+
444
+ expr
445
+ end
446
+
447
+ # @param [Node::Base]
448
+ # @return [Node::Expression::Base]
449
+ def parse_subscript_expression_array(node)
450
+ stream = parser.stream
451
+ token = stream.current
452
+ lineno = token.lineno
453
+ arguments = Node::Expression::Array.new({}, lineno)
454
+
455
+ slice = false
456
+ if stream.test(Token::PUNCTUATION_TYPE, ':')
457
+ slice = true
458
+ attribute = Node::Expression::Constant.new(0, token.lineno)
459
+ else
460
+ attribute = parse_expression
461
+ end
462
+
463
+ if stream.next_if(Token::PUNCTUATION_TYPE, ':')
464
+ slice = true
465
+ end
466
+
467
+ if slice
468
+ length = if stream.test(Token::PUNCTUATION_TYPE, ']')
469
+ Node::Expression::Constant.new(nil, token.lineno)
470
+ else
471
+ parse_expression
472
+ end
473
+
474
+ filter = get_filter('slice', token.lineno)
475
+ arguments = Node::Nodes.new(AutoHash.new.add(attribute, length))
476
+ filter = filter.node_class.new(node, filter, arguments, token.lineno)
477
+
478
+ stream.expect(Token::PUNCTUATION_TYPE, ']')
479
+
480
+ return filter
481
+ end
482
+
483
+ stream.expect(Token::PUNCTUATION_TYPE, ']')
484
+
485
+ Node::Expression::GetAttribute.new(node, attribute, arguments, Template::ARRAY_CALL, lineno)
486
+ end
487
+
488
+ # @return [Hash]
489
+ def unary_operators
490
+ @unary_operators ||= environment.operators[0]
491
+ end
492
+
493
+ # @return [Hash]
494
+ def binary_operators
495
+ @binary_operators ||= environment.operators[1]
496
+ end
497
+
498
+ # @param [Token] token
499
+ def unary?(token)
500
+ token.test(Token::OPERATOR_TYPE) && unary_operators.key?(token.value.to_sym)
501
+ end
502
+
503
+ # @param [Token] token
504
+ def binary?(token)
505
+ token.test(Token::OPERATOR_TYPE) && binary_operators.key?(token.value.to_sym)
506
+ end
507
+
508
+ # @return [Filter]
509
+ def get_filter(name, lineno)
510
+ unless (filter = environment.filter(name))
511
+ raise Error::Syntax.new("Unknown '#{name}' filter", lineno, parser.stream.source)
512
+ end
513
+
514
+ filter
515
+ end
516
+ end
517
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class Base
6
+ def operators
7
+ [{}, {}]
8
+ end
9
+
10
+ def filters
11
+ {}
12
+ end
13
+
14
+ def token_parsers
15
+ []
16
+ end
17
+
18
+ def helper_methods
19
+ []
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class Core < Base
6
+ def operators
7
+ unary = Node::Expression::Unary
8
+ binary = Node::Expression::Binary
9
+
10
+ [
11
+ {
12
+ not: { precedence: 70, class: unary::Not },
13
+ '-': { precedence: 500, class: unary::Neg },
14
+ '+': { precedence: 500, class: unary::Pos },
15
+ },
16
+ {
17
+ or: { precedence: 10, class: binary::Or, associativity: ExpressionParser::OPERATOR_LEFT },
18
+ xor: { precedence: 12, class: binary::Xor, associativity: ExpressionParser::OPERATOR_LEFT },
19
+ and: { precedence: 15, class: binary::And, associativity: ExpressionParser::OPERATOR_LEFT },
20
+
21
+ '==': { precedence: 20, class: binary::Equal, associativity: ExpressionParser::OPERATOR_LEFT },
22
+ '!=': { precedence: 20, class: binary::NotEqual, associativity: ExpressionParser::OPERATOR_LEFT },
23
+ '<=>': { precedence: 20, class: binary::Spaceship, associativity: ExpressionParser::OPERATOR_LEFT },
24
+ '<': { precedence: 20, class: binary::Less, associativity: ExpressionParser::OPERATOR_LEFT },
25
+ '>': { precedence: 20, class: binary::Greater, associativity: ExpressionParser::OPERATOR_LEFT },
26
+ '>=': { precedence: 20, class: binary::GreaterEqual, associativity: ExpressionParser::OPERATOR_LEFT },
27
+ '<=': { precedence: 20, class: binary::LessEqual, associativity: ExpressionParser::OPERATOR_LEFT },
28
+
29
+ # @todo this needs a custom class but just needs to be parsed as operaor for for loops
30
+ in: { precedence: 20, class: binary::LessEqual, associativity: ExpressionParser::OPERATOR_LEFT },
31
+
32
+ '+': { precedence: 30, class: binary::Add, associativity: ExpressionParser::OPERATOR_LEFT },
33
+ '-': { precedence: 30, class: binary::Sub, associativity: ExpressionParser::OPERATOR_LEFT },
34
+ '~': { precedence: 40, class: binary::Concat, associativity: ExpressionParser::OPERATOR_LEFT },
35
+ '*': { precedence: 60, class: binary::Mul, associativity: ExpressionParser::OPERATOR_LEFT },
36
+ '/': { precedence: 60, class: binary::Div, associativity: ExpressionParser::OPERATOR_LEFT },
37
+ },
38
+ ]
39
+ end
40
+
41
+ def filters
42
+ {
43
+ capitalize: TwigFilter.new('capitalize', [self, :capitalize]),
44
+ upper: TwigFilter.new('upper', [self, :upper]),
45
+ lower: TwigFilter.new('lower', [self, :lower]),
46
+ raw: TwigFilter.new('raw', [self, :raw]),
47
+ }
48
+ end
49
+
50
+ def token_parsers
51
+ [
52
+ TokenParser::Block.new,
53
+ TokenParser::Extends.new,
54
+ TokenParser::For.new,
55
+ TokenParser::If.new,
56
+ TokenParser::Include.new,
57
+ TokenParser::Yield.new,
58
+ ]
59
+ end
60
+
61
+ def capitalize(string)
62
+ string.capitalize
63
+ end
64
+
65
+ def upper(string)
66
+ string.upcase
67
+ end
68
+
69
+ def lower(string)
70
+ string.downcase
71
+ end
72
+
73
+ def self.ensure_hash(value)
74
+ return value if value.class < Hash
75
+
76
+ AutoHash.new.add(*value)
77
+ end
78
+
79
+ def self.get_attribute(object, attribute, type)
80
+ case type
81
+ when Template::ARRAY_CALL
82
+ object[attribute] || (attribute.is_a?(String) ? object[attribute.to_sym] : object[attribute.to_s])
83
+ else
84
+ raise NotImplementedError, 'Need to implement other get_attribute calls'
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Extension
5
+ class Rails < Extension::Base
6
+ def filters
7
+ {}
8
+ end
9
+
10
+ def helper_methods
11
+ %w[
12
+ render
13
+
14
+ distance_of_time_in_words
15
+ time_ago_in_words
16
+
17
+ number_to_currency
18
+ number_to_human
19
+ number_to_human_size
20
+ number_to_percentage
21
+ number_to_phone
22
+ number_with_delimiter
23
+ number_with_precision
24
+
25
+ excerpt
26
+ pluralize
27
+ truncate
28
+ word_wrap
29
+
30
+ button_to
31
+ current_page?
32
+ link_to
33
+ mail_to
34
+ url_for
35
+
36
+ raw
37
+ sanitize
38
+ sanitize_css
39
+ strip_links
40
+ strip_tags
41
+
42
+ audio_tag
43
+ auto_discovery_link_tag
44
+ favicon_link_tag
45
+ image_tag
46
+ javascript_include_tag
47
+ picture_tag
48
+ preload_link_tag
49
+ stylesheet_link_tag
50
+ video_tag
51
+
52
+ escape_javascript
53
+ javascript_tag
54
+
55
+ benchmark
56
+ cache
57
+ debug
58
+
59
+ tag
60
+ token_list
61
+
62
+ form_for
63
+ form_with
64
+
65
+ date_test
66
+ ]
67
+ end
68
+ end
69
+ end
70
+ end