luoma 0.1.0
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
- checksums.yaml.gz.sig +2 -0
- data/.rdoc_options +16 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +69 -0
- data/Steepfile +41 -0
- data/certs/jgrp.pem +27 -0
- data/docs/configuration.md +180 -0
- data/docs/custom_filters.md +3 -0
- data/docs/custom_tags.md +3 -0
- data/docs/expressions.md +3 -0
- data/docs/extension_types.md +41 -0
- data/docs/filter_reference.md +1702 -0
- data/docs/index.md +73 -0
- data/docs/luoma_for_template_authors.md +3 -0
- data/docs/markdown.md +111 -0
- data/docs/predicate_reference.md +3 -0
- data/docs/static_analysis.md +3 -0
- data/docs/tag_reference.md +352 -0
- data/docs/template_loaders.md +177 -0
- data/docs/undefined_variables.md +3 -0
- data/docs-requirements.txt +11 -0
- data/docs_/cycle.md +17 -0
- data/docs_/font_rendering_example.md +157 -0
- data/docs_/header_block_example.md +51 -0
- data/docs_/increment_and_decrement.md +49 -0
- data/docs_/logo_style_example.md +40 -0
- data/docs_/migration.md +11 -0
- data/docs_/notes.md +19 -0
- data/lib/luoma/cache.rb +80 -0
- data/lib/luoma/chain_hash.rb +54 -0
- data/lib/luoma/context.rb +227 -0
- data/lib/luoma/drop.rb +84 -0
- data/lib/luoma/drops/block.rb +33 -0
- data/lib/luoma/drops/expression.rb +29 -0
- data/lib/luoma/drops/html_safe.rb +36 -0
- data/lib/luoma/drops/range.rb +69 -0
- data/lib/luoma/drops/undefined.rb +119 -0
- data/lib/luoma/environment.rb +479 -0
- data/lib/luoma/errors.rb +79 -0
- data/lib/luoma/escape.rb +35 -0
- data/lib/luoma/expression.rb +1271 -0
- data/lib/luoma/filter.rb +97 -0
- data/lib/luoma/filters/array.rb +372 -0
- data/lib/luoma/filters/date.rb +19 -0
- data/lib/luoma/filters/default.rb +16 -0
- data/lib/luoma/filters/json.rb +14 -0
- data/lib/luoma/filters/math.rb +84 -0
- data/lib/luoma/filters/size.rb +13 -0
- data/lib/luoma/filters/slice.rb +52 -0
- data/lib/luoma/filters/sort.rb +113 -0
- data/lib/luoma/filters/string.rb +186 -0
- data/lib/luoma/fnv.rb +15 -0
- data/lib/luoma/lexer.rb +106 -0
- data/lib/luoma/lexer_legacy.rb +396 -0
- data/lib/luoma/lexer_unified.rb +279 -0
- data/lib/luoma/loader.rb +50 -0
- data/lib/luoma/loaders/choice_loader.rb +20 -0
- data/lib/luoma/loaders/file_system_loader.rb +75 -0
- data/lib/luoma/loaders/mixins.rb +52 -0
- data/lib/luoma/markup.rb +109 -0
- data/lib/luoma/parser.rb +252 -0
- data/lib/luoma/parser_unified.rb +979 -0
- data/lib/luoma/predicates/blank.rb +19 -0
- data/lib/luoma/predicates/defined.rb +10 -0
- data/lib/luoma/predicates/empty.rb +15 -0
- data/lib/luoma/predicates/type.rb +35 -0
- data/lib/luoma/static_analysis.rb +427 -0
- data/lib/luoma/tags/assign.rb +63 -0
- data/lib/luoma/tags/break.rb +23 -0
- data/lib/luoma/tags/capture.rb +43 -0
- data/lib/luoma/tags/case.rb +137 -0
- data/lib/luoma/tags/comment.rb +17 -0
- data/lib/luoma/tags/continue.rb +23 -0
- data/lib/luoma/tags/define.rb +42 -0
- data/lib/luoma/tags/else.rb +30 -0
- data/lib/luoma/tags/for.rb +108 -0
- data/lib/luoma/tags/if.rb +109 -0
- data/lib/luoma/tags/import.rb +91 -0
- data/lib/luoma/tags/include.rb +77 -0
- data/lib/luoma/tags/output.rb +20 -0
- data/lib/luoma/tags/raw.rb +26 -0
- data/lib/luoma/tags/render.rb +92 -0
- data/lib/luoma/tags/with.rb +55 -0
- data/lib/luoma/template.rb +178 -0
- data/lib/luoma/token.rb +84 -0
- data/lib/luoma/version.rb +5 -0
- data/lib/luoma.rb +75 -0
- data/sig/luoma/cache.rbs +44 -0
- data/sig/luoma/chain_hash.rbs +26 -0
- data/sig/luoma/context.rbs +115 -0
- data/sig/luoma/drop.rbs +44 -0
- data/sig/luoma/drops/block.rbs +10 -0
- data/sig/luoma/drops/expression.rbs +10 -0
- data/sig/luoma/drops/html_safe.rbs +14 -0
- data/sig/luoma/drops/range.rbs +15 -0
- data/sig/luoma/drops/undefined.rbs +27 -0
- data/sig/luoma/environment.rbs +212 -0
- data/sig/luoma/errors.rbs +67 -0
- data/sig/luoma/escape.rbs +15 -0
- data/sig/luoma/expression.rbs +514 -0
- data/sig/luoma/filter.rbs +66 -0
- data/sig/luoma/filters/array.rbs +74 -0
- data/sig/luoma/filters/date.rbs +9 -0
- data/sig/luoma/filters/default.rbs +8 -0
- data/sig/luoma/filters/json.rbs +7 -0
- data/sig/luoma/filters/math.rbs +37 -0
- data/sig/luoma/filters/size.rbs +6 -0
- data/sig/luoma/filters/slice.rbs +10 -0
- data/sig/luoma/filters/sort.rbs +17 -0
- data/sig/luoma/filters/string.rbs +103 -0
- data/sig/luoma/fnv.rbs +3 -0
- data/sig/luoma/lexer.rbs +42 -0
- data/sig/luoma/lexer_legacy.rbs +81 -0
- data/sig/luoma/lexer_unified.rbs +49 -0
- data/sig/luoma/loader.rbs +40 -0
- data/sig/luoma/loaders/choice_loader.rbs +9 -0
- data/sig/luoma/loaders/file_system_loader.rbs +19 -0
- data/sig/luoma/loaders/mixins.rbs +16 -0
- data/sig/luoma/markup.rbs +68 -0
- data/sig/luoma/parser.rbs +105 -0
- data/sig/luoma/parser_unified.rbs +150 -0
- data/sig/luoma/predicates/blank.rbs +6 -0
- data/sig/luoma/predicates/defined.rbs +6 -0
- data/sig/luoma/predicates/empty.rbs +6 -0
- data/sig/luoma/predicates/type.rbs +21 -0
- data/sig/luoma/static_analysis.rbs +141 -0
- data/sig/luoma/tags/assign.rbs +12 -0
- data/sig/luoma/tags/break.rbs +11 -0
- data/sig/luoma/tags/capture.rbs +14 -0
- data/sig/luoma/tags/case.rbs +36 -0
- data/sig/luoma/tags/comment.rbs +11 -0
- data/sig/luoma/tags/continue.rbs +11 -0
- data/sig/luoma/tags/define.rbs +16 -0
- data/sig/luoma/tags/else.rbs +17 -0
- data/sig/luoma/tags/for.rbs +26 -0
- data/sig/luoma/tags/if.rbs +45 -0
- data/sig/luoma/tags/import.rbs +13 -0
- data/sig/luoma/tags/include.rbs +13 -0
- data/sig/luoma/tags/output.rbs +13 -0
- data/sig/luoma/tags/raw.rbs +11 -0
- data/sig/luoma/tags/render.rbs +13 -0
- data/sig/luoma/tags/with.rbs +15 -0
- data/sig/luoma/template.rbs +117 -0
- data/sig/luoma/token.rbs +81 -0
- data/sig/luoma.rbs +11 -0
- data/zensical.toml +363 -0
- data.tar.gz.sig +0 -0
- metadata +233 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class CaseTag < Markup
|
|
5
|
+
END_CASE_BLOCK = Set["endcase", "when", "else"]
|
|
6
|
+
|
|
7
|
+
#: (t_token, String, Parser) -> Markup
|
|
8
|
+
def self.parse(token, tag_name, parser)
|
|
9
|
+
parser.expect_expression
|
|
10
|
+
expression = parser.parse_expression
|
|
11
|
+
parser.carry_whitespace_control
|
|
12
|
+
parser.eat(:token_tag_end)
|
|
13
|
+
|
|
14
|
+
# Junk between `{% case %}` and first `{% when %}`.
|
|
15
|
+
parser.eat(:token_text) if parser.kind == :token_text
|
|
16
|
+
|
|
17
|
+
blocks = [] #: Array[WhenBlock|ElseBlock]
|
|
18
|
+
blocks << WhenBlock.parse("when", parser) while parser.tag?("when")
|
|
19
|
+
|
|
20
|
+
if parser.tag?("else")
|
|
21
|
+
else_token = parser.eat_empty_tag("else")
|
|
22
|
+
blocks << ElseBlock.new(
|
|
23
|
+
else_token,
|
|
24
|
+
"else",
|
|
25
|
+
parser.parse_block(stop: END_CASE_BLOCK)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
parser.eat_empty_tag("endcase")
|
|
30
|
+
new(token, tag_name, expression, blocks)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#: (t_token, String, Expression, Array[WhenBlock|ElseBlock]) -> void
|
|
34
|
+
def initialize(token, tag_name, expression, blocks)
|
|
35
|
+
super(token)
|
|
36
|
+
@tag_name = tag_name
|
|
37
|
+
@expression = expression
|
|
38
|
+
@blocks = blocks
|
|
39
|
+
@blank = blocks.all?(&:blank)
|
|
40
|
+
@blocks.each(&:filter_strings) if @blank
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (RenderContext, String) -> void
|
|
44
|
+
def render(context, buffer)
|
|
45
|
+
left = @expression.evaluate(context)
|
|
46
|
+
|
|
47
|
+
@blocks.each do |block|
|
|
48
|
+
if block.is_a?(ElseBlock)
|
|
49
|
+
return Luoma.render_block(block.block, context, buffer)
|
|
50
|
+
else
|
|
51
|
+
block.right.each do |expr|
|
|
52
|
+
right = expr.evaluate(context)
|
|
53
|
+
if (right.is_a?(PredicateFunction) && context.env.truthy?(right.call(context, left), context)) ||
|
|
54
|
+
context.env.eq?(left, right, context, expr.span)
|
|
55
|
+
return Luoma.render_block(block.block, context, buffer)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#: (RenderContext) -> Array[Markup]
|
|
63
|
+
def children(static_context)
|
|
64
|
+
@blocks
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#: () -> Array[Expression]
|
|
68
|
+
def expressions
|
|
69
|
+
[@expression]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class WhenBlock < Markup
|
|
74
|
+
attr_reader :block, :right
|
|
75
|
+
|
|
76
|
+
END_CASE_BLOCK = Set["endcase", "when", "else"]
|
|
77
|
+
|
|
78
|
+
#: (String, Parser) -> WhenBlock
|
|
79
|
+
def self.parse(tag_name, parser)
|
|
80
|
+
parser.eat(:token_tag_start)
|
|
81
|
+
parser.skip_whitespace_control
|
|
82
|
+
token = parser.eat(:token_tag_name)
|
|
83
|
+
|
|
84
|
+
right = [] #: Array[Expression]
|
|
85
|
+
|
|
86
|
+
loop do
|
|
87
|
+
expr = parser.parse_expression
|
|
88
|
+
right << if parser.kind == :token_question &&
|
|
89
|
+
expr.is_a?(Variable) &&
|
|
90
|
+
expr.segments.empty? &&
|
|
91
|
+
expr.root.is_a?(Name)
|
|
92
|
+
parser.next
|
|
93
|
+
Predicate.new(expr.token, expr.root.value)
|
|
94
|
+
else
|
|
95
|
+
expr
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
break unless parser.kind == :token_comma
|
|
99
|
+
|
|
100
|
+
parser.next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
parser.carry_whitespace_control
|
|
104
|
+
parser.eat(:token_tag_end)
|
|
105
|
+
block = parser.parse_block(stop: END_CASE_BLOCK)
|
|
106
|
+
new(token, tag_name, right, block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#: (t_token, String, Array[Expression], t_block) -> void
|
|
110
|
+
def initialize(token, tag_name, right, block)
|
|
111
|
+
super(token)
|
|
112
|
+
@tag_name = tag_name
|
|
113
|
+
@right = right
|
|
114
|
+
@block = block
|
|
115
|
+
@blank = Luoma.blank_block?(block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#: (RenderContext, String) -> void
|
|
119
|
+
def render(context, buffer)
|
|
120
|
+
Luoma.render_block(@block, context, buffer)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#: (RenderContext) -> Array[Markup]
|
|
124
|
+
def children(static_context)
|
|
125
|
+
@block.grep_v(String) #: Array[Markup]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#: () -> Array[Expression]
|
|
129
|
+
def expressions
|
|
130
|
+
@right
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def filter_strings
|
|
134
|
+
@block.filter! { |node| !node.is_a?(String) }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class Comment < Markup
|
|
5
|
+
attr_reader :text
|
|
6
|
+
|
|
7
|
+
#: (t_token, String) -> void
|
|
8
|
+
def initialize(token, text)
|
|
9
|
+
super(token)
|
|
10
|
+
@text = text
|
|
11
|
+
@blank = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#: (RenderContext, String) -> void
|
|
15
|
+
def render(context, buffer) end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ContinueTag < Markup
|
|
5
|
+
#: (t_token, String, Parser) -> Markup
|
|
6
|
+
def self.parse(token, tag_name, parser)
|
|
7
|
+
parser.carry_whitespace_control
|
|
8
|
+
parser.eat(:token_tag_end)
|
|
9
|
+
new(token, tag_name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(token, tag_name)
|
|
13
|
+
super(token)
|
|
14
|
+
@tag_name = tag_name
|
|
15
|
+
@blank = true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: (RenderContext, String) -> void
|
|
19
|
+
def render(context, buffer)
|
|
20
|
+
context.interrupts.push(:continue)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class DefineTag < Markup
|
|
5
|
+
END_DEFINE_BLOCK = Set["enddefine"]
|
|
6
|
+
|
|
7
|
+
#: (t_token, String, Parser) -> Markup
|
|
8
|
+
def self.parse(token, tag_name, parser)
|
|
9
|
+
identifier = parser.parse_ident
|
|
10
|
+
parser.carry_whitespace_control
|
|
11
|
+
parser.eat(:token_tag_end)
|
|
12
|
+
block = parser.parse_block(stop: END_DEFINE_BLOCK)
|
|
13
|
+
parser.eat_empty_tag("enddefine")
|
|
14
|
+
new(token, tag_name, identifier, block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: (t_token, String, Name, t_block) -> void
|
|
18
|
+
def initialize(token, tag_name, identifier, block)
|
|
19
|
+
super(token)
|
|
20
|
+
@tag_name = tag_name
|
|
21
|
+
@identifier = identifier
|
|
22
|
+
@block = block
|
|
23
|
+
@blank = true
|
|
24
|
+
@drop = BlockDrop.new(block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: (RenderContext, String) -> void
|
|
28
|
+
def render(context, buffer)
|
|
29
|
+
context.assign(@identifier.value, @drop)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: (RenderContext) -> Array[Markup]
|
|
33
|
+
def children(static_context)
|
|
34
|
+
@block.grep_v(String) #: Array[Markup]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: () -> Array[Name]
|
|
38
|
+
def template_scope
|
|
39
|
+
[@identifier]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ElseBlock < Markup
|
|
5
|
+
attr_reader :expression, :block
|
|
6
|
+
|
|
7
|
+
#: (t_token, String, t_block) -> void
|
|
8
|
+
def initialize(token, tag_name, block)
|
|
9
|
+
super(token)
|
|
10
|
+
@tag_name = tag_name
|
|
11
|
+
@expression = BooleanLiteral.new(token, true)
|
|
12
|
+
@block = block
|
|
13
|
+
@blank = Luoma.blank_block?(block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: (RenderContext, String) -> void
|
|
17
|
+
def render(context, buffer)
|
|
18
|
+
Luoma.render_block(@block, context, buffer)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: (RenderContext) -> Array[Markup]
|
|
22
|
+
def children(static_context)
|
|
23
|
+
@block.grep_v(String) #: Array[Markup]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def filter_strings
|
|
27
|
+
@block.filter! { |node| !node.is_a?(String) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ForTag < Markup
|
|
5
|
+
END_FOR_BLOCK = Set["else", "endfor"]
|
|
6
|
+
|
|
7
|
+
#: (t_token, String, Parser) -> Markup
|
|
8
|
+
def self.parse(token, tag_name, parser)
|
|
9
|
+
params = [parser.parse_ident] #: Array[Name]
|
|
10
|
+
|
|
11
|
+
while parser.kind == :token_comma
|
|
12
|
+
parser.next
|
|
13
|
+
params << parser.parse_ident
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
parser.eat(:token_in, message: "missing 'in'")
|
|
17
|
+
parser.expect_expression
|
|
18
|
+
expr = parser.parse_expression
|
|
19
|
+
parser.carry_whitespace_control
|
|
20
|
+
parser.eat(:token_tag_end)
|
|
21
|
+
|
|
22
|
+
block = parser.parse_block(stop: END_FOR_BLOCK)
|
|
23
|
+
|
|
24
|
+
default = if parser.tag?("else")
|
|
25
|
+
parser.eat_empty_tag("else")
|
|
26
|
+
parser.parse_block(stop: END_FOR_BLOCK)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
parser.eat_empty_tag("endfor")
|
|
30
|
+
|
|
31
|
+
new(token, tag_name, params, expr, block, default)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#: (t_token, String, Array[Name], Expression, t_block, t_block?) -> void
|
|
35
|
+
def initialize(token, tag_name, params, expression, block, default)
|
|
36
|
+
super(token)
|
|
37
|
+
@tag_name = tag_name
|
|
38
|
+
@params = params
|
|
39
|
+
@expression = expression
|
|
40
|
+
|
|
41
|
+
@blank = Luoma.blank_block?(block) && (!default || Luoma.blank_block?(default))
|
|
42
|
+
|
|
43
|
+
@block = if @blank
|
|
44
|
+
block.grep_v(String)
|
|
45
|
+
else
|
|
46
|
+
block
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@default = if blank && default
|
|
50
|
+
default.grep_v(String)
|
|
51
|
+
else
|
|
52
|
+
default
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#: (RenderContext, String) -> void
|
|
57
|
+
def render(context, buffer)
|
|
58
|
+
array = context.env.to_a(@expression.evaluate(context), context)
|
|
59
|
+
|
|
60
|
+
if array.empty? && @default
|
|
61
|
+
Luoma.render_block(@default || raise, context, buffer)
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
namespace = {} #: Hash[String, untyped]
|
|
66
|
+
namespace[@params[2].value] = array if @params.length > 2
|
|
67
|
+
|
|
68
|
+
name_param = @params.first.value
|
|
69
|
+
index_param = @params[1].value if @params.length > 1
|
|
70
|
+
length = array.length
|
|
71
|
+
|
|
72
|
+
context.extends(namespace) do
|
|
73
|
+
index = 0
|
|
74
|
+
while index < length
|
|
75
|
+
namespace[name_param] = array[index]
|
|
76
|
+
namespace[index_param] = index if index_param
|
|
77
|
+
index += 1
|
|
78
|
+
|
|
79
|
+
Luoma.render_block(@block, context, buffer)
|
|
80
|
+
|
|
81
|
+
case context.interrupts.pop
|
|
82
|
+
when :continue
|
|
83
|
+
next
|
|
84
|
+
when :break
|
|
85
|
+
break
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#: (RenderContext) -> Array[Markup]
|
|
92
|
+
def children(static_context)
|
|
93
|
+
result = @block.grep_v(String) #: Array[Markup]
|
|
94
|
+
result.concat(@default.grep_v(String)) if @default # steep:ignore
|
|
95
|
+
result
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#: () -> Array[Expression]
|
|
99
|
+
def expressions
|
|
100
|
+
[@expression, @offset, @limit].compact
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#: () -> Array[Name]
|
|
104
|
+
def block_scope
|
|
105
|
+
@params
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class IfTag < Markup
|
|
5
|
+
END_IF_BLOCK = Set["else", "elif", "elsif", "endif"]
|
|
6
|
+
ELSE_IF_TAGS = Set["elif", "elsif"]
|
|
7
|
+
|
|
8
|
+
#: (t_token, String, Parser) -> Markup
|
|
9
|
+
def self.parse(token, tag_name, parser)
|
|
10
|
+
blocks = [] #: Array[IfBlock | ElseBlock]
|
|
11
|
+
parser.expect_expression
|
|
12
|
+
expression = parser.parse_expression
|
|
13
|
+
parser.carry_whitespace_control
|
|
14
|
+
parser.eat(:token_tag_end)
|
|
15
|
+
|
|
16
|
+
block = parser.parse_block(stop: END_IF_BLOCK)
|
|
17
|
+
blocks << IfBlock.new(token, "if", expression, block)
|
|
18
|
+
|
|
19
|
+
loop do
|
|
20
|
+
inner_tag_name = parser.tags(ELSE_IF_TAGS)
|
|
21
|
+
break unless inner_tag_name
|
|
22
|
+
|
|
23
|
+
blocks << IfBlock.parse(inner_tag_name, parser)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if parser.tag?("else")
|
|
27
|
+
name_token = parser.eat_empty_tag("else")
|
|
28
|
+
blocks << ElseBlock.new(
|
|
29
|
+
name_token,
|
|
30
|
+
"else",
|
|
31
|
+
parser.parse_block(stop: END_IF_BLOCK)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
parser.eat_empty_tag("endif")
|
|
36
|
+
IfTag.new(token, tag_name, blocks)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize(token, tag_name, alts)
|
|
40
|
+
super(token)
|
|
41
|
+
@tag_name = tag_name
|
|
42
|
+
@alts = alts
|
|
43
|
+
@blank = alts.all?(&:blank)
|
|
44
|
+
@alts.each(&:filter_strings) if @blank
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: (RenderContext, String) -> void
|
|
48
|
+
def render(context, buffer)
|
|
49
|
+
index = 0
|
|
50
|
+
while (alt = @alts[index])
|
|
51
|
+
if context.env.truthy?(alt.expression.evaluate(context), context)
|
|
52
|
+
Luoma.render_block(alt.block, context, buffer)
|
|
53
|
+
break
|
|
54
|
+
end
|
|
55
|
+
index += 1
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#: (RenderContext) -> Array[Markup]
|
|
60
|
+
def children(static_context)
|
|
61
|
+
@alts
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class IfBlock < Markup
|
|
66
|
+
attr_reader :expression, :block
|
|
67
|
+
|
|
68
|
+
END_IF_BLOCK = Set["else", "elsif", "elif", "endif", "endunless"]
|
|
69
|
+
|
|
70
|
+
#: (Parser) -> Markup
|
|
71
|
+
def self.parse(tag_name, parser)
|
|
72
|
+
parser.eat(:token_tag_start)
|
|
73
|
+
parser.skip_whitespace_control
|
|
74
|
+
token = parser.eat(:token_tag_name)
|
|
75
|
+
parser.expect_expression
|
|
76
|
+
expression = parser.parse_expression
|
|
77
|
+
parser.carry_whitespace_control
|
|
78
|
+
parser.eat(:token_tag_end)
|
|
79
|
+
IfBlock.new(token, tag_name, expression, parser.parse_block(stop: END_IF_BLOCK))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def initialize(token, tag_name, expression, block)
|
|
83
|
+
super(token)
|
|
84
|
+
@tag_name = tag_name
|
|
85
|
+
@expression = expression
|
|
86
|
+
@block = block
|
|
87
|
+
@blank = Luoma.blank_block?(block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#: (RenderContext, String) -> void
|
|
91
|
+
def render(context, buffer)
|
|
92
|
+
Luoma.render_block(@block, context, buffer)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#: (RenderContext) -> Array[Markup]
|
|
96
|
+
def children(static_context)
|
|
97
|
+
@block.grep_v(String) #: Array[Markup]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: () -> Array[Expression]
|
|
101
|
+
def expressions
|
|
102
|
+
[@expression]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def filter_strings
|
|
106
|
+
@block.filter! { |node| !node.is_a?(String) }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ImportTag < Markup
|
|
5
|
+
#: (t_token, String, Parser) -> Markup
|
|
6
|
+
def self.parse(token, tag_name, parser)
|
|
7
|
+
name_expr = parser.parse_string_literal
|
|
8
|
+
name_value = name_expr.value
|
|
9
|
+
|
|
10
|
+
unless name_value
|
|
11
|
+
raise TemplateSyntaxError.new(
|
|
12
|
+
"expected a string literal",
|
|
13
|
+
name_expr.span,
|
|
14
|
+
parser.source,
|
|
15
|
+
parser.template_name
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
name = Name.new(name_expr.token, name_value)
|
|
20
|
+
|
|
21
|
+
namespace = if parser.current_value == "as"
|
|
22
|
+
parser.next
|
|
23
|
+
parser.parse_ident
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
parser.carry_whitespace_control
|
|
27
|
+
parser.eat(:token_tag_end)
|
|
28
|
+
new(token, tag_name, name, namespace)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (t_token, String, Name, Name?) -> void
|
|
32
|
+
def initialize(token, tag_name, template_name, namespace)
|
|
33
|
+
super(token)
|
|
34
|
+
@tag_name = tag_name
|
|
35
|
+
@template_name = template_name
|
|
36
|
+
@namespace = namespace
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#: (RenderContext, String) -> void
|
|
40
|
+
def render(context, buffer)
|
|
41
|
+
begin
|
|
42
|
+
template = context.env.get_template(
|
|
43
|
+
@template_name.value,
|
|
44
|
+
context: context,
|
|
45
|
+
tag: "import"
|
|
46
|
+
)
|
|
47
|
+
rescue TemplateNotFoundError => e
|
|
48
|
+
raise NoSuchTemplateError.new(
|
|
49
|
+
e.message,
|
|
50
|
+
@template_name.span,
|
|
51
|
+
context.template.source,
|
|
52
|
+
context.template.name
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
ctx = context.copy(
|
|
57
|
+
{},
|
|
58
|
+
block_scope: false,
|
|
59
|
+
template: template
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
template.render_with_context(ctx, +"") # Discard output.
|
|
63
|
+
context.render_score_cumulative += ctx.render_score
|
|
64
|
+
|
|
65
|
+
# Update locals only.
|
|
66
|
+
if @namespace
|
|
67
|
+
context.assign(@namespace.value, ctx.locals) # steep:ignore
|
|
68
|
+
else
|
|
69
|
+
context.locals.update(ctx.locals)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#: (RenderContext) -> Partial?
|
|
74
|
+
def partial(static_context)
|
|
75
|
+
name = static_context.env.to_string(@template_name.evaluate(static_context), static_context)
|
|
76
|
+
|
|
77
|
+
template = static_context.env.get_template(
|
|
78
|
+
name,
|
|
79
|
+
context: static_context,
|
|
80
|
+
tag: "import"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
Partial.new(
|
|
84
|
+
template,
|
|
85
|
+
:shared,
|
|
86
|
+
{}, #: untyped
|
|
87
|
+
Luoma.fnv1a32("im[ort-#{name}")
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class IncludeTag < Markup
|
|
5
|
+
#: (t_token, String, Parser) -> Markup
|
|
6
|
+
def self.parse(token, tag_name, parser)
|
|
7
|
+
name_expr = parser.parse_expression
|
|
8
|
+
|
|
9
|
+
# Leading commas are OK
|
|
10
|
+
parser.next if parser.kind == :token_comma
|
|
11
|
+
|
|
12
|
+
args = parser.parse_keyword_arguments(require_commas: true)
|
|
13
|
+
parser.carry_whitespace_control
|
|
14
|
+
parser.eat(:token_tag_end)
|
|
15
|
+
new(token, tag_name, name_expr, args)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: (t_token, String, Expression, Array[KeywordArgument]) -> void
|
|
19
|
+
def initialize(token, tag_name, template_name, args)
|
|
20
|
+
super(token)
|
|
21
|
+
@tag_name = tag_name
|
|
22
|
+
@template_name = template_name
|
|
23
|
+
@args = args
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: (RenderContext, String) -> void
|
|
27
|
+
def render(context, buffer)
|
|
28
|
+
name = context.env.to_string(@template_name.evaluate(context), context)
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
template = context.env.get_template(
|
|
32
|
+
name,
|
|
33
|
+
context: context,
|
|
34
|
+
tag: "include"
|
|
35
|
+
)
|
|
36
|
+
rescue TemplateNotFoundError => e
|
|
37
|
+
raise NoSuchTemplateError.new(
|
|
38
|
+
e.message,
|
|
39
|
+
@template_name.span,
|
|
40
|
+
context.template.source,
|
|
41
|
+
context.template.name
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
scope = @args.to_h { |arg| [arg.name.value, arg.expression.evaluate(context)] }
|
|
46
|
+
|
|
47
|
+
context.extends(scope, template: template) do
|
|
48
|
+
template.render_with_context(context, buffer, isolated: false)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: () -> Array[Expression]
|
|
53
|
+
def expressions
|
|
54
|
+
@args.map(&:expression)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#: (RenderContext) -> Partial?
|
|
58
|
+
def partial(static_context)
|
|
59
|
+
name = static_context.env.to_string(@template_name.evaluate(static_context), static_context)
|
|
60
|
+
|
|
61
|
+
template = static_context.env.get_template(
|
|
62
|
+
name,
|
|
63
|
+
context: static_context,
|
|
64
|
+
tag: "include"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
scope = @args.map(&:name)
|
|
68
|
+
|
|
69
|
+
Partial.new(
|
|
70
|
+
template,
|
|
71
|
+
:shared,
|
|
72
|
+
scope,
|
|
73
|
+
Luoma.fnv1a32("#{name}-#{scope.map(&:value).join(":")}")
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class OutputStatement < Markup
|
|
5
|
+
def initialize(token, expression)
|
|
6
|
+
super(token)
|
|
7
|
+
@expression = expression
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
#: (RenderContext, String) -> void
|
|
11
|
+
def render(context, buffer)
|
|
12
|
+
buffer << context.env.serialize(@expression.evaluate(context), context)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: () -> Array[Expression]
|
|
16
|
+
def expressions
|
|
17
|
+
[@expression]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class RawTag < Markup
|
|
5
|
+
#: (t_token, String, Parser) -> Markup
|
|
6
|
+
def self.parse(token, tag_name, parser)
|
|
7
|
+
parser.carry_whitespace_control
|
|
8
|
+
parser.eat(:token_tag_end)
|
|
9
|
+
text_token = parser.eat(:token_text)
|
|
10
|
+
parser.eat_empty_tag("endraw")
|
|
11
|
+
new(token, tag_name, Luoma.get_token_value(text_token, parser.source))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#: (t_token, String, String) -> void
|
|
15
|
+
def initialize(token, tag_name, text)
|
|
16
|
+
super(token)
|
|
17
|
+
@tag_name = tag_name
|
|
18
|
+
@text = text
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: (RenderContext, String) -> void
|
|
22
|
+
def render(context, buffer)
|
|
23
|
+
buffer << @text
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|