Permalink
Browse files

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.
  • Loading branch information...
ged committed Jun 22, 2012
1 parent 1a67c6c commit 290bf87763ab7a20b3c785926860e41ecdc5bb1a
Showing with 81 additions and 46 deletions.
  1. +32 −0 lib/inversion/mixins.rb
  2. +2 −30 lib/inversion/renderstate.rb
  3. +24 −11 lib/inversion/template.rb
  4. +23 −5 spec/inversion/template_spec.rb
View
@@ -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
@@ -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
View
@@ -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
@@ -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( '<?rumple an unknown tag ?>' )
+ }.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( '<?attr something ?>' )
+ tmpl.inspect.should =~ /node_tree/
+ ensure
+ $DEBUG = debuglevel
+ end
end
it "provides accessors for attributes that aren't identifiers in the template" do

0 comments on commit 290bf87

Please sign in to comment.