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

base_scope_depth[RW]
disabled_tags[W]
environment[RW]
environments[R]
errors[RW]
exception_renderer[RW]
filters[W]
global_filter[RW]
partial[RW]
registers[R]
resource_limits[R]
scopes[R]
static_environments[R]
static_registers[R]
strainer[W]
strict_filters[RW]
strict_variables[RW]
template_name[RW]
warnings[W]

Public Class Methods

build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block) click to toggle source

rubocop:disable Metrics/ParameterLists

# File lib/liquid/context.rb, line 21
def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
  new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)
end
new(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default) { |self| ... } click to toggle source
# File lib/liquid/context.rb, line 25
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
  @environment = environment
  @environments = [environments]
  @environments.flatten!

  @static_environments = [static_environments].flatten(1).freeze
  @scopes              = [outer_scope || {}]
  @registers           = registers.is_a?(Registers) ? registers : Registers.new(registers)
  @errors              = []
  @partial             = false
  @strict_variables    = false
  @resource_limits     = resource_limits || ResourceLimits.new(environment.default_resource_limits)
  @base_scope_depth    = 0
  @interrupts          = []
  @filters             = []
  @global_filter       = nil
  @disabled_tags       = {}

  @registers.static[:cached_partials] ||= {}
  @registers.static[:file_system] ||= environment.file_system
  @registers.static[:template_factory] ||= Liquid::TemplateFactory.new

  self.exception_renderer = environment.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

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 178
def [](expression)
  evaluate(Expression.parse(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 166
def []=(key, value)
  @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 71
def add_filters(filters)
  filters = [filters].flatten.compact
  @filters += filters
  @strainer = nil
end
apply_global_filter(obj) click to toggle source
# File lib/liquid/context.rb, line 77
def apply_global_filter(obj)
  global_filter.nil? ? obj : global_filter.call(obj)
end
clear_instance_assigns() click to toggle source
# File lib/liquid/context.rb, line 161
def clear_instance_assigns
  @scopes[0] = {}
end
evaluate(object) click to toggle source
# File lib/liquid/context.rb, line 186
def evaluate(object)
  object.respond_to?(:evaluate) ? object.evaluate(self) : object
end
find_variable(key, raise_on_not_found: true) click to toggle source

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

# File lib/liquid/context.rb, line 191
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

  # update variable's context before invoking #to_liquid
  variable.context = self if variable.respond_to?(:context=)

  liquid_variable = variable.to_liquid

  liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)

  liquid_variable
end
handle_error(e, line_number = nil) click to toggle source
# File lib/liquid/context.rb, line 96
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?() click to toggle source

are there any not handled interrupts?

# File lib/liquid/context.rb, line 82
def interrupt?
  !@interrupts.empty?
end
invoke(method, *args) click to toggle source
# File lib/liquid/context.rb, line 104
def invoke(method, *args)
  strainer.invoke(method, *args).to_liquid
end
key?(key) click to toggle source
# File lib/liquid/context.rb, line 182
def key?(key)
  self[key] != nil
end
lookup_and_evaluate(obj, key, raise_on_not_found: true) click to toggle source
# File lib/liquid/context.rb, line 212
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) click to toggle source

Merge a hash of variables in the current local scope

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

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

# File lib/liquid/context.rb, line 142
def new_isolated_subcontext
  check_overflow

  self.class.build(
    environment: @environment,
    resource_limits: resource_limits,
    static_environments: static_environments,
    registers: Registers.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
pop() click to toggle source

Pop from the stack. use Context#stack instead

# File lib/liquid/context.rb, line 120
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 109
def push(new_scope = {})
  @scopes.unshift(new_scope)
  check_overflow
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
stack(new_scope = {}) { || ... } 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 133
def stack(new_scope = {})
  push(new_scope)
  yield
ensure
  pop
end
strainer() click to toggle source
# File lib/liquid/context.rb, line 63
def strainer
  @strainer ||= @environment.create_strainer(self, @filters)
end
tag_disabled?(tag_name) click to toggle source
# File lib/liquid/context.rb, line 237
def tag_disabled?(tag_name)
  @disabled_tags.fetch(tag_name, 0) > 0
end
warnings() click to toggle source

rubocop:enable Metrics/ParameterLists

# File lib/liquid/context.rb, line 59
def warnings
  @warnings ||= []
end
with_disabled_tags(tag_names) { || ... } click to toggle source
# File lib/liquid/context.rb, line 226
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

Private Instance Methods

check_overflow() click to toggle source
# File lib/liquid/context.rb, line 265
def check_overflow
  raise StackLevelError, "Nesting too deep" if overflow?
end
internal_error() click to toggle source
# File lib/liquid/context.rb, line 273
def internal_error
  # raise and catch to set backtrace and cause on exception
  raise Liquid::InternalError, 'internal'
rescue Liquid::InternalError => exc
  exc
end
overflow?() click to toggle source
# File lib/liquid/context.rb, line 269
def overflow?
  base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
squash_instance_assigns_with_environments() click to toggle source
# File lib/liquid/context.rb, line 280
def squash_instance_assigns_with_environments
  @scopes.last.each_key do |k|
    @environments.each do |env|
      if env.key?(k)
        scopes.last[k] = lookup_and_evaluate(env, k)
        break
      end
    end
  end
end
try_variable_find_in_environments(key, raise_on_not_found:) click to toggle source
# File lib/liquid/context.rb, line 249
def try_variable_find_in_environments(key, raise_on_not_found:)
  @environments.each do |environment|
    found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
    if !found_variable.nil? || @strict_variables && raise_on_not_found
      return found_variable
    end
  end
  @static_environments.each do |environment|
    found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
    if !found_variable.nil? || @strict_variables && raise_on_not_found
      return found_variable
    end
  end
  nil
end