mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-15 08:50:45 -07:00
Stack scope by variable and not by level
This commit is contained in:
+26
-13
@@ -88,7 +88,7 @@ module Liquid
|
|||||||
|
|
||||||
# Merge a hash of variables in the current local scope
|
# Merge a hash of variables in the current local scope
|
||||||
def merge(new_scopes)
|
def merge(new_scopes)
|
||||||
@scope.merge!(new_scopes)
|
new_scopes.each { |k, v| self[k] = v }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Pushes a new local scope on the stack, pops it at the end of the block
|
# Pushes a new local scope on the stack, pops it at the end of the block
|
||||||
@@ -100,25 +100,19 @@ module Liquid
|
|||||||
#
|
#
|
||||||
# context['var] #=> nil
|
# context['var] #=> nil
|
||||||
def stack(*variable_names)
|
def stack(*variable_names)
|
||||||
previous_values = {}
|
|
||||||
variable_names.each do |variable_name|
|
|
||||||
previous_values[variable_name] = @scope[variable_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
@stack_level += 1
|
@stack_level += 1
|
||||||
raise StackLevelError, "Nesting too deep".freeze if @stack_level > Block::MAX_DEPTH
|
raise StackLevelError, "Nesting too deep".freeze if @stack_level > Block::MAX_DEPTH
|
||||||
|
|
||||||
begin
|
begin
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
@scope.merge!(previous_values)
|
|
||||||
@stack_level -= 1
|
@stack_level -= 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
@scope[key] = value
|
(@scope[key] ||= [nil]) << value
|
||||||
end
|
end
|
||||||
|
|
||||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||||
@@ -133,6 +127,19 @@ module Liquid
|
|||||||
evaluate(Expression.parse(expression))
|
evaluate(Expression.parse(expression))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unset(key)
|
||||||
|
if @scope[key].size <= 1
|
||||||
|
@scope.delete(key)
|
||||||
|
else
|
||||||
|
@scope[key].pop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_root(key, val)
|
||||||
|
@scope[key] ||= []
|
||||||
|
@scope[key][0] = val
|
||||||
|
end
|
||||||
|
|
||||||
def key?(key)
|
def key?(key)
|
||||||
self[key] != nil
|
self[key] != nil
|
||||||
end
|
end
|
||||||
@@ -143,8 +150,10 @@ module Liquid
|
|||||||
|
|
||||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||||
def find_variable(key, raise_on_not_found: true)
|
def find_variable(key, raise_on_not_found: true)
|
||||||
|
trigger = false
|
||||||
value = @scope[key]
|
value = @scope[key]
|
||||||
scope = @scope if value != nil
|
scope = @scope unless value.nil?
|
||||||
|
trigger = true unless value.nil?
|
||||||
|
|
||||||
if scope.nil?
|
if scope.nil?
|
||||||
index = @environments.find_index do |e|
|
index = @environments.find_index do |e|
|
||||||
@@ -157,7 +166,7 @@ module Liquid
|
|||||||
scope = @environments[index || -1]
|
scope = @environments[index || -1]
|
||||||
end
|
end
|
||||||
|
|
||||||
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
variable ||= lookup_and_evaluate(scope, key, trigger, raise_on_not_found: raise_on_not_found)
|
||||||
|
|
||||||
variable = variable.to_liquid
|
variable = variable.to_liquid
|
||||||
variable.context = self if variable.respond_to?(:context=)
|
variable.context = self if variable.respond_to?(:context=)
|
||||||
@@ -165,12 +174,16 @@ module Liquid
|
|||||||
variable
|
variable
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
def lookup_and_evaluate(obj, key, trigger = false, raise_on_not_found: true)
|
||||||
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
||||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
value = obj[key]
|
value = if trigger == true
|
||||||
|
obj[key][-1]
|
||||||
|
else
|
||||||
|
obj[key]
|
||||||
|
end
|
||||||
|
|
||||||
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
||||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||||
@@ -192,7 +205,7 @@ module Liquid
|
|||||||
@scope.each_key do |k|
|
@scope.each_key do |k|
|
||||||
@environments.each do |env|
|
@environments.each do |env|
|
||||||
if env.key?(k)
|
if env.key?(k)
|
||||||
@scope[k] = lookup_and_evaluate(env, k)
|
@scope[k] = [lookup_and_evaluate(env, k)]
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module Liquid
|
|||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context[@to] = val
|
context.set_root(@to, val)
|
||||||
context.resource_limits.assign_score += assign_score_of(val)
|
context.resource_limits.assign_score += assign_score_of(val)
|
||||||
''.freeze
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module Liquid
|
|||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
output = super
|
output = super
|
||||||
context[@to] = output
|
context.set_root(@to, output)
|
||||||
context.resource_limits.assign_score += output.bytesize
|
context.resource_limits.assign_score += output.bytesize
|
||||||
''.freeze
|
''.freeze
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ module Liquid
|
|||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
result << @for_block.render(context)
|
result << @for_block.render(context)
|
||||||
loop_vars.send(:increment!)
|
loop_vars.send(:increment!)
|
||||||
|
context.unset(@variable_name)
|
||||||
|
|
||||||
# Handle any interrupts if they exist.
|
# Handle any interrupts if they exist.
|
||||||
if context.interrupt?
|
if context.interrupt?
|
||||||
@@ -176,6 +177,8 @@ module Liquid
|
|||||||
next if interrupt.is_a? ContinueInterrupt
|
next if interrupt.is_a? ContinueInterrupt
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context.unset('forloop'.freeze)
|
||||||
ensure
|
ensure
|
||||||
for_stack.pop
|
for_stack.pop
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ HERE
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_overwriting_internal_variable
|
def test_overwriting_internal_variable
|
||||||
template = <<-END_TEMPLATE
|
template = <<-HEREDOC
|
||||||
{% assign forloop = 'first' %}
|
{% assign forloop = 'first' %}
|
||||||
|
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
@@ -379,7 +379,7 @@ HERE
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{{ forloop }}
|
{{ forloop }}
|
||||||
END_TEMPLATE
|
HEREDOC
|
||||||
|
|
||||||
result = Liquid::Template.parse(template).render('items' => '1')
|
result = Liquid::Template.parse(template).render('items' => '1')
|
||||||
assert_equal 'Liquid::ForloopDrop Liquid::ForloopDrop second', result.split.map(&:strip).join(' ')
|
assert_equal 'Liquid::ForloopDrop Liquid::ForloopDrop second', result.split.map(&:strip).join(' ')
|
||||||
|
|||||||
@@ -163,35 +163,6 @@ class ContextUnitTest < Minitest::Test
|
|||||||
assert_equal 'test', @context['test']
|
assert_equal 'test', @context['test']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_add_item_in_inner_scope
|
|
||||||
@context.stack('test') do
|
|
||||||
@context['test'] = 'test'
|
|
||||||
assert_equal 'test', @context['test']
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_nil @context['test']
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_scopes
|
|
||||||
@context['test'] = 1
|
|
||||||
|
|
||||||
@context.stack('test') do
|
|
||||||
assert_equal 1, @context['test']
|
|
||||||
@context['test'] = 2
|
|
||||||
assert_equal 2, @context['test']
|
|
||||||
|
|
||||||
@context.stack('test') do
|
|
||||||
assert_equal 2, @context['test']
|
|
||||||
@context['test'] = 3
|
|
||||||
assert_equal 3, @context['test']
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal 2, @context['test']
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal 1, @context['test']
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hierachical_data
|
def test_hierachical_data
|
||||||
@context['hash'] = { "name" => 'tobi' }
|
@context['hash'] = { "name" => 'tobi' }
|
||||||
assert_equal 'tobi', @context['hash.name']
|
assert_equal 'tobi', @context['hash.name']
|
||||||
|
|||||||
Reference in New Issue
Block a user