Class: Liquid2::RenderContext
- Inherits:
-
Object
- Object
- Liquid2::RenderContext
- Defined in:
- lib/liquid2/context.rb
Overview
Per render contextual information. A new RenderContext is created automatically
every time Template#render is called.
Constant Summary collapse
- BUILT_IN =
BuiltIn.new
Instance Attribute Summary collapse
-
#disabled_tags ⇒ Object
readonly
Returns the value of attribute disabled_tags.
-
#env ⇒ Object
readonly
Returns the value of attribute env.
-
#globals ⇒ Object
readonly
Returns the value of attribute globals.
-
#interrupts ⇒ Object
Returns the value of attribute interrupts.
-
#tag_namespace ⇒ Object
Returns the value of attribute tag_namespace.
-
#template ⇒ Object
Returns the value of attribute template.
Instance Method Summary collapse
-
#assign(key, value) ⇒ nil
(also: #[]=)
Add key to the local scope with value value.
-
#copy(namespace, template: nil, disabled_tags: nil, carry_loop_iterations: false, block_scope: false) ⇒ Object
Copy this render context and add namespace to the new scope.
- #decrement(name) ⇒ Object
-
#evaluate(obj) ⇒ Object
Evaluate obj as an expression in the render current context.
-
#extend(namespace, template: nil) ⇒ Object
Extend the scope of this context with the given namespace.
-
#fetch(head, path, node:, default: :undefined) ⇒ Object
Resolve path to variable/data in the current scope.
- #get_output_buffer(parent_buffer) ⇒ Object
- #increment(name) ⇒ Object
-
#initialize(template, globals: nil, disabled_tags: nil, copy_depth: 0, parent: nil, loop_carry: 1, local_namespace_carry: 0) ⇒ RenderContext
constructor
A new instance of RenderContext.
-
#loop(namespace, forloop) ⇒ Object
Push a new namespace and forloop for the duration of a block.
-
#parent_loop(node) ⇒ Object
Return the last ForLoop obj if one is available, or an instance of Undefined otherwise.
- #raise_for_loop_limit(length: 1) ⇒ Object
- #raise_for_output_limit(length) ⇒ Object
-
#resolve(name) ⇒ Object?
(also: #[])
Resolve variable name in the current scope.
-
#stop_index(key, index: nil) ⇒ Object
Get or set the stop index of a for loop.
Constructor Details
#initialize(template, globals: nil, disabled_tags: nil, copy_depth: 0, parent: nil, loop_carry: 1, local_namespace_carry: 0) ⇒ RenderContext
Returns a new instance of RenderContext.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/liquid2/context.rb', line 45 def initialize( template, globals: nil, disabled_tags: nil, copy_depth: 0, parent: nil, loop_carry: 1, local_namespace_carry: 0 ) @env = template.env @template = template @globals = globals || {} # steep:ignore UnannotatedEmptyCollection @disabled_tags = || [] @copy_depth = copy_depth @parent = parent @loop_carry = loop_carry # The current size of the local namespace. _size_ is a non-specific measure of the # amount of memory used to store template local variables. @assign_score = local_namespace_carry # A namespace for template local variables (those bound with `assign` or `capture`). @locals = {} # A namespace for `increment` and `decrement` counters. @counters = Hash.new(0) # Namespaces are searched from right to left. When a RenderContext is extended, the # temporary namespace is pushed to the end of this queue. @scope = ReadOnlyChainHash.new(@counters, BUILT_IN, @globals, @locals) # A namespace supporting stateful tags, such as `cycle` and `increment`. # It's OK to use this hash for storing custom tag state. @tag_namespace = { cycles: Hash.new(0), stop_index: {}, extends: Hash.new { |hash, key| hash[key] = [] }, macros: {} } # A stack of forloop objs used for populating forloop.parentloop. @loops = [] # : Array[ForLoop] # A stack of interrupts used to signal breaking and continuing `for` loops. @interrupts = [] # : Array[Symbol] end |
Instance Attribute Details
#disabled_tags ⇒ Object (readonly)
Returns the value of attribute disabled_tags.
32 33 34 |
# File 'lib/liquid2/context.rb', line 32 def @disabled_tags end |
#env ⇒ Object (readonly)
Returns the value of attribute env.
32 33 34 |
# File 'lib/liquid2/context.rb', line 32 def env @env end |
#globals ⇒ Object (readonly)
Returns the value of attribute globals.
32 33 34 |
# File 'lib/liquid2/context.rb', line 32 def globals @globals end |
#interrupts ⇒ Object
Returns the value of attribute interrupts.
33 34 35 |
# File 'lib/liquid2/context.rb', line 33 def interrupts @interrupts end |
#tag_namespace ⇒ Object
Returns the value of attribute tag_namespace.
33 34 35 |
# File 'lib/liquid2/context.rb', line 33 def tag_namespace @tag_namespace end |
#template ⇒ Object
Returns the value of attribute template.
33 34 35 |
# File 'lib/liquid2/context.rb', line 33 def template @template end |
Instance Method Details
#assign(key, value) ⇒ nil Also known as: []=
Add key to the local scope with value value.
101 102 103 104 105 106 107 108 109 |
# File 'lib/liquid2/context.rb', line 101 def assign(key, value) @locals[key] = value if (limit = @env.local_namespace_limit) # Note that this approach does not account for overwriting keys/values # in the local scope. The assign score is always incremented. @assign_score += assign_score(value) raise LiquidResourceLimitError, "local namespace limit reached" if @assign_score > limit end end |
#copy(namespace, template: nil, disabled_tags: nil, carry_loop_iterations: false, block_scope: false) ⇒ Object
Copy this render context and add namespace to the new scope.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/liquid2/context.rb', line 193 def copy(namespace, template: nil, disabled_tags: nil, carry_loop_iterations: false, block_scope: false) if @copy_depth > @env.context_depth_limit raise LiquidResourceLimitError, "context depth limit reached" end loop_carry = if carry_loop_iterations @loops.map(&:length).reduce(@loop_carry) { |acc, value| acc * value } else 1 end scope = if block_scope ReadOnlyChainHash.new(@scope, namespace) else ReadOnlyChainHash.new(@globals, namespace) end context = self.class.new(template || @template, globals: scope, disabled_tags: , copy_depth: @copy_depth + 1, parent: self, loop_carry: loop_carry, local_namespace_carry: @assign_score) @env.persistent_namespaces.each do |ns| context.tag_namespace[ns] = @tag_namespace[ns] if @tag_namespace[ns] end context end |
#decrement(name) ⇒ Object
289 290 291 292 293 |
# File 'lib/liquid2/context.rb', line 289 def decrement(name) val = @counters[name] - 1 @counters[name] = val val end |
#evaluate(obj) ⇒ Object
Evaluate obj as an expression in the render current context.
93 94 95 |
# File 'lib/liquid2/context.rb', line 93 def evaluate(obj) obj.respond_to?(:evaluate) ? obj.evaluate(self) : obj end |
#extend(namespace, template: nil) ⇒ Object
Extend the scope of this context with the given namespace. Expects a block.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/liquid2/context.rb', line 165 def extend(namespace, template: nil) if @scope.size > @env.context_depth_limit raise LiquidResourceLimitError, "context depth limit reached" end template_ = @template @template = template if template @scope << namespace begin yield rescue LiquidError => e e.template_name = template.full_name if template && !e.template_name e.source = template.source if template && !e.source raise ensure @template = template_ @scope.pop end end |
#fetch(head, path, node:, default: :undefined) ⇒ Object
Resolve path to variable/data in the current scope.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/liquid2/context.rb', line 119 def fetch(head, path, node:, default: :undefined) obj = @scope.fetch(evaluate(head)) if obj == :undefined return @env.undefined(head, node: node) if default == :undefined return default end index = 0 while (segment = path[index]) index += 1 segment = evaluate(segment) segment = segment.to_liquid(self) if segment.respond_to?(:to_liquid) if obj.respond_to?(:[]) && ((obj.respond_to?(:key?) && obj.key?(segment)) || (obj.respond_to?(:fetch) && segment.is_a?(Integer))) obj = obj[segment] next end obj = if segment == "size" && obj.respond_to?(:size) obj.size elsif segment == "first" && obj.respond_to?(:first) obj.first elsif segment == "last" && obj.respond_to?(:last) obj.last else return default == :undefined ? @env.undefined(head, node: node) : default end end obj end |
#get_output_buffer(parent_buffer) ⇒ Object
276 277 278 279 280 281 |
# File 'lib/liquid2/context.rb', line 276 def get_output_buffer(parent_buffer) return StringIO.new unless @env.output_stream_limit carry = parent_buffer.is_a?(LimitedStringIO) ? parent_buffer.size : 0 LimitedStringIO.new((@env.output_stream_limit || raise) - carry) end |
#increment(name) ⇒ Object
283 284 285 286 287 |
# File 'lib/liquid2/context.rb', line 283 def increment(name) val = @counters[name] @counters[name] = val + 1 val end |
#loop(namespace, forloop) ⇒ Object
Push a new namespace and forloop for the duration of a block.
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/liquid2/context.rb', line 232 def loop(namespace, forloop) raise_for_loop_limit(length: forloop.length) @loops << forloop @scope << namespace begin yield ensure @scope.pop @loops.pop end end |
#parent_loop(node) ⇒ Object
Return the last ForLoop obj if one is available, or an instance of Undefined otherwise.
245 246 247 248 249 |
# File 'lib/liquid2/context.rb', line 245 def parent_loop(node) return @env.undefined("parentloop", node: node) if @loops.empty? @loops.last end |
#raise_for_loop_limit(length: 1) ⇒ Object
260 261 262 263 264 265 266 267 268 |
# File 'lib/liquid2/context.rb', line 260 def raise_for_loop_limit(length: 1) return nil unless @env.loop_iteration_limit loop_count = @loops.map(&:length).reduce(length * @loop_carry) { |acc, value| acc * value } return unless loop_count > (@env.loop_iteration_limit || raise) raise LiquidResourceLimitError, "loop iteration limit reached" end |
#raise_for_output_limit(length) ⇒ Object
270 271 272 273 274 |
# File 'lib/liquid2/context.rb', line 270 def raise_for_output_limit(length) return unless @env.output_stream_limit && (@env.output_stream_limit || raise) < length raise LiquidResourceLimitError, "output limit reached" end |
#resolve(name) ⇒ Object? Also known as: []
Resolve variable name in the current scope.
158 |
# File 'lib/liquid2/context.rb', line 158 def resolve(name) = @scope.fetch(name) |
#stop_index(key, index: nil) ⇒ Object
Get or set the stop index of a for loop.
252 253 254 255 256 257 258 |
# File 'lib/liquid2/context.rb', line 252 def stop_index(key, index: nil) if index @tag_namespace[:stop_index][key] = index else @tag_namespace[:stop_index].fetch(key, 0) end end |