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.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -0
  3. data/lib/tasks/twig_parity.rake +278 -0
  4. data/lib/twig/auto_hash.rb +7 -1
  5. data/lib/twig/callable.rb +28 -1
  6. data/lib/twig/compiler.rb +35 -3
  7. data/lib/twig/environment.rb +198 -41
  8. data/lib/twig/error/base.rb +81 -16
  9. data/lib/twig/error/loader.rb +8 -0
  10. data/lib/twig/error/logic.rb +8 -0
  11. data/lib/twig/error/runtime.rb +8 -0
  12. data/lib/twig/expression_parser/base.rb +30 -0
  13. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  14. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  15. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  16. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  17. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  18. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  19. data/lib/twig/expression_parser/infix/function.rb +67 -0
  20. data/lib/twig/expression_parser/infix/is.rb +53 -0
  21. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  22. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  23. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  24. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  25. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  26. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  27. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  28. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  29. data/lib/twig/extension/base.rb +26 -4
  30. data/lib/twig/extension/core.rb +1076 -48
  31. data/lib/twig/extension/debug.rb +25 -0
  32. data/lib/twig/extension/escaper.rb +73 -0
  33. data/lib/twig/extension/rails.rb +10 -57
  34. data/lib/twig/extension/string_loader.rb +19 -0
  35. data/lib/twig/extension_set.rb +117 -20
  36. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  37. data/lib/twig/lexer.rb +225 -81
  38. data/lib/twig/loader/array.rb +25 -8
  39. data/lib/twig/loader/chain.rb +93 -0
  40. data/lib/twig/loader/filesystem.rb +106 -7
  41. data/lib/twig/node/auto_escape.rb +18 -0
  42. data/lib/twig/node/base.rb +58 -2
  43. data/lib/twig/node/block.rb +2 -0
  44. data/lib/twig/node/block_reference.rb +5 -1
  45. data/lib/twig/node/body.rb +7 -0
  46. data/lib/twig/node/cache.rb +50 -0
  47. data/lib/twig/node/capture.rb +22 -0
  48. data/lib/twig/node/deprecated.rb +53 -0
  49. data/lib/twig/node/do.rb +19 -0
  50. data/lib/twig/node/embed.rb +43 -0
  51. data/lib/twig/node/expression/array.rb +29 -20
  52. data/lib/twig/node/expression/arrow_function.rb +55 -0
  53. data/lib/twig/node/expression/assign_name.rb +1 -1
  54. data/lib/twig/node/expression/binary/and.rb +17 -0
  55. data/lib/twig/node/expression/binary/base.rb +6 -4
  56. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  57. data/lib/twig/node/expression/binary/concat.rb +20 -0
  58. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  59. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  60. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  61. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  62. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  63. data/lib/twig/node/expression/binary/in.rb +20 -0
  64. data/lib/twig/node/expression/binary/matches.rb +24 -0
  65. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  66. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  67. data/lib/twig/node/expression/binary/or.rb +15 -0
  68. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  69. data/lib/twig/node/expression/binary/xor.rb +17 -0
  70. data/lib/twig/node/expression/block_reference.rb +62 -0
  71. data/lib/twig/node/expression/call.rb +126 -6
  72. data/lib/twig/node/expression/constant.rb +3 -1
  73. data/lib/twig/node/expression/filter/default.rb +37 -0
  74. data/lib/twig/node/expression/filter/raw.rb +31 -0
  75. data/lib/twig/node/expression/filter.rb +2 -2
  76. data/lib/twig/node/expression/function.rb +37 -0
  77. data/lib/twig/node/expression/get_attribute.rb +51 -7
  78. data/lib/twig/node/expression/hash.rb +75 -0
  79. data/lib/twig/node/expression/helper_method.rb +6 -18
  80. data/lib/twig/node/expression/macro_reference.rb +43 -0
  81. data/lib/twig/node/expression/name.rb +42 -8
  82. data/lib/twig/node/expression/operator_escape.rb +13 -0
  83. data/lib/twig/node/expression/parent.rb +28 -0
  84. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  85. data/lib/twig/node/expression/ternary.rb +7 -1
  86. data/lib/twig/node/expression/test/base.rb +26 -0
  87. data/lib/twig/node/expression/test/constant.rb +35 -0
  88. data/lib/twig/node/expression/test/defined.rb +33 -0
  89. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  90. data/lib/twig/node/expression/test/even.rb +21 -0
  91. data/lib/twig/node/expression/test/iterable.rb +21 -0
  92. data/lib/twig/node/expression/test/mapping.rb +21 -0
  93. data/lib/twig/node/expression/test/null.rb +21 -0
  94. data/lib/twig/node/expression/test/odd.rb +21 -0
  95. data/lib/twig/node/expression/test/same_as.rb +23 -0
  96. data/lib/twig/node/expression/test/sequence.rb +21 -0
  97. data/lib/twig/node/expression/unary/base.rb +3 -1
  98. data/lib/twig/node/expression/unary/not.rb +18 -0
  99. data/lib/twig/node/expression/unary/spread.rb +18 -0
  100. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  101. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  102. data/lib/twig/node/expression/variable/local.rb +35 -0
  103. data/lib/twig/node/expression/variable/template.rb +54 -0
  104. data/lib/twig/node/for.rb +38 -8
  105. data/lib/twig/node/for_loop.rb +0 -22
  106. data/lib/twig/node/if.rb +4 -1
  107. data/lib/twig/node/import.rb +32 -0
  108. data/lib/twig/node/include.rb +38 -8
  109. data/lib/twig/node/macro.rb +79 -0
  110. data/lib/twig/node/module.rb +278 -23
  111. data/lib/twig/node/output.rb +7 -0
  112. data/lib/twig/node/print.rb +4 -1
  113. data/lib/twig/node/set.rb +72 -0
  114. data/lib/twig/node/text.rb +4 -1
  115. data/lib/twig/node/with.rb +50 -0
  116. data/lib/twig/node/yield.rb +6 -1
  117. data/lib/twig/node_traverser.rb +50 -0
  118. data/lib/twig/node_visitor/base.rb +30 -0
  119. data/lib/twig/node_visitor/escaper.rb +165 -0
  120. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  121. data/lib/twig/node_visitor/spreader.rb +39 -0
  122. data/lib/twig/output_buffer.rb +14 -12
  123. data/lib/twig/parser.rb +281 -8
  124. data/lib/twig/rails/config.rb +33 -0
  125. data/lib/twig/rails/engine.rb +44 -0
  126. data/lib/twig/rails/renderer.rb +41 -0
  127. data/lib/twig/runtime/argument_spreader.rb +46 -0
  128. data/lib/twig/runtime/context.rb +154 -0
  129. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  130. data/lib/twig/runtime/escaper.rb +155 -0
  131. data/lib/twig/runtime/loop_context.rb +81 -0
  132. data/lib/twig/runtime/loop_iterator.rb +60 -0
  133. data/lib/twig/runtime/spread.rb +21 -0
  134. data/lib/twig/runtime_loader/base.rb +12 -0
  135. data/lib/twig/runtime_loader/factory.rb +23 -0
  136. data/lib/twig/template.rb +267 -14
  137. data/lib/twig/template_wrapper.rb +42 -0
  138. data/lib/twig/token.rb +28 -2
  139. data/lib/twig/token_parser/apply.rb +48 -0
  140. data/lib/twig/token_parser/auto_escape.rb +45 -0
  141. data/lib/twig/token_parser/base.rb +26 -0
  142. data/lib/twig/token_parser/block.rb +4 -4
  143. data/lib/twig/token_parser/cache.rb +31 -0
  144. data/lib/twig/token_parser/deprecated.rb +40 -0
  145. data/lib/twig/token_parser/do.rb +19 -0
  146. data/lib/twig/token_parser/embed.rb +62 -0
  147. data/lib/twig/token_parser/extends.rb +4 -3
  148. data/lib/twig/token_parser/for.rb +14 -9
  149. data/lib/twig/token_parser/from.rb +57 -0
  150. data/lib/twig/token_parser/guard.rb +65 -0
  151. data/lib/twig/token_parser/if.rb +9 -9
  152. data/lib/twig/token_parser/import.rb +29 -0
  153. data/lib/twig/token_parser/include.rb +2 -2
  154. data/lib/twig/token_parser/macro.rb +109 -0
  155. data/lib/twig/token_parser/set.rb +76 -0
  156. data/lib/twig/token_parser/use.rb +54 -0
  157. data/lib/twig/token_parser/with.rb +36 -0
  158. data/lib/twig/token_parser/yield.rb +7 -7
  159. data/lib/twig/token_stream.rb +23 -3
  160. data/lib/twig/twig_filter.rb +20 -0
  161. data/lib/twig/twig_function.rb +37 -0
  162. data/lib/twig/twig_test.rb +31 -0
  163. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  164. data/lib/twig_ruby.rb +21 -2
  165. metadata +148 -6
  166. data/lib/twig/context.rb +0 -64
  167. data/lib/twig/expression_parser.rb +0 -517
  168. data/lib/twig/railtie.rb +0 -60
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ class TemplateWrapper
5
+ def initialize(environment, template)
6
+ @environment = environment
7
+ @template = template
8
+ end
9
+
10
+ def render(context = {}, call_context: nil, output_buffer: nil)
11
+ context = Runtime::Context.from(context, call_context:, output_buffer:)
12
+
13
+ template.render(context).to_s
14
+ end
15
+
16
+ def render_block(name, context = {}, call_context: nil, output_buffer: nil)
17
+ context = Runtime::Context.from(context, call_context:, output_buffer:)
18
+ context.merge!(environment.globals)
19
+
20
+ template.render_block(name, context).to_s
21
+ end
22
+
23
+ def block?(name, context = {})
24
+ context = Runtime::Context.from(context)
25
+
26
+ template.block?(name, context)
27
+ end
28
+
29
+ # @return [Template]
30
+ def unwrap
31
+ template
32
+ end
33
+
34
+ private
35
+
36
+ # @return [Environment]
37
+ attr_reader :environment
38
+
39
+ # @return [Twig::Template]
40
+ attr_accessor :template
41
+ end
42
+ end
data/lib/twig/token.rb CHANGED
@@ -10,14 +10,30 @@ module Twig
10
10
  VAR_END_TYPE = :var_end
11
11
  NAME_TYPE = :name
12
12
  SYMBOL_TYPE = :symbol
13
+ CLASS_VAR_TYPE = :class_var
13
14
  NUMBER_TYPE = :number
14
15
  STRING_TYPE = :string
15
16
  OPERATOR_TYPE = :operator
16
17
  PUNCTUATION_TYPE = :punctuation
17
18
  INTERPOLATION_START_TYPE = :interpolation_start
18
19
  INTERPOLATION_END_TYPE = :interpolation_end
19
- ARROW_TYPE = :arrow
20
- SPREAD_TYPE = :spread
20
+
21
+ TOKEN_TO_ENGLISH = {
22
+ EOF_TYPE => 'end of template',
23
+ TEXT_TYPE => 'text',
24
+ BLOCK_START_TYPE => 'begin of statement block',
25
+ VAR_START_TYPE => 'begin of print statement',
26
+ BLOCK_END_TYPE => 'end of statement block',
27
+ VAR_END_TYPE => 'end of print statement',
28
+ NAME_TYPE => 'name',
29
+ NUMBER_TYPE => 'number',
30
+ STRING_TYPE => 'string',
31
+ OPERATOR_TYPE => 'operator',
32
+ PUNCTUATION_TYPE => 'punctuation',
33
+ INTERPOLATION_START_TYPE => 'begin of string interpolation',
34
+ INTERPOLATION_END_TYPE => 'end of string interpolation',
35
+ SYMBOL_TYPE => 'symbol',
36
+ }.freeze
21
37
 
22
38
  attr_reader :type, :value, :lineno
23
39
 
@@ -44,5 +60,15 @@ module Twig
44
60
  def debug
45
61
  [type, value]
46
62
  end
63
+
64
+ def to_english
65
+ self.class.type_to_english(@type)
66
+ end
67
+
68
+ def self.type_to_english(type)
69
+ TOKEN_TO_ENGLISH.fetch(type) do
70
+ raise ArgumentError, "Token of type \"#{type}\" does not exist."
71
+ end
72
+ end
47
73
  end
48
74
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ # Applies filters on a section of a template.
6
+ #
7
+ # {% apply upper %}
8
+ # This text becomes uppercase
9
+ # {% endapply %}
10
+ class Apply < Base
11
+ def parse(token)
12
+ lineno = token.lineno
13
+ ref = Node::Expression::Variable::Local.new(nil, lineno)
14
+ filter = ref
15
+ ep = parser.environment.expression_parsers.by_class(ExpressionParser::Infix::Filter.name)
16
+
17
+ loop do
18
+ filter = ep.parse(parser, filter, parser.current_token)
19
+
20
+ unless parser.stream.test(Token::OPERATOR_TYPE, '|')
21
+ break
22
+ end
23
+
24
+ parser.stream.next
25
+ end
26
+
27
+ parser.stream.expect(Token::BLOCK_END_TYPE)
28
+ body = parser.subparse(method(:decide_apply_end), drop_needle: true)
29
+ parser.stream.expect(Token::BLOCK_END_TYPE)
30
+
31
+ Node::Nodes.new({
32
+ 0 => Node::Set.new(true, ref, body, lineno),
33
+ 1 => Node::Print.new(filter, lineno),
34
+ }, lineno)
35
+ end
36
+
37
+ def tag
38
+ 'apply'
39
+ end
40
+
41
+ private
42
+
43
+ def decide_apply_end(token)
44
+ token.test('endapply')
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ class AutoEscape < TokenParser::Base
6
+ def parse(token)
7
+ lineno = token.lineno
8
+ stream = parser.stream
9
+
10
+ if stream.test(Token::BLOCK_END_TYPE)
11
+ value = :html
12
+ else
13
+ expr = parser.parse_expression
14
+
15
+ unless expr.is_a?(Node::Expression::Constant)
16
+ raise Error::Syntax.new(
17
+ 'An escaping strategy must be a string or false.',
18
+ stream.current.lineno,
19
+ stream.source
20
+ )
21
+ end
22
+
23
+ value = expr.attributes[:value]
24
+ value = value.to_sym if value.is_a?(String)
25
+ end
26
+
27
+ stream.expect(Token::BLOCK_END_TYPE)
28
+ body = parser.subparse(method(:decide_block_end), drop_needle: true)
29
+ stream.expect(Token::BLOCK_END_TYPE)
30
+
31
+ Node::AutoEscape.new(value, body, lineno)
32
+ end
33
+
34
+ def tag
35
+ 'autoescape'
36
+ end
37
+
38
+ private
39
+
40
+ def decide_block_end(token)
41
+ token.test('endautoescape')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -15,6 +15,32 @@ module Twig
15
15
  def tag
16
16
  raise 'tag is not implemented'
17
17
  end
18
+
19
+ private
20
+
21
+ def parse_assignment_expression
22
+ stream = parser.stream
23
+ targets = AutoHash.new
24
+
25
+ loop do
26
+ token = parser.current_token
27
+
28
+ if stream.test(Token::OPERATOR_TYPE) && token.value.match(Lexer::REGEX_NAME)
29
+ # in this context, string operators are variables names
30
+ parser.stream.next
31
+ else
32
+ stream.expect(Token::NAME_TYPE, nil, 'Only variables can be assigned to')
33
+ end
34
+
35
+ targets << Node::Expression::Variable::AssignContext.new(token.value, token.lineno)
36
+
37
+ unless stream.next_if(Token::PUNCTUATION_TYPE, ',')
38
+ break
39
+ end
40
+ end
41
+
42
+ Node::Nodes.new(targets)
43
+ end
18
44
  end
19
45
  end
20
46
  end
@@ -20,14 +20,14 @@ module Twig
20
20
  parser.push_block_stack(name)
21
21
 
22
22
  if stream.next_if(Token::BLOCK_END_TYPE)
23
- body = parser.subparse(decide_block_end, drop_needle: true)
23
+ body = parser.subparse(method(:decide_block_end), drop_needle: true)
24
24
 
25
25
  if (token = stream.next_if(Token::NAME_TYPE)) && token.value != name
26
26
  raise "Expected end block for #{name}, given #{token.value}"
27
27
  end
28
28
  else
29
29
  body = Node::Nodes.new({
30
- 0 => Node::Print.new(parser.expression_parser.parse_expression, lineno),
30
+ 0 => Node::Print.new(parser.parse_expression, lineno),
31
31
  })
32
32
  end
33
33
 
@@ -46,8 +46,8 @@ module Twig
46
46
 
47
47
  private
48
48
 
49
- def decide_block_end
50
- ->(token) { token.test('endblock') }
49
+ def decide_block_end(token)
50
+ token.test('endblock')
51
51
  end
52
52
  end
53
53
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ class Cache < TokenParser::Base
6
+ include Twig::ExpressionParser::ParsesArguments
7
+
8
+ def parse(token)
9
+ stream = parser.stream
10
+ lineno = token.lineno
11
+ arguments = parse_named_arguments(parser)
12
+
13
+ stream.expect(Token::BLOCK_END_TYPE)
14
+ body = parser.subparse(method(:decide_cache_end), drop_needle: true)
15
+ stream.expect(Token::BLOCK_END_TYPE)
16
+
17
+ Node::Cache.new(arguments, body, lineno)
18
+ end
19
+
20
+ def tag
21
+ 'cache'
22
+ end
23
+
24
+ private
25
+
26
+ def decide_cache_end(token)
27
+ token.test('endcache')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ class Deprecated < Base
6
+ def parse(token)
7
+ stream = parser.stream
8
+ expr = parser.parse_expression
9
+ node = Node::Deprecated.new(expr, token.lineno)
10
+
11
+ while stream.test(Token::NAME_TYPE)
12
+ k = stream.current.value
13
+ stream.next
14
+ stream.expect(Token::OPERATOR_TYPE, '=')
15
+
16
+ case k
17
+ when 'package'
18
+ node.nodes[:package] = parser.parse_expression
19
+ when 'version'
20
+ node.nodes[:version] = parser.parse_expression
21
+ else
22
+ raise Error::Syntax.new(
23
+ "Unknown \"#{k}\" option.",
24
+ stream.current.lineno,
25
+ stream.source_context
26
+ )
27
+ end
28
+ end
29
+
30
+ stream.expect(Token::BLOCK_END_TYPE)
31
+
32
+ node
33
+ end
34
+
35
+ def tag
36
+ 'deprecated'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ class Do < TokenParser::Base
6
+ def parse(token)
7
+ expr = parser.parse_expression
8
+
9
+ parser.stream.expect(Token::BLOCK_END_TYPE)
10
+
11
+ Node::Do.new(expr, token.lineno)
12
+ end
13
+
14
+ def tag
15
+ 'do'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'include'
4
+
5
+ module Twig
6
+ module TokenParser
7
+ class Embed < Include
8
+ def parse(token)
9
+ stream = parser.stream
10
+ parent = parser.parse_expression
11
+
12
+ variables, only, ignore_missing = parse_arguments
13
+
14
+ parent_token = fake_parent_token = Token.new(Token::STRING_TYPE, '__parent__', token.lineno)
15
+
16
+ if parent.is_a?(Node::Expression::Constant)
17
+ parent_token = Token.new(Token::STRING_TYPE, parent.attributes[:value], token.lineno)
18
+ elsif parent.is_a?(Node::Expression::Variable::Context)
19
+ parent_token = Token.new(Token::NAME_TYPE, parent.attributes[:name], token.lineno)
20
+ end
21
+
22
+ # inject a fake parent to make the parent() function work
23
+ stream.inject([
24
+ Token.new(Token::BLOCK_START_TYPE, '', token.lineno),
25
+ Token.new(Token::NAME_TYPE, 'extends', token.lineno),
26
+ parent_token,
27
+ Token.new(Token::BLOCK_END_TYPE, '', token.lineno),
28
+ ])
29
+
30
+ node = parser.parse(stream, method(:decide_block_end), drop_needle: true)
31
+
32
+ # override the parent with the correct one
33
+ if fake_parent_token == parent_token
34
+ node.nodes[:parent] = parent
35
+ end
36
+
37
+ parser.embed_template(node)
38
+
39
+ stream.expect(Token::BLOCK_END_TYPE)
40
+
41
+ Node::Embed.new(
42
+ node.template_name,
43
+ node.attributes[:index],
44
+ variables,
45
+ only,
46
+ ignore_missing,
47
+ token.lineno
48
+ )
49
+ end
50
+
51
+ def tag
52
+ 'embed'
53
+ end
54
+
55
+ private
56
+
57
+ def decide_block_end(token)
58
+ token.test('endembed')
59
+ end
60
+ end
61
+ end
62
+ end
@@ -7,11 +7,12 @@ module Twig
7
7
  stream = parser.stream
8
8
 
9
9
  if parser.peek_block_stack
10
- raise Error::Syntax.new('Cannot raise from inside a block', token.lineno, stream.source)
11
- # elsif parser.main_scope? @todo
10
+ raise Error::Syntax.new('Cannot use "extend" in a block.', token.lineno, stream.source)
11
+ elsif !parser.main_scope?
12
+ raise Error::Syntax.new('Cannot use "extend" in a macro.', token.lineno, stream.source)
12
13
  end
13
14
 
14
- parser.parent = parser.expression_parser.parse_expression
15
+ parser.parent = parser.parse_expression
15
16
  stream.expect(Token::BLOCK_END_TYPE)
16
17
 
17
18
  Node::Empty.new(token.lineno)
@@ -10,16 +10,21 @@ module Twig
10
10
  lineno = token.lineno
11
11
  stream = parser.stream
12
12
 
13
- targets = parser.expression_parser.parse_assignment_expression
13
+ targets = parse_assignment_expression
14
14
  stream.expect(Token::OPERATOR_TYPE, 'in')
15
- seq = parser.expression_parser.parse_expression
15
+ seq = parser.parse_expression
16
+
17
+ if_expr = nil
18
+ if stream.next_if(Token::NAME_TYPE, 'if')
19
+ if_expr = parser.parse_expression
20
+ end
16
21
 
17
22
  stream.expect(Token::BLOCK_END_TYPE)
18
- body = parser.subparse(decide_for_fork)
23
+ body = parser.subparse(method(:decide_for_fork))
19
24
 
20
25
  if stream.next.value == 'else'
21
26
  stream.expect(Token::BLOCK_END_TYPE)
22
- else_expr = parser.subparse(decide_for_end, drop_needle: true)
27
+ else_expr = parser.subparse(method(:decide_for_end), drop_needle: true)
23
28
  else
24
29
  else_expr = nil
25
30
  end
@@ -43,7 +48,7 @@ module Twig
43
48
  value_target.lineno
44
49
  )
45
50
 
46
- Node::For.new(key_target, value_target, seq, nil, body, else_expr, lineno)
51
+ Node::For.new(key_target, value_target, seq, if_expr, body, else_expr, lineno)
47
52
  end
48
53
 
49
54
  def tag
@@ -52,12 +57,12 @@ module Twig
52
57
 
53
58
  private
54
59
 
55
- def decide_for_fork
56
- ->(token) { token.test(%w[else endfor]) }
60
+ def decide_for_fork(token)
61
+ token.test(%w[else endfor])
57
62
  end
58
63
 
59
- def decide_for_end
60
- ->(token) { token.test(%w[endfor]) }
64
+ def decide_for_end(token)
65
+ token.test(%w[endfor])
61
66
  end
62
67
  end
63
68
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ # Imports macros.
6
+ #
7
+ # {% from 'forms.html.twig' import forms %}
8
+ #
9
+ class From < Base
10
+ # @param [Token] token
11
+ def parse(token)
12
+ macro = parser.parse_expression
13
+ stream = parser.stream
14
+ stream.expect(Token::NAME_TYPE, 'import')
15
+
16
+ targets = {}
17
+ loop do
18
+ name = stream.expect(Token::NAME_TYPE).value
19
+
20
+ aliased = if stream.next_if('as')
21
+ Node::Expression::Variable::AssignContext.new(
22
+ stream.expect(Token::NAME_TYPE).value,
23
+ token.lineno
24
+ )
25
+ else
26
+ Node::Expression::Variable::AssignContext.new(name, token.lineno)
27
+ end
28
+
29
+ targets[name] = aliased
30
+
31
+ unless stream.next_if(Token::PUNCTUATION_TYPE, ',')
32
+ break
33
+ end
34
+ end
35
+
36
+ stream.expect(Token::BLOCK_END_TYPE)
37
+
38
+ internal_ref = Node::Expression::Variable::AssignTemplate.new(
39
+ Node::Expression::Variable::Template.new(nil, token.lineno),
40
+ global: parser.main_scope?
41
+ )
42
+
43
+ node = Node::Import.new(macro, internal_ref, token.lineno)
44
+
45
+ targets.each do |name, aliased|
46
+ parser.add_imported_symbol(:function, aliased.attributes[:name], "macro_#{name}", internal_ref)
47
+ end
48
+
49
+ node
50
+ end
51
+
52
+ def tag
53
+ 'from'
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ # Prevents compilation of block if function/filter/test does not exist.
6
+ # Since this is at compilation time, this DOES NOT work with Rails helper functions
7
+ # as those can't be determined fully at compile time as of now.
8
+ class Guard < Base
9
+ def parse(token)
10
+ stream = parser.stream
11
+ type_token = stream.expect(Token::NAME_TYPE)
12
+
13
+ unless %w[function filter test].include?(type_token.value)
14
+ raise Error::Syntax.new(
15
+ "Supported guard types are function, filter, and test, \"#{token.value}\" given.",
16
+ type_token.lineno,
17
+ stream.source
18
+ )
19
+ end
20
+
21
+ name_token = stream.expect(Token::NAME_TYPE)
22
+
23
+ begin
24
+ exists = !parser.environment.send(type_token.value, name_token.value).nil?
25
+ rescue Error::Syntax
26
+ exists = false
27
+ end
28
+
29
+ stream.expect(Token::BLOCK_END_TYPE)
30
+
31
+ if exists
32
+ body = parser.subparse(method(:decide_guard_fork))
33
+ else
34
+ body = Node::Empty.new
35
+ parser.subparse_ignore_unknown_twig_callables(method(:decide_guard_fork))
36
+ end
37
+
38
+ else_node = Node::Empty.new
39
+
40
+ if stream.next.value == 'else'
41
+ stream.expect(Token::BLOCK_END_TYPE)
42
+ else_node = parser.subparse(method(:decide_guard_end), drop_needle: true)
43
+ end
44
+
45
+ stream.expect(Token::BLOCK_END_TYPE)
46
+
47
+ Node::Nodes.new(AutoHash.new.add(exists ? body : else_node))
48
+ end
49
+
50
+ def tag
51
+ 'guard'
52
+ end
53
+
54
+ private
55
+
56
+ def decide_guard_fork(token)
57
+ token.test(%w[else endguard])
58
+ end
59
+
60
+ def decide_guard_end(token)
61
+ token.test(['endguard'])
62
+ end
63
+ end
64
+ end
65
+ end
@@ -12,10 +12,10 @@ module Twig
12
12
  class If < Base
13
13
  def parse(token)
14
14
  lineno = token.lineno
15
- expr = parser.expression_parser.parse_expression
15
+ expr = parser.parse_expression
16
16
  stream = parser.stream
17
17
  stream.expect(Token::BLOCK_END_TYPE)
18
- body = parser.subparse(decide_if_fork)
18
+ body = parser.subparse(method(:decide_if_fork))
19
19
  tests = [expr, body]
20
20
  else_node = nil
21
21
 
@@ -24,11 +24,11 @@ module Twig
24
24
  case stream.next.value
25
25
  when 'else'
26
26
  stream.expect(Token::BLOCK_END_TYPE)
27
- else_node = parser.subparse(decide_if_end)
27
+ else_node = parser.subparse(method(:decide_if_end))
28
28
  when 'elsif', 'elseif'
29
- expr = parser.expression_parser.parse_expression
29
+ expr = parser.parse_expression
30
30
  stream.expect(Token::BLOCK_END_TYPE)
31
- body = parser.subparse(decide_if_fork)
31
+ body = parser.subparse(method(:decide_if_fork))
32
32
  tests.push(expr, body)
33
33
  when 'endif'
34
34
  if_ended = true
@@ -52,12 +52,12 @@ module Twig
52
52
 
53
53
  private
54
54
 
55
- def decide_if_end
56
- ->(token) { token.test(%w[endif]) }
55
+ def decide_if_end(token)
56
+ token.test(%w[endif])
57
57
  end
58
58
 
59
- def decide_if_fork
60
- ->(token) { token.test(%w[elseif elsif else endif]) }
59
+ def decide_if_fork(token)
60
+ token.test(%w[elseif elsif else endif])
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module TokenParser
5
+ # Imports macros.
6
+ #
7
+ # {% import 'forms.html.twig' as forms %}
8
+ #
9
+ class Import < Base
10
+ def parse(token)
11
+ macro = parser.parse_expression
12
+ parser.stream.expect(Token::NAME_TYPE, 'as')
13
+ name = parser.stream.expect(Token::NAME_TYPE).value
14
+ var = Node::Expression::Variable::AssignTemplate.new(
15
+ Node::Expression::Variable::Template.new(name, token.lineno),
16
+ global: parser.main_scope?
17
+ )
18
+ parser.stream.expect(Token::BLOCK_END_TYPE)
19
+ parser.add_imported_symbol(:template, name)
20
+
21
+ Node::Import.new(macro, var, token.lineno)
22
+ end
23
+
24
+ def tag
25
+ 'import'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -4,7 +4,7 @@ module Twig
4
4
  module TokenParser
5
5
  class Include < TokenParser::Base
6
6
  def parse(token)
7
- expr = parser.expression_parser.parse_expression
7
+ expr = parser.parse_expression
8
8
  variables, only, ignore_missing = parse_arguments
9
9
 
10
10
  Node::Include.new(
@@ -34,7 +34,7 @@ module Twig
34
34
 
35
35
  variables = nil
36
36
  if stream.next_if(Token::NAME_TYPE, 'with')
37
- variables = parser.expression_parser.parse_expression
37
+ variables = parser.parse_expression
38
38
  end
39
39
 
40
40
  only = false