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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Array < Expression::Base
7
+ def initialize(elements, lineno)
8
+ super(elements, {}, lineno)
9
+
10
+ @index = -1
11
+ end
12
+
13
+ # @param [Expression::Base] value
14
+ # @param [Expression::Base|nil] key
15
+ def add_element(value, key = nil)
16
+ if key.nil?
17
+ @index += 1
18
+ key = Constant.new(@index, value.lineno)
19
+ end
20
+
21
+ nodes.add(key, value)
22
+ end
23
+
24
+ def compile(compiler)
25
+ compiler.
26
+ raw('{').
27
+ indent
28
+
29
+ key_value_pairs.each do |key, value|
30
+ compiler.
31
+ subcompile(key).
32
+ raw(' => ').
33
+ subcompile(value).
34
+ raw(', ')
35
+ end
36
+
37
+ compiler.
38
+ outdent.
39
+ raw('}')
40
+ end
41
+
42
+ private
43
+
44
+ def key_value_pairs
45
+ nodes.each_value.each_slice(2)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'name'
4
+
5
+ module Twig
6
+ module Node
7
+ module Expression
8
+ class AssignName < Expression::Name
9
+ # @param [String] name
10
+ # @param [Integer] lineno
11
+ def initialize(name, lineno)
12
+ if %w[true false none null nil].include?(name.downcase)
13
+ raise Error::Syntax.new("You cannot assign a value to #{name}", lineno)
14
+ end
15
+
16
+ super
17
+ end
18
+
19
+ def compile(compiler)
20
+ compiler.
21
+ raw('context[').
22
+ string(attributes[:name]).
23
+ raw(']')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Base < Node::Base
7
+ # @return [Expression::Base]
8
+ def set_explicit_parentheses
9
+ attributes[:with_parentheses] = true
10
+
11
+ self
12
+ end
13
+
14
+ def explicit_parentheses?
15
+ attributes.key?(:with_parentheses) && attributes[:with_parentheses]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Base < Expression::Base
8
+ def initialize(left, right, lineno)
9
+ super({ left:, right: }, {}, lineno)
10
+ end
11
+
12
+ # @param [Compiler] compiler
13
+ def compile(compiler)
14
+ compiler.
15
+ raw('(').
16
+ subcompile(nodes[:left]).
17
+ raw(' ')
18
+
19
+ operator(compiler)
20
+
21
+ compiler.
22
+ raw(' ').
23
+ subcompile(nodes[:right]).
24
+ raw(')')
25
+ end
26
+
27
+ # @param [Compiler] compiler
28
+ def operator(compiler)
29
+ raise 'operator is not implemented'
30
+ end
31
+ end
32
+
33
+ OPERATORS = {
34
+ Equal: '==',
35
+ NotEqual: '!=',
36
+ Spaceship: '<=>',
37
+ Less: '<',
38
+ Greater: '>',
39
+ LessEqual: '<=',
40
+ GreaterEqual: '>=',
41
+
42
+ Or: '||',
43
+ Xor: '^',
44
+ And: '&&',
45
+ Add: '+',
46
+ Sub: '-',
47
+ Concat: '+',
48
+ Mul: '*',
49
+ Div: '/',
50
+ }.freeze
51
+
52
+ # Lots of simple operator classes can just be generated dynamically
53
+ OPERATORS.each do |name, operation|
54
+ const_set(name.to_s, Class.new(Binary::Base) do
55
+ def operator(compiler)
56
+ compiler.raw(self.class.const_get('OPERATOR'))
57
+ end
58
+ end).const_set('OPERATOR', operation)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Call < Expression::Base
7
+ private
8
+
9
+ # @param [Compiler] compiler
10
+ def compile_callable(compiler)
11
+ callable = attributes[:twig_callable].callable
12
+
13
+ compiler.
14
+ raw("env.extension(%q[#{callable[0].class.name}]).#{callable[1]}")
15
+
16
+ compile_arguments(compiler)
17
+ end
18
+
19
+ def compile_arguments(compiler)
20
+ compiler.
21
+ raw('(').
22
+ subcompile(nodes[:node]).
23
+ raw(')')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Constant < Expression::Base
7
+ def initialize(value, lineno)
8
+ super({}, { value: }, lineno)
9
+ end
10
+
11
+ def compile(compiler)
12
+ compiler.repr(attributes[:value])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Filter < Call
7
+ # @param [Node::Base] node
8
+ # @param [TwigFilter|Constant] filter
9
+ def initialize(node, filter, arguments, lineno)
10
+ if filter.is_a?(TwigFilter)
11
+ name = filter.name
12
+ filter_name = Constant.new(name, lineno)
13
+ else
14
+ name = filter.attributes[:value]
15
+ filter_name = filter
16
+ end
17
+
18
+ super({
19
+ node:,
20
+ filter: filter_name,
21
+ arguments:,
22
+ }, {
23
+ name:,
24
+ type: 'filter',
25
+ }, lineno)
26
+
27
+ if filter.is_a?(Filter)
28
+ attributes[:twig_callable] = filter
29
+ end
30
+ end
31
+
32
+ def compile(compiler)
33
+ name = nodes[:filter].attributes[:value]
34
+
35
+ if name != attributes[:name]
36
+ raise 'Changing the value of a "filter" node is not supported'
37
+ end
38
+
39
+ if name == 'raw'
40
+ raise 'Cannot create raw filter via expression'
41
+ end
42
+
43
+ unless attributes.key?(:twig_callable)
44
+ attributes[:twig_callable] = compiler.environment.filter(name)
45
+ end
46
+
47
+ compile_callable(compiler)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class GetAttribute < Expression::Base
7
+ def initialize(node, attribute, arguments, type, lineno)
8
+ nodes = { node:, attribute: }
9
+ nodes[:arguments] = arguments if arguments
10
+
11
+ super(nodes, { type: }, lineno)
12
+ end
13
+
14
+ def compile(compiler)
15
+ var = compiler.var_name
16
+
17
+ compiler.
18
+ raw("(#{var} = ").
19
+ subcompile(nodes[:node]).
20
+ raw("\n").
21
+ write("::Twig::Extension::Core.get_attribute(#{var}, ").
22
+ subcompile(nodes[:attribute]).
23
+ raw(', ').
24
+ repr(attributes[:type]).
25
+ raw('))')
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class HelperMethod < Expression::Base
7
+ def initialize(name, args, lineno)
8
+ super({ args: }, { name: }, lineno)
9
+ end
10
+
11
+ def compile(compiler)
12
+ compiler.
13
+ raw("@call_context.#{attributes[:name]}(")
14
+
15
+ nodes[:args].nodes.each do |key, value|
16
+ unless key.is_a?(Integer)
17
+ compiler.raw("#{key}: ")
18
+ end
19
+
20
+ compiler.
21
+ subcompile(value).
22
+ raw(',')
23
+ end
24
+
25
+ compiler.
26
+ raw(')')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Name < Expression::Base
7
+ SPECIAL_VARS = {
8
+ '_self' => 'get_template_name',
9
+ '_context' => 'context',
10
+ '_charset' => 'env.charset',
11
+ }.freeze
12
+
13
+ # @param [String] name
14
+ # @param [Integer] lineno
15
+ def initialize(name, lineno)
16
+ super({}, {
17
+ name:,
18
+ is_defined_test: false,
19
+ ignore_strict_check: false,
20
+ alwways_defined: false,
21
+ }, lineno)
22
+ end
23
+
24
+ def compile(compiler)
25
+ name = attributes[:name]
26
+
27
+ compiler.
28
+ raw("(context.key?(:#{name})").
29
+ raw(" ? context[:#{name}]").
30
+ raw(' : raise("#{').
31
+ string(name).
32
+ raw('} does not exist"))')
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class Ternary < Expression::Base
7
+ def initialize(test, left, right, lineno)
8
+ super({
9
+ test:,
10
+ left:,
11
+ right:,
12
+ }, {}, lineno)
13
+ end
14
+
15
+ def compile(compiler)
16
+ compiler.
17
+ raw('((').
18
+ subcompile(nodes[:test]).
19
+ raw(') ? (').
20
+ subcompile(nodes[:left]).
21
+ raw(') : (').
22
+ subcompile(nodes[:right]).
23
+ raw('))')
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Unary
7
+ class Base < Expression::Base
8
+ # @param [Node::Base] node
9
+ # @param [Integer] lineno
10
+ def initialize(node, lineno)
11
+ super({ node: }, { with_parenthesis: false }, lineno)
12
+ end
13
+
14
+ def compile(compiler)
15
+ if explicit_parentheses?
16
+ compiler.raw('(')
17
+ else
18
+ compiler.raw(' ')
19
+ end
20
+
21
+ operator(compiler)
22
+ compiler.subcompile(nodes[:node])
23
+
24
+ if explicit_parentheses?
25
+ compiler.raw(')')
26
+ end
27
+ end
28
+
29
+ # @param [Compiler] compiler
30
+ def operator(compiler)
31
+ raise 'operator is not implemented'
32
+ end
33
+ end
34
+
35
+ OPERATORS = {
36
+ Not: '!',
37
+ Neg: '-',
38
+ Pos: '+',
39
+ }.freeze
40
+
41
+ # Lots of simple operator classes can just be generated dynamically
42
+ OPERATORS.each do |name, operation|
43
+ const_set(name.to_s, Class.new(Unary::Base) do
44
+ def operator(compiler)
45
+ compiler.raw(self.class.const_get('OPERATOR'))
46
+ end
47
+ end).const_set('OPERATOR', operation)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Variable
7
+ class AssignContext < Expression::AssignName; end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Variable
7
+ class Context < Expression::Name; end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class For < Node::Base
6
+ def initialize(key_target, value_target, seq, if_expr, body, else_expr, lineno)
7
+ loop = ForLoop.new(lineno)
8
+ body = Nodes.new(AutoHash.new.add(body, loop))
9
+
10
+ nodes = {
11
+ key_target:,
12
+ value_target:,
13
+ seq:,
14
+ body:,
15
+ }
16
+
17
+ unless else_expr.nil?
18
+ nodes[:else_expr] = else_expr
19
+ end
20
+
21
+ super(nodes, { with_loop: true }, lineno)
22
+ end
23
+
24
+ def compile(compiler)
25
+ compiler.
26
+ write("context.push_stack\n").
27
+ write('context[:_seq] = ::Twig::Extension::Core.ensure_hash(').
28
+ subcompile(nodes[:seq]).
29
+ raw(")\n")
30
+
31
+ # @todo Missing some more loops stuff here
32
+
33
+ if nodes.key?(:else_expr)
34
+ compiler.write("context[:_iterated] = false\n")
35
+ end
36
+
37
+ compiler.
38
+ write("context[:_seq].each do |k, v|\n").
39
+ indent.
40
+ write('').
41
+ subcompile(nodes[:key_target]).
42
+ raw(" = k\n").
43
+ write('').
44
+ subcompile(nodes[:value_target]).
45
+ raw(" = v\n\n").
46
+ subcompile(nodes[:body]).
47
+ outdent.
48
+ write("end\n")
49
+
50
+ if nodes.key?(:else_expr)
51
+ compiler.
52
+ write("unless context[:_iterated]\n").
53
+ indent.
54
+ subcompile(nodes[:else_expr]).
55
+ outdent.
56
+ write("end\n")
57
+ end
58
+
59
+ compiler.
60
+ write("context.pop_stack\n")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class ForLoop < Node::Base
6
+ def initialize(lineno)
7
+ super({}, { with_loop: false, if_expr: false, else_expr: false }, lineno)
8
+ end
9
+
10
+ def compile(compiler)
11
+ if attributes.key?(:else_expr)
12
+ compiler.write("context[:_iterated] = true\n")
13
+ end
14
+
15
+ # @todo if with loop
16
+ compiler.
17
+ write("context[:loop] = {\n").
18
+ write(" index0: 0,\n").
19
+ write(" index: 1,\n").
20
+ write(" first: true,\n").
21
+ write("}\n")
22
+
23
+ if attributes.key?(:with_loop)
24
+ compiler.
25
+ write("context[:loop][:index0] += 1\n").
26
+ write("context[:loop][:index] += 1\n").
27
+ write("context[:loop][:first] = false\n").
28
+ write("if context[:loop].key?(:revindex0) && context[:loop].key?(:revindex)\n").
29
+ indent.
30
+ write("context[:loop][:revindex0] -= 1\n").
31
+ write("context[:loop][:revindex] -= 1\n").
32
+ write("context[:loop][:last] = (context[:loop][:revindex0] == 0)\n").
33
+ outdent.
34
+ write("end\n")
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class If < Node::Base
6
+ def initialize(tests, else_node, lineno)
7
+ nodes = { tests: }
8
+ nodes[:else] = else_node if else_node
9
+
10
+ super(nodes, {}, lineno)
11
+ end
12
+
13
+ def compile(compiler)
14
+ (0...nodes[:tests].nodes.length).step(2).each do |i|
15
+ if i.zero?
16
+ compiler.
17
+ raw("\n").
18
+ write('if (')
19
+ else
20
+ compiler.
21
+ outdent.
22
+ write('elsif (')
23
+ end
24
+
25
+ compiler.
26
+ subcompile(nodes[:tests].nodes[i]).
27
+ raw(")\n").
28
+ indent
29
+
30
+ if nodes[:tests].nodes.key?(i + 1)
31
+ compiler.
32
+ subcompile(nodes[:tests].nodes[i + 1])
33
+ end
34
+ end
35
+
36
+ if nodes.key?(:else)
37
+ compiler.
38
+ outdent.
39
+ write("else\n").
40
+ indent.
41
+ subcompile(nodes[:else])
42
+ end
43
+
44
+ compiler.
45
+ outdent.
46
+ write("end\n\n")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Include < Node::Base
6
+ # @param [Expression::Base] expr
7
+ # @param [Expression::Base, nil] variables
8
+ # @param [Boolean] only
9
+ # @param [Boolean] ignore_missing
10
+ # @param [Integer] lineno
11
+ def initialize(expr, variables, only, ignore_missing, lineno)
12
+ nodes = { expr: }
13
+ nodes[:variables] = variables if variables
14
+
15
+ super(nodes, {
16
+ only:,
17
+ ignore_missing:,
18
+ }, lineno)
19
+ end
20
+
21
+ def compile(compiler)
22
+ if attributes[:ignore_missing]
23
+ # @todo
24
+ raise 'not implemented yet'
25
+ else
26
+ compiler.
27
+ write('')
28
+
29
+ add_get_template(compiler)
30
+
31
+ compiler.
32
+ raw('.render(')
33
+
34
+ add_template_arguments(compiler)
35
+
36
+ compiler.
37
+ raw(");\n")
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # @param [Compiler] compiler
44
+ def add_get_template(compiler)
45
+ compiler.
46
+ raw('load_template(').
47
+ subcompile(nodes[:expr]).
48
+ raw(', ').
49
+ repr(template_name).
50
+ raw(', ').
51
+ repr(lineno).
52
+ raw(')')
53
+ end
54
+
55
+ # @param [Compiler] compiler
56
+ def add_template_arguments(compiler)
57
+ if !nodes.key?(:variables)
58
+ compiler.raw(attributes[:only] == false ? 'context' : '{}')
59
+ elsif attributes[:only] == false
60
+ compiler.
61
+ raw('context.merge(').
62
+ subcompile(nodes[:variables]).
63
+ raw(')')
64
+ else
65
+ compiler.
66
+ subcompile(nodes[:variables])
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end