further differentiate between environments and only evaluate Procs once

This commit is contained in:
James MacAulay
2009-08-07 11:24:15 -04:00
parent c792c29066
commit 167825aa92
4 changed files with 55 additions and 28 deletions
+31 -18
View File
@@ -13,16 +13,15 @@ module Liquid
# #
# context['bob'] #=> nil class Context # context['bob'] #=> nil class Context
class Context class Context
attr_reader :scopes attr_reader :scopes, :errors, :registers, :environments
attr_reader :errors, :registers, :environment
def initialize(environment = {}, instance_assigns = {}, registers = {}, rethrow_errors = false) def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
@environment = environment @environments = [environments].flatten
@scopes = [(instance_assigns || {})] @scopes = [(outer_scope || {})]
@registers = registers @registers = registers
@errors = [] @errors = []
@rethrow_errors = rethrow_errors @rethrow_errors = rethrow_errors
squash_instance_assigns_with_environment squash_instance_assigns_with_environments
end end
def strainer def strainer
@@ -162,13 +161,17 @@ module Liquid
# fetches an object starting at the local scope and then moving up # fetches an object starting at the local scope and then moving up
# the hierachy # the hierachy
def find_variable(key) def find_variable(key)
scope = @scopes.find { |s| s.has_key?(key) } || environment scope = @scopes.find { |s| s.has_key?(key) }
if scope.nil?
if scope[key].is_a?(Proc) @environments.each do |e|
variable = scope[key] = scope[key].call(self) if variable = lookup_and_evaluate(e, key)
else scope = e
variable = scope[key] break
end
end
end end
scope ||= @environments.last || @scopes.last
variable ||= lookup_and_evaluate(scope, key)
variable = variable.to_liquid variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=) variable.context = self if variable.respond_to?(:context=)
@@ -204,8 +207,7 @@ module Liquid
(object.respond_to?(:fetch) and part.is_a?(Integer))) (object.respond_to?(:fetch) and part.is_a?(Integer)))
# if its a proc we will replace the entry with the proc # if its a proc we will replace the entry with the proc
res = object[part] res = lookup_and_evaluate(object, part)
res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
object = res.to_liquid object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and # Some special cases. If the part wasn't in square brackets and
@@ -229,10 +231,21 @@ module Liquid
object object
end end
def squash_instance_assigns_with_environment def lookup_and_evaluate(obj, key)
scopes[0].each_key do |k| if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
if environment.has_key?(k) obj[key] = value.call(self)
scopes[0][k] = environment[k] else
value
end
end
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 end
end end
+2 -5
View File
@@ -93,12 +93,9 @@ module Liquid
when Liquid::Context when Liquid::Context
args.shift args.shift
when Hash when Hash
environment = args.shift Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
environment.merge!(assigns) {|k,v1,v2| v1}
Context.new(environment, instance_assigns, registers, @rethrow_errors)
when nil when nil
environment = assigns.dup Context.new(assigns, instance_assigns, registers, @rethrow_errors)
Context.new(environment, instance_assigns, registers, @rethrow_errors)
else else
raise ArgumentError, "Expect Hash or Liquid::Context as parameter" raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end end
+4 -5
View File
@@ -67,8 +67,7 @@ class ContextTest < Test::Unit::TestCase
include Liquid include Liquid
def setup def setup
@template = Liquid::Template.new @context = Liquid::Context.new
@context = Liquid::Context.new(@template.assigns, @template.registers)
end end
def test_variables def test_variables
@@ -151,11 +150,11 @@ class ContextTest < Test::Unit::TestCase
end end
end end
context = Context.new(@template) context = Context.new
context.add_filters(filter) context.add_filters(filter)
assert_equal 'hi? hi!', context.invoke(:hi, 'hi?') assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
context = Context.new(@template) context = Context.new
assert_raises(FilterNotFound) { assert_raises(FilterNotFound) {
context.invoke(:hi, 'hi?') context.invoke(:hi, 'hi?')
} }
@@ -191,7 +190,7 @@ class ContextTest < Test::Unit::TestCase
end end
end end
context = Context.new(@template) context = Context.new
methods_before = context.strainer.methods.map { |method| method.to_s } methods_before = context.strainer.methods.map { |method| method.to_s }
context.add_filters(filter) context.add_filters(filter)
methods_after = context.strainer.methods.map { |method| method.to_s } methods_after = context.strainer.methods.map { |method| method.to_s }
+18
View File
@@ -54,4 +54,22 @@ class TemplateTest < Test::Unit::TestCase
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render assert_equal 'from persistent assigns', t.parse("{{ foo }}").render
end end
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
t = Template.new
t.assigns['number'] = lambda { @global ||= 0; @global += 1 }
assert_equal '1', t.parse("{{number}}").render
assert_equal '1', t.parse("{{number}}").render
assert_equal '1', t.render
@global = nil
end
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = {'number' => lambda { @global ||= 0; @global += 1 }}
assert_equal '1', t.parse("{{number}}").render(assigns)
assert_equal '1', t.parse("{{number}}").render(assigns)
assert_equal '1', t.render(assigns)
@global = nil
end
end end