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
Public Class Methods
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
# 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
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
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
# File lib/liquid/context.rb, line 77 def apply_global_filter(obj) global_filter.nil? ? obj : global_filter.call(obj) end
# File lib/liquid/context.rb, line 161 def clear_instance_assigns @scopes[0] = {} end
# File lib/liquid/context.rb, line 186 def evaluate(object) object.respond_to?(:evaluate) ? object.evaluate(self) : object end
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
# 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
are there any not handled interrupts?
# File lib/liquid/context.rb, line 82 def interrupt? !@interrupts.empty? end
# File lib/liquid/context.rb, line 104 def invoke(method, *args) strainer.invoke(method, *args).to_liquid end
# File lib/liquid/context.rb, line 182 def key?(key) self[key] != nil end
# 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 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
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 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 an interrupt from the stack
# File lib/liquid/context.rb, line 92 def pop_interrupt @interrupts.pop end
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 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
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
# File lib/liquid/context.rb, line 63 def strainer @strainer ||= @environment.create_strainer(self, @filters) end
# File lib/liquid/context.rb, line 237 def tag_disabled?(tag_name) @disabled_tags.fetch(tag_name, 0) > 0 end
rubocop:enable Metrics/ParameterLists
# File lib/liquid/context.rb, line 59 def warnings @warnings ||= [] end
Private Instance Methods
# File lib/liquid/context.rb, line 265 def check_overflow raise StackLevelError, "Nesting too deep" if overflow? end
# 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
# File lib/liquid/context.rb, line 269 def overflow? base_scope_depth + @scopes.length > Block::MAX_DEPTH end
# 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
# 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