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.
Files changed (152) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data/.rdoc_options +16 -0
  4. data/CHANGELOG.md +5 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +68 -0
  7. data/Rakefile +69 -0
  8. data/Steepfile +41 -0
  9. data/certs/jgrp.pem +27 -0
  10. data/docs/configuration.md +180 -0
  11. data/docs/custom_filters.md +3 -0
  12. data/docs/custom_tags.md +3 -0
  13. data/docs/expressions.md +3 -0
  14. data/docs/extension_types.md +41 -0
  15. data/docs/filter_reference.md +1702 -0
  16. data/docs/index.md +73 -0
  17. data/docs/luoma_for_template_authors.md +3 -0
  18. data/docs/markdown.md +111 -0
  19. data/docs/predicate_reference.md +3 -0
  20. data/docs/static_analysis.md +3 -0
  21. data/docs/tag_reference.md +352 -0
  22. data/docs/template_loaders.md +177 -0
  23. data/docs/undefined_variables.md +3 -0
  24. data/docs-requirements.txt +11 -0
  25. data/docs_/cycle.md +17 -0
  26. data/docs_/font_rendering_example.md +157 -0
  27. data/docs_/header_block_example.md +51 -0
  28. data/docs_/increment_and_decrement.md +49 -0
  29. data/docs_/logo_style_example.md +40 -0
  30. data/docs_/migration.md +11 -0
  31. data/docs_/notes.md +19 -0
  32. data/lib/luoma/cache.rb +80 -0
  33. data/lib/luoma/chain_hash.rb +54 -0
  34. data/lib/luoma/context.rb +227 -0
  35. data/lib/luoma/drop.rb +84 -0
  36. data/lib/luoma/drops/block.rb +33 -0
  37. data/lib/luoma/drops/expression.rb +29 -0
  38. data/lib/luoma/drops/html_safe.rb +36 -0
  39. data/lib/luoma/drops/range.rb +69 -0
  40. data/lib/luoma/drops/undefined.rb +119 -0
  41. data/lib/luoma/environment.rb +479 -0
  42. data/lib/luoma/errors.rb +79 -0
  43. data/lib/luoma/escape.rb +35 -0
  44. data/lib/luoma/expression.rb +1271 -0
  45. data/lib/luoma/filter.rb +97 -0
  46. data/lib/luoma/filters/array.rb +372 -0
  47. data/lib/luoma/filters/date.rb +19 -0
  48. data/lib/luoma/filters/default.rb +16 -0
  49. data/lib/luoma/filters/json.rb +14 -0
  50. data/lib/luoma/filters/math.rb +84 -0
  51. data/lib/luoma/filters/size.rb +13 -0
  52. data/lib/luoma/filters/slice.rb +52 -0
  53. data/lib/luoma/filters/sort.rb +113 -0
  54. data/lib/luoma/filters/string.rb +186 -0
  55. data/lib/luoma/fnv.rb +15 -0
  56. data/lib/luoma/lexer.rb +106 -0
  57. data/lib/luoma/lexer_legacy.rb +396 -0
  58. data/lib/luoma/lexer_unified.rb +279 -0
  59. data/lib/luoma/loader.rb +50 -0
  60. data/lib/luoma/loaders/choice_loader.rb +20 -0
  61. data/lib/luoma/loaders/file_system_loader.rb +75 -0
  62. data/lib/luoma/loaders/mixins.rb +52 -0
  63. data/lib/luoma/markup.rb +109 -0
  64. data/lib/luoma/parser.rb +252 -0
  65. data/lib/luoma/parser_unified.rb +979 -0
  66. data/lib/luoma/predicates/blank.rb +19 -0
  67. data/lib/luoma/predicates/defined.rb +10 -0
  68. data/lib/luoma/predicates/empty.rb +15 -0
  69. data/lib/luoma/predicates/type.rb +35 -0
  70. data/lib/luoma/static_analysis.rb +427 -0
  71. data/lib/luoma/tags/assign.rb +63 -0
  72. data/lib/luoma/tags/break.rb +23 -0
  73. data/lib/luoma/tags/capture.rb +43 -0
  74. data/lib/luoma/tags/case.rb +137 -0
  75. data/lib/luoma/tags/comment.rb +17 -0
  76. data/lib/luoma/tags/continue.rb +23 -0
  77. data/lib/luoma/tags/define.rb +42 -0
  78. data/lib/luoma/tags/else.rb +30 -0
  79. data/lib/luoma/tags/for.rb +108 -0
  80. data/lib/luoma/tags/if.rb +109 -0
  81. data/lib/luoma/tags/import.rb +91 -0
  82. data/lib/luoma/tags/include.rb +77 -0
  83. data/lib/luoma/tags/output.rb +20 -0
  84. data/lib/luoma/tags/raw.rb +26 -0
  85. data/lib/luoma/tags/render.rb +92 -0
  86. data/lib/luoma/tags/with.rb +55 -0
  87. data/lib/luoma/template.rb +178 -0
  88. data/lib/luoma/token.rb +84 -0
  89. data/lib/luoma/version.rb +5 -0
  90. data/lib/luoma.rb +75 -0
  91. data/sig/luoma/cache.rbs +44 -0
  92. data/sig/luoma/chain_hash.rbs +26 -0
  93. data/sig/luoma/context.rbs +115 -0
  94. data/sig/luoma/drop.rbs +44 -0
  95. data/sig/luoma/drops/block.rbs +10 -0
  96. data/sig/luoma/drops/expression.rbs +10 -0
  97. data/sig/luoma/drops/html_safe.rbs +14 -0
  98. data/sig/luoma/drops/range.rbs +15 -0
  99. data/sig/luoma/drops/undefined.rbs +27 -0
  100. data/sig/luoma/environment.rbs +212 -0
  101. data/sig/luoma/errors.rbs +67 -0
  102. data/sig/luoma/escape.rbs +15 -0
  103. data/sig/luoma/expression.rbs +514 -0
  104. data/sig/luoma/filter.rbs +66 -0
  105. data/sig/luoma/filters/array.rbs +74 -0
  106. data/sig/luoma/filters/date.rbs +9 -0
  107. data/sig/luoma/filters/default.rbs +8 -0
  108. data/sig/luoma/filters/json.rbs +7 -0
  109. data/sig/luoma/filters/math.rbs +37 -0
  110. data/sig/luoma/filters/size.rbs +6 -0
  111. data/sig/luoma/filters/slice.rbs +10 -0
  112. data/sig/luoma/filters/sort.rbs +17 -0
  113. data/sig/luoma/filters/string.rbs +103 -0
  114. data/sig/luoma/fnv.rbs +3 -0
  115. data/sig/luoma/lexer.rbs +42 -0
  116. data/sig/luoma/lexer_legacy.rbs +81 -0
  117. data/sig/luoma/lexer_unified.rbs +49 -0
  118. data/sig/luoma/loader.rbs +40 -0
  119. data/sig/luoma/loaders/choice_loader.rbs +9 -0
  120. data/sig/luoma/loaders/file_system_loader.rbs +19 -0
  121. data/sig/luoma/loaders/mixins.rbs +16 -0
  122. data/sig/luoma/markup.rbs +68 -0
  123. data/sig/luoma/parser.rbs +105 -0
  124. data/sig/luoma/parser_unified.rbs +150 -0
  125. data/sig/luoma/predicates/blank.rbs +6 -0
  126. data/sig/luoma/predicates/defined.rbs +6 -0
  127. data/sig/luoma/predicates/empty.rbs +6 -0
  128. data/sig/luoma/predicates/type.rbs +21 -0
  129. data/sig/luoma/static_analysis.rbs +141 -0
  130. data/sig/luoma/tags/assign.rbs +12 -0
  131. data/sig/luoma/tags/break.rbs +11 -0
  132. data/sig/luoma/tags/capture.rbs +14 -0
  133. data/sig/luoma/tags/case.rbs +36 -0
  134. data/sig/luoma/tags/comment.rbs +11 -0
  135. data/sig/luoma/tags/continue.rbs +11 -0
  136. data/sig/luoma/tags/define.rbs +16 -0
  137. data/sig/luoma/tags/else.rbs +17 -0
  138. data/sig/luoma/tags/for.rbs +26 -0
  139. data/sig/luoma/tags/if.rbs +45 -0
  140. data/sig/luoma/tags/import.rbs +13 -0
  141. data/sig/luoma/tags/include.rbs +13 -0
  142. data/sig/luoma/tags/output.rbs +13 -0
  143. data/sig/luoma/tags/raw.rbs +11 -0
  144. data/sig/luoma/tags/render.rbs +13 -0
  145. data/sig/luoma/tags/with.rbs +15 -0
  146. data/sig/luoma/template.rbs +117 -0
  147. data/sig/luoma/token.rbs +81 -0
  148. data/sig/luoma.rbs +11 -0
  149. data/zensical.toml +363 -0
  150. data.tar.gz.sig +0 -0
  151. metadata +233 -0
  152. 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