diff options
author | Eric Davis <edavis@littlestreamsoftware.com> | 2009-11-25 05:36:44 +0000 |
---|---|---|
committer | Eric Davis <edavis@littlestreamsoftware.com> | 2009-11-25 05:36:44 +0000 |
commit | 1f06cf889990d9640f7160c4969ed074fb68a7ca (patch) | |
tree | 034ebf2c1567b0d707af669b3ccd1f403237a866 /vendor/gems/rubytree-0.5.2/lib/tree.rb | |
parent | 5a9528cf3d9eb4afbec81cf1d79f7134596906f1 (diff) | |
download | redmine-1f06cf889990d9640f7160c4969ed074fb68a7ca.tar.gz redmine-1f06cf889990d9640f7160c4969ed074fb68a7ca.zip |
Converted Menus to a Tree structure to allow submenus.
* Bundle the rubytree gem
* Patched RubyTree's TreeNode to add some additional methods.
* Converted the menu rendering to walk the Tree of MenuItems to render
each item
* Added a menu option for :parent_menu to make this menu a child of the parent
* Added a bunch of tests
* Made MenuItem a subclass of Tree::TreeNode in order to use it's methods
directly
* Changed the exceptions in MenuItem#new to be ArgumentErrors instead of the
generic RuntimeError
#4250
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3090 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'vendor/gems/rubytree-0.5.2/lib/tree.rb')
-rw-r--r-- | vendor/gems/rubytree-0.5.2/lib/tree.rb | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/vendor/gems/rubytree-0.5.2/lib/tree.rb b/vendor/gems/rubytree-0.5.2/lib/tree.rb new file mode 100644 index 000000000..9b5062aa7 --- /dev/null +++ b/vendor/gems/rubytree-0.5.2/lib/tree.rb @@ -0,0 +1,539 @@ +# tree.rb +# +# $Revision: 1.29 $ by $Author: anupamsg $ +# $Name: $ +# +# = tree.rb - Generic Multi-way Tree implementation +# +# Provides a generic tree data structure with ability to +# store keyed node elements in the tree. The implementation +# mixes in the Enumerable module. +# +# Author:: Anupam Sengupta (anupamsg@gmail.com) +# + +# Copyright (c) 2006, 2007 Anupam Sengupta +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# - Neither the name of the organization nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# This module provides a TreeNode class which is the primary class for all +# nodes represented in the Tree. +# This module mixes in the Enumerable module. +module Tree + + # Rubytree Package Version + VERSION = '0.5.2' + + # == TreeNode Class Description + # + # The node class for the tree representation. the nodes are named and have a + # place-holder for the node data (i.e., the `content' of the node). The node + # names are expected to be unique. In addition, the node provides navigation + # methods to traverse the tree. + # + # The nodes can have any number of child nodes attached to it. Note that while + # this implementation does not support directed graphs, the class itself makes + # no restrictions on associating a node's CONTENT with multiple parent nodes. + # + # + # == Example + # + # The following code-snippet implements this tree structure: + # + # +------------+ + # | ROOT | + # +-----+------+ + # +-------------+------------+ + # | | + # +-------+-------+ +-------+-------+ + # | CHILD 1 | | CHILD 2 | + # +-------+-------+ +---------------+ + # | + # | + # +-------+-------+ + # | GRANDCHILD 1 | + # +---------------+ + # + # require 'tree' + # + # myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content") + # + # myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content") + # + # myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content") + # + # myTreeRoot.printTree + # + # child1 = myTreeRoot["CHILD1"] + # + # grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"] + # + # siblingsOfChild1Array = child1.siblings + # + # immediateChildrenArray = myTreeRoot.children + # + # # Process all nodes + # + # myTreeRoot.each { |node| node.content.reverse } + # + # myTreeRoot.remove!(child1) # Remove the child + class TreeNode + include Enumerable + + attr_reader :content, :name, :parent + attr_writer :content + + # Constructor which expects the name of the node + # + # Name of the node is expected to be unique across the + # tree. + # + # The content can be of any type, and is defaulted to _nil_. + def initialize(name, content = nil) + raise "Node name HAS to be provided" if name == nil + @name = name + @content = content + self.setAsRoot! + + @childrenHash = Hash.new + @children = [] + end + + # Returns a copy of this node, with the parent and children links removed. + def detached_copy + Tree::TreeNode.new(@name, @content ? @content.clone : nil) + end + + # Print the string representation of this node. + def to_s + "Node Name: #{@name}" + + " Content: " + (@content || "<Empty>") + + " Parent: " + (isRoot?() ? "<None>" : @parent.name) + + " Children: #{@children.length}" + + " Total Nodes: #{size()}" + end + + # Returns an array of ancestors in reversed order (the first element is the + # immediate parent). Returns nil if this is a root node. + def parentage + return nil if isRoot? + + parentageArray = [] + prevParent = self.parent + while (prevParent) + parentageArray << prevParent + prevParent = prevParent.parent + end + + parentageArray + end + + # Protected method to set the parent node. + # This method should NOT be invoked by client code. + def parent=(parent) + @parent = parent + end + + # Convenience synonym for TreeNode#add method. This method allows a convenient + # method to add children hierarchies in the tree. + # + # E.g. root << child << grand_child + def <<(child) + add(child) + end + + # Adds the specified child node to the receiver node. The child node's + # parent is set to be the receiver. The child is added as the last child in + # the current list of children for the receiver node. + def add(child) + raise "Child already added" if @childrenHash.has_key?(child.name) + + @childrenHash[child.name] = child + @children << child + child.parent = self + return child + + end + + # Removes the specified child node from the receiver node. The removed + # children nodes are orphaned but available if an alternate reference + # exists. + # + # Returns the child node. + def remove!(child) + @childrenHash.delete(child.name) + @children.delete(child) + child.setAsRoot! unless child == nil + return child + end + + # Removes this node from its parent. If this is the root node, then does + # nothing. + def removeFromParent! + @parent.remove!(self) unless isRoot? + end + + # Removes all children from the receiver node. + def removeAll! + for child in @children + child.setAsRoot! + end + @childrenHash.clear + @children.clear + self + end + + # Indicates whether this node has any associated content. + def hasContent? + @content != nil + end + + # Protected method which sets this node as a root node. + def setAsRoot! + @parent = nil + end + + # Indicates whether this node is a root node. Note that + # orphaned children will also be reported as root nodes. + def isRoot? + @parent == nil + end + + # Indicates whether this node has any immediate child nodes. + def hasChildren? + @children.length != 0 + end + + # Indicates whether this node is a 'leaf' - i.e., one without + # any children + def isLeaf? + !hasChildren? + end + + # Returns an array of all the immediate children. If a block is given, + # yields each child node to the block. + def children + if block_given? + @children.each {|child| yield child} + else + @children + end + end + + # Returns the first child of this node. Will return nil if no children are + # present. + def firstChild + children.first + end + + # Returns the last child of this node. Will return nil if no children are + # present. + def lastChild + children.last + end + + # Returns every node (including the receiver node) from the tree to the + # specified block. The traversal is depth first and from left to right in + # pre-ordered sequence. + def each &block + yield self + children { |child| child.each(&block) } + end + + # Traverses the tree in a pre-ordered sequence. This is equivalent to + # TreeNode#each + def preordered_each &block + each(&block) + end + + # Performs breadth first traversal of the tree rooted at this node. The + # traversal in a given level is from left to right. + def breadth_each &block + node_queue = [self] # Create a queue with self as the initial entry + + # Use a queue to do breadth traversal + until node_queue.empty? + node_to_traverse = node_queue.shift + yield node_to_traverse + # Enqueue the children from left to right. + node_to_traverse.children { |child| node_queue.push child } + end + end + + # Yields all leaf nodes from this node to the specified block. May yield + # this node as well if this is a leaf node. Leaf traversal depth first and + # left to right. + def each_leaf &block + self.each { |node| yield(node) if node.isLeaf? } + end + + # Returns the requested node from the set of immediate children. + # + # If the parameter is _numeric_, then the in-sequence array of children is + # accessed (see Tree#children). If the parameter is not _numeric_, then it + # is assumed to be the *name* of the child node to be returned. + def [](name_or_index) + raise "Name_or_index needs to be provided" if name_or_index == nil + + if name_or_index.kind_of?(Integer) + @children[name_or_index] + else + @childrenHash[name_or_index] + end + end + + # Returns the total number of nodes in this tree, rooted at the receiver + # node. + def size + @children.inject(1) {|sum, node| sum + node.size} + end + + # Convenience synonym for Tree#size + def length + size() + end + + # Pretty prints the tree starting with the receiver node. + def printTree(level = 0) + + if isRoot? + print "*" + else + print "|" unless parent.isLastSibling? + print(' ' * (level - 1) * 4) + print(isLastSibling? ? "+" : "|") + print "---" + print(hasChildren? ? "+" : ">") + end + + puts " #{name}" + + children { |child| child.printTree(level + 1)} + end + + # Returns the root for this tree. Root's root is itself. + def root + root = self + root = root.parent while !root.isRoot? + root + end + + # Returns the first sibling for this node. If this is the root node, returns + # itself. + def firstSibling + if isRoot? + self + else + parent.children.first + end + end + + # Returns true if this node is the first sibling. + def isFirstSibling? + firstSibling == self + end + + # Returns the last sibling for this node. If this node is the root, returns + # itself. + def lastSibling + if isRoot? + self + else + parent.children.last + end + end + + # Returns true if his node is the last sibling + def isLastSibling? + lastSibling == self + end + + # Returns an array of siblings for this node. + # If a block is provided, yields each of the sibling + # nodes to the block. The root always has nil siblings. + def siblings + return nil if isRoot? + if block_given? + for sibling in parent.children + yield sibling if sibling != self + end + else + siblings = [] + parent.children {|sibling| siblings << sibling if sibling != self} + siblings + end + end + + # Returns true if this node is the only child of its parent + def isOnlyChild? + parent.children.size == 1 + end + + # Returns the next sibling for this node. Will return nil if no subsequent + # node is present. + def nextSibling + if myidx = parent.children.index(self) + parent.children.at(myidx + 1) + end + end + + # Returns the previous sibling for this node. Will return nil if no + # subsequent node is present. + def previousSibling + if myidx = parent.children.index(self) + parent.children.at(myidx - 1) if myidx > 0 + end + end + + # Provides a comparision operation for the nodes. Comparision + # is based on the natural character-set ordering for the + # node names. + def <=>(other) + return +1 if other == nil + self.name <=> other.name + end + + # Freezes all nodes in the tree + def freezeTree! + each {|node| node.freeze} + end + + # Creates the marshal-dump represention of the tree rooted at this node. + def marshal_dump + self.collect { |node| node.createDumpRep } + end + + # Creates a dump representation and returns the same as a hash. + def createDumpRep + { :name => @name, :parent => (isRoot? ? nil : @parent.name), :content => Marshal.dump(@content)} + end + + # Loads a marshalled dump of the tree and returns the root node of the + # reconstructed tree. See the Marshal class for additional details. + def marshal_load(dumped_tree_array) + nodes = { } + for node_hash in dumped_tree_array do + name = node_hash[:name] + parent_name = node_hash[:parent] + content = Marshal.load(node_hash[:content]) + + if parent_name then + nodes[name] = current_node = Tree::TreeNode.new(name, content) + nodes[parent_name].add current_node + else + # This is the root node, hence initialize self. + initialize(name, content) + + nodes[name] = self # Add self to the list of nodes + end + end + end + + # Returns depth of the tree from this node. A single leaf node has a + # depth of 1. + def depth + return 1 if isLeaf? + 1 + @children.collect { |child| child.depth }.max + end + + # Returns breadth of the tree at this node level. A single node has a + # breadth of 1. + def breadth + return 1 if isRoot? + parent.children.size + end + + protected :parent=, :setAsRoot!, :createDumpRep + + end +end + +# $Log: tree.rb,v $ +# Revision 1.29 2007/12/22 00:28:59 anupamsg +# Added more test cases, and enabled ZenTest compatibility. +# +# Revision 1.28 2007/12/20 03:19:33 anupamsg +# * README (Module): Modified the install instructions from source. +# (Module): Updated the minor version number. +# +# Revision 1.27 2007/12/20 03:00:03 anupamsg +# Minor code changes. Removed self_initialize from the protected methods' list. +# +# Revision 1.26 2007/12/20 02:50:04 anupamsg +# (Tree::TreeNode): Removed the spurious self_initialize from the protected list. +# +# Revision 1.25 2007/12/19 20:28:05 anupamsg +# Removed the unnecesary self_initialize method. +# +# Revision 1.24 2007/12/19 06:39:17 anupamsg +# Removed the unnecessary field and record separator constants. Also updated the +# history.txt file. +# +# Revision 1.23 2007/12/19 06:25:00 anupamsg +# (Tree::TreeNode): Minor fix to the comments. Also fixed the private/protected +# scope issue with the createDumpRep method. +# +# Revision 1.22 2007/12/19 06:22:03 anupamsg +# Updated the marshalling logic to correctly handle non-string content. This +# should fix the bug # 15614 ("When dumping with an Object as the content, you get +# a delimiter collision") +# +# Revision 1.21 2007/12/19 02:24:17 anupamsg +# Updated the marshalling logic to handle non-string contents on the nodes. +# +# Revision 1.20 2007/10/10 08:42:57 anupamsg +# Release 0.4.3 +# +# Revision 1.19 2007/08/31 01:16:27 anupamsg +# Added breadth and pre-order traversals for the tree. Also added a method +# to return the detached copy of a node from the tree. +# +# Revision 1.18 2007/07/21 05:14:44 anupamsg +# Added a VERSION constant to the Tree module, +# and using the same in the Rakefile. +# +# Revision 1.17 2007/07/21 03:24:25 anupamsg +# Minor edits to parameter names. User visible functionality does not change. +# +# Revision 1.16 2007/07/18 23:38:55 anupamsg +# Minor updates to tree.rb +# +# Revision 1.15 2007/07/18 22:11:50 anupamsg +# Added depth and breadth methods for the TreeNode. +# +# Revision 1.14 2007/07/18 19:33:27 anupamsg +# Added a new binary tree implementation. +# +# Revision 1.13 2007/07/18 07:17:34 anupamsg +# Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method +# has been renamed to TreeNode.parentage. +# +# Revision 1.12 2007/07/17 03:39:28 anupamsg +# Moved the CVS Log keyword to end of the files. +# |