annotate .svn/pristine/b8/b871d50d611e927908723febabdcb8cd0183c9ab.svn-base @ 1519:afce8026aaeb redmine-2.4-integration

Merge from branch "live"
author Chris Cannam
date Tue, 09 Sep 2014 09:34:53 +0100
parents cbb26bc654de
children
rev   line source
Chris@909 1 # tree.rb
Chris@909 2 #
Chris@909 3 # $Revision: 1.29 $ by $Author: anupamsg $
Chris@909 4 # $Name: $
Chris@909 5 #
Chris@909 6 # = tree.rb - Generic Multi-way Tree implementation
Chris@909 7 #
Chris@909 8 # Provides a generic tree data structure with ability to
Chris@909 9 # store keyed node elements in the tree. The implementation
Chris@909 10 # mixes in the Enumerable module.
Chris@909 11 #
Chris@909 12 # Author:: Anupam Sengupta (anupamsg@gmail.com)
Chris@909 13 #
Chris@909 14
Chris@909 15 # Copyright (c) 2006, 2007 Anupam Sengupta
Chris@909 16 #
Chris@909 17 # All rights reserved.
Chris@909 18 #
Chris@909 19 # Redistribution and use in source and binary forms, with or without modification,
Chris@909 20 # are permitted provided that the following conditions are met:
Chris@909 21 #
Chris@909 22 # - Redistributions of source code must retain the above copyright notice, this
Chris@909 23 # list of conditions and the following disclaimer.
Chris@909 24 #
Chris@909 25 # - Redistributions in binary form must reproduce the above copyright notice, this
Chris@909 26 # list of conditions and the following disclaimer in the documentation and/or
Chris@909 27 # other materials provided with the distribution.
Chris@909 28 #
Chris@909 29 # - Neither the name of the organization nor the names of its contributors may
Chris@909 30 # be used to endorse or promote products derived from this software without
Chris@909 31 # specific prior written permission.
Chris@909 32 #
Chris@909 33 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
Chris@909 34 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
Chris@909 35 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
Chris@909 36 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
Chris@909 37 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
Chris@909 38 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
Chris@909 39 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
Chris@909 40 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
Chris@909 41 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
Chris@909 42 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Chris@909 43 #
Chris@909 44
Chris@909 45 # This module provides a TreeNode class which is the primary class for all
Chris@909 46 # nodes represented in the Tree.
Chris@909 47 # This module mixes in the Enumerable module.
Chris@909 48 module Tree
Chris@909 49
Chris@909 50 # Rubytree Package Version
Chris@909 51 VERSION = '0.5.2'
Chris@909 52
Chris@909 53 # == TreeNode Class Description
Chris@909 54 #
Chris@909 55 # The node class for the tree representation. the nodes are named and have a
Chris@909 56 # place-holder for the node data (i.e., the `content' of the node). The node
Chris@909 57 # names are expected to be unique. In addition, the node provides navigation
Chris@909 58 # methods to traverse the tree.
Chris@909 59 #
Chris@909 60 # The nodes can have any number of child nodes attached to it. Note that while
Chris@909 61 # this implementation does not support directed graphs, the class itself makes
Chris@909 62 # no restrictions on associating a node's CONTENT with multiple parent nodes.
Chris@909 63 #
Chris@909 64 #
Chris@909 65 # == Example
Chris@909 66 #
Chris@909 67 # The following code-snippet implements this tree structure:
Chris@909 68 #
Chris@909 69 # +------------+
Chris@909 70 # | ROOT |
Chris@909 71 # +-----+------+
Chris@909 72 # +-------------+------------+
Chris@909 73 # | |
Chris@909 74 # +-------+-------+ +-------+-------+
Chris@909 75 # | CHILD 1 | | CHILD 2 |
Chris@909 76 # +-------+-------+ +---------------+
Chris@909 77 # |
Chris@909 78 # |
Chris@909 79 # +-------+-------+
Chris@909 80 # | GRANDCHILD 1 |
Chris@909 81 # +---------------+
Chris@909 82 #
Chris@909 83 # require 'tree'
Chris@909 84 #
Chris@909 85 # myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content")
Chris@909 86 #
Chris@909 87 # myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content")
Chris@909 88 #
Chris@909 89 # myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content")
Chris@909 90 #
Chris@909 91 # myTreeRoot.printTree
Chris@909 92 #
Chris@909 93 # child1 = myTreeRoot["CHILD1"]
Chris@909 94 #
Chris@909 95 # grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"]
Chris@909 96 #
Chris@909 97 # siblingsOfChild1Array = child1.siblings
Chris@909 98 #
Chris@909 99 # immediateChildrenArray = myTreeRoot.children
Chris@909 100 #
Chris@909 101 # # Process all nodes
Chris@909 102 #
Chris@909 103 # myTreeRoot.each { |node| node.content.reverse }
Chris@909 104 #
Chris@909 105 # myTreeRoot.remove!(child1) # Remove the child
Chris@909 106 class TreeNode
Chris@909 107 include Enumerable
Chris@909 108
Chris@909 109 attr_reader :content, :name, :parent
Chris@909 110 attr_writer :content
Chris@909 111
Chris@909 112 # Constructor which expects the name of the node
Chris@909 113 #
Chris@909 114 # Name of the node is expected to be unique across the
Chris@909 115 # tree.
Chris@909 116 #
Chris@909 117 # The content can be of any type, and is defaulted to _nil_.
Chris@909 118 def initialize(name, content = nil)
Chris@909 119 raise "Node name HAS to be provided" if name == nil
Chris@909 120 @name = name
Chris@909 121 @content = content
Chris@909 122 self.setAsRoot!
Chris@909 123
Chris@909 124 @childrenHash = Hash.new
Chris@909 125 @children = []
Chris@909 126 end
Chris@909 127
Chris@909 128 # Returns a copy of this node, with the parent and children links removed.
Chris@909 129 def detached_copy
Chris@909 130 Tree::TreeNode.new(@name, @content ? @content.clone : nil)
Chris@909 131 end
Chris@909 132
Chris@909 133 # Print the string representation of this node.
Chris@909 134 def to_s
Chris@909 135 "Node Name: #{@name}" +
Chris@909 136 " Content: " + (@content || "<Empty>") +
Chris@909 137 " Parent: " + (isRoot?() ? "<None>" : @parent.name) +
Chris@909 138 " Children: #{@children.length}" +
Chris@909 139 " Total Nodes: #{size()}"
Chris@909 140 end
Chris@909 141
Chris@909 142 # Returns an array of ancestors in reversed order (the first element is the
Chris@909 143 # immediate parent). Returns nil if this is a root node.
Chris@909 144 def parentage
Chris@909 145 return nil if isRoot?
Chris@909 146
Chris@909 147 parentageArray = []
Chris@909 148 prevParent = self.parent
Chris@909 149 while (prevParent)
Chris@909 150 parentageArray << prevParent
Chris@909 151 prevParent = prevParent.parent
Chris@909 152 end
Chris@909 153
Chris@909 154 parentageArray
Chris@909 155 end
Chris@909 156
Chris@909 157 # Protected method to set the parent node.
Chris@909 158 # This method should NOT be invoked by client code.
Chris@909 159 def parent=(parent)
Chris@909 160 @parent = parent
Chris@909 161 end
Chris@909 162
Chris@909 163 # Convenience synonym for TreeNode#add method. This method allows a convenient
Chris@909 164 # method to add children hierarchies in the tree.
Chris@909 165 #
Chris@909 166 # E.g. root << child << grand_child
Chris@909 167 def <<(child)
Chris@909 168 add(child)
Chris@909 169 end
Chris@909 170
Chris@909 171 # Adds the specified child node to the receiver node. The child node's
Chris@909 172 # parent is set to be the receiver. The child is added as the last child in
Chris@909 173 # the current list of children for the receiver node.
Chris@909 174 def add(child)
Chris@909 175 raise "Child already added" if @childrenHash.has_key?(child.name)
Chris@909 176
Chris@909 177 @childrenHash[child.name] = child
Chris@909 178 @children << child
Chris@909 179 child.parent = self
Chris@909 180 return child
Chris@909 181
Chris@909 182 end
Chris@909 183
Chris@909 184 # Removes the specified child node from the receiver node. The removed
Chris@909 185 # children nodes are orphaned but available if an alternate reference
Chris@909 186 # exists.
Chris@909 187 #
Chris@909 188 # Returns the child node.
Chris@909 189 def remove!(child)
Chris@909 190 @childrenHash.delete(child.name)
Chris@909 191 @children.delete(child)
Chris@909 192 child.setAsRoot! unless child == nil
Chris@909 193 return child
Chris@909 194 end
Chris@909 195
Chris@909 196 # Removes this node from its parent. If this is the root node, then does
Chris@909 197 # nothing.
Chris@909 198 def removeFromParent!
Chris@909 199 @parent.remove!(self) unless isRoot?
Chris@909 200 end
Chris@909 201
Chris@909 202 # Removes all children from the receiver node.
Chris@909 203 def removeAll!
Chris@909 204 for child in @children
Chris@909 205 child.setAsRoot!
Chris@909 206 end
Chris@909 207 @childrenHash.clear
Chris@909 208 @children.clear
Chris@909 209 self
Chris@909 210 end
Chris@909 211
Chris@909 212 # Indicates whether this node has any associated content.
Chris@909 213 def hasContent?
Chris@909 214 @content != nil
Chris@909 215 end
Chris@909 216
Chris@909 217 # Protected method which sets this node as a root node.
Chris@909 218 def setAsRoot!
Chris@909 219 @parent = nil
Chris@909 220 end
Chris@909 221
Chris@909 222 # Indicates whether this node is a root node. Note that
Chris@909 223 # orphaned children will also be reported as root nodes.
Chris@909 224 def isRoot?
Chris@909 225 @parent == nil
Chris@909 226 end
Chris@909 227
Chris@909 228 # Indicates whether this node has any immediate child nodes.
Chris@909 229 def hasChildren?
Chris@909 230 @children.length != 0
Chris@909 231 end
Chris@909 232
Chris@909 233 # Indicates whether this node is a 'leaf' - i.e., one without
Chris@909 234 # any children
Chris@909 235 def isLeaf?
Chris@909 236 !hasChildren?
Chris@909 237 end
Chris@909 238
Chris@909 239 # Returns an array of all the immediate children. If a block is given,
Chris@909 240 # yields each child node to the block.
Chris@909 241 def children
Chris@909 242 if block_given?
Chris@909 243 @children.each {|child| yield child}
Chris@909 244 else
Chris@909 245 @children
Chris@909 246 end
Chris@909 247 end
Chris@909 248
Chris@909 249 # Returns the first child of this node. Will return nil if no children are
Chris@909 250 # present.
Chris@909 251 def firstChild
Chris@909 252 children.first
Chris@909 253 end
Chris@909 254
Chris@909 255 # Returns the last child of this node. Will return nil if no children are
Chris@909 256 # present.
Chris@909 257 def lastChild
Chris@909 258 children.last
Chris@909 259 end
Chris@909 260
Chris@909 261 # Returns every node (including the receiver node) from the tree to the
Chris@909 262 # specified block. The traversal is depth first and from left to right in
Chris@909 263 # pre-ordered sequence.
Chris@909 264 def each &block
Chris@909 265 yield self
Chris@909 266 children { |child| child.each(&block) }
Chris@909 267 end
Chris@909 268
Chris@909 269 # Traverses the tree in a pre-ordered sequence. This is equivalent to
Chris@909 270 # TreeNode#each
Chris@909 271 def preordered_each &block
Chris@909 272 each(&block)
Chris@909 273 end
Chris@909 274
Chris@909 275 # Performs breadth first traversal of the tree rooted at this node. The
Chris@909 276 # traversal in a given level is from left to right.
Chris@909 277 def breadth_each &block
Chris@909 278 node_queue = [self] # Create a queue with self as the initial entry
Chris@909 279
Chris@909 280 # Use a queue to do breadth traversal
Chris@909 281 until node_queue.empty?
Chris@909 282 node_to_traverse = node_queue.shift
Chris@909 283 yield node_to_traverse
Chris@909 284 # Enqueue the children from left to right.
Chris@909 285 node_to_traverse.children { |child| node_queue.push child }
Chris@909 286 end
Chris@909 287 end
Chris@909 288
Chris@909 289 # Yields all leaf nodes from this node to the specified block. May yield
Chris@909 290 # this node as well if this is a leaf node. Leaf traversal depth first and
Chris@909 291 # left to right.
Chris@909 292 def each_leaf &block
Chris@909 293 self.each { |node| yield(node) if node.isLeaf? }
Chris@909 294 end
Chris@909 295
Chris@909 296 # Returns the requested node from the set of immediate children.
Chris@909 297 #
Chris@909 298 # If the parameter is _numeric_, then the in-sequence array of children is
Chris@909 299 # accessed (see Tree#children). If the parameter is not _numeric_, then it
Chris@909 300 # is assumed to be the *name* of the child node to be returned.
Chris@909 301 def [](name_or_index)
Chris@909 302 raise "Name_or_index needs to be provided" if name_or_index == nil
Chris@909 303
Chris@909 304 if name_or_index.kind_of?(Integer)
Chris@909 305 @children[name_or_index]
Chris@909 306 else
Chris@909 307 @childrenHash[name_or_index]
Chris@909 308 end
Chris@909 309 end
Chris@909 310
Chris@909 311 # Returns the total number of nodes in this tree, rooted at the receiver
Chris@909 312 # node.
Chris@909 313 def size
Chris@909 314 @children.inject(1) {|sum, node| sum + node.size}
Chris@909 315 end
Chris@909 316
Chris@909 317 # Convenience synonym for Tree#size
Chris@909 318 def length
Chris@909 319 size()
Chris@909 320 end
Chris@909 321
Chris@909 322 # Pretty prints the tree starting with the receiver node.
Chris@909 323 def printTree(level = 0)
Chris@909 324
Chris@909 325 if isRoot?
Chris@909 326 print "*"
Chris@909 327 else
Chris@909 328 print "|" unless parent.isLastSibling?
Chris@909 329 print(' ' * (level - 1) * 4)
Chris@909 330 print(isLastSibling? ? "+" : "|")
Chris@909 331 print "---"
Chris@909 332 print(hasChildren? ? "+" : ">")
Chris@909 333 end
Chris@909 334
Chris@909 335 puts " #{name}"
Chris@909 336
Chris@909 337 children { |child| child.printTree(level + 1)}
Chris@909 338 end
Chris@909 339
Chris@909 340 # Returns the root for this tree. Root's root is itself.
Chris@909 341 def root
Chris@909 342 root = self
Chris@909 343 root = root.parent while !root.isRoot?
Chris@909 344 root
Chris@909 345 end
Chris@909 346
Chris@909 347 # Returns the first sibling for this node. If this is the root node, returns
Chris@909 348 # itself.
Chris@909 349 def firstSibling
Chris@909 350 if isRoot?
Chris@909 351 self
Chris@909 352 else
Chris@909 353 parent.children.first
Chris@909 354 end
Chris@909 355 end
Chris@909 356
Chris@909 357 # Returns true if this node is the first sibling.
Chris@909 358 def isFirstSibling?
Chris@909 359 firstSibling == self
Chris@909 360 end
Chris@909 361
Chris@909 362 # Returns the last sibling for this node. If this node is the root, returns
Chris@909 363 # itself.
Chris@909 364 def lastSibling
Chris@909 365 if isRoot?
Chris@909 366 self
Chris@909 367 else
Chris@909 368 parent.children.last
Chris@909 369 end
Chris@909 370 end
Chris@909 371
Chris@909 372 # Returns true if his node is the last sibling
Chris@909 373 def isLastSibling?
Chris@909 374 lastSibling == self
Chris@909 375 end
Chris@909 376
Chris@909 377 # Returns an array of siblings for this node.
Chris@909 378 # If a block is provided, yields each of the sibling
Chris@909 379 # nodes to the block. The root always has nil siblings.
Chris@909 380 def siblings
Chris@909 381 return nil if isRoot?
Chris@909 382 if block_given?
Chris@909 383 for sibling in parent.children
Chris@909 384 yield sibling if sibling != self
Chris@909 385 end
Chris@909 386 else
Chris@909 387 siblings = []
Chris@909 388 parent.children {|sibling| siblings << sibling if sibling != self}
Chris@909 389 siblings
Chris@909 390 end
Chris@909 391 end
Chris@909 392
Chris@909 393 # Returns true if this node is the only child of its parent
Chris@909 394 def isOnlyChild?
Chris@909 395 parent.children.size == 1
Chris@909 396 end
Chris@909 397
Chris@909 398 # Returns the next sibling for this node. Will return nil if no subsequent
Chris@909 399 # node is present.
Chris@909 400 def nextSibling
Chris@909 401 if myidx = parent.children.index(self)
Chris@909 402 parent.children.at(myidx + 1)
Chris@909 403 end
Chris@909 404 end
Chris@909 405
Chris@909 406 # Returns the previous sibling for this node. Will return nil if no
Chris@909 407 # subsequent node is present.
Chris@909 408 def previousSibling
Chris@909 409 if myidx = parent.children.index(self)
Chris@909 410 parent.children.at(myidx - 1) if myidx > 0
Chris@909 411 end
Chris@909 412 end
Chris@909 413
Chris@909 414 # Provides a comparision operation for the nodes. Comparision
Chris@909 415 # is based on the natural character-set ordering for the
Chris@909 416 # node names.
Chris@909 417 def <=>(other)
Chris@909 418 return +1 if other == nil
Chris@909 419 self.name <=> other.name
Chris@909 420 end
Chris@909 421
Chris@909 422 # Freezes all nodes in the tree
Chris@909 423 def freezeTree!
Chris@909 424 each {|node| node.freeze}
Chris@909 425 end
Chris@909 426
Chris@909 427 # Creates the marshal-dump represention of the tree rooted at this node.
Chris@909 428 def marshal_dump
Chris@909 429 self.collect { |node| node.createDumpRep }
Chris@909 430 end
Chris@909 431
Chris@909 432 # Creates a dump representation and returns the same as a hash.
Chris@909 433 def createDumpRep
Chris@909 434 { :name => @name, :parent => (isRoot? ? nil : @parent.name), :content => Marshal.dump(@content)}
Chris@909 435 end
Chris@909 436
Chris@909 437 # Loads a marshalled dump of the tree and returns the root node of the
Chris@909 438 # reconstructed tree. See the Marshal class for additional details.
Chris@909 439 def marshal_load(dumped_tree_array)
Chris@909 440 nodes = { }
Chris@909 441 for node_hash in dumped_tree_array do
Chris@909 442 name = node_hash[:name]
Chris@909 443 parent_name = node_hash[:parent]
Chris@909 444 content = Marshal.load(node_hash[:content])
Chris@909 445
Chris@909 446 if parent_name then
Chris@909 447 nodes[name] = current_node = Tree::TreeNode.new(name, content)
Chris@909 448 nodes[parent_name].add current_node
Chris@909 449 else
Chris@909 450 # This is the root node, hence initialize self.
Chris@909 451 initialize(name, content)
Chris@909 452
Chris@909 453 nodes[name] = self # Add self to the list of nodes
Chris@909 454 end
Chris@909 455 end
Chris@909 456 end
Chris@909 457
Chris@909 458 # Returns depth of the tree from this node. A single leaf node has a
Chris@909 459 # depth of 1.
Chris@909 460 def depth
Chris@909 461 return 1 if isLeaf?
Chris@909 462 1 + @children.collect { |child| child.depth }.max
Chris@909 463 end
Chris@909 464
Chris@909 465 # Returns breadth of the tree at this node level. A single node has a
Chris@909 466 # breadth of 1.
Chris@909 467 def breadth
Chris@909 468 return 1 if isRoot?
Chris@909 469 parent.children.size
Chris@909 470 end
Chris@909 471
Chris@909 472 protected :parent=, :setAsRoot!, :createDumpRep
Chris@909 473
Chris@909 474 end
Chris@909 475 end
Chris@909 476
Chris@909 477 # $Log: tree.rb,v $
Chris@909 478 # Revision 1.29 2007/12/22 00:28:59 anupamsg
Chris@909 479 # Added more test cases, and enabled ZenTest compatibility.
Chris@909 480 #
Chris@909 481 # Revision 1.28 2007/12/20 03:19:33 anupamsg
Chris@909 482 # * README (Module): Modified the install instructions from source.
Chris@909 483 # (Module): Updated the minor version number.
Chris@909 484 #
Chris@909 485 # Revision 1.27 2007/12/20 03:00:03 anupamsg
Chris@909 486 # Minor code changes. Removed self_initialize from the protected methods' list.
Chris@909 487 #
Chris@909 488 # Revision 1.26 2007/12/20 02:50:04 anupamsg
Chris@909 489 # (Tree::TreeNode): Removed the spurious self_initialize from the protected list.
Chris@909 490 #
Chris@909 491 # Revision 1.25 2007/12/19 20:28:05 anupamsg
Chris@909 492 # Removed the unnecesary self_initialize method.
Chris@909 493 #
Chris@909 494 # Revision 1.24 2007/12/19 06:39:17 anupamsg
Chris@909 495 # Removed the unnecessary field and record separator constants. Also updated the
Chris@909 496 # history.txt file.
Chris@909 497 #
Chris@909 498 # Revision 1.23 2007/12/19 06:25:00 anupamsg
Chris@909 499 # (Tree::TreeNode): Minor fix to the comments. Also fixed the private/protected
Chris@909 500 # scope issue with the createDumpRep method.
Chris@909 501 #
Chris@909 502 # Revision 1.22 2007/12/19 06:22:03 anupamsg
Chris@909 503 # Updated the marshalling logic to correctly handle non-string content. This
Chris@909 504 # should fix the bug # 15614 ("When dumping with an Object as the content, you get
Chris@909 505 # a delimiter collision")
Chris@909 506 #
Chris@909 507 # Revision 1.21 2007/12/19 02:24:17 anupamsg
Chris@909 508 # Updated the marshalling logic to handle non-string contents on the nodes.
Chris@909 509 #
Chris@909 510 # Revision 1.20 2007/10/10 08:42:57 anupamsg
Chris@909 511 # Release 0.4.3
Chris@909 512 #
Chris@909 513 # Revision 1.19 2007/08/31 01:16:27 anupamsg
Chris@909 514 # Added breadth and pre-order traversals for the tree. Also added a method
Chris@909 515 # to return the detached copy of a node from the tree.
Chris@909 516 #
Chris@909 517 # Revision 1.18 2007/07/21 05:14:44 anupamsg
Chris@909 518 # Added a VERSION constant to the Tree module,
Chris@909 519 # and using the same in the Rakefile.
Chris@909 520 #
Chris@909 521 # Revision 1.17 2007/07/21 03:24:25 anupamsg
Chris@909 522 # Minor edits to parameter names. User visible functionality does not change.
Chris@909 523 #
Chris@909 524 # Revision 1.16 2007/07/18 23:38:55 anupamsg
Chris@909 525 # Minor updates to tree.rb
Chris@909 526 #
Chris@909 527 # Revision 1.15 2007/07/18 22:11:50 anupamsg
Chris@909 528 # Added depth and breadth methods for the TreeNode.
Chris@909 529 #
Chris@909 530 # Revision 1.14 2007/07/18 19:33:27 anupamsg
Chris@909 531 # Added a new binary tree implementation.
Chris@909 532 #
Chris@909 533 # Revision 1.13 2007/07/18 07:17:34 anupamsg
Chris@909 534 # Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method
Chris@909 535 # has been renamed to TreeNode.parentage.
Chris@909 536 #
Chris@909 537 # Revision 1.12 2007/07/17 03:39:28 anupamsg
Chris@909 538 # Moved the CVS Log keyword to end of the files.
Chris@909 539 #