Class: Liquid::Context

Inherits:
Object
  • Object
show all
Includes:
ContextProfilingHook
Defined in:
lib/liquid/context.rb

Overview

Context keeps the variable stack and resolves variables, as well as keywords

context['variable'] = 'testing'
context['variable'] #=> 'testing'
context['true']     #=> true
context['10.2232']  #=> 10.2232

context.stack do
   context['bob'] = 'bobsen'
end

context['bob']  #=> nil  class Context

Instance Attribute Summary collapse

Attributes included from ContextProfilingHook

#profiler

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}) {|_self| ... } ⇒ Context

Returns a new instance of Context.

Yields:

  • (_self)

Yield Parameters:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/liquid/context.rb', line 25

def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
  @environments = [environments]
  @environments.flatten!

  @static_environments = [static_environments].flat_map(&:freeze).freeze
  @scopes              = [(outer_scope || {})]
  @registers           = registers
  @errors              = []
  @partial             = false
  @strict_variables    = false
  @resource_limits     = resource_limits || ResourceLimits.new(Template.default_resource_limits)
  @base_scope_depth    = 0
  @interrupts          = []
  @filters             = []
  @global_filter       = nil
  @disabled_tags       = {}

  self.exception_renderer = Template.default_exception_renderer
  if rethrow_errors
    self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
  end

  yield self if block_given?

  # Do this last, since it could result in this object being passed to a Proc in the environment
  squash_instance_assigns_with_environments
end

Instance Attribute Details

#environmentsObject (readonly)

Returns the value of attribute environments.



17
18
19
# File 'lib/liquid/context.rb', line 17

def environments
  @environments
end

#errorsObject

Returns the value of attribute errors.



17
18
19
# File 'lib/liquid/context.rb', line 17

def errors
  @errors
end

#exception_rendererObject

Returns the value of attribute exception_renderer.



18
19
20
# File 'lib/liquid/context.rb', line 18

def exception_renderer
  @exception_renderer
end

#global_filterObject

Returns the value of attribute global_filter.



18
19
20
# File 'lib/liquid/context.rb', line 18

def global_filter
  @global_filter
end

#partialObject

Returns the value of attribute partial.



18
19
20
# File 'lib/liquid/context.rb', line 18

def partial
  @partial
end

#registersObject (readonly)

Returns the value of attribute registers.



17
18
19
# File 'lib/liquid/context.rb', line 17

def registers
  @registers
end

#resource_limitsObject (readonly)

Returns the value of attribute resource_limits.



17
18
19
# File 'lib/liquid/context.rb', line 17

def resource_limits
  @resource_limits
end

#scopesObject (readonly)

Returns the value of attribute scopes.



17
18
19
# File 'lib/liquid/context.rb', line 17

def scopes
  @scopes
end

#static_environmentsObject (readonly)

Returns the value of attribute static_environments.



17
18
19
# File 'lib/liquid/context.rb', line 17

def static_environments
  @static_environments
end

#static_registersObject (readonly)

Returns the value of attribute static_registers.



17
18
19
# File 'lib/liquid/context.rb', line 17

def static_registers
  @static_registers
end

#strainerObject



58
59
60
# File 'lib/liquid/context.rb', line 58

def strainer
  @strainer ||= StrainerFactory.create(self, @filters)
end

#strict_filtersObject

Returns the value of attribute strict_filters.



18
19
20
# File 'lib/liquid/context.rb', line 18

def strict_filters
  @strict_filters
end

#strict_variablesObject

Returns the value of attribute strict_variables.



18
19
20
# File 'lib/liquid/context.rb', line 18

def strict_variables
  @strict_variables
end

#template_nameObject

Returns the value of attribute template_name.



18
19
20
# File 'lib/liquid/context.rb', line 18

def template_name
  @template_name
end

#warningsObject

rubocop:enable Metrics/ParameterLists



54
55
56
# File 'lib/liquid/context.rb', line 54

def warnings
  @warnings ||= []
end

Class Method Details

.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block) ⇒ Object

rubocop:disable Metrics/ParameterLists



21
22
23
# File 'lib/liquid/context.rb', line 21

def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
  new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
end

Instance Method Details

#[](expression) ⇒ Object

Look up variable, either resolve directly after considering the name. We can directly handle Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and later move up to the parent blocks to see if we can resolve the variable somewhere up the tree. Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions

Example:

products == empty #=> products.empty?


172
173
174
# File 'lib/liquid/context.rb', line 172

def [](expression)
  evaluate(Expression.parse(expression))
end

#[]=(key, value) ⇒ Object

Only allow String, Numeric, Hash, Array, Proc, Boolean or Liquid::Drop



160
161
162
# File 'lib/liquid/context.rb', line 160

def []=(key, value)
  @scopes[0][key] = value
end

#add_filters(filters) ⇒ Object

Adds filters to this context.

Note that this does not register the filters with the main Template object. see Template.register_filter for that



66
67
68
69
70
# File 'lib/liquid/context.rb', line 66

def add_filters(filters)
  filters = [filters].flatten.compact
  @filters += filters
  @strainer = nil
end

#apply_global_filter(obj) ⇒ Object



72
73
74
# File 'lib/liquid/context.rb', line 72

def apply_global_filter(obj)
  global_filter.nil? ? obj : global_filter.call(obj)
end

#clear_instance_assignsObject



155
156
157
# File 'lib/liquid/context.rb', line 155

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



180
181
182
# File 'lib/liquid/context.rb', line 180

def evaluate(object)
  object.respond_to?(:evaluate) ? object.evaluate(self) : object
end

#find_variable(key, raise_on_not_found: true) ⇒ Object

Fetches an object starting at the local scope and then moving up the hierachy



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/liquid/context.rb', line 185

def find_variable(key, raise_on_not_found: true)
  # This was changed from find() to find_index() because this is a very hot
  # path and find_index() is optimized in MRI to reduce object allocation
  index = @scopes.find_index { |s| s.key?(key) }

  variable = if index
    lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
  else
    try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
  end

  variable         = variable.to_liquid
  variable.context = self if variable.respond_to?(:context=)

  variable
end

#handle_error(e, line_number = nil) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/liquid/context.rb', line 91

def handle_error(e, line_number = nil)
  e = internal_error unless e.is_a?(Liquid::Error)
  e.template_name ||= template_name
  e.line_number   ||= line_number
  errors.push(e)
  exception_renderer.call(e).to_s
end

#interrupt?Boolean

are there any not handled interrupts?

Returns:

  • (Boolean)


77
78
79
# File 'lib/liquid/context.rb', line 77

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object



99
100
101
# File 'lib/liquid/context.rb', line 99

def invoke(method, *args)
  strainer.invoke(method, *args).to_liquid
end

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/liquid/context.rb', line 176

def key?(key)
  self[key] != nil
end

#lookup_and_evaluate(obj, key, raise_on_not_found: true) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/liquid/context.rb', line 202

def lookup_and_evaluate(obj, key, raise_on_not_found: true)
  if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
    raise Liquid::UndefinedVariable, "undefined variable #{key}"
  end

  value = obj[key]

  if value.is_a?(Proc) && obj.respond_to?(:[]=)
    obj[key] = value.arity == 0 ? value.call : value.call(self)
  else
    value
  end
end

#merge(new_scopes) ⇒ Object

Merge a hash of variables in the current local scope



110
111
112
# File 'lib/liquid/context.rb', line 110

def merge(new_scopes)
  @scopes[0].merge!(new_scopes)
end

#new_isolated_subcontextObject

Creates a new context inheriting resource limits, filters, environment etc., but with an isolated scope.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/liquid/context.rb', line 137

def new_isolated_subcontext
  check_overflow

  self.class.build(
    resource_limits: resource_limits,
    static_environments: static_environments,
    registers: StaticRegisters.new(registers)
  ).tap do |subcontext|
    subcontext.base_scope_depth   = base_scope_depth + 1
    subcontext.exception_renderer = exception_renderer
    subcontext.filters  = @filters
    subcontext.strainer = nil
    subcontext.errors   = errors
    subcontext.warnings = warnings
    subcontext.disabled_tags = @disabled_tags
  end
end

#popObject

Pop from the stack. use Context#stack instead

Raises:



115
116
117
118
# File 'lib/liquid/context.rb', line 115

def pop
  raise ContextError if @scopes.size == 1
  @scopes.shift
end

#pop_interruptObject

pop an interrupt from the stack



87
88
89
# File 'lib/liquid/context.rb', line 87

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

Push new local scope on the stack. use Context#stack instead



104
105
106
107
# File 'lib/liquid/context.rb', line 104

def push(new_scope = {})
  @scopes.unshift(new_scope)
  check_overflow
end

#push_interrupt(e) ⇒ Object

push an interrupt to the stack. this interrupt is considered not handled.



82
83
84
# File 'lib/liquid/context.rb', line 82

def push_interrupt(e)
  @interrupts.push(e)
end

#stack(new_scope = {}) ⇒ Object

Pushes a new local scope on the stack, pops it at the end of the block

Example:

context.stack do
   context['var'] = 'hi'
end

context['var']  #=> nil


128
129
130
131
132
133
# File 'lib/liquid/context.rb', line 128

def stack(new_scope = {})
  push(new_scope)
  yield
ensure
  pop
end

#tag_disabled?(tag_name) ⇒ Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/liquid/context.rb', line 227

def tag_disabled?(tag_name)
  @disabled_tags.fetch(tag_name, 0) > 0
end

#with_disabled_tags(tag_names) ⇒ Object



216
217
218
219
220
221
222
223
224
225
# File 'lib/liquid/context.rb', line 216

def with_disabled_tags(tag_names)
  tag_names.each do |name|
    @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
  end
  yield
ensure
  tag_names.each do |name|
    @disabled_tags[name] -= 1
  end
end