class Liquid::Context
context #=> nil class Context<br><br>end<br>context = ‘bobsen’
context.stack do<br><br>context #=> 10.2232<br>context #=> true<br>context #=> ‘testing’<br>context = ‘testing’
Context keeps the variable stack and resolves variables, as well as keywords
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
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
def [](expression)
Example:
Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
If no match is made we lookup the variable in the current scope and
Strings, digits, floats and booleans (true,false).
Look up variable, either resolve directly after considering the name. We can directly handle
def [](expression) evaluate(Expression.parse(expression)) end
def []=(key, value)
def []=(key, value) @scopes[0][key] = value end
def add_filters(filters)
Note that this does not register the filters with the main Template object. see Template.register_filter
Adds filters to this context.
def add_filters(filters) filters = [filters].flatten.compact @filters += filters @strainer = nil end
def apply_global_filter(obj)
def apply_global_filter(obj) global_filter.nil? ? obj : global_filter.call(obj) end
def check_overflow
def check_overflow raise StackLevelError, "Nesting too deep" if overflow? end
def clear_instance_assigns
def clear_instance_assigns @scopes[0] = {} end
def evaluate(object)
def evaluate(object) object.respond_to?(:evaluate) ? object.evaluate(self) : object end
def find_variable(key, raise_on_not_found: true)
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
def handle_error(e, line_number = nil)
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
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}) @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(Template.default_resource_limits) @base_scope_depth = 0 @interrupts = [] @filters = [] @global_filter = nil @disabled_tags = {} @registers.static[:cached_partials] ||= {} @registers.static[:file_system] ||= Liquid::Template.file_system @registers.static[:template_factory] ||= Liquid::TemplateFactory.new 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
def internal_error
def internal_error # raise and catch to set backtrace and cause on exception raise Liquid::InternalError, 'internal' rescue Liquid::InternalError => exc exc end
def interrupt?
def interrupt? !@interrupts.empty? end
def invoke(method, *args)
def invoke(method, *args) strainer.invoke(method, *args).to_liquid end
def key?(key)
def key?(key) self[key] != nil end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
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
def merge(new_scopes)
def merge(new_scopes) @scopes[0].merge!(new_scopes) end
def new_isolated_subcontext
Creates a new context inheriting resource limits, filters, environment etc.,
def new_isolated_subcontext check_overflow self.class.build( 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
def overflow?
def overflow? base_scope_depth + @scopes.length > Block::MAX_DEPTH end
def pop
def pop raise ContextError if @scopes.size == 1 @scopes.shift end
def pop_interrupt
def pop_interrupt @interrupts.pop end
def push(new_scope = {})
def push(new_scope = {}) @scopes.unshift(new_scope) check_overflow end
def push_interrupt(e)
def push_interrupt(e) @interrupts.push(e) end
def squash_instance_assigns_with_environments
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 # squash_instance_assigns_with_environments
def stack(new_scope = {})
end
context['var'] = 'hi'
context.stack do
Example:
Pushes a new local scope on the stack, pops it at the end of the block
def stack(new_scope = {}) push(new_scope) yield ensure pop end
def strainer
def strainer @strainer ||= StrainerFactory.create(self, @filters) end
def tag_disabled?(tag_name)
def tag_disabled?(tag_name) @disabled_tags.fetch(tag_name, 0) > 0 end
def try_variable_find_in_environments(key, raise_on_not_found:)
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
def warnings
def warnings @warnings ||= [] end
def with_disabled_tags(tag_names)
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