From 290bf87763ab7a20b3c785926860e41ecdc5bb1a Mon Sep 17 00:00:00 2001 From: Michael Granger Date: Fri, 22 Jun 2012 14:35:41 -0700 Subject: [PATCH] Fix attribute changes being shared between duplicated templates. - Pull up deep_copy function into a mixin - Use deep_copy in a copy constructor on Inversion::Template to make internal datastructures distinct, including @attributes. - Also clean up some old cruft with the way the Parser is used. --- lib/inversion/mixins.rb | 32 ++++++++++++++++++++++++++++++ lib/inversion/renderstate.rb | 32 ++---------------------------- lib/inversion/template.rb | 35 ++++++++++++++++++++++----------- spec/inversion/template_spec.rb | 28 +++++++++++++++++++++----- 4 files changed, 81 insertions(+), 46 deletions(-) diff --git a/lib/inversion/mixins.rb b/lib/inversion/mixins.rb index 0b3293e..d175291 100644 --- a/lib/inversion/mixins.rb +++ b/lib/inversion/mixins.rb @@ -196,5 +196,37 @@ def singleton_attr_accessor( *symbols ) end # module MethodUtilities + + # A collection of data-manipulation functions. + module DataUtilities + + ### Recursively copy the specified +obj+ and return the result. + def deep_copy( obj ) + # self.log.debug "Deep copying: %p" % [ obj ] + + # Handle mocks during testing + return obj if obj.class.name == 'RSpec::Mocks::Mock' + + return case obj + when NilClass, Numeric, TrueClass, FalseClass, Symbol + obj + + when Array + obj.map {|o| deep_copy(o) } + + when Hash + newhash = {} + obj.each do |k,v| + newhash[ deep_copy(k) ] = deep_copy( v ) + end + newhash + + else + obj.clone + end + end + + end # module DataUtilities + end # module Inversion diff --git a/lib/inversion/renderstate.rb b/lib/inversion/renderstate.rb index 5abfca0..285319f 100644 --- a/lib/inversion/renderstate.rb +++ b/lib/inversion/renderstate.rb @@ -8,6 +8,8 @@ # An object that provides an encapsulation of the template's state while it is rendering. class Inversion::RenderState extend Loggability + include Inversion::DataUtilities + # Loggability API -- set up logging through the Inversion module's logger log_to :inversion @@ -436,35 +438,5 @@ def method_missing( sym, *args, &block ) end - ####### - private - ####### - - ### Recursively copy the specified +obj+ and return the result. - def deep_copy( obj ) - # self.log.debug "Deep copying: %p" % [ obj ] - - # Handle mocks during testing - return obj if obj.class.name == 'RSpec::Mocks::Mock' - - return case obj - when NilClass, Numeric, TrueClass, FalseClass, Symbol - obj - - when Array - obj.map {|o| deep_copy(o) } - - when Hash - newhash = {} - obj.each do |k,v| - newhash[ deep_copy(k) ] = deep_copy( v ) - end - newhash - - else - obj.clone - end - end - end # class Inversion::RenderState diff --git a/lib/inversion/template.rb b/lib/inversion/template.rb index 6734d06..0f4ee55 100644 --- a/lib/inversion/template.rb +++ b/lib/inversion/template.rb @@ -18,6 +18,8 @@ # can be used to populate it when rendered. class Inversion::Template extend Loggability + include Inversion::DataUtilities + # Loggability API -- set up logging through the Inversion module's logger log_to :inversion @@ -140,14 +142,9 @@ def initialize( source, parsestate=nil, opts={} ) self.log.debug "Parse state is: %p" % [ parsestate ] end - # carry global template options to the parser. - opts = self.class.config.merge( opts ) - @source = source - @parser = Inversion::Parser.new( self, opts ) @node_tree = [] # Parser expects this to always be an Array - @init_options = opts - @options = nil + @options = opts @attributes = {} @source_file = nil @created_at = Time.now @@ -157,6 +154,14 @@ def initialize( source, parsestate=nil, opts={} ) end + ### Copy constructor -- make copies of some internal data structures, too. + def initialize_copy( other ) + @options = deep_copy( other.options ) + @node_tree = deep_copy( other.node_tree ) + @attributes = deep_copy( other.attributes ) + end + + ###### public ###### @@ -223,13 +228,19 @@ def render( parentstate=nil, &block ) ### Return a human-readable representation of the template object suitable ### for debugging. def inspect - return "#<%s:%08x (loaded from %s) attributes: %p, node_tree: %p, options: %p>" % [ + nodemap = if $DEBUG + ", node_tree: %p" % [ self.node_tree.map(&:as_comment_body) ] + else + '' + end + + return "#<%s:%08x (loaded from %s) attributes: %p, options: %p%s>" % [ self.class.name, self.object_id / 2, self.source_file ? self.source_file : "memory", - self.attributes, - self.node_tree.map(&:as_comment_body), + self.attributes.keys, self.options, + nodemap ] end @@ -252,9 +263,11 @@ def method_missing( sym, *args, &block ) ### Parse the given +source+ into the template node tree. def parse( source, parsestate=nil ) - @options = self.class.config.merge( @init_options ) + opts = self.class.config.merge( self.options ) + parser = Inversion::Parser.new( self, opts ) + @attributes.clear - @node_tree = @parser.parse( source, parsestate ) + @node_tree = parser.parse( source, parsestate ) @source = source self.define_attribute_accessors diff --git a/spec/inversion/template_spec.rb b/spec/inversion/template_spec.rb index da68921..c97fd11 100644 --- a/spec/inversion/template_spec.rb +++ b/spec/inversion/template_spec.rb @@ -62,10 +62,16 @@ end it "carries its global configuration to the parser" do - Inversion::Template.configure( :i_exist => true ) - tmpl = Inversion::Template.new( '' ) - parser = tmpl.instance_variable_get( :@parser ) - parser.options.should have_key( :i_exist ) + begin + orig_config = Inversion::Template.config + Inversion::Template.configure( :ignore_unknown_tags => false ) + + expect { + Inversion::Template.new( '' ) + }.to raise_error( Inversion::ParseError, /unknown tag/i ) + ensure + Inversion::Template.config = orig_config + end end it "can make an human-readable string version of itself suitable for debugging" do @@ -74,7 +80,19 @@ tmpl.inspect.should =~ /Inversion::Template/ tmpl.inspect.should =~ %r{/tmp/inspect.tmpl} tmpl.inspect.should =~ /attributes/ - tmpl.inspect.should =~ /node_tree/ + tmpl.inspect.should_not =~ /node_tree/ + end + + it "includes the node tree in the inspected object if debugging is enabled" do + begin + debuglevel = $DEBUG + $DEBUG = true + + tmpl = Inversion::Template.new( '' ) + tmpl.inspect.should =~ /node_tree/ + ensure + $DEBUG = debuglevel + end end it "provides accessors for attributes that aren't identifiers in the template" do