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,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ module Predicates
5
+ #: (RenderContext, untyped) -> bool
6
+ def self.blank?(context, obj)
7
+ case obj
8
+ when nil
9
+ true
10
+ when String
11
+ obj.strip == ""
12
+ when Array, Hash
13
+ obj.empty?
14
+ else
15
+ obj.respond_to?(:length) ? obj.length.zero? : false # rubocop:disable Style/ZeroLengthPredicate
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ module Predicates
5
+ #: (RenderContext, untyped) -> bool
6
+ def self.defined?(context, obj)
7
+ !(obj == :nothing || obj.is_a?(UndefinedDrop))
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ module Predicates
5
+ #: (RenderContext, untyped) -> bool
6
+ def self.empty?(context, obj)
7
+ case obj
8
+ when Array, Hash, String
9
+ obj.empty?
10
+ else
11
+ obj.respond_to?(:length) ? obj.length.zero? : false # rubocop:disable Style/ZeroLengthPredicate
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ module Predicates
5
+ #: (RenderContext, untyped) -> bool
6
+ def self.null?(context, obj)
7
+ obj.nil?
8
+ end
9
+
10
+ #: (RenderContext, untyped) -> bool
11
+ def self.string?(context, obj)
12
+ obj.is_a?(String)
13
+ end
14
+
15
+ #: (RenderContext, untyped) -> bool
16
+ def self.number?(context, obj)
17
+ obj.is_a?(Numeric)
18
+ end
19
+
20
+ #: (RenderContext, untyped) -> bool
21
+ def self.array?(context, obj)
22
+ obj.is_a?(Array)
23
+ end
24
+
25
+ #: (RenderContext, untyped) -> bool
26
+ def self.object?(context, obj)
27
+ obj.is_a?(Hash)
28
+ end
29
+
30
+ #: (RenderContext, untyped) -> bool
31
+ def self.numeric?(context, obj)
32
+ obj.is_a?(Numeric) || (context.env.to_numeric(obj, context) != :nothing)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,427 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ # Template static analysis.
5
+ module StaticAnalysis
6
+ # The location of a variable, tag or filter.
7
+ class Location
8
+ attr_reader :template, :token
9
+
10
+ #: (Template, t_token) -> void
11
+ def initialize(template, token)
12
+ @template = template
13
+ @token = token
14
+ end
15
+
16
+ # Return the line and column number of the given index.
17
+ #
18
+ #: (Integer) -> [Integer, Integer]
19
+ def line_col(index)
20
+ lines = @template.lines
21
+ cumulative_length = 0
22
+ target_line_index = -1
23
+
24
+ lines.each_with_index do |line, i|
25
+ cumulative_length += line.length
26
+
27
+ if index < cumulative_length
28
+ target_line_index = i
29
+ break
30
+ end
31
+ end
32
+
33
+ raise LuomaError.new("index out of bounds") if target_line_index == -1
34
+
35
+ line_number = target_line_index + 1
36
+ line = lines[target_line_index]
37
+ column_number = index - (cumulative_length - line.length)
38
+ [line_number, column_number]
39
+ end
40
+
41
+ def ==(other)
42
+ self.class == other.class &&
43
+ @template.name == other.template.name &&
44
+ @token == other.token
45
+ end
46
+
47
+ alias eql? ==
48
+
49
+ def hash
50
+ [@template.name, @token].hash
51
+ end
52
+
53
+ # Return the line and column number for the start and end index spanning
54
+ # this location.
55
+ #
56
+ #: () -> [[Integer, Integer],[Integer, Integer]]
57
+ def span
58
+ [line_col(@token[1]), line_col(@token[2])]
59
+ end
60
+
61
+ # Return the substring spanning this location.
62
+ #
63
+ #: () -> String
64
+ def value
65
+ Luoma.get_token_value(@token, @template.source)
66
+ end
67
+ end
68
+
69
+ # A variable as a sequence of segments and its location.
70
+ class StaticVariable
71
+ attr_reader :segments, :location
72
+
73
+ RE_PROPERTY = /\A[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*\Z/
74
+
75
+ def initialize(segments, location)
76
+ @segments = segments
77
+ @location = location
78
+ end
79
+
80
+ def to_s
81
+ segments_to_s(@segments)
82
+ end
83
+
84
+ def ==(other)
85
+ self.class == other.class &&
86
+ @segments == other.segments
87
+ end
88
+
89
+ alias eql? ==
90
+
91
+ def hash
92
+ @segments.hash
93
+ end
94
+
95
+ #: () -> String
96
+ def root
97
+ segment = @segments.first || ""
98
+ segment.is_a?(Array) ? segments_to_s(segment) : segment.to_s
99
+ end
100
+
101
+ protected
102
+
103
+ def segments_to_s(segments)
104
+ head, *rest = segments
105
+
106
+ head.to_s + rest.map do |segment|
107
+ case segment
108
+ when Array
109
+ "[#{segments_to_s(segment)}]"
110
+ when String
111
+ if segment.match?(RE_PROPERTY) || segment.end_with?("?")
112
+ ".#{segment}"
113
+ else
114
+ "[#{segment.inspect}]"
115
+ end
116
+ else
117
+ "[#{segment}]"
118
+ end
119
+ end.join
120
+ end
121
+ end
122
+
123
+ # Helper to manage variable scope during static analysis.
124
+ class StaticScope
125
+ #: (Set[String]) -> void
126
+ def initialize(globals)
127
+ @stack = [globals] #: Array[Set[String]]
128
+ end
129
+
130
+ def include?(key)
131
+ @stack.any? { |scope| scope.include?(key) }
132
+ end
133
+
134
+ def push(scope)
135
+ @stack << scope
136
+ self
137
+ end
138
+
139
+ def pop
140
+ @stack.pop
141
+ end
142
+
143
+ def add(name)
144
+ @stack.first.add(name)
145
+ end
146
+ end
147
+
148
+ # Helper to group variables by their root segment during static analysis.
149
+ class VariableMap
150
+ attr_reader :data
151
+
152
+ def initialize
153
+ @data = {}
154
+ end
155
+
156
+ def [](var)
157
+ key = var.root
158
+ @data[key] = [] unless @data.include?(key)
159
+ @data[key]
160
+ end
161
+
162
+ def add(var)
163
+ send(:[], var) << var
164
+ end
165
+
166
+ #: () -> Hash[String, Array[_Var]]
167
+ def to_h
168
+ result = {} #: Hash[String, Array[untyped]]
169
+
170
+ @data.each do |k, v|
171
+ a = [] #: Array[untyped]
172
+
173
+ v.each do |sv|
174
+ start_line, start_column, end_line, end_column = sv.location.span
175
+
176
+ a << {
177
+ segments: sv.segments,
178
+ path: sv.to_s,
179
+ start_index: sv.location.token[1],
180
+ end_index: sv.location.token[2],
181
+ start_line: start_line,
182
+ start_column: start_column,
183
+ end_line: end_line,
184
+ end_column: end_column,
185
+ value: sv.location.value,
186
+ template_name: sv.location.template.name
187
+ }
188
+ end
189
+
190
+ result[k] = a
191
+ end
192
+
193
+ result
194
+ end
195
+ end
196
+
197
+ # The result of analyzing a template.
198
+ class Result
199
+ attr_reader :variables, :globals, :locals, :filters, :tags
200
+
201
+ def initialize(variables, globals, locals, filters, tags)
202
+ @variables = variables
203
+ @globals = globals
204
+ @locals = locals
205
+ @filters = filters
206
+ @tags = tags
207
+ end
208
+
209
+ def to_h
210
+ {
211
+ variables: @variables,
212
+ globals: @globals,
213
+ locals: @locals,
214
+ filters: @filters,
215
+ tags: @tags
216
+ }
217
+ end
218
+ end
219
+
220
+ def self.analyze(template, include_partials:)
221
+ variables = VariableMap.new
222
+ globals = VariableMap.new
223
+ locals = VariableMap.new
224
+
225
+ # @type var filters: Hash[String, Array[Location]]
226
+ filters = Hash.new { |hash, key| hash[key] = [] }
227
+ # @type var tags: Hash[String, Array[Location]]
228
+ tags = Hash.new { |hash, key| hash[key] = [] }
229
+
230
+ # @type var template_scope: Set[String]
231
+ template_scope = Set[]
232
+ root_scope = StaticScope.new(template_scope)
233
+ static_context = Luoma::RenderContext.new(template)
234
+
235
+ # Names of partial templates that have already been analyzed.
236
+ # Keys are hashes of partial template name and its arguments. If we've
237
+ # visited a template before but with different arguments, later visits
238
+ # only record global variables so as not to double count locals, filters
239
+ # and tags.
240
+ seen = Hash.new { |hash, key| hash[key] = Set[] } #: Hash[String,Set[untyped]]
241
+
242
+ # @type var visit: ^(Luoma::Markup, Luoma::Template, StaticScope, bool) -> void
243
+ visit = lambda do |node, template, scope, just_globals|
244
+ seen[template.name].add(nil) if !template.name.empty? && !just_globals
245
+
246
+ # Update tags
247
+ # Markup with empty `tag_name` is silenced.
248
+ tags[node.tag_name] << Location.new(template, node.token) if !just_globals && !node.tag_name.empty?
249
+
250
+ # Update variables from node.expressions
251
+ node.expressions.each do |expr|
252
+ if expr.is_a?(Luoma::Expression)
253
+ analyze_variables(
254
+ expr,
255
+ template,
256
+ scope,
257
+ globals,
258
+ just_globals ? VariableMap.new : variables,
259
+ static_context
260
+ )
261
+ end
262
+
263
+ next if just_globals
264
+
265
+ # Update filters from expr
266
+ extract_filters(expr, template, static_context).each do |name, span|
267
+ filters[name] << span
268
+ end
269
+ end
270
+
271
+ # Update template scope from node.template_scope
272
+ node.template_scope.each do |name|
273
+ scope.add(name.value)
274
+ locals.add(StaticVariable.new([name.value], Location.new(template, name.token)))
275
+ end
276
+
277
+ # Set block scope before descending into child nodes.
278
+ scope.push(node.block_scope.to_set(&:value))
279
+
280
+ node.children(static_context).each do |child|
281
+ visit.call(child, template, scope, just_globals)
282
+ end
283
+
284
+ scope.pop
285
+
286
+ # Descend into partial templates?
287
+ partial = include_partials && node.partial(static_context)
288
+ if partial.is_a?(Luoma::Partial)
289
+ name = partial.template.name
290
+ just_globals_ = seen.include?(name)
291
+
292
+ unless seen[name].include?(partial.key)
293
+ seen[name].add(partial.key)
294
+
295
+ partial_scope = if partial.scope_kind == :isolated
296
+ StaticScope.new(partial.in_scope.to_set(&:value))
297
+ else
298
+ root_scope.push(partial.in_scope.to_set(&:value))
299
+ end
300
+
301
+ partial.template.nodes.each do |p_node|
302
+ visit.call(p_node, partial.template, partial_scope, just_globals_) if p_node.is_a?(Luoma::Markup)
303
+ end
304
+
305
+ partial_scope.pop if partial.scope_kind == :isolated
306
+ end
307
+ end
308
+ end
309
+
310
+ template.nodes.each do |node|
311
+ visit.call(node, template, root_scope, false) if node.is_a?(Luoma::Markup)
312
+ end
313
+
314
+ Result.new(
315
+ variables.to_h,
316
+ globals.to_h,
317
+ locals.to_h,
318
+ to_locations(filters),
319
+ to_locations(tags)
320
+ )
321
+ end
322
+
323
+ #: (Luoma::_Traversable, Luoma::Template, Luoma::RenderContext) -> Array[[String, Location]]
324
+ def self.extract_filters(expression, template, static_context)
325
+ filters = [] # : Array[[String, Location]]
326
+
327
+ if expression.is_a?(Luoma::Filter) # steep:ignore
328
+ filters << [expression.name.to_s, Location.new(template, expression.span)]
329
+ end
330
+
331
+ expression.children.each do |expr|
332
+ filters.concat(extract_filters(expr, template, static_context))
333
+ end
334
+
335
+ filters
336
+ end
337
+
338
+ # (Luoma:_Traversable, Luoma::Template, StaticScope, VariableMap, VariableMap, RenderContext) -> void
339
+ def self.analyze_variables(expression, template, scope, globals, variables, static_context)
340
+ if expression.is_a?(Luoma::Variable) # steep:ignore
341
+ token = if expression.segments.last.is_a?(Luoma::Predicate)
342
+ # Don't include a the trailing predicate.
343
+ if expression.segments.length > 1
344
+ Luoma.span(expression.root.span,
345
+ expression.segments[-2].span)
346
+ else
347
+ expression.root.span
348
+ end
349
+ else
350
+ expression.span
351
+ end
352
+
353
+ var = StaticVariable.new(segments(expression, template), Location.new(template, token))
354
+
355
+ variables.add(var)
356
+ globals.add(var) unless scope.include?(expression.root.to_s)
357
+ end
358
+
359
+ # For lambda expressions.
360
+ child_scope = expression.is_a?(Luoma::Expression) ? expression.scope : [] # steep:ignore
361
+ scope.push(child_scope.to_set(&:value))
362
+
363
+ expression.children.each do |expr|
364
+ analyze_variables(
365
+ expr,
366
+ template,
367
+ scope,
368
+ globals,
369
+ variables,
370
+ static_context
371
+ )
372
+ end
373
+
374
+ scope.pop
375
+ end
376
+
377
+ #: (Luoma::Variable, Luoma::Template) -> Array[untyped]
378
+ def self.segments(var, template)
379
+ segments_ = [] #: Array[untyped]
380
+
381
+ segments_ << if var.root.is_a?(Luoma::Variable)
382
+ segments(var.root, template)
383
+ else
384
+ var.root.value
385
+ end
386
+
387
+ var.segments.each do |s|
388
+ segments_ << if s.is_a?(Luoma::Variable)
389
+ segments(s, template)
390
+ elsif s.is_a?(Luoma::Predicate)
391
+ next
392
+ else
393
+ s.value
394
+ end
395
+ end
396
+
397
+ segments_
398
+ end
399
+
400
+ #: (Hash[String, Array[Location]]) -> Hash[String, Array[untyped]]
401
+ def self.to_locations(map)
402
+ result = {} #: Hash[String, Array[untyped]]
403
+
404
+ map.each do |k, v|
405
+ a = [] #: Array[untyped]
406
+
407
+ v.each do |l|
408
+ span = l.span
409
+ a << {
410
+ start_index: l.token[1],
411
+ end_index: l.token[2],
412
+ start_line: span.first.first,
413
+ start_column: span.first.last,
414
+ end_line: span.last.first,
415
+ end_column: span.last.last,
416
+ value: l.value,
417
+ template_name: l.template.name
418
+ }
419
+ end
420
+
421
+ result[k] = a
422
+ end
423
+
424
+ result
425
+ end
426
+ end
427
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class AssignTag < Markup
5
+ attr_reader :bindings
6
+
7
+ #: (t_token, String, Parser) -> Markup
8
+ def self.parse(token, tag_name, parser)
9
+ bindings = [] #: Array[Item]
10
+
11
+ name = parser.parse_ident
12
+ assign_token = parser.eat(
13
+ :token_assign,
14
+ message: "bad identifier or missing assignment operator"
15
+ )
16
+
17
+ bindings << Item.new(assign_token, name, parser.parse_expression)
18
+
19
+ until parser.kind != :token_comma
20
+ parser.next
21
+ name = parser.parse_ident
22
+ assign_token = parser.eat(
23
+ :token_assign,
24
+ message: "bad identifier or missing assignment operator"
25
+ )
26
+
27
+ bindings << Item.new(assign_token, name, parser.parse_expression)
28
+ end
29
+
30
+ parser.carry_whitespace_control
31
+ parser.eat(
32
+ :token_tag_end,
33
+ message: "bad expression or missing markup delimiter"
34
+ )
35
+ new(token, tag_name, bindings)
36
+ end
37
+
38
+ #: (t_token, String, Array[Item]) -> void
39
+ def initialize(token, tag_name, bindings)
40
+ super(token)
41
+ @blank = true
42
+ @tag_name = tag_name
43
+ @bindings = bindings
44
+ end
45
+
46
+ #: (RenderContext, String) -> void
47
+ def render(context, buffer)
48
+ @bindings.each do |binding|
49
+ context.assign(binding.key.value, binding.expr.evaluate(context))
50
+ end
51
+ end
52
+
53
+ #: () -> Array[Expression]
54
+ def expressions
55
+ @bindings.map(&:expr)
56
+ end
57
+
58
+ #: () -> Array[Name]
59
+ def template_scope
60
+ @bindings.map(&:key)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class BreakTag < 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(:break)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class CaptureTag < Markup
5
+ END_CAPTURE_BLOCK = Set["endcapture"]
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_CAPTURE_BLOCK)
13
+ parser.eat_empty_tag("endcapture")
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
+ end
25
+
26
+ #: (RenderContext, String) -> void
27
+ def render(context, buffer)
28
+ buf = +""
29
+ Luoma.render_block(@block, context, buf)
30
+ context.assign(@identifier.value, buf)
31
+ end
32
+
33
+ #: (RenderContext) -> Array[Markup]
34
+ def children(static_context)
35
+ @block.grep_v(String) #: Array[Markup]
36
+ end
37
+
38
+ #: () -> Array[Name]
39
+ def template_scope
40
+ [@identifier]
41
+ end
42
+ end
43
+ end