class Liquid::Context

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

Attributes

environments[R]
errors[R]
exception_handler[RW]
registers[R]
resource_limits[R]
scopes[R]

Public Class Methods

new(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) click to toggle source
# File lib/liquid/context.rb, line 19
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
  @environments     = [environments].flatten
  @scopes           = [(outer_scope || {})]
  @registers        = registers
  @errors           = []
  @resource_limits  = resource_limits || Template.default_resource_limits.dup
  @resource_limits[:render_score_current] = 0
  @resource_limits[:assign_score_current] = 0
  @parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
  squash_instance_assigns_with_environments

  @this_stack_used = false

  if rethrow_errors
    self.exception_handler = ->(e) { true }
  end

  @interrupts = []
  @filters = []
end

Public Instance Methods

[](expression) click to toggle source

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?
# File lib/liquid/context.rb, line 172
def [](expression)
  evaluate(@parsed_expression[expression])
end
[]=(key, value) click to toggle source

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

# File lib/liquid/context.rb, line 156
def []=(key, value)
  unless @this_stack_used
    @this_stack_used = true
    push({})
  end
  @scopes[0][key] = value
end
add_filters(filters) click to toggle source

Adds filters to this context.

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

# File lib/liquid/context.rb, line 62
def add_filters(filters)
  filters = [filters].flatten.compact
  filters.each do |f|
    raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
    Strainer.add_known_filter(f)
  end

  # If strainer is already setup then there's no choice but to use a runtime
  # extend call. If strainer is not yet created, we can utilize strainers
  # cached class based API, which avoids busting the method cache.
  if @strainer
    filters.each do |f|
      strainer.extend(f)
    end
  else
    @filters.concat filters
  end
end
clear_instance_assigns() click to toggle source
# File lib/liquid/context.rb, line 151
def clear_instance_assigns
  @scopes[0] = {}
end
evaluate(object) click to toggle source
# File lib/liquid/context.rb, line 180
def evaluate(object)
  object.respond_to?(:evaluate) ? object.evaluate(self) : object
end
find_variable(key) click to toggle source

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

# File lib/liquid/context.rb, line 185
def find_variable(key)

  # 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.has_key?(key) }
  scope = @scopes[index] if index

  variable = nil

  if scope.nil?
    @environments.each do |e|
      variable = lookup_and_evaluate(e, key)
      unless variable.nil?
        scope = e
        break
      end
    end
  end

  scope     ||= @environments.last || @scopes.last
  variable  ||= lookup_and_evaluate(scope, key)

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

  return variable
end
handle_error(e, token=nil) click to toggle source
# File lib/liquid/context.rb, line 97
def handle_error(e, token=nil)
  if e.is_a?(Liquid::Error)
    e.set_line_number_from_token(token)
  end

  errors.push(e)
  raise if exception_handler && exception_handler.call(e)
  Liquid::Error.render(e)
end
has_interrupt?() click to toggle source

are there any not handled interrupts?

# File lib/liquid/context.rb, line 82
def has_interrupt?
  !@interrupts.empty?
end
has_key?(key) click to toggle source
# File lib/liquid/context.rb, line 176
def has_key?(key)
  self[key] != nil
end
increment_used_resources(key, obj) click to toggle source
# File lib/liquid/context.rb, line 40
def increment_used_resources(key, obj)
  @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
    obj.length
  else
    1
  end
end
invoke(method, *args) click to toggle source
# File lib/liquid/context.rb, line 107
def invoke(method, *args)
  strainer.invoke(method, *args).to_liquid
end
lookup_and_evaluate(obj, key) click to toggle source
# File lib/liquid/context.rb, line 213
def lookup_and_evaluate(obj, key)
  if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
    obj[key] = (value.arity == 0) ? value.call : value.call(self)
  else
    value
  end
end
merge(new_scopes) click to toggle source

Merge a hash of variables in the current local scope

# File lib/liquid/context.rb, line 118
def merge(new_scopes)
  @scopes[0].merge!(new_scopes)
end
pop() click to toggle source

Pop from the stack. use Context#stack instead

# File lib/liquid/context.rb, line 123
def pop
  raise ContextError if @scopes.size == 1
  @scopes.shift
end
pop_interrupt() click to toggle source

pop an interrupt from the stack

# File lib/liquid/context.rb, line 92
def pop_interrupt
  @interrupts.pop
end
push(new_scope={}) click to toggle source

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

# File lib/liquid/context.rb, line 112
def push(new_scope={})
  @scopes.unshift(new_scope)
  raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
end
push_interrupt(e) click to toggle source

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

# File lib/liquid/context.rb, line 87
def push_interrupt(e)
  @interrupts.push(e)
end
resource_limits_reached?() click to toggle source
# File lib/liquid/context.rb, line 48
def resource_limits_reached?
  (@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
  (@resource_limits[:render_score_limit]  && @resource_limits[:render_score_current]  > @resource_limits[:render_score_limit] ) ||
  (@resource_limits[:assign_score_limit]  && @resource_limits[:assign_score_current]  > @resource_limits[:assign_score_limit] )
end
stack(new_scope=nil) { || ... } click to toggle source

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
# File lib/liquid/context.rb, line 136
def stack(new_scope=nil)
  old_stack_used = @this_stack_used
  if new_scope
    push(new_scope)
    @this_stack_used = true
  else
    @this_stack_used = false
  end

  yield
ensure
  pop if @this_stack_used
  @this_stack_used = old_stack_used
end
strainer() click to toggle source
# File lib/liquid/context.rb, line 54
def strainer
  @strainer ||= Strainer.create(self, @filters)
end

Private Instance Methods

squash_instance_assigns_with_environments() click to toggle source
# File lib/liquid/context.rb, line 222
def squash_instance_assigns_with_environments
  @scopes.last.each_key do |k|
    @environments.each do |env|
      if env.has_key?(k)
        scopes.last[k] = lookup_and_evaluate(env, k)
        break
      end
    end
  end
end