Skip to content

Commit

Permalink
Fix attribute changes being shared between duplicated templates.
Browse files Browse the repository at this point in the history
- 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 290bf87
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 46 deletions.
32 changes: 32 additions & 0 deletions lib/inversion/mixins.rb
Expand Up @@ -196,5 +196,37 @@ def singleton_attr_accessor( *symbols )


end # module MethodUtilities 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 end # module Inversion


32 changes: 2 additions & 30 deletions lib/inversion/renderstate.rb
Expand Up @@ -8,6 +8,8 @@
# An object that provides an encapsulation of the template's state while it is rendering. # An object that provides an encapsulation of the template's state while it is rendering.
class Inversion::RenderState class Inversion::RenderState
extend Loggability extend Loggability
include Inversion::DataUtilities



# Loggability API -- set up logging through the Inversion module's logger # Loggability API -- set up logging through the Inversion module's logger
log_to :inversion log_to :inversion
Expand Down Expand Up @@ -436,35 +438,5 @@ def method_missing( sym, *args, &block )
end 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 end # class Inversion::RenderState


35 changes: 24 additions & 11 deletions lib/inversion/template.rb
Expand Up @@ -18,6 +18,8 @@
# can be used to populate it when rendered. # can be used to populate it when rendered.
class Inversion::Template class Inversion::Template
extend Loggability extend Loggability
include Inversion::DataUtilities



# Loggability API -- set up logging through the Inversion module's logger # Loggability API -- set up logging through the Inversion module's logger
log_to :inversion log_to :inversion
Expand Down Expand Up @@ -140,14 +142,9 @@ def initialize( source, parsestate=nil, opts={} )
self.log.debug "Parse state is: %p" % [ parsestate ] self.log.debug "Parse state is: %p" % [ parsestate ]
end end


# carry global template options to the parser.
opts = self.class.config.merge( opts )

@source = source @source = source
@parser = Inversion::Parser.new( self, opts )
@node_tree = [] # Parser expects this to always be an Array @node_tree = [] # Parser expects this to always be an Array
@init_options = opts @options = opts
@options = nil
@attributes = {} @attributes = {}
@source_file = nil @source_file = nil
@created_at = Time.now @created_at = Time.now
Expand All @@ -157,6 +154,14 @@ def initialize( source, parsestate=nil, opts={} )
end 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 public
###### ######
Expand Down Expand Up @@ -223,13 +228,19 @@ def render( parentstate=nil, &block )
### Return a human-readable representation of the template object suitable ### Return a human-readable representation of the template object suitable
### for debugging. ### for debugging.
def inspect 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.class.name,
self.object_id / 2, self.object_id / 2,
self.source_file ? self.source_file : "memory", self.source_file ? self.source_file : "memory",
self.attributes, self.attributes.keys,
self.node_tree.map(&:as_comment_body),
self.options, self.options,
nodemap
] ]
end end


Expand All @@ -252,9 +263,11 @@ def method_missing( sym, *args, &block )


### Parse the given +source+ into the template node tree. ### Parse the given +source+ into the template node tree.
def parse( source, parsestate=nil ) 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 @attributes.clear
@node_tree = @parser.parse( source, parsestate ) @node_tree = parser.parse( source, parsestate )
@source = source @source = source


self.define_attribute_accessors self.define_attribute_accessors
Expand Down
28 changes: 23 additions & 5 deletions spec/inversion/template_spec.rb
Expand Up @@ -62,10 +62,16 @@
end end


it "carries its global configuration to the parser" do it "carries its global configuration to the parser" do
Inversion::Template.configure( :i_exist => true ) begin
tmpl = Inversion::Template.new( '' ) orig_config = Inversion::Template.config
parser = tmpl.instance_variable_get( :@parser ) Inversion::Template.configure( :ignore_unknown_tags => false )
parser.options.should have_key( :i_exist )
expect {
Inversion::Template.new( '<?rumple an unknown tag ?>' )
}.to raise_error( Inversion::ParseError, /unknown tag/i )
ensure
Inversion::Template.config = orig_config
end
end end


it "can make an human-readable string version of itself suitable for debugging" do it "can make an human-readable string version of itself suitable for debugging" do
Expand All @@ -74,7 +80,19 @@
tmpl.inspect.should =~ /Inversion::Template/ tmpl.inspect.should =~ /Inversion::Template/
tmpl.inspect.should =~ %r{/tmp/inspect.tmpl} tmpl.inspect.should =~ %r{/tmp/inspect.tmpl}
tmpl.inspect.should =~ /attributes/ 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 end


it "provides accessors for attributes that aren't identifiers in the template" do it "provides accessors for attributes that aren't identifiers in the template" do
Expand Down

0 comments on commit 290bf87

Please sign in to comment.