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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chain_hash"
4
+
5
+ module Luoma
6
+ class RenderContext
7
+ attr_reader :env, :template, :disabled_tags, :context_depth, :assign_score, :assign_score_cumulative,
8
+ :registers, :globals, :scopes, :locals
9
+
10
+ attr_accessor :render_score, :render_score_cumulative, :interrupts
11
+
12
+ #: (Template, ?globals: _Namespace?) -> void
13
+ def initialize(
14
+ template,
15
+ globals: nil,
16
+ disabled_tags: nil,
17
+ context_depth: nil,
18
+ assign_score_carry: nil,
19
+ render_score_carry: nil
20
+ )
21
+ # The template being rendered.
22
+ @template = template
23
+
24
+ # The environment this render context and associated template is
25
+ # bound to.
26
+ @env = template.env
27
+
28
+ # Developer-defined template variables passed down from the environment
29
+ # and template.
30
+ @globals = globals || {} # steep:ignore
31
+
32
+ # The namespace for variables defined with `{% assign %}` and
33
+ # `{% capture %}`.
34
+ @locals = {} #: t_namespace
35
+
36
+ # A namespace for `{% increment %}` and `{% decrement %}`.
37
+ @counters = {} #: t_namespace
38
+
39
+ # The current template scope including `locals`, `globals` and `counters`.
40
+ # New block-scoped namespaces get pushed onto and popped off this chain
41
+ # map.
42
+ #
43
+ # Scopes are searched from right to left. New scopes are push on the
44
+ # right.
45
+ @scopes = ChainHash.new(@counters, @globals, @locals)
46
+
47
+ # Names of tags that are disallowed in this context.
48
+ @disabled_tags = disabled_tags
49
+
50
+ # The number of times this render context has been extended or copied.
51
+ @context_depth = context_depth || 0
52
+
53
+ # A non-specific indicator of template local scope usage.
54
+ @assign_score = 0
55
+
56
+ # A non-specific indicator of template local scope usage for the current
57
+ # template and all partial templates combined.
58
+ @assign_score_cumulative = assign_score_carry || 0
59
+
60
+ # The number of nodes rendered for the current template.
61
+ @render_score = 0
62
+
63
+ # The number of nodes rendered for the current template and all partial
64
+ # templates.
65
+ @render_score_cumulative = render_score_carry || 0
66
+
67
+ # A stack of interrupt signals used by `{% break %}` and
68
+ # `{% continue %}`, for example.
69
+ @interrupts = [] #: Array[Symbol]
70
+
71
+ # Registers supporting stateful tags. It's OK to use this map for storing
72
+ # custom tag state.
73
+ @registers = {}
74
+ end
75
+
76
+ #: (String) -> untyped
77
+ def resolve(name)
78
+ @scopes[name]
79
+ end
80
+
81
+ # Follow path segments starting at `obj`. If the path from `obj` does not
82
+ # exist, :nothing is returned along with the index of the last segment to
83
+ # be successfully resolved.
84
+ #
85
+ #: (untyped, Array[untyped]) -> [untyped, Integer]
86
+ def resolve_path(obj, segments)
87
+ segment_index = -1
88
+
89
+ segments.each do |segment|
90
+ segment_index += 1
91
+
92
+ return [segment.call(self, obj), segment_index] if segment.is_a?(PredicateFunction)
93
+
94
+ obj = case obj
95
+ when Drop
96
+ segment = segment.to_primitive(:string, self) if segment.is_a?(Drop)
97
+ obj.fetch(segment, self, default: :nothing)
98
+ when Array
99
+ segment = segment.to_primitive(:numeric, self) if segment.is_a?(Drop)
100
+ resolve_array_segment(obj, segment)
101
+ when Hash
102
+ segment = segment.to_primitive(:data, self) if segment.is_a?(Drop)
103
+ resolve_hash_segment(obj, segment)
104
+ else
105
+ :nothing
106
+ end
107
+
108
+ return [:nothing, segment_index] if obj == :nothing
109
+ end
110
+
111
+ [obj, segment_index]
112
+ end
113
+
114
+ #: (String, untyped) -> void
115
+ def assign(name, value)
116
+ if @env.max_assign_score || @env.max_assign_score_cumulative
117
+ score = assign_score_of(value)
118
+ @assign_score += score
119
+ @assign_score_cumulative += score
120
+
121
+ if (@env.max_assign_score && @assign_score > @env.max_assign_score) || # steep:ignore
122
+ (@env.max_assign_score_cumulative &&
123
+ @assign_score_cumulative > @env.max_assign_score_cumulative) # steep:ignore
124
+ raise ResourceLimitError.new("memory limits reached")
125
+ end
126
+
127
+ end
128
+
129
+ @locals[name] = value
130
+ end
131
+
132
+ # Return a new render context with render state from this context.
133
+ #
134
+ # The caller is responsible for updating renderScoreCumulative when the new
135
+ # context is no longer needed.
136
+ #: (t_namespace, bool?, Set[String]?, Template?) -> RenderContext
137
+ def copy(namespace, block_scope: nil, disabled_tags: nil, template: nil)
138
+ raise_for_context_depth
139
+
140
+ ctx = RenderContext.new(
141
+ template || @template,
142
+ globals: block_scope ? ChainHash.new(namespace, @scopes) : ChainHash.new(namespace, @globals),
143
+ context_depth: @context_depth + 1,
144
+ assign_score_carry: @assign_score_cumulative,
145
+ render_score_carry: @render_score_cumulative
146
+ )
147
+
148
+ @env.persistent_registers.each { |r| ctx.registers[r] = @registers[r] if @registers.include?(r) }
149
+ ctx
150
+ end
151
+
152
+ # Extend the scope of this context with the given namespace for the
153
+ # duration of a block.
154
+ #
155
+ #: (t_namespace, ?template: Template?) { () -> untyped } -> void
156
+ def extends(namespace, template: nil)
157
+ raise_for_context_depth
158
+
159
+ assign_score_ = @assign_score
160
+ render_score_ = @render_score
161
+ template_ = @template
162
+
163
+ @template = template if template
164
+ @scopes << namespace
165
+ @context_depth += 1
166
+ @assign_score = 0
167
+ @render_score = 0
168
+
169
+ begin
170
+ yield
171
+ ensure
172
+ @template = template_ if template
173
+ @scopes.pop
174
+ @context_depth -= 1
175
+ @assign_score = assign_score_
176
+ @render_score = render_score_
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ #: () -> void
183
+ def raise_for_context_depth
184
+ if @context_depth + 1 > @env.max_context_depth
185
+ raise ContextDepthError.new(
186
+ "maximum context depth reached, possible recursive render"
187
+ )
188
+ end
189
+ end
190
+
191
+ #: (untyped) -> Integer
192
+ def assign_score_of(value)
193
+ case value
194
+ when String
195
+ value.bytesize
196
+ when Array
197
+ value.sum { |i| assign_score_of(i) } + 1
198
+ when Hash
199
+ value.reduce(1) { |a, p| a + assign_score_of(p.first) + assign_score_of(p.last) }
200
+ else
201
+ 1
202
+ end
203
+ end
204
+
205
+ #: (Array[untyped], untyped) -> untyped
206
+ def resolve_array_segment(obj, segment)
207
+ index = if segment.is_a?(Integer)
208
+ segment.negative? && obj.length >= segment.abs ? obj.length + segment : segment
209
+ end
210
+
211
+ if index && index < obj.length
212
+ obj[index]
213
+ else
214
+ :nothing
215
+ end
216
+ end
217
+
218
+ #: (Hash[untyped, untyped], untyped) -> untyped
219
+ def resolve_hash_segment(obj, segment)
220
+ if obj.key?(segment)
221
+ obj[segment]
222
+ else
223
+ :nothing
224
+ end
225
+ end
226
+ end
227
+ end
data/lib/luoma/drop.rb ADDED
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class Drop
5
+ include Enumerable
6
+
7
+ #: (untyped, RenderContext) -> bool
8
+ def key?(obj, context)
9
+ false
10
+ end
11
+
12
+ #: (String, RenderContext) -> untyped
13
+ def fetch(name, context, default: :nothing)
14
+ default
15
+ end
16
+
17
+ def each(&block)
18
+ return enum_for(:each) unless block
19
+
20
+ [].to_enum # steep:ignore
21
+ end
22
+
23
+ def to_a
24
+ each.to_a
25
+ end
26
+
27
+ #: (untyped, RenderContext) -> bool
28
+ def eq?(obj, context)
29
+ false
30
+ end
31
+
32
+ #: (untyped, RenderContext) -> bool
33
+ def lt?(obj, context)
34
+ false
35
+ end
36
+
37
+ #: (untyped, RenderContext) -> bool
38
+ def gt?(obj, context)
39
+ false
40
+ end
41
+
42
+ #: (obj, RenderContext) -> bool
43
+ def contains?(obj, context)
44
+ false
45
+ end
46
+
47
+ # Return the length of this object.
48
+ # Along with `#slice`, `#length` is part of the iterator protocol.
49
+ #: (RenderContext) -> Integer
50
+ def length(context)
51
+ 0
52
+ end
53
+
54
+ # TODO: pass context
55
+ #: (Integer, Integer, Integer) -> Enumerable[untyped]
56
+ def slice(start, stop, step)
57
+ self
58
+ end
59
+
60
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
61
+ def to_primitive(hint, context)
62
+ case hint
63
+ when :data
64
+ nil
65
+ when :numeric
66
+ 0
67
+ when :string
68
+ ""
69
+ when :boolean
70
+ false
71
+ end
72
+ end
73
+
74
+ #: () -> String
75
+ def to_s
76
+ ""
77
+ end
78
+
79
+ #: (RenderContext) -> String?
80
+ def render(context)
81
+ nil
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class BlockDrop < Drop
5
+ #: (t_block) -> void
6
+ def initialize(block)
7
+ super()
8
+ @block = block
9
+ end
10
+
11
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
12
+ def to_primitive(hint, context)
13
+ case hint
14
+ when :numeric
15
+ :nothing
16
+ when :data, :string
17
+ render(context)
18
+ when :boolean
19
+ true
20
+ end
21
+ end
22
+
23
+ def render(context)
24
+ buf = +""
25
+
26
+ context.extends({}) do # For recursive block detection.
27
+ Luoma.render_block(@block, context, buf)
28
+ end
29
+
30
+ buf
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class ExpressionDrop < Drop
5
+ attr_reader :expr
6
+
7
+ #: (LambdaExpr) -> void
8
+ def initialize(expr)
9
+ super()
10
+ @expr = expr
11
+ end
12
+
13
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
14
+ def to_primitive(hint, context)
15
+ case hint
16
+ when :numeric
17
+ :nothing
18
+ when :data, :string
19
+ render(context)
20
+ when :boolean
21
+ true
22
+ end
23
+ end
24
+
25
+ def render(context)
26
+ @expr.to_s
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class HTMLSafe < Drop
5
+ #: (String | HTMLSafe) -> HTMLSafe
6
+ def self.escape(value)
7
+ value.is_a?(String) ? HTMLSafe.new(Luoma.escape(value)) : value
8
+ end
9
+
10
+ #: (String | HTMLSafe) -> HTMLSafe
11
+ def self.from(value)
12
+ value.is_a?(String) ? HTMLSafe.new(value) : value
13
+ end
14
+
15
+ #: (String) -> void
16
+ def initialize(s)
17
+ super()
18
+ @s = s
19
+ end
20
+
21
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
22
+ def to_primitive(hint, context)
23
+ case hint
24
+ when :string, :data
25
+ @s
26
+ else
27
+ :nothing
28
+ end
29
+ end
30
+
31
+ #: (RenderContext) -> String?
32
+ def render(context)
33
+ @s
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ class RangeDrop < Drop
5
+ attr_reader :start, :stop
6
+
7
+ def initialize(start, stop)
8
+ super()
9
+ @start = start
10
+ @stop = stop
11
+ @range = (start..stop)
12
+ end
13
+
14
+ #: (untyped, RenderContext) -> bool
15
+ def key?(obj, context)
16
+ obj == "first" || obj == "last" || obj == "size"
17
+ end
18
+
19
+ #: (String, RenderContext) -> untyped
20
+ def fetch(name, context, default: :nothing)
21
+ case name
22
+ when "first"
23
+ @start
24
+ when "last"
25
+ @stop
26
+ when "size"
27
+ @range.size
28
+ else
29
+ default
30
+ end
31
+ end
32
+
33
+ def to_a
34
+ @range.to_a
35
+ end
36
+
37
+ def each(&)
38
+ @range.each
39
+ end
40
+
41
+ #: (untyped, RenderContext) -> bool
42
+ def eq?(obj, context)
43
+ obj.is_a?(RangeDrop) && obj.start == obj.stop
44
+ end
45
+
46
+ #: (RenderContext) -> Integer
47
+ def length(context) # steep:ignore
48
+ @range.size
49
+ end
50
+
51
+ def slice(start, stop, step)
52
+ @range.to_a[(start...stop).step(step)] # steep:ignore
53
+ end
54
+
55
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
56
+ def to_primitive(hint, context)
57
+ case hint
58
+ when :data
59
+ @range.to_a
60
+ when :numeric
61
+ :nothing
62
+ when :string
63
+ JSON.generate(@range.to_a)
64
+ when :boolean
65
+ false
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luoma
4
+ attr_reader :path
5
+
6
+ # The default and base "undefined" type.
7
+ class UndefinedDrop < Drop
8
+ #: (String, t_token, String, String) -> void
9
+ def initialize(path, token, source, template_name)
10
+ super()
11
+ @path = path
12
+ @token = token
13
+ @source = source
14
+ @template_name = template_name
15
+ end
16
+
17
+ #: (String, RenderContext) -> untyped
18
+ def fetch(name, context, default: :nothing)
19
+ self
20
+ end
21
+
22
+ #: (untyped, RenderContext) -> bool
23
+ def eq?(obj, context)
24
+ obj.is_a?(UndefinedDrop)
25
+ end
26
+
27
+ #: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
28
+ def to_primitive(hint, context)
29
+ case hint
30
+ when :data
31
+ nil
32
+ when :numeric
33
+ :nothing
34
+ when :string
35
+ ""
36
+ when :boolean
37
+ false
38
+ end
39
+ end
40
+ end
41
+
42
+ # An "undefined" type that raises `UndefinedVariableError` in all contexts.
43
+ class StrictUndefinedDrop < UndefinedDrop
44
+ def key?(obj, context)
45
+ error
46
+ end
47
+
48
+ def fetch(name, context, default: :nothing)
49
+ error
50
+ end
51
+
52
+ def each(&)
53
+ error
54
+ end
55
+
56
+ def eq?(obj, context)
57
+ error
58
+ end
59
+
60
+ def lt?(obj, context)
61
+ error
62
+ end
63
+
64
+ def gt?(obj, context)
65
+ error
66
+ end
67
+
68
+ def contains?(obj, context)
69
+ error
70
+ end
71
+
72
+ def length(context)
73
+ error
74
+ end
75
+
76
+ def slice(start, stop, step)
77
+ error
78
+ end
79
+
80
+ def to_primitive(hint, context)
81
+ error
82
+ end
83
+
84
+ def to_s
85
+ error
86
+ end
87
+
88
+ def render(context)
89
+ error
90
+ end
91
+
92
+ protected
93
+
94
+ def error
95
+ raise UndefinedVariableError.new(
96
+ "#{@path.inspect} is undefined",
97
+ @token,
98
+ @source,
99
+ @template_name
100
+ )
101
+ end
102
+ end
103
+
104
+ # An "undefined" type that can be tested for truthiness and compared to
105
+ # other objects without raising an error.
106
+ class FalsyStrictUndefinedDrop < StrictUndefinedDrop
107
+ def eq?(obj, context)
108
+ context.env.nothing?(obj)
109
+ end
110
+
111
+ def to_primitive(hint, context)
112
+ hint == :boolean ? false : error
113
+ end
114
+
115
+ def key?(obj, context)
116
+ false
117
+ end
118
+ end
119
+ end