twig_ruby 0.0.1 → 0.0.2

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tasks/twig_parity.rake +278 -0
  3. data/lib/twig/auto_hash.rb +7 -1
  4. data/lib/twig/callable.rb +28 -1
  5. data/lib/twig/compiler.rb +35 -3
  6. data/lib/twig/environment.rb +198 -41
  7. data/lib/twig/error/base.rb +81 -16
  8. data/lib/twig/error/loader.rb +8 -0
  9. data/lib/twig/error/logic.rb +8 -0
  10. data/lib/twig/error/runtime.rb +8 -0
  11. data/lib/twig/expression_parser/base.rb +30 -0
  12. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  13. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  14. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  15. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  16. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  17. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  18. data/lib/twig/expression_parser/infix/function.rb +67 -0
  19. data/lib/twig/expression_parser/infix/is.rb +53 -0
  20. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  21. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  22. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  23. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  24. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  25. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  26. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  27. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  28. data/lib/twig/extension/base.rb +26 -4
  29. data/lib/twig/extension/core.rb +1076 -48
  30. data/lib/twig/extension/debug.rb +25 -0
  31. data/lib/twig/extension/escaper.rb +73 -0
  32. data/lib/twig/extension/rails.rb +10 -57
  33. data/lib/twig/extension/string_loader.rb +19 -0
  34. data/lib/twig/extension_set.rb +117 -20
  35. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  36. data/lib/twig/lexer.rb +225 -81
  37. data/lib/twig/loader/array.rb +25 -8
  38. data/lib/twig/loader/chain.rb +93 -0
  39. data/lib/twig/loader/filesystem.rb +106 -7
  40. data/lib/twig/node/auto_escape.rb +18 -0
  41. data/lib/twig/node/base.rb +58 -2
  42. data/lib/twig/node/block.rb +2 -0
  43. data/lib/twig/node/block_reference.rb +5 -1
  44. data/lib/twig/node/body.rb +7 -0
  45. data/lib/twig/node/cache.rb +50 -0
  46. data/lib/twig/node/capture.rb +22 -0
  47. data/lib/twig/node/deprecated.rb +53 -0
  48. data/lib/twig/node/do.rb +19 -0
  49. data/lib/twig/node/embed.rb +43 -0
  50. data/lib/twig/node/expression/array.rb +29 -20
  51. data/lib/twig/node/expression/arrow_function.rb +55 -0
  52. data/lib/twig/node/expression/assign_name.rb +1 -1
  53. data/lib/twig/node/expression/binary/and.rb +17 -0
  54. data/lib/twig/node/expression/binary/base.rb +6 -4
  55. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  56. data/lib/twig/node/expression/binary/concat.rb +20 -0
  57. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  58. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  59. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  60. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  61. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  62. data/lib/twig/node/expression/binary/in.rb +20 -0
  63. data/lib/twig/node/expression/binary/matches.rb +24 -0
  64. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  65. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  66. data/lib/twig/node/expression/binary/or.rb +15 -0
  67. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  68. data/lib/twig/node/expression/binary/xor.rb +17 -0
  69. data/lib/twig/node/expression/block_reference.rb +62 -0
  70. data/lib/twig/node/expression/call.rb +126 -6
  71. data/lib/twig/node/expression/constant.rb +3 -1
  72. data/lib/twig/node/expression/filter/default.rb +37 -0
  73. data/lib/twig/node/expression/filter/raw.rb +31 -0
  74. data/lib/twig/node/expression/filter.rb +2 -2
  75. data/lib/twig/node/expression/function.rb +37 -0
  76. data/lib/twig/node/expression/get_attribute.rb +51 -7
  77. data/lib/twig/node/expression/hash.rb +75 -0
  78. data/lib/twig/node/expression/helper_method.rb +6 -18
  79. data/lib/twig/node/expression/macro_reference.rb +43 -0
  80. data/lib/twig/node/expression/name.rb +42 -8
  81. data/lib/twig/node/expression/operator_escape.rb +13 -0
  82. data/lib/twig/node/expression/parent.rb +28 -0
  83. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  84. data/lib/twig/node/expression/ternary.rb +7 -1
  85. data/lib/twig/node/expression/test/base.rb +26 -0
  86. data/lib/twig/node/expression/test/constant.rb +35 -0
  87. data/lib/twig/node/expression/test/defined.rb +33 -0
  88. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  89. data/lib/twig/node/expression/test/even.rb +21 -0
  90. data/lib/twig/node/expression/test/iterable.rb +21 -0
  91. data/lib/twig/node/expression/test/mapping.rb +21 -0
  92. data/lib/twig/node/expression/test/null.rb +21 -0
  93. data/lib/twig/node/expression/test/odd.rb +21 -0
  94. data/lib/twig/node/expression/test/same_as.rb +23 -0
  95. data/lib/twig/node/expression/test/sequence.rb +21 -0
  96. data/lib/twig/node/expression/unary/base.rb +3 -1
  97. data/lib/twig/node/expression/unary/not.rb +18 -0
  98. data/lib/twig/node/expression/unary/spread.rb +18 -0
  99. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  100. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  101. data/lib/twig/node/expression/variable/local.rb +35 -0
  102. data/lib/twig/node/expression/variable/template.rb +54 -0
  103. data/lib/twig/node/for.rb +38 -8
  104. data/lib/twig/node/for_loop.rb +0 -22
  105. data/lib/twig/node/if.rb +4 -1
  106. data/lib/twig/node/import.rb +32 -0
  107. data/lib/twig/node/include.rb +38 -8
  108. data/lib/twig/node/macro.rb +79 -0
  109. data/lib/twig/node/module.rb +278 -23
  110. data/lib/twig/node/output.rb +7 -0
  111. data/lib/twig/node/print.rb +4 -1
  112. data/lib/twig/node/set.rb +72 -0
  113. data/lib/twig/node/text.rb +4 -1
  114. data/lib/twig/node/with.rb +50 -0
  115. data/lib/twig/node/yield.rb +6 -1
  116. data/lib/twig/node_traverser.rb +50 -0
  117. data/lib/twig/node_visitor/base.rb +30 -0
  118. data/lib/twig/node_visitor/escaper.rb +165 -0
  119. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  120. data/lib/twig/node_visitor/spreader.rb +39 -0
  121. data/lib/twig/output_buffer.rb +14 -12
  122. data/lib/twig/parser.rb +281 -8
  123. data/lib/twig/rails/config.rb +33 -0
  124. data/lib/twig/rails/engine.rb +44 -0
  125. data/lib/twig/rails/renderer.rb +41 -0
  126. data/lib/twig/runtime/argument_spreader.rb +46 -0
  127. data/lib/twig/runtime/context.rb +154 -0
  128. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  129. data/lib/twig/runtime/escaper.rb +155 -0
  130. data/lib/twig/runtime/loop_context.rb +81 -0
  131. data/lib/twig/runtime/loop_iterator.rb +60 -0
  132. data/lib/twig/runtime/spread.rb +21 -0
  133. data/lib/twig/runtime_loader/base.rb +12 -0
  134. data/lib/twig/runtime_loader/factory.rb +23 -0
  135. data/lib/twig/template.rb +267 -14
  136. data/lib/twig/template_wrapper.rb +42 -0
  137. data/lib/twig/token.rb +28 -2
  138. data/lib/twig/token_parser/apply.rb +48 -0
  139. data/lib/twig/token_parser/auto_escape.rb +45 -0
  140. data/lib/twig/token_parser/base.rb +26 -0
  141. data/lib/twig/token_parser/block.rb +4 -4
  142. data/lib/twig/token_parser/cache.rb +31 -0
  143. data/lib/twig/token_parser/deprecated.rb +40 -0
  144. data/lib/twig/token_parser/do.rb +19 -0
  145. data/lib/twig/token_parser/embed.rb +62 -0
  146. data/lib/twig/token_parser/extends.rb +4 -3
  147. data/lib/twig/token_parser/for.rb +14 -9
  148. data/lib/twig/token_parser/from.rb +57 -0
  149. data/lib/twig/token_parser/guard.rb +65 -0
  150. data/lib/twig/token_parser/if.rb +9 -9
  151. data/lib/twig/token_parser/import.rb +29 -0
  152. data/lib/twig/token_parser/include.rb +2 -2
  153. data/lib/twig/token_parser/macro.rb +109 -0
  154. data/lib/twig/token_parser/set.rb +76 -0
  155. data/lib/twig/token_parser/use.rb +54 -0
  156. data/lib/twig/token_parser/with.rb +36 -0
  157. data/lib/twig/token_parser/yield.rb +7 -7
  158. data/lib/twig/token_stream.rb +23 -3
  159. data/lib/twig/twig_filter.rb +20 -0
  160. data/lib/twig/twig_function.rb +37 -0
  161. data/lib/twig/twig_test.rb +31 -0
  162. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  163. data/lib/twig_ruby.rb +21 -2
  164. metadata +145 -6
  165. data/lib/twig/context.rb +0 -64
  166. data/lib/twig/expression_parser.rb +0 -517
  167. data/lib/twig/railtie.rb +0 -60
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Prefix
6
+ class Literal < PrefixExpressionParser
7
+ def parse(parser, token)
8
+ case token.type
9
+ when Token::SYMBOL_TYPE
10
+ parser.stream.next
11
+ @type = :constant
12
+
13
+ return Node::Expression::Constant.new(token.value.to_sym, token.lineno)
14
+ when Token::CLASS_VAR_TYPE
15
+ parser.stream.next
16
+ @type = :variable
17
+
18
+ return Node::Expression::Variable::Context.new(token.value, token.lineno)
19
+ when Token::NAME_TYPE
20
+ parser.stream.next
21
+
22
+ # We can adjust for a ternary with no space here like a? b : c, we should however ignore function
23
+ # nodes because a?() is fine
24
+ token_value = token.value
25
+ if token.value.end_with?('?') && parser.current_token.value != '('
26
+ parser.stream.inject([Token.new(Token::OPERATOR_TYPE, '?', token.lineno)])
27
+ token_value = token.value.chomp('?')
28
+ end
29
+
30
+ case token_value
31
+ when 'true', 'TRUE'
32
+ @type = :constant
33
+
34
+ return Node::Expression::Constant.new(true, token.lineno)
35
+ when 'false', 'FALSE'
36
+ @type = :constant
37
+
38
+ return Node::Expression::Constant.new(false, token.lineno)
39
+ when 'null', 'NULL', 'nil', 'none', 'NONE'
40
+ @type = :constant
41
+
42
+ return Node::Expression::Constant.new(nil, token.lineno)
43
+ else
44
+ @type = :variable
45
+
46
+ return Node::Expression::Variable::Context.new(token_value, token.lineno)
47
+ end
48
+ when Token::NUMBER_TYPE
49
+ parser.stream.next
50
+ @type = :constant
51
+
52
+ return Node::Expression::Constant.new(token.value, token.lineno)
53
+ when Token::STRING_TYPE, Token::INTERPOLATION_START_TYPE
54
+ @type = :string
55
+
56
+ return parse_string_expression(parser)
57
+ when Token::PUNCTUATION_TYPE
58
+ if token.value == '{'
59
+ return parse_mapping_expression(parser)
60
+ else
61
+ raise Error::Syntax.new(
62
+ "Unexpected token \"#{token.to_english}\" of value \"#{token.value}\".",
63
+ token.lineno,
64
+ parser.stream.source
65
+ )
66
+ end
67
+ when Token::OPERATOR_TYPE
68
+ if token.value == '['
69
+ return parse_sequence_expression(parser)
70
+ end
71
+
72
+ if (match = token.value.match(Lexer::REGEX_NAME)) && match.to_s == token.value
73
+ # in this context, string operators are variable names
74
+ parser.stream.next
75
+ @type = :variable
76
+
77
+ return Node::Expression::Variable::Context.new(token.value, token.lineno)
78
+ end
79
+
80
+ if token.value == '=' && %w[== !=].include?(parser.stream.look(-1).value)
81
+ raise Error::Syntax.new(
82
+ "Unexpected operator of value \"#{token.value}\". Did you try to use \"===\" or \"!==\" for " \
83
+ 'strict comparison? Use "is same as(value)" instead.',
84
+ token.lineno,
85
+ parser.stream.source
86
+ )
87
+ end
88
+ end
89
+
90
+ raise Error::Syntax.new(
91
+ "Unexpected token \"#{token.type}\" of value \"#{token.value}\".",
92
+ token.lineno,
93
+ parser.stream.source
94
+ )
95
+ end
96
+
97
+ def name
98
+ @type || :literal
99
+ end
100
+
101
+ def precedence
102
+ 0
103
+ end
104
+
105
+ private
106
+
107
+ # @param [Parser]
108
+ def parse_string_expression(parser)
109
+ stream = parser.stream
110
+ nodes = []
111
+
112
+ # a string cannot be followed by another string in a single expression
113
+ next_can_be_string = true
114
+
115
+ loop do
116
+ if next_can_be_string && (token = stream.next_if(Token::STRING_TYPE))
117
+ nodes << Node::Expression::Constant.new(token.value, token.lineno)
118
+ next_can_be_string = false
119
+ elsif stream.next_if(Token::INTERPOLATION_START_TYPE)
120
+ nodes << parser.parse_expression
121
+ stream.expect(Token::INTERPOLATION_END_TYPE)
122
+ next_can_be_string = true
123
+ else
124
+ break
125
+ end
126
+ end
127
+
128
+ expr = nodes.shift
129
+ nodes.each do |node|
130
+ expr = Node::Expression::Binary::Concat.new(expr, node, node.lineno)
131
+ end
132
+
133
+ expr
134
+ end
135
+
136
+ # @param [Parser] parser
137
+ def parse_sequence_expression(parser)
138
+ @type = :sequence
139
+
140
+ stream = parser.stream
141
+ stream.expect(Token::OPERATOR_TYPE, '[', 'A sequence element was expected')
142
+
143
+ node = Node::Expression::Array.new(AutoHash.new, stream.current.lineno)
144
+ first = true
145
+
146
+ # raise stream.debug
147
+
148
+ until stream.test(Token::PUNCTUATION_TYPE, ']')
149
+ unless first
150
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma')
151
+
152
+ # trailing comma
153
+ break if stream.test(Token::PUNCTUATION_TYPE, ']')
154
+ end
155
+
156
+ first = false
157
+
158
+ if stream.next_if(Token::OPERATOR_TYPE, '...')
159
+ expr = parser.parse_expression
160
+ node.add_element(Node::Expression::Unary::ArraySpread.new(expr, expr.lineno))
161
+ else
162
+ node.add_element(parser.parse_expression)
163
+ end
164
+ end
165
+
166
+ stream.expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed')
167
+
168
+ node
169
+ end
170
+
171
+ # @param [Parser] parser
172
+ def parse_mapping_expression(parser)
173
+ @type = :mapping
174
+
175
+ stream = parser.stream
176
+ stream.expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected')
177
+
178
+ node = Node::Expression::Hash.new({}, stream.current.lineno)
179
+ first = true
180
+
181
+ until stream.test(Token::PUNCTUATION_TYPE, '}')
182
+ unless first
183
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma')
184
+
185
+ # trailing comma
186
+ if stream.test(Token::PUNCTUATION_TYPE, '}')
187
+ break
188
+ end
189
+ end
190
+
191
+ first = false
192
+
193
+ if stream.next_if(Token::OPERATOR_TYPE, '...')
194
+ value = parser.parse_expression
195
+ node.add_element(Node::Expression::Unary::HashSpread.new(value, value.lineno))
196
+
197
+ next
198
+ end
199
+
200
+ # a mapping key can be:
201
+ #
202
+ # * a number -- 12
203
+ # * a string -- 'a'
204
+ # * a name, which is equivalent to a symbol -- a
205
+ # * an expression, which must be enclosed in parentheses -- (1 + 2)
206
+ if (token = stream.next_if(Token::NAME_TYPE))
207
+ key = Node::Expression::Constant.new(token.value.to_sym, token.lineno)
208
+
209
+ # {a} is a shortcut for {a: a}
210
+ if stream.test(Token::PUNCTUATION_TYPE, %w[, }])
211
+ value = Node::Expression::Variable::Context.new(key.attributes[:value], key.lineno)
212
+ node.add_element(value, key)
213
+
214
+ next
215
+ end
216
+ elsif (token = stream.next_if(Token::STRING_TYPE)) || (token = stream.next_if(Token::NUMBER_TYPE))
217
+ key = Node::Expression::Constant.new(token.value, token.lineno)
218
+ elsif stream.test(Token::OPERATOR_TYPE, '(')
219
+ key = parser.parse_expression
220
+ else
221
+ current = stream.current
222
+
223
+ raise Error::Syntax.new(
224
+ 'A mapping key must be a quoted string, number, name, or expression in parentheses ' \
225
+ "expected token '#{current.type}' of value '#{current.value}'.",
226
+ current.lineno,
227
+ stream.source
228
+ )
229
+ end
230
+
231
+ stream.expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)')
232
+ value = parser.parse_expression
233
+
234
+ node.add_element(value, key)
235
+ end
236
+
237
+ stream.expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed')
238
+
239
+ node
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Prefix
6
+ class Unary < PrefixExpressionParser
7
+ attr_reader :aliases, :precedence, :name
8
+
9
+ def initialize(node_class, name, precedence, description: nil, aliases: [])
10
+ super()
11
+
12
+ @node_class = node_class
13
+ @name = name
14
+ @precedence = precedence
15
+ @description = description
16
+ @aliases = aliases
17
+ end
18
+
19
+ def parse(parser, token)
20
+ @node_class.new(parser.parse_expression(precedence), token.lineno)
21
+ end
22
+
23
+ def description
24
+ @description || ''
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ class PrefixExpressionParser < ExpressionParser::Base
6
+ # @param [Parser] parser
7
+ # @param [Token] token
8
+ # @return [Node::Expression::Base]
9
+ def parse(parser, token)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def type
14
+ :prefix
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,21 +3,43 @@
3
3
  module Twig
4
4
  module Extension
5
5
  class Base
6
- def operators
7
- [{}, {}]
6
+ def expression_parsers
7
+ []
8
8
  end
9
9
 
10
10
  def filters
11
- {}
11
+ []
12
+ end
13
+
14
+ def functions
15
+ []
12
16
  end
13
17
 
14
18
  def token_parsers
15
19
  []
16
20
  end
17
21
 
18
- def helper_methods
22
+ def tests
23
+ []
24
+ end
25
+
26
+ def node_visitors
19
27
  []
20
28
  end
29
+
30
+ def globals
31
+ {}
32
+ end
33
+
34
+ private
35
+
36
+ def static(method)
37
+ self.class.method(method)
38
+ end
39
+
40
+ def runtime(klass, method)
41
+ [:runtime, klass, method]
42
+ end
21
43
  end
22
44
  end
23
45
  end