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.
- checksums.yaml +7 -0
- data/lib/twig/auto_hash.rb +17 -0
- data/lib/twig/cache/base.rb +31 -0
- data/lib/twig/cache/filesystem.rb +47 -0
- data/lib/twig/cache/nil.rb +19 -0
- data/lib/twig/callable.rb +21 -0
- data/lib/twig/compiler.rb +123 -0
- data/lib/twig/context.rb +64 -0
- data/lib/twig/environment.rb +161 -0
- data/lib/twig/error/base.rb +37 -0
- data/lib/twig/error/syntax.rb +8 -0
- data/lib/twig/expression_parser.rb +517 -0
- data/lib/twig/extension/base.rb +23 -0
- data/lib/twig/extension/core.rb +89 -0
- data/lib/twig/extension/rails.rb +70 -0
- data/lib/twig/extension_set.rb +69 -0
- data/lib/twig/lexer.rb +372 -0
- data/lib/twig/loader/array.rb +39 -0
- data/lib/twig/loader/base.rb +32 -0
- data/lib/twig/loader/filesystem.rb +45 -0
- data/lib/twig/node/base.rb +61 -0
- data/lib/twig/node/block.rb +20 -0
- data/lib/twig/node/block_reference.rb +17 -0
- data/lib/twig/node/empty.rb +11 -0
- data/lib/twig/node/expression/array.rb +50 -0
- data/lib/twig/node/expression/assign_name.rb +28 -0
- data/lib/twig/node/expression/base.rb +20 -0
- data/lib/twig/node/expression/binary/base.rb +63 -0
- data/lib/twig/node/expression/call.rb +28 -0
- data/lib/twig/node/expression/constant.rb +17 -0
- data/lib/twig/node/expression/filter.rb +52 -0
- data/lib/twig/node/expression/get_attribute.rb +30 -0
- data/lib/twig/node/expression/helper_method.rb +31 -0
- data/lib/twig/node/expression/name.rb +37 -0
- data/lib/twig/node/expression/ternary.rb +28 -0
- data/lib/twig/node/expression/unary/base.rb +52 -0
- data/lib/twig/node/expression/variable/assign_context.rb +11 -0
- data/lib/twig/node/expression/variable/context.rb +11 -0
- data/lib/twig/node/for.rb +64 -0
- data/lib/twig/node/for_loop.rb +39 -0
- data/lib/twig/node/if.rb +50 -0
- data/lib/twig/node/include.rb +71 -0
- data/lib/twig/node/module.rb +74 -0
- data/lib/twig/node/nodes.rb +13 -0
- data/lib/twig/node/print.rb +18 -0
- data/lib/twig/node/text.rb +20 -0
- data/lib/twig/node/yield.rb +54 -0
- data/lib/twig/output_buffer.rb +29 -0
- data/lib/twig/parser.rb +131 -0
- data/lib/twig/railtie.rb +60 -0
- data/lib/twig/source.rb +13 -0
- data/lib/twig/template.rb +50 -0
- data/lib/twig/token.rb +48 -0
- data/lib/twig/token_parser/base.rb +20 -0
- data/lib/twig/token_parser/block.rb +54 -0
- data/lib/twig/token_parser/extends.rb +25 -0
- data/lib/twig/token_parser/for.rb +64 -0
- data/lib/twig/token_parser/if.rb +64 -0
- data/lib/twig/token_parser/include.rb +51 -0
- data/lib/twig/token_parser/yield.rb +44 -0
- data/lib/twig/token_stream.rb +73 -0
- data/lib/twig/twig_filter.rb +21 -0
- data/lib/twig_ruby.rb +36 -0
- metadata +103 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module Node
|
5
|
+
class Module < Node::Base
|
6
|
+
def initialize(body, parent, blocks, source)
|
7
|
+
nodes = {
|
8
|
+
body:,
|
9
|
+
blocks:,
|
10
|
+
}
|
11
|
+
nodes[:parent] = parent if parent
|
12
|
+
|
13
|
+
super(nodes)
|
14
|
+
|
15
|
+
self.source_context = source
|
16
|
+
end
|
17
|
+
|
18
|
+
def compile(compiler)
|
19
|
+
class_begin = <<~CLASS
|
20
|
+
class Twig::#{compiler.environment.template_class(source_context.name)} < ::Twig::Template
|
21
|
+
CLASS
|
22
|
+
|
23
|
+
class_end = <<~CLASS
|
24
|
+
end
|
25
|
+
CLASS
|
26
|
+
|
27
|
+
compiler.
|
28
|
+
raw(class_begin).
|
29
|
+
indent.
|
30
|
+
write("def call(context = {}, blocks = {})\n").
|
31
|
+
indent
|
32
|
+
|
33
|
+
if nodes.key?(:parent)
|
34
|
+
compiler.
|
35
|
+
write('load_template(').
|
36
|
+
subcompile(nodes[:parent]).
|
37
|
+
raw(").render(context, block_list.merge(blocks));\n")
|
38
|
+
else
|
39
|
+
compiler.
|
40
|
+
subcompile(nodes[:body])
|
41
|
+
end
|
42
|
+
|
43
|
+
compiler.
|
44
|
+
write("@output_buffer\n").
|
45
|
+
outdent.
|
46
|
+
write("end\n\n").
|
47
|
+
subcompile(nodes[:blocks]).
|
48
|
+
outdent
|
49
|
+
|
50
|
+
compiler.
|
51
|
+
indent.
|
52
|
+
write("def block_list\n").
|
53
|
+
indent.
|
54
|
+
write("{\n").
|
55
|
+
indent
|
56
|
+
|
57
|
+
nodes[:blocks].nodes.each_value do |block|
|
58
|
+
compiler.
|
59
|
+
write("#{block.attributes[:name]}: self,\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
compiler.
|
63
|
+
outdent.
|
64
|
+
write("}\n").
|
65
|
+
outdent.
|
66
|
+
write("end\n").
|
67
|
+
outdent
|
68
|
+
|
69
|
+
compiler.
|
70
|
+
raw(class_end)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module Node
|
5
|
+
class Print < Node::Base
|
6
|
+
def initialize(expr, lineno)
|
7
|
+
super({ expr: }, {}, lineno)
|
8
|
+
end
|
9
|
+
|
10
|
+
def compile(compiler)
|
11
|
+
compiler.
|
12
|
+
write('@output_buffer.append = ').
|
13
|
+
subcompile(nodes[:expr]).
|
14
|
+
raw(";\n")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module Node
|
5
|
+
class Text < Node::Base
|
6
|
+
# @param [String] data
|
7
|
+
# @param [Integer] lineno
|
8
|
+
def initialize(data, lineno = 0)
|
9
|
+
super({}, { data: }, lineno)
|
10
|
+
end
|
11
|
+
|
12
|
+
def compile(compiler)
|
13
|
+
compiler.
|
14
|
+
write('@output_buffer.safe_append = ').
|
15
|
+
string(attributes[:data]).
|
16
|
+
raw(";\n")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module Node
|
5
|
+
class Yield < Node::Base
|
6
|
+
def initialize(expr, body, arguments, lineno)
|
7
|
+
arguments = arguments.empty? ? {} : { arguments: }
|
8
|
+
super({ expr:, body: }, arguments, lineno)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile(compiler)
|
12
|
+
compiler.
|
13
|
+
write('@output_buffer.append = (').
|
14
|
+
subcompile(nodes[:expr]).
|
15
|
+
raw(' do')
|
16
|
+
|
17
|
+
if attributes.key?(:arguments)
|
18
|
+
compiler.
|
19
|
+
raw(" |#{attributes[:arguments].join(', ')}|")
|
20
|
+
end
|
21
|
+
|
22
|
+
compiler.
|
23
|
+
raw("\n").
|
24
|
+
indent
|
25
|
+
|
26
|
+
if attributes.key?(:arguments)
|
27
|
+
compiler.
|
28
|
+
write("context.push_stack\n").
|
29
|
+
write('context.merge!({')
|
30
|
+
|
31
|
+
attributes[:arguments].each do |argument|
|
32
|
+
compiler.raw("#{argument}:,")
|
33
|
+
end
|
34
|
+
|
35
|
+
compiler.
|
36
|
+
raw("})\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
compiler.
|
40
|
+
subcompile(nodes[:body]).
|
41
|
+
raw("\n")
|
42
|
+
|
43
|
+
if attributes.key?(:arguments)
|
44
|
+
compiler.
|
45
|
+
write("context.pop_stack\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
compiler.
|
49
|
+
outdent.
|
50
|
+
write("end);\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
class OutputBuffer
|
5
|
+
def initialize
|
6
|
+
@buffer = +''
|
7
|
+
end
|
8
|
+
|
9
|
+
def append=(string)
|
10
|
+
unless string.nil?
|
11
|
+
string = string.to_s
|
12
|
+
|
13
|
+
@buffer << if string.html_safe?
|
14
|
+
string
|
15
|
+
else
|
16
|
+
CGI.escapeHTML(string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def safe_append=(string)
|
22
|
+
@buffer << string.html_safe
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@buffer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/twig/parser.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
# @!attribute [r] stream
|
5
|
+
# @return [TokenStream]
|
6
|
+
class Parser
|
7
|
+
attr_reader :stream, :block_stack
|
8
|
+
|
9
|
+
# @param [Environment] environment
|
10
|
+
def initialize(environment)
|
11
|
+
@environment = environment
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [TokenStream] stream
|
15
|
+
def parse(stream, test = nil, drop_needle: false)
|
16
|
+
@stream = stream
|
17
|
+
@parent = nil
|
18
|
+
@blocks = {}
|
19
|
+
@block_stack = []
|
20
|
+
@imported_symbols = [{}]
|
21
|
+
|
22
|
+
body = subparse(test, drop_needle:)
|
23
|
+
|
24
|
+
Node::Module.new(body, @parent, Node::Nodes.new(@blocks), stream.source)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Proc] test
|
28
|
+
# @return [Node::Base]
|
29
|
+
def subparse(test, drop_needle: false)
|
30
|
+
lineno = current_token.lineno
|
31
|
+
rv = AutoHash.new
|
32
|
+
|
33
|
+
until stream.eof?
|
34
|
+
case current_token.type
|
35
|
+
when Token::TEXT_TYPE
|
36
|
+
token = stream.next
|
37
|
+
rv.add(Node::Text.new(token.value, token.lineno))
|
38
|
+
when Token::VAR_START_TYPE
|
39
|
+
token = stream.next
|
40
|
+
expr = expression_parser.parse_expression
|
41
|
+
stream.expect(Token::VAR_END_TYPE)
|
42
|
+
|
43
|
+
rv.add(Node::Print.new(expr, token.lineno))
|
44
|
+
when Token::BLOCK_START_TYPE
|
45
|
+
stream.next
|
46
|
+
token = current_token
|
47
|
+
|
48
|
+
unless token.type == Token::NAME_TYPE
|
49
|
+
raise Error::Syntax.new('A block must start with a tag name.', token.lineno, stream.source)
|
50
|
+
end
|
51
|
+
|
52
|
+
if test&.call(token)
|
53
|
+
stream.next if drop_needle
|
54
|
+
return rv.values.first if rv.length == 1
|
55
|
+
|
56
|
+
return Node::Nodes.new(rv, lineno)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @todo Check that there is a token parser for this token value
|
60
|
+
subparser = @environment.token_parser(token.value)
|
61
|
+
|
62
|
+
unless subparser
|
63
|
+
raise Error::Syntax.new("Unexpected '#{token.value}' tag.", token.lineno, stream.source)
|
64
|
+
end
|
65
|
+
|
66
|
+
stream.next
|
67
|
+
subparser.parser = self
|
68
|
+
node = subparser.parse(token)
|
69
|
+
|
70
|
+
raise 'Cannot return nil from TokenParser' unless node
|
71
|
+
|
72
|
+
node.tag = subparser.tag
|
73
|
+
|
74
|
+
rv.add(node)
|
75
|
+
else
|
76
|
+
raise "Unable to parse token of type #{current_token.type}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Node::Nodes.new(rv)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Token]
|
84
|
+
def current_token
|
85
|
+
stream.current
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [ExpressionParser]
|
89
|
+
def expression_parser
|
90
|
+
@expression_parser ||= ExpressionParser.new(self, @environment)
|
91
|
+
end
|
92
|
+
|
93
|
+
def push_local_scope
|
94
|
+
@imported_symbols.unshift({})
|
95
|
+
end
|
96
|
+
|
97
|
+
def pop_local_scope
|
98
|
+
@imported_symbols.shift
|
99
|
+
end
|
100
|
+
|
101
|
+
def peek_block_stack
|
102
|
+
@block_stack[-1]
|
103
|
+
end
|
104
|
+
|
105
|
+
def pop_block_stack
|
106
|
+
@block_stack.pop
|
107
|
+
end
|
108
|
+
|
109
|
+
def push_block_stack(name)
|
110
|
+
@block_stack << name
|
111
|
+
end
|
112
|
+
|
113
|
+
def block?(name)
|
114
|
+
@blocks.key?(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
# @todo type value as BlockNode and also set it to a BodyNode
|
118
|
+
def set_block(name, value)
|
119
|
+
@blocks[name] = value
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param [Node::Base] parent
|
123
|
+
def parent=(parent)
|
124
|
+
if @parent
|
125
|
+
raise Error::Syntax.new('Cannot extends twice', parent.lineno, parent.source_context)
|
126
|
+
end
|
127
|
+
|
128
|
+
@parent = parent
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/twig/railtie.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
class RailsRenderer
|
5
|
+
def call(template, source)
|
6
|
+
<<~TEMPLATE
|
7
|
+
::#{self.class.name}.
|
8
|
+
environment.
|
9
|
+
load_template("#{template.short_identifier}", call_context: self, output_buffer: @output_buffer).
|
10
|
+
render(local_assigns)
|
11
|
+
|
12
|
+
@output_buffer
|
13
|
+
TEMPLATE
|
14
|
+
end
|
15
|
+
|
16
|
+
def translate_location(spot, backtrace_location, source)
|
17
|
+
template = backtrace_location.path.delete_prefix(Rails.root.to_s)
|
18
|
+
|
19
|
+
# Attempt to recompile the template to find where the syntax error is
|
20
|
+
# otherwise just do what would have happened anyway
|
21
|
+
begin
|
22
|
+
self.class.environment.render_ruby(template)
|
23
|
+
rescue ::Twig::Error::Syntax => e
|
24
|
+
return spot.merge({
|
25
|
+
first_lineno: e.lineno,
|
26
|
+
last_lineno: e.lineno + 1,
|
27
|
+
script_lines: source.lines,
|
28
|
+
})
|
29
|
+
rescue StandardError
|
30
|
+
# Nothing, don't add another exception to the problem
|
31
|
+
end
|
32
|
+
|
33
|
+
spot
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.loader
|
37
|
+
@loader ||= ::Twig::Loader::Filesystem.new(
|
38
|
+
Rails.root,
|
39
|
+
%w[/ /app/views]
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.environment
|
44
|
+
options = {
|
45
|
+
cache: Rails.root.join('tmp/cache/twig').to_s,
|
46
|
+
debug: Rails.env.development?,
|
47
|
+
}
|
48
|
+
|
49
|
+
@environment ||= ::Twig::Environment.new(loader, options).tap do |env|
|
50
|
+
env.add_extension(::Twig::Extension::Rails.new)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Railtie < ::Rails::Railtie
|
56
|
+
initializer 'twig_ruby.configure_rails_initialization' do
|
57
|
+
ActionView::Template.register_template_handler :twig, RailsRenderer.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/twig/source.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
# Base class for compiled templates
|
5
|
+
class Template
|
6
|
+
ARRAY_CALL = :array_call
|
7
|
+
METHOD_CALL = :method_call
|
8
|
+
ANY_CALL = :any_call
|
9
|
+
|
10
|
+
# @param [Environment] environment
|
11
|
+
def initialize(environment, call_context: nil, output_buffer: nil)
|
12
|
+
@environment = environment
|
13
|
+
@parents = {}
|
14
|
+
@blocks = {}
|
15
|
+
@call_context = call_context
|
16
|
+
@output_buffer = output_buffer || OutputBuffer.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(context = {}, blocks = {})
|
20
|
+
raise 'call is not implemented'
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(context = {}, blocks = {})
|
24
|
+
call(Context.new(context), blocks)
|
25
|
+
end
|
26
|
+
|
27
|
+
def yield_block(name, context = {}, blocks = {})
|
28
|
+
object = self
|
29
|
+
|
30
|
+
if blocks.key?(name)
|
31
|
+
object = blocks[name]
|
32
|
+
end
|
33
|
+
|
34
|
+
object.public_send(:"block_#{name}", context, blocks)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @param [String] name
|
40
|
+
# @return ]Template]
|
41
|
+
def load_template(name, template_name = '', template_line = nil)
|
42
|
+
env.load_template(name, call_context: @call_context, output_buffer: @output_buffer)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Environment]
|
46
|
+
def env
|
47
|
+
@environment
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/twig/token.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
class Token
|
5
|
+
EOF_TYPE = :eof
|
6
|
+
TEXT_TYPE = :text
|
7
|
+
BLOCK_START_TYPE = :block_start
|
8
|
+
VAR_START_TYPE = :var_start
|
9
|
+
BLOCK_END_TYPE = :block_end
|
10
|
+
VAR_END_TYPE = :var_end
|
11
|
+
NAME_TYPE = :name
|
12
|
+
SYMBOL_TYPE = :symbol
|
13
|
+
NUMBER_TYPE = :number
|
14
|
+
STRING_TYPE = :string
|
15
|
+
OPERATOR_TYPE = :operator
|
16
|
+
PUNCTUATION_TYPE = :punctuation
|
17
|
+
INTERPOLATION_START_TYPE = :interpolation_start
|
18
|
+
INTERPOLATION_END_TYPE = :interpolation_end
|
19
|
+
ARROW_TYPE = :arrow
|
20
|
+
SPREAD_TYPE = :spread
|
21
|
+
|
22
|
+
attr_reader :type, :value, :lineno
|
23
|
+
|
24
|
+
def initialize(type, value, lineno)
|
25
|
+
@type = type
|
26
|
+
@value = value
|
27
|
+
@lineno = lineno
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Symbol | String] type
|
31
|
+
def test(type, values = nil)
|
32
|
+
if values.nil? && !type.is_a?(Symbol)
|
33
|
+
values = type
|
34
|
+
type = NAME_TYPE
|
35
|
+
end
|
36
|
+
|
37
|
+
@type == type && (
|
38
|
+
values.nil? ||
|
39
|
+
(values.is_a?(Array) && values.include?(@value)) ||
|
40
|
+
(@value == values)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug
|
45
|
+
[type, value]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module TokenParser
|
5
|
+
class Base
|
6
|
+
# @return [Parser]
|
7
|
+
attr_accessor :parser
|
8
|
+
|
9
|
+
# @param [Token] token
|
10
|
+
def parse(token)
|
11
|
+
raise 'parse is not implemented'
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
def tag
|
16
|
+
raise 'tag is not implemented'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module TokenParser
|
5
|
+
# Marks a section of a template as being reusable.
|
6
|
+
#
|
7
|
+
# {% block head %}
|
8
|
+
# <link rel="stylesheet" href="style.css" />
|
9
|
+
# <title>{% block title %}{% endblock %} - My Webpage</title>
|
10
|
+
# {% endblock %}
|
11
|
+
class Block < Base
|
12
|
+
def parse(token)
|
13
|
+
lineno = token.lineno
|
14
|
+
stream = parser.stream
|
15
|
+
name = stream.expect(Token::NAME_TYPE).value
|
16
|
+
block = Node::Block.new(name, Node::Empty.new, lineno)
|
17
|
+
|
18
|
+
parser.set_block(name, block)
|
19
|
+
parser.push_local_scope
|
20
|
+
parser.push_block_stack(name)
|
21
|
+
|
22
|
+
if stream.next_if(Token::BLOCK_END_TYPE)
|
23
|
+
body = parser.subparse(decide_block_end, drop_needle: true)
|
24
|
+
|
25
|
+
if (token = stream.next_if(Token::NAME_TYPE)) && token.value != name
|
26
|
+
raise "Expected end block for #{name}, given #{token.value}"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
body = Node::Nodes.new({
|
30
|
+
0 => Node::Print.new(parser.expression_parser.parse_expression, lineno),
|
31
|
+
})
|
32
|
+
end
|
33
|
+
|
34
|
+
stream.expect(Token::BLOCK_END_TYPE)
|
35
|
+
block.nodes[:body] = body
|
36
|
+
|
37
|
+
parser.pop_block_stack
|
38
|
+
parser.pop_local_scope
|
39
|
+
|
40
|
+
Node::BlockReference.new(name, lineno)
|
41
|
+
end
|
42
|
+
|
43
|
+
def tag
|
44
|
+
'block'
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def decide_block_end
|
50
|
+
->(token) { token.test('endblock') }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module TokenParser
|
5
|
+
class Extends < TokenParser::Base
|
6
|
+
def parse(token)
|
7
|
+
stream = parser.stream
|
8
|
+
|
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
|
12
|
+
end
|
13
|
+
|
14
|
+
parser.parent = parser.expression_parser.parse_expression
|
15
|
+
stream.expect(Token::BLOCK_END_TYPE)
|
16
|
+
|
17
|
+
Node::Empty.new(token.lineno)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tag
|
21
|
+
'extends'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twig
|
4
|
+
module TokenParser
|
5
|
+
# {% for user in users %}
|
6
|
+
# <li>{{ user.name }}</li>
|
7
|
+
# {% endfor %}
|
8
|
+
class For < Base
|
9
|
+
def parse(token)
|
10
|
+
lineno = token.lineno
|
11
|
+
stream = parser.stream
|
12
|
+
|
13
|
+
targets = parser.expression_parser.parse_assignment_expression
|
14
|
+
stream.expect(Token::OPERATOR_TYPE, 'in')
|
15
|
+
seq = parser.expression_parser.parse_expression
|
16
|
+
|
17
|
+
stream.expect(Token::BLOCK_END_TYPE)
|
18
|
+
body = parser.subparse(decide_for_fork)
|
19
|
+
|
20
|
+
if stream.next.value == 'else'
|
21
|
+
stream.expect(Token::BLOCK_END_TYPE)
|
22
|
+
else_expr = parser.subparse(decide_for_end, drop_needle: true)
|
23
|
+
else
|
24
|
+
else_expr = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
stream.expect(Token::BLOCK_END_TYPE)
|
28
|
+
|
29
|
+
if targets.nodes.length > 1
|
30
|
+
key_target = targets.nodes[0]
|
31
|
+
key_target = Node::Expression::Variable::AssignContext.new(
|
32
|
+
key_target.attributes[:name],
|
33
|
+
key_target.lineno
|
34
|
+
)
|
35
|
+
value_target = targets.nodes[1]
|
36
|
+
else
|
37
|
+
key_target = Node::Expression::Variable::AssignContext.new('_key', lineno)
|
38
|
+
value_target = targets.nodes[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
value_target = Node::Expression::Variable::AssignContext.new(
|
42
|
+
value_target.attributes[:name],
|
43
|
+
value_target.lineno
|
44
|
+
)
|
45
|
+
|
46
|
+
Node::For.new(key_target, value_target, seq, nil, body, else_expr, lineno)
|
47
|
+
end
|
48
|
+
|
49
|
+
def tag
|
50
|
+
'for'
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def decide_for_fork
|
56
|
+
->(token) { token.test(%w[else endfor]) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def decide_for_end
|
60
|
+
->(token) { token.test(%w[endfor]) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|