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,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Concat < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('(').
11
+ subcompile(nodes[:left]).
12
+ raw(').to_s + (').
13
+ subcompile(nodes[:right]).
14
+ raw(').to_s')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Elvis < Binary::Base
8
+ include OperatorEscape
9
+
10
+ def initialize(left, right, lineno)
11
+ super
12
+
13
+ nodes[:test] = left.dup
14
+ left.attributes[:always_defined] = true
15
+ end
16
+
17
+ def compile(compiler)
18
+ compiler.
19
+ raw('(::Twig::Extension::Core.bool(').
20
+ subcompile(nodes[:test]).
21
+ raw(') ? (').
22
+ subcompile(nodes[:left]).
23
+ raw(') : (').
24
+ subcompile(nodes[:right]).
25
+ raw('))')
26
+ end
27
+
28
+ def operand_names_to_escape
29
+ %i[left right]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class EndsWith < Binary::Base
8
+ def compile(compiler)
9
+ left = compiler.var_name
10
+ right = compiler.var_name
11
+
12
+ compiler.
13
+ raw("(#{left} = ").
14
+ subcompile(nodes[:left]).
15
+ raw(').respond_to?(:end_with?) && ').
16
+ raw("(#{right} = ").
17
+ subcompile(nodes[:right]).
18
+ raw(").respond_to?(:end_with?) && (#{left}.end_with?(#{right}))")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class FloorDiv < Binary::Base
8
+ def compile(compiler)
9
+ compiler.raw('(')
10
+ super
11
+ compiler.raw(').floor.to_i')
12
+ end
13
+
14
+ def operator(compiler)
15
+ compiler.raw('/')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class HasEvery < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('::Twig::Extension::Core.array_every?(').
11
+ subcompile(nodes[:left]).
12
+ raw(', ').
13
+ subcompile(nodes[:right]).
14
+ raw(')')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class HasSome < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('::Twig::Extension::Core.array_some?(').
11
+ subcompile(nodes[:left]).
12
+ raw(', ').
13
+ subcompile(nodes[:right]).
14
+ raw(')')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class In < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('::Twig::Extension::Core.in_filter(').
11
+ subcompile(nodes[:left]).
12
+ raw(', ').
13
+ subcompile(nodes[:right]).
14
+ raw(')')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Matches < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('::Twig::Extension::Core.matches(').
11
+ subcompile(nodes[:right]).
12
+ raw(', ').
13
+ subcompile(nodes[:left]).
14
+ raw(')')
15
+ end
16
+
17
+ def operator(compiler)
18
+ compiler.raw('')
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class NotIn < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('!::Twig::Extension::Core.in_filter(').
11
+ subcompile(nodes[:left]).
12
+ raw(', ').
13
+ subcompile(nodes[:right]).
14
+ raw(')')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class NullCoalesce < Binary::Base
8
+ include OperatorEscape
9
+
10
+ def initialize(left, right, lineno)
11
+ super
12
+
13
+ test = Test::Defined.new(left.dup, TwigTest.new('defined'), Node::Empty.new, left.lineno)
14
+
15
+ unless left.is_a?(Expression::BlockReference)
16
+ test = Binary::And.new(
17
+ test,
18
+ Unary::Not.new(
19
+ Test::Null.new(left, TwigTest.new('null'), Node::Empty.new, left.lineno),
20
+ left.lineno
21
+ ),
22
+ left.lineno
23
+ )
24
+ end
25
+
26
+ left.attributes[:always_defined] = true
27
+ nodes[:test] = test
28
+ end
29
+
30
+ # @param [Compiler] compiler
31
+ def compile(compiler)
32
+ compiler.
33
+ raw('((').
34
+ subcompile(nodes[:test]).
35
+ raw(') ? (').
36
+ subcompile(nodes[:left]).
37
+ raw(') : (').
38
+ subcompile(nodes[:right]).
39
+ raw('))')
40
+ end
41
+
42
+ def operand_names_to_escape
43
+ %i[left right]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Or < Boolean
8
+ def operator(compiler)
9
+ compiler.raw('||')
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class StartsWith < Binary::Base
8
+ def compile(compiler)
9
+ left = compiler.var_name
10
+ right = compiler.var_name
11
+
12
+ compiler.
13
+ raw("(#{left} = ").
14
+ subcompile(nodes[:left]).
15
+ raw(').respond_to?(:start_with?) && ').
16
+ raw("(#{right} = ").
17
+ subcompile(nodes[:right]).
18
+ raw(").respond_to?(:start_with?) && (#{left}.start_with?(#{right}))")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'boolean'
4
+
5
+ module Twig
6
+ module Node
7
+ module Expression
8
+ module Binary
9
+ class Xor < Boolean
10
+ def operator(compiler)
11
+ compiler.raw('^')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ # Represents a block reference node
7
+ class BlockReference < Expression::Base
8
+ include SupportDefinedTest
9
+
10
+ # @param [String] name
11
+ # @param [String, nil] template
12
+ # @param [Integer] lineno
13
+ def initialize(name, template, lineno)
14
+ nodes = { name: }
15
+ nodes[:template] = template unless template.nil?
16
+
17
+ super(nodes, { output: false }, lineno)
18
+ end
19
+
20
+ def compile(compiler)
21
+ if define_test_enabled?
22
+ compile_template_call(compiler, 'block?')
23
+ else
24
+ compile_template_call(compiler, 'render_block')
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def compile_template_call(compiler, method)
31
+ if nodes.key?(:template)
32
+ compiler.
33
+ write('load(').
34
+ subcompile(nodes[:template]).
35
+ raw(', ').
36
+ repr(lineno).
37
+ raw(').unwrap')
38
+ else
39
+ compiler.write('self')
40
+ end
41
+
42
+ compiler.raw(".#{method}")
43
+
44
+ compile_block_arguments(compiler)
45
+ end
46
+
47
+ def compile_block_arguments(compiler)
48
+ compiler.
49
+ raw('(').
50
+ subcompile(nodes[:name]).
51
+ raw(', context')
52
+
53
+ unless nodes.key?(:template)
54
+ compiler.raw(', blocks')
55
+ end
56
+
57
+ compiler.raw(')')
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -4,23 +4,143 @@ module Twig
4
4
  module Node
5
5
  module Expression
6
6
  class Call < Expression::Base
7
+ def compile(compiler)
8
+ compile_callable(compiler)
9
+ end
10
+
7
11
  private
8
12
 
9
13
  # @param [Compiler] compiler
10
14
  def compile_callable(compiler)
11
- callable = attributes[:twig_callable].callable
15
+ argument_nodes = [nodes.key?(:node) ? nodes[:node] : nil, *nodes[:arguments].nodes.values]
16
+ has_spread = argument_nodes.any? { |node| node.is_a?(Unary::Spread) }
17
+
18
+ if has_spread
19
+ compiler.
20
+ raw('::Twig::Runtime::ArgumentSpreader.new(').
21
+ raw(callable_method).
22
+ raw(').call')
23
+ else
24
+ compiler.
25
+ raw(callable_method).
26
+ raw('.call')
27
+ end
12
28
 
13
29
  compiler.
14
- raw("env.extension(%q[#{callable[0].class.name}]).#{callable[1]}")
30
+ raw("(\n").
31
+ indent.
32
+ write('')
15
33
 
16
34
  compile_arguments(compiler)
35
+
36
+ compiler.
37
+ raw("\n").
38
+ outdent.
39
+ write(')')
40
+ end
41
+
42
+ # @return [String]
43
+ def callable_method
44
+ # @type [Twig::Callable] callable
45
+ twig_callable = attributes[:twig_callable]
46
+ callable = twig_callable.callable
47
+
48
+ case callable
49
+ when ::Array
50
+ if callable[0] == :runtime
51
+ _, klass, method = callable
52
+
53
+ "env.runtime(%q[#{klass}]).method(:#{method})"
54
+ else
55
+ extension, method = callable[0, 2]
56
+ extension = extension.class.name if extension.is_a?(Extension::Base)
57
+
58
+ "env.extension(%q[#{extension.delete_prefix('::')}]).method(:#{method})"
59
+ end
60
+ when ::Method
61
+ # Instance method
62
+ if callable.receiver.is_a?(Extension::Base)
63
+ "env.extension(%q[#{callable.owner.name}]).method(:#{callable.name})"
64
+ # Class method
65
+ else
66
+ "#{callable.receiver.name}.method(:#{callable.name})"
67
+ end
68
+ when ::Proc
69
+ "env.#{attributes[:type]}(%q[#{twig_callable.dynamic_name}]).callable"
70
+ else
71
+ raise "Callable not supported: #{callable.inspect}"
72
+ end
17
73
  end
18
74
 
19
75
  def compile_arguments(compiler)
20
- compiler.
21
- raw('(').
22
- subcompile(nodes[:node]).
23
- raw(')')
76
+ first = true
77
+ # @type [Twig::Callable] callable
78
+ callable = attributes.fetch(:twig_callable, nil)
79
+
80
+ callable&.arguments&.each do |argument|
81
+ compiler.raw(', ') unless first
82
+
83
+ compiler.string(argument)
84
+
85
+ first = false
86
+ end
87
+
88
+ if callable&.needs_charset?
89
+ compiler.raw(', ') unless first
90
+ compiler.raw('env.charset')
91
+ first = false
92
+ end
93
+
94
+ if callable&.needs_environment?
95
+ compiler.raw(', ') unless first
96
+ compiler.raw('env')
97
+ first = false
98
+ end
99
+
100
+ if callable&.needs_context?
101
+ compiler.raw(', ') unless first
102
+ compiler.raw('context')
103
+ first = false
104
+ end
105
+
106
+ if nodes.key?(:node)
107
+ compiler.raw(', ') unless first
108
+
109
+ compiler.
110
+ subcompile(nodes[:node])
111
+
112
+ first = false
113
+ end
114
+
115
+ # Only callables that come through without are helper methods, which still
116
+ # can't be determined at compile time for Rails
117
+ if callable
118
+ positional, kwargs = Util::CallableArgumentsExtractor.
119
+ new(self, callable, compiler.environment).
120
+ extract_arguments(nodes[:arguments])
121
+ else
122
+ positional, kwargs = nodes[:arguments].nodes.partition do |key, node|
123
+ key.is_a?(Integer) || node.is_a?(Unary::Spread)
124
+ end.map(&:to_h)
125
+
126
+ positional = positional.values
127
+ end
128
+
129
+ positional.each do |node|
130
+ compiler.raw(', ') unless first
131
+ compiler.subcompile(node)
132
+
133
+ first = false
134
+ end
135
+
136
+ kwargs.each do |key, node|
137
+ compiler.raw(', ') unless first
138
+ compiler.
139
+ raw("'#{key}': ").
140
+ subcompile(node)
141
+
142
+ first = false
143
+ end
24
144
  end
25
145
  end
26
146
  end
@@ -4,12 +4,14 @@ module Twig
4
4
  module Node
5
5
  module Expression
6
6
  class Constant < Expression::Base
7
+ include Expression::SupportDefinedTest
8
+
7
9
  def initialize(value, lineno)
8
10
  super({}, { value: }, lineno)
9
11
  end
10
12
 
11
13
  def compile(compiler)
12
- compiler.repr(attributes[:value])
14
+ compiler.repr(define_test_enabled? || attributes[:value])
13
15
  end
14
16
  end
15
17
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Filter
7
+ # Returns the value or the default value when it is undefined or empty.
8
+ #
9
+ # {{ var.foo|default('foo item on var is not defined') }}
10
+ class Default < Expression::Filter
11
+ # @param [Expression::Base] node
12
+ # @param [TwigFilter] filter
13
+ # @param [Node::Base] arguments
14
+ # @param [Integer] lineno
15
+ def initialize(node, filter, arguments, lineno)
16
+ name = filter.name
17
+ default = Expression::Filter.new(node, filter, arguments, node.lineno)
18
+
19
+ if name == 'default' && (node.is_a?(Variable::Context) || node.is_a?(GetAttribute))
20
+ test = Test::Defined.new(node.dup, TwigTest.new('defined'), Empty.new, node.lineno)
21
+ false_case = arguments.empty? ? Expression::Constant.new('', node.lineno) : arguments.nodes[0]
22
+ node = Ternary.new(test, default, false_case, node.lineno)
23
+ else
24
+ node = default
25
+ end
26
+
27
+ super
28
+ end
29
+
30
+ def compile(compiler)
31
+ compiler.subcompile(nodes[:node])
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Filter
7
+ class Raw < Expression::Filter
8
+ # @param [Expression::Base] node
9
+ # @param [TwigFilter|nil] filter
10
+ # @param [Node::Base|nil] arguments
11
+ # @param [Integer] lineno
12
+ def initialize(node, filter = nil, arguments = nil, lineno = nil)
13
+ super(
14
+ node,
15
+ filter || TwigFilter.new('raw', nil, { is_safe: [:all] }),
16
+ arguments || Node::Empty.new,
17
+ lineno || node.lineno
18
+ )
19
+ end
20
+
21
+ def compile(compiler)
22
+ compiler.
23
+ raw('((tmp = ').
24
+ subcompile(nodes[:node]).
25
+ raw(').respond_to?(:html_safe) ? tmp.html_safe : tmp)')
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -21,10 +21,10 @@ module Twig
21
21
  arguments:,
22
22
  }, {
23
23
  name:,
24
- type: 'filter',
24
+ type: :filter,
25
25
  }, lineno)
26
26
 
27
- if filter.is_a?(Filter)
27
+ if filter.is_a?(TwigFilter)
28
28
  attributes[:twig_callable] = filter
29
29
  end
30
30
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Function < Call
7
+ include SupportDefinedTest
8
+
9
+ # @param [TwigFunction] function
10
+ # @param [Node::Base] arguments
11
+ # @param [Integer] lineno
12
+ def initialize(function, arguments, lineno)
13
+ super({ arguments: }, {
14
+ name: function.name,
15
+ type: :function,
16
+ twig_callable: function,
17
+ is_defined_test: false,
18
+ }, lineno)
19
+ end
20
+
21
+ def enable_defined_test
22
+ if attributes[:name] == 'constant'
23
+ super
24
+ end
25
+ end
26
+
27
+ def compile(compiler)
28
+ if attributes[:name] == 'constant' && define_test_enabled?
29
+ nodes[:arguments].nodes[:defined_test] = Expression::Constant.new(true, lineno)
30
+ end
31
+
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end