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
This commit is contained in:
parent
5a9528cf3d
commit
1f06cf8899
|
@ -50,6 +50,8 @@ Rails::Initializer.run do |config|
|
||||||
# It will automatically turn deliveries on
|
# It will automatically turn deliveries on
|
||||||
config.action_mailer.perform_deliveries = false
|
config.action_mailer.perform_deliveries = false
|
||||||
|
|
||||||
|
config.gem 'rubytree', :lib => 'tree'
|
||||||
|
|
||||||
# Load any local configuration that is kept out of source control
|
# Load any local configuration that is kept out of source control
|
||||||
# (e.g. gems, patches).
|
# (e.g. gems, patches).
|
||||||
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
|
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
|
||||||
|
|
|
@ -15,6 +15,84 @@
|
||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require 'tree' # gem install rubytree
|
||||||
|
|
||||||
|
# Monkey patch the TreeNode to add on a few more methods :nodoc:
|
||||||
|
module TreeNodePatch
|
||||||
|
def self.included(base)
|
||||||
|
base.class_eval do
|
||||||
|
attr_reader :last_items_count
|
||||||
|
|
||||||
|
alias :old_initilize :initialize
|
||||||
|
def initialize(name, content = nil)
|
||||||
|
old_initilize(name, content)
|
||||||
|
@last_items_count = 0
|
||||||
|
extend(InstanceMethods)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
# 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 first child in
|
||||||
|
# the current list of children for the receiver node.
|
||||||
|
def prepend(child)
|
||||||
|
raise "Child already added" if @childrenHash.has_key?(child.name)
|
||||||
|
|
||||||
|
@childrenHash[child.name] = child
|
||||||
|
@children = [child] + @children
|
||||||
|
child.parent = self
|
||||||
|
return 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 at the position
|
||||||
|
# into the current list of children for the receiver node.
|
||||||
|
def add_at(child, position)
|
||||||
|
raise "Child already added" if @childrenHash.has_key?(child.name)
|
||||||
|
|
||||||
|
@childrenHash[child.name] = child
|
||||||
|
@children = @children.insert(position, child)
|
||||||
|
child.parent = self
|
||||||
|
return child
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_last(child)
|
||||||
|
raise "Child already added" if @childrenHash.has_key?(child.name)
|
||||||
|
|
||||||
|
@childrenHash[child.name] = child
|
||||||
|
@children << child
|
||||||
|
@last_items_count += 1
|
||||||
|
child.parent = self
|
||||||
|
return 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
|
||||||
|
position = @children.size - @last_items_count
|
||||||
|
@children.insert(position, child)
|
||||||
|
child.parent = self
|
||||||
|
return child
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Will return the position (zero-based) of the current child in
|
||||||
|
# it's parent
|
||||||
|
def position
|
||||||
|
self.parent.children.index(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Tree::TreeNode.send(:include, TreeNodePatch)
|
||||||
|
|
||||||
module Redmine
|
module Redmine
|
||||||
module MenuManager
|
module MenuManager
|
||||||
module MenuController
|
module MenuController
|
||||||
|
@ -79,35 +157,80 @@ module Redmine
|
||||||
|
|
||||||
def render_menu(menu, project=nil)
|
def render_menu(menu, project=nil)
|
||||||
links = []
|
links = []
|
||||||
menu_items_for(menu, project) do |item, caption, url, selected|
|
menu_items_for(menu, project) do |node|
|
||||||
links << content_tag('li',
|
links << render_menu_node(node, project)
|
||||||
link_to(h(caption), url, item.html_options(:selected => selected)))
|
|
||||||
end
|
end
|
||||||
links.empty? ? nil : content_tag('ul', links.join("\n"))
|
links.empty? ? nil : content_tag('ul', links.join("\n"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_menu_node(node, project=nil)
|
||||||
|
caption, url, selected = extract_node_details(node, project)
|
||||||
|
if node.hasChildren?
|
||||||
|
html = []
|
||||||
|
html << '<li>'
|
||||||
|
html << render_single_menu_node(node, caption, url, selected) # parent
|
||||||
|
html << ' <ul>'
|
||||||
|
node.children.each do |child|
|
||||||
|
html << render_menu_node(child, project)
|
||||||
|
end
|
||||||
|
html << ' </ul>'
|
||||||
|
html << '</li>'
|
||||||
|
return html.join("\n")
|
||||||
|
else
|
||||||
|
return content_tag('li',
|
||||||
|
render_single_menu_node(node, caption, url, selected))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_single_menu_node(item, caption, url, selected)
|
||||||
|
link_to(h(caption), url, item.html_options(:selected => selected))
|
||||||
|
end
|
||||||
|
|
||||||
def menu_items_for(menu, project=nil)
|
def menu_items_for(menu, project=nil)
|
||||||
items = []
|
items = []
|
||||||
Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
|
Redmine::MenuManager.items(menu).root.children.each do |node|
|
||||||
unless item.condition && !item.condition.call(project)
|
if allowed_node?(node, User.current, project)
|
||||||
url = case item.url
|
|
||||||
when Hash
|
|
||||||
project.nil? ? item.url : {item.param => project}.merge(item.url)
|
|
||||||
when Symbol
|
|
||||||
send(item.url)
|
|
||||||
else
|
|
||||||
item.url
|
|
||||||
end
|
|
||||||
caption = item.caption(project)
|
|
||||||
if block_given?
|
if block_given?
|
||||||
yield item, caption, url, (current_menu_item == item.name)
|
yield node
|
||||||
else
|
else
|
||||||
items << [item, caption, url, (current_menu_item == item.name)]
|
items << node # TODO: not used?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return block_given? ? nil : items
|
return block_given? ? nil : items
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def extract_node_details(node, project=nil)
|
||||||
|
item = node
|
||||||
|
url = case item.url
|
||||||
|
when Hash
|
||||||
|
project.nil? ? item.url : {item.param => project}.merge(item.url)
|
||||||
|
when Symbol
|
||||||
|
send(item.url)
|
||||||
|
else
|
||||||
|
item.url
|
||||||
|
end
|
||||||
|
caption = item.caption(project)
|
||||||
|
return [caption, url, (current_menu_item == item.name)]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if a user is allowed to access the menu item by:
|
||||||
|
#
|
||||||
|
# * Checking the conditions of the item
|
||||||
|
# * Checking the url target (project only)
|
||||||
|
def allowed_node?(node, user, project)
|
||||||
|
if node.condition && !node.condition.call(project)
|
||||||
|
# Condition that doesn't pass
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if project
|
||||||
|
return user && user.allowed_to?(node.url, project)
|
||||||
|
else
|
||||||
|
# outside a project, all menu items allowed
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
@ -122,17 +245,13 @@ module Redmine
|
||||||
end
|
end
|
||||||
|
|
||||||
def items(menu_name)
|
def items(menu_name)
|
||||||
@items[menu_name.to_sym] || []
|
@items[menu_name.to_sym] || Tree::TreeNode.new(:root, {})
|
||||||
end
|
|
||||||
|
|
||||||
def allowed_items(menu_name, user, project)
|
|
||||||
project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Mapper
|
class Mapper
|
||||||
def initialize(menu, items)
|
def initialize(menu, items)
|
||||||
items[menu] ||= []
|
items[menu] ||= Tree::TreeNode.new(:root, {})
|
||||||
@menu = menu
|
@menu = menu
|
||||||
@menu_items = items[menu]
|
@menu_items = items[menu]
|
||||||
end
|
end
|
||||||
|
@ -151,36 +270,78 @@ module Redmine
|
||||||
# * html_options: a hash of html options that are passed to link_to
|
# * html_options: a hash of html options that are passed to link_to
|
||||||
def push(name, url, options={})
|
def push(name, url, options={})
|
||||||
options = options.dup
|
options = options.dup
|
||||||
|
|
||||||
# menu item position
|
if options[:parent_menu]
|
||||||
if before = options.delete(:before)
|
subtree = self.find(options[:parent_menu])
|
||||||
position = @menu_items.collect(&:name).index(before)
|
if subtree
|
||||||
elsif after = options.delete(:after)
|
target_root = subtree
|
||||||
position = @menu_items.collect(&:name).index(after)
|
else
|
||||||
position += 1 unless position.nil?
|
target_root = @menu_items.root
|
||||||
elsif options.delete(:last)
|
end
|
||||||
position = @menu_items.size
|
|
||||||
@@last_items_count[@menu] += 1
|
else
|
||||||
|
target_root = @menu_items.root
|
||||||
|
end
|
||||||
|
|
||||||
|
# menu item position
|
||||||
|
if first = options.delete(:first)
|
||||||
|
target_root.prepend(MenuItem.new(name, url, options))
|
||||||
|
elsif before = options.delete(:before)
|
||||||
|
|
||||||
|
if exists?(before)
|
||||||
|
target_root.add_at(MenuItem.new(name, url, options), position_of(before))
|
||||||
|
else
|
||||||
|
target_root.add(MenuItem.new(name, url, options))
|
||||||
|
end
|
||||||
|
|
||||||
|
elsif after = options.delete(:after)
|
||||||
|
|
||||||
|
if exists?(after)
|
||||||
|
target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
|
||||||
|
else
|
||||||
|
target_root.add(MenuItem.new(name, url, options))
|
||||||
|
end
|
||||||
|
|
||||||
|
elsif options.delete(:last)
|
||||||
|
target_root.add_last(MenuItem.new(name, url, options))
|
||||||
|
else
|
||||||
|
target_root.add(MenuItem.new(name, url, options))
|
||||||
end
|
end
|
||||||
# default position
|
|
||||||
position ||= @menu_items.size - @@last_items_count[@menu]
|
|
||||||
|
|
||||||
@menu_items.insert(position, MenuItem.new(name, url, options))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes a menu item
|
# Removes a menu item
|
||||||
def delete(name)
|
def delete(name)
|
||||||
@menu_items.delete_if {|i| i.name == name}
|
if found = self.find(name)
|
||||||
|
@menu_items.remove!(found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if a menu item exists
|
||||||
|
def exists?(name)
|
||||||
|
@menu_items.any? {|node| node.name == name}
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(name)
|
||||||
|
@menu_items.find {|node| node.name == name}
|
||||||
|
end
|
||||||
|
|
||||||
|
def position_of(name)
|
||||||
|
@menu_items.each do |node|
|
||||||
|
if node.name == name
|
||||||
|
return node.position
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class MenuItem
|
class MenuItem < Tree::TreeNode
|
||||||
include Redmine::I18n
|
include Redmine::I18n
|
||||||
attr_reader :name, :url, :param, :condition
|
attr_reader :name, :url, :param, :condition, :parent_menu
|
||||||
|
|
||||||
def initialize(name, url, options)
|
def initialize(name, url, options)
|
||||||
raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
|
raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
|
||||||
raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
|
raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
|
||||||
|
raise ArgumentError, "Cannot set the :parent_menu to be the same as this item" if options[:parent_menu] == name.to_sym
|
||||||
@name = name
|
@name = name
|
||||||
@url = url
|
@url = url
|
||||||
@condition = options[:if]
|
@condition = options[:if]
|
||||||
|
@ -189,6 +350,8 @@ module Redmine
|
||||||
@html_options = options[:html] || {}
|
@html_options = options[:html] || {}
|
||||||
# Adds a unique class to each menu item based on its name
|
# Adds a unique class to each menu item based on its name
|
||||||
@html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
|
@html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
|
||||||
|
@parent_menu = options[:parent_menu]
|
||||||
|
super @name.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def caption(project=nil)
|
def caption(project=nil)
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||||
|
|
||||||
|
class Redmine::MenuManager::MapperTest < Test::Unit::TestCase
|
||||||
|
context "Mapper#initialize" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_onto_root
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
|
||||||
|
menu_mapper.exists?(:test_overview)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_onto_parent
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview}
|
||||||
|
|
||||||
|
assert menu_mapper.exists?(:test_child)
|
||||||
|
assert_equal :test_child, menu_mapper.find(:test_child).name
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_onto_grandparent
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview}
|
||||||
|
menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_child}
|
||||||
|
|
||||||
|
assert menu_mapper.exists?(:test_grandchild)
|
||||||
|
grandchild = menu_mapper.find(:test_grandchild)
|
||||||
|
assert_equal :test_grandchild, grandchild.name
|
||||||
|
assert_equal :test_child, grandchild.parent_menu
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_first
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true}
|
||||||
|
|
||||||
|
root = menu_mapper.find(:root)
|
||||||
|
assert_equal 5, root.children.size
|
||||||
|
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
|
||||||
|
assert_not_nil root.children[position]
|
||||||
|
assert_equal name, root.children[position].name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_before
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth}
|
||||||
|
|
||||||
|
root = menu_mapper.find(:root)
|
||||||
|
assert_equal 5, root.children.size
|
||||||
|
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
|
||||||
|
assert_not_nil root.children[position]
|
||||||
|
assert_equal name, root.children[position].name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_after
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third}
|
||||||
|
|
||||||
|
|
||||||
|
root = menu_mapper.find(:root)
|
||||||
|
assert_equal 5, root.children.size
|
||||||
|
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
|
||||||
|
assert_not_nil root.children[position]
|
||||||
|
assert_equal name, root.children[position].name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_push_last
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true}
|
||||||
|
menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
|
||||||
|
root = menu_mapper.find(:root)
|
||||||
|
assert_equal 5, root.children.size
|
||||||
|
{0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
|
||||||
|
assert_not_nil root.children[position]
|
||||||
|
assert_equal name, root.children[position].name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exists_for_child_node
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview }
|
||||||
|
|
||||||
|
assert menu_mapper.exists?(:test_child)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exists_for_invalid_node
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
|
||||||
|
assert !menu_mapper.exists?(:nothing)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_find
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
|
||||||
|
item = menu_mapper.find(:test_overview)
|
||||||
|
assert_equal :test_overview, item.name
|
||||||
|
assert_equal({:controller => 'projects', :action => 'show'}, item.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_find_missing
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
|
||||||
|
item = menu_mapper.find(:nothing)
|
||||||
|
assert_equal nil, item
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
|
||||||
|
assert_not_nil menu_mapper.delete(:test_overview)
|
||||||
|
|
||||||
|
assert_nil menu_mapper.find(:test_overview)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_delete_missing
|
||||||
|
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
|
||||||
|
assert_nil menu_mapper.delete(:test_missing)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Redmine::MenuManager::MenuHelperTest < HelperTestCase
|
||||||
|
include Redmine::MenuManager::MenuHelper
|
||||||
|
include ActionController::Assertions::SelectorAssertions
|
||||||
|
fixtures :users, :members, :projects, :enabled_modules
|
||||||
|
|
||||||
|
# Used by assert_select
|
||||||
|
def html_document
|
||||||
|
HTML::Document.new(@response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@response = ActionController::TestResponse.new
|
||||||
|
# Stub the current menu item in the controller
|
||||||
|
def @controller.current_menu_item
|
||||||
|
:index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
context "MenuManager#current_menu_item" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuManager#render_main_menu" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuManager#render_menu" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuManager#menu_item_and_children" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuManager#extract_node_details" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_single_menu_node
|
||||||
|
node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { })
|
||||||
|
@response.body = render_single_menu_node(node, 'This is a test', node.url, false)
|
||||||
|
|
||||||
|
assert_select("a.testing", "This is a test")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_menu_node
|
||||||
|
single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { })
|
||||||
|
@response.body = render_menu_node(single_node, nil)
|
||||||
|
|
||||||
|
assert_select("li") do
|
||||||
|
assert_select("a.single-node", "Single node")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_menu_node_with_nested_items
|
||||||
|
parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { })
|
||||||
|
parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { })
|
||||||
|
parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { })
|
||||||
|
parent_node <<
|
||||||
|
Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) <<
|
||||||
|
Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { })
|
||||||
|
|
||||||
|
@response.body = render_menu_node(parent_node, nil)
|
||||||
|
|
||||||
|
assert_select("li") do
|
||||||
|
assert_select("a.parent-node", "Parent node")
|
||||||
|
assert_select("ul") do
|
||||||
|
assert_select("li a.child-one-node", "Child one node")
|
||||||
|
assert_select("li a.child-two-node", "Child two node")
|
||||||
|
assert_select("li") do
|
||||||
|
assert_select("a.child-three-node", "Child three node")
|
||||||
|
assert_select("ul") do
|
||||||
|
assert_select("li a.child-three-inner-node", "Child three inner node")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_menu_items_for_should_yield_all_items_if_passed_a_block
|
||||||
|
menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block
|
||||||
|
Redmine::MenuManager.map menu_name do |menu|
|
||||||
|
menu.push(:a_menu, '/', { })
|
||||||
|
menu.push(:a_menu_2, '/', { })
|
||||||
|
menu.push(:a_menu_3, '/', { })
|
||||||
|
end
|
||||||
|
|
||||||
|
items_yielded = []
|
||||||
|
menu_items_for(menu_name) do |item|
|
||||||
|
items_yielded << item
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 3, items_yielded.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_menu_items_for_should_return_all_items
|
||||||
|
menu_name = :test_menu_items_for_should_return_all_items
|
||||||
|
Redmine::MenuManager.map menu_name do |menu|
|
||||||
|
menu.push(:a_menu, '/', { })
|
||||||
|
menu.push(:a_menu_2, '/', { })
|
||||||
|
menu.push(:a_menu_3, '/', { })
|
||||||
|
end
|
||||||
|
|
||||||
|
items = menu_items_for(menu_name)
|
||||||
|
assert_equal 3, items.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_menu_items_for_should_skip_unallowed_items_on_a_project
|
||||||
|
menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project
|
||||||
|
Redmine::MenuManager.map menu_name do |menu|
|
||||||
|
menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
|
||||||
|
menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { })
|
||||||
|
menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { })
|
||||||
|
end
|
||||||
|
|
||||||
|
User.current = User.find(2)
|
||||||
|
|
||||||
|
items = menu_items_for(menu_name, Project.find(1))
|
||||||
|
assert_equal 2, items.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_menu_items_for_should_skip_items_that_fail_the_conditions
|
||||||
|
menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions
|
||||||
|
Redmine::MenuManager.map menu_name do |menu|
|
||||||
|
menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
|
||||||
|
menu.push(:unallowed,
|
||||||
|
{:controller => 'issues', :action => 'index' },
|
||||||
|
{ :if => Proc.new { false }})
|
||||||
|
end
|
||||||
|
|
||||||
|
User.current = User.find(2)
|
||||||
|
|
||||||
|
items = menu_items_for(menu_name, Project.find(1))
|
||||||
|
assert_equal 1, items.size
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,108 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||||
|
|
||||||
|
module RedmineMenuTestHelper
|
||||||
|
# Helpers
|
||||||
|
def get_menu_item(menu_name, item_name)
|
||||||
|
Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Redmine::MenuManager::MenuItemTest < Test::Unit::TestCase
|
||||||
|
include RedmineMenuTestHelper
|
||||||
|
|
||||||
|
Redmine::MenuManager.map :test_menu do |menu|
|
||||||
|
menu.push(:parent_menu, '/test', { })
|
||||||
|
menu.push(:child_menu, '/test', { :parent_menu => :parent_menu})
|
||||||
|
menu.push(:child2_menu, '/test', { :parent_menu => :parent_menu})
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuItem#caption" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuItem#html_options" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
# context new menu item
|
||||||
|
def test_new_menu_item_should_require_a_name
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_menu_item_should_require_an_url
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new(:test_missing_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_menu_item_should_require_the_options
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new(:test_missing_options, '/test')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_menu_item_with_all_required_parameters
|
||||||
|
assert Redmine::MenuManager::MenuItem.new(:test_good_menu, '/test', {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_menu_item_should_require_a_proc_to_use_for_the_if_condition
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new(:test_error, '/test',
|
||||||
|
{
|
||||||
|
:if => ['not_a_proc']
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
assert Redmine::MenuManager::MenuItem.new(:test_good_if, '/test',
|
||||||
|
{
|
||||||
|
:if => Proc.new{}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_menu_item_should_allow_a_hash_for_extra_html_options
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new(:test_error, '/test',
|
||||||
|
{
|
||||||
|
:html => ['not_a_hash']
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
assert Redmine::MenuManager::MenuItem.new(:test_good_html, '/test',
|
||||||
|
{
|
||||||
|
:html => { :onclick => 'doSomething'}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_should_not_allow_setting_the_parent_menu_item_to_the_current_item
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
Redmine::MenuManager::MenuItem.new(:test_error, '/test', { :parent_menu => :test_error })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_children
|
||||||
|
parent_item = get_menu_item(:test_menu, :parent_menu)
|
||||||
|
assert parent_item.hasChildren?
|
||||||
|
assert_equal 2, parent_item.children.size
|
||||||
|
assert_equal get_menu_item(:test_menu, :child_menu), parent_item.children[0]
|
||||||
|
assert_equal get_menu_item(:test_menu, :child2_menu), parent_item.children[1]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||||
|
|
||||||
|
class Redmine::MenuManagerTest < Test::Unit::TestCase
|
||||||
|
context "MenuManager#map" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "MenuManager#items" do
|
||||||
|
should "be tested"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2009 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../../test_helper'
|
||||||
|
|
||||||
|
module RedmineMenuTestHelper
|
||||||
|
# Assertions
|
||||||
|
def assert_number_of_items_in_menu(menu_name, count)
|
||||||
|
assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items"
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_menu_contains_item_named(menu_name, item_name)
|
||||||
|
assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
def get_menu_item(menu_name, item_name)
|
||||||
|
Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class RedmineTest < Test::Unit::TestCase
|
||||||
|
include RedmineMenuTestHelper
|
||||||
|
|
||||||
|
def test_top_menu
|
||||||
|
assert_number_of_items_in_menu :top_menu, 5
|
||||||
|
assert_menu_contains_item_named :top_menu, :home
|
||||||
|
assert_menu_contains_item_named :top_menu, :my_page
|
||||||
|
assert_menu_contains_item_named :top_menu, :projects
|
||||||
|
assert_menu_contains_item_named :top_menu, :administration
|
||||||
|
assert_menu_contains_item_named :top_menu, :help
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_account_menu
|
||||||
|
assert_number_of_items_in_menu :account_menu, 4
|
||||||
|
assert_menu_contains_item_named :account_menu, :login
|
||||||
|
assert_menu_contains_item_named :account_menu, :register
|
||||||
|
assert_menu_contains_item_named :account_menu, :my_account
|
||||||
|
assert_menu_contains_item_named :account_menu, :logout
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_application_menu
|
||||||
|
assert_number_of_items_in_menu :application_menu, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_admin_menu
|
||||||
|
assert_number_of_items_in_menu :admin_menu, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_project_menu
|
||||||
|
assert_number_of_items_in_menu :project_menu, 12
|
||||||
|
assert_menu_contains_item_named :project_menu, :overview
|
||||||
|
assert_menu_contains_item_named :project_menu, :activity
|
||||||
|
assert_menu_contains_item_named :project_menu, :roadmap
|
||||||
|
assert_menu_contains_item_named :project_menu, :issues
|
||||||
|
assert_menu_contains_item_named :project_menu, :new_issue
|
||||||
|
assert_menu_contains_item_named :project_menu, :news
|
||||||
|
assert_menu_contains_item_named :project_menu, :documents
|
||||||
|
assert_menu_contains_item_named :project_menu, :wiki
|
||||||
|
assert_menu_contains_item_named :project_menu, :boards
|
||||||
|
assert_menu_contains_item_named :project_menu, :files
|
||||||
|
assert_menu_contains_item_named :project_menu, :repository
|
||||||
|
assert_menu_contains_item_named :project_menu, :settings
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_issue_should_have_root_as_a_parent
|
||||||
|
new_issue = get_menu_item(:project_menu, :new_issue)
|
||||||
|
assert_equal :root, new_issue.parent.name
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,80 @@
|
||||||
|
--- !ruby/object:Gem::Specification
|
||||||
|
name: rubytree
|
||||||
|
version: !ruby/object:Gem::Version
|
||||||
|
version: 0.5.2
|
||||||
|
platform: ruby
|
||||||
|
authors:
|
||||||
|
- Anupam Sengupta
|
||||||
|
autorequire: tree
|
||||||
|
bindir: bin
|
||||||
|
cert_chain: []
|
||||||
|
|
||||||
|
date: 2007-12-20 00:00:00 -08:00
|
||||||
|
default_executable:
|
||||||
|
dependencies:
|
||||||
|
- !ruby/object:Gem::Dependency
|
||||||
|
name: hoe
|
||||||
|
type: :runtime
|
||||||
|
version_requirement:
|
||||||
|
version_requirements: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 1.3.0
|
||||||
|
version:
|
||||||
|
description: "Provides a generic tree data-structure with ability to store keyed node-elements in the tree. The implementation mixes in the Enumerable module. Website: http://rubytree.rubyforge.org/"
|
||||||
|
email: anupamsg@gmail.com
|
||||||
|
executables: []
|
||||||
|
|
||||||
|
extensions: []
|
||||||
|
|
||||||
|
extra_rdoc_files:
|
||||||
|
- README
|
||||||
|
- COPYING
|
||||||
|
- ChangeLog
|
||||||
|
- History.txt
|
||||||
|
files:
|
||||||
|
- COPYING
|
||||||
|
- ChangeLog
|
||||||
|
- History.txt
|
||||||
|
- Manifest.txt
|
||||||
|
- README
|
||||||
|
- Rakefile
|
||||||
|
- TODO
|
||||||
|
- lib/tree.rb
|
||||||
|
- lib/tree/binarytree.rb
|
||||||
|
- setup.rb
|
||||||
|
- test/test_binarytree.rb
|
||||||
|
- test/test_tree.rb
|
||||||
|
has_rdoc: true
|
||||||
|
homepage: http://rubytree.rubyforge.org/
|
||||||
|
licenses: []
|
||||||
|
|
||||||
|
post_install_message:
|
||||||
|
rdoc_options:
|
||||||
|
- --main
|
||||||
|
- README
|
||||||
|
require_paths:
|
||||||
|
- lib
|
||||||
|
required_ruby_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: "0"
|
||||||
|
version:
|
||||||
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: "0"
|
||||||
|
version:
|
||||||
|
requirements: []
|
||||||
|
|
||||||
|
rubyforge_project: rubytree
|
||||||
|
rubygems_version: 1.3.5
|
||||||
|
signing_key:
|
||||||
|
specification_version: 2
|
||||||
|
summary: Ruby implementation of the Tree data structure.
|
||||||
|
test_files:
|
||||||
|
- test/test_binarytree.rb
|
||||||
|
- test/test_tree.rb
|
|
@ -0,0 +1,31 @@
|
||||||
|
RUBYTREE - http://rubytree.rubyforge.org
|
||||||
|
========================================
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,163 @@
|
||||||
|
2007-12-21 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* Rakefile: Added the rcov option to exclude rcov itself from
|
||||||
|
coverage reports.
|
||||||
|
|
||||||
|
* lib/tree.rb: Minor comment changes.
|
||||||
|
|
||||||
|
* test/test_tree.rb: Added the TestTree enclosing module, and
|
||||||
|
renamed tests to meet ZenTest requirements. Also added a few
|
||||||
|
missing test cases.
|
||||||
|
|
||||||
|
* test/test_binarytree.rb: Added the TestTree enclosing Module,
|
||||||
|
and renamed the tests to meet ZenTest requirements.
|
||||||
|
|
||||||
|
2007-12-19 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* README (Module): Modified the install instructions from source.
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode::initialize): Removed the
|
||||||
|
unnecessary self_initialize method.
|
||||||
|
(Tree::TreeNode): Removed the spurious self_initialize from the
|
||||||
|
protected list.
|
||||||
|
(Module): Updated the minor version number.
|
||||||
|
|
||||||
|
* Rakefile: Fixed a problem with reading the Tree::VERSION for the
|
||||||
|
gem packaging, if any prior version of the gem is already installed.
|
||||||
|
|
||||||
|
2007-12-18 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* lib/tree.rb: Updated the marshalling logic to correctly handle
|
||||||
|
non-string content.
|
||||||
|
(Tree::TreeNode::createDumpRep): Minor code change to use symbols
|
||||||
|
instead of string key names.
|
||||||
|
(Tree): Version number change to 0.5.0
|
||||||
|
(Tree::TreeNode::hasContent): Minor fix to the comments.
|
||||||
|
|
||||||
|
* test/test_tree.rb (TC_TreeTest::test_breadth_each): Updated test
|
||||||
|
cases for the marshalling logic.
|
||||||
|
|
||||||
|
2007-11-12 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* test/test_binarytree.rb: Minor documentation correction.
|
||||||
|
|
||||||
|
* lib/tree/binarytree.rb (Tree::BinaryTreeNode::isRightChild):
|
||||||
|
Minor documentation change.
|
||||||
|
|
||||||
|
2007-10-10 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* README: Restructured the format.
|
||||||
|
|
||||||
|
* Rakefile: Added Hoe related logic. If not present, the Rakefile
|
||||||
|
will default to old behavior.
|
||||||
|
|
||||||
|
2007-10-09 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* Rakefile: Added setup.rb related tasks. Also added the setup.rb in the PKG_FILES list.
|
||||||
|
|
||||||
|
2007-10-01 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* Rakefile: Added an optional task for rcov code coverage.
|
||||||
|
Added a dependency for rake in the Gem Specification.
|
||||||
|
|
||||||
|
* test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
|
||||||
|
|
||||||
|
* test/test_tree.rb: Removed dependency on the redundant "Person" class.
|
||||||
|
(TC_TreeTest::test_comparator): Added a new test for the spaceship operator.
|
||||||
|
(TC_TreeTest::test_hasContent): Added tests for hasContent? and length methods.
|
||||||
|
|
||||||
|
2007-08-30 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* test/test_tree.rb (TC_TreeTest::test_preordered_each, TC_TreeTest::test_breadth_each, TC_TreeTest::test_detached_copy):
|
||||||
|
Added new tests for the new functions added to tree.rb.
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode::detached_copy, Tree::TreeNode::preordered_each, Tree::TreeNode::breadth_each):
|
||||||
|
Added new functions for returning a detached copy of the node and
|
||||||
|
for performing breadth first traversal. Also added the pre-ordered
|
||||||
|
traversal function which is an alias of the existing 'each' method.
|
||||||
|
|
||||||
|
* test/test_binarytree.rb (TC_BinaryTreeTest::test_swap_children):
|
||||||
|
Added a test case for the children swap function.
|
||||||
|
|
||||||
|
* lib/tree/binarytree.rb (Tree::BinaryTreeNode::swap_children):
|
||||||
|
Added new function to swap the children. Other minor changes in
|
||||||
|
comments and code.
|
||||||
|
|
||||||
|
2007-07-18 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* lib/tree/binarytree.rb (Tree::BinaryTreeNode::leftChild /
|
||||||
|
rightChild): Minor cosmetic change on the parameter name.
|
||||||
|
|
||||||
|
* test/testbinarytree.rb (TC_BinaryTreeTest::test_isLeftChild):
|
||||||
|
Minor syntax correction.
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode::depth): Added a tree depth
|
||||||
|
computation method.
|
||||||
|
(Tree::TreeNode::breadth): Added a tree breadth method.
|
||||||
|
|
||||||
|
* test/testtree.rb (TC_TreeTest::test_depth/test_breadth): Added a
|
||||||
|
test for the depth and breadth method.
|
||||||
|
|
||||||
|
* lib/tree/binarytree.rb (Tree::BinaryTreeNode::is*Child):
|
||||||
|
Added tests for determining whether a node is a left or right
|
||||||
|
child.
|
||||||
|
|
||||||
|
* test/testbinarytree.rb: Added the test cases for the binary tree
|
||||||
|
implementation.
|
||||||
|
(TC_BinaryTreeTest::test_is*Child): Added tests for right or left
|
||||||
|
childs.
|
||||||
|
|
||||||
|
* lib/tree/binarytree.rb: Added the binary tree implementation.
|
||||||
|
|
||||||
|
2007-07-17 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode::parentage): Renamed 'ancestors'
|
||||||
|
method to 'parentage' to avoid clobbering Module.ancestors
|
||||||
|
|
||||||
|
2007-07-16 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* Rakefile: Added an optional rtags task to generate TAGS file for
|
||||||
|
Emacs.
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode): Added navigation methods for
|
||||||
|
siblings and children. Also added some convenience methods.
|
||||||
|
|
||||||
|
2007-07-08 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* Rakefile: Added a developer target for generating rdoc for the
|
||||||
|
website.
|
||||||
|
|
||||||
|
2007-06-24 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* test/testtree.rb, lib/tree.rb: Added the each_leaf traversal method.
|
||||||
|
|
||||||
|
* README: Replaced all occurrances of LICENSE with COPYING
|
||||||
|
and lowercased all instances of the word 'RubyTree'.
|
||||||
|
|
||||||
|
* Rakefile: Replaced all occurrances of LICENSE with COPYING
|
||||||
|
|
||||||
|
2007-06-23 Anupam Sengupta <anupamsg@gmail.com>
|
||||||
|
|
||||||
|
* lib/tree.rb (Tree::TreeNode::isLeaf): Added a isLeaf? method.
|
||||||
|
|
||||||
|
* test/testtree.rb (TC_TreeTest::test_removeFromParent): Added
|
||||||
|
test for isLeaf? method
|
||||||
|
|
||||||
|
* Rakefile: Added the LICENSE and ChangeLog to the extra RDoc files.
|
||||||
|
|
||||||
|
* lib/tree.rb: Minor updates to the comments.
|
||||||
|
|
||||||
|
* test/testtree.rb: Added the Copyright and License header.
|
||||||
|
|
||||||
|
* test/person.rb: Added the Copyright and License header.
|
||||||
|
|
||||||
|
* lib/tree.rb: Added the Copyright and License header.
|
||||||
|
|
||||||
|
* Rakefile: Added the LICENSE and Changelog to be part of the RDoc task.
|
||||||
|
|
||||||
|
* README: Added documentation in the README, including install
|
||||||
|
instructions and an example.
|
||||||
|
|
||||||
|
* LICENSE: Added the BSD LICENSE file.
|
||||||
|
|
||||||
|
* Changelog: Added the Changelog file.
|
|
@ -0,0 +1,20 @@
|
||||||
|
= 0.5.2 / 2007-12-21
|
||||||
|
|
||||||
|
* Added more test cases and enabled ZenTest compatibility for the test case
|
||||||
|
names.
|
||||||
|
|
||||||
|
= 0.5.1 / 2007-12-20
|
||||||
|
|
||||||
|
* Minor code refactoring.
|
||||||
|
|
||||||
|
= 0.5.0 / 2007-12-18
|
||||||
|
|
||||||
|
* Fixed the marshalling code to correctly handle non-string content.
|
||||||
|
|
||||||
|
= 0.4.3 / 2007-10-09
|
||||||
|
|
||||||
|
* Changes to the build mechanism (now uses Hoe).
|
||||||
|
|
||||||
|
= 0.4.2 / 2007-10-01
|
||||||
|
|
||||||
|
* Minor code refactoring. Changes in the Rakefile.
|
|
@ -0,0 +1,12 @@
|
||||||
|
COPYING
|
||||||
|
ChangeLog
|
||||||
|
History.txt
|
||||||
|
Manifest.txt
|
||||||
|
README
|
||||||
|
Rakefile
|
||||||
|
TODO
|
||||||
|
lib/tree.rb
|
||||||
|
lib/tree/binarytree.rb
|
||||||
|
setup.rb
|
||||||
|
test/test_binarytree.rb
|
||||||
|
test/test_tree.rb
|
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
__ _ _
|
||||||
|
/__\_ _| |__ _ _| |_ _ __ ___ ___
|
||||||
|
/ \// | | | '_ \| | | | __| '__/ _ \/ _ \
|
||||||
|
/ _ \ |_| | |_) | |_| | |_| | | __/ __/
|
||||||
|
\/ \_/\__,_|_.__/ \__, |\__|_| \___|\___|
|
||||||
|
|___/
|
||||||
|
|
||||||
|
(c) 2006, 2007 Anupam Sengupta
|
||||||
|
http://rubytree.rubyforge.org
|
||||||
|
|
||||||
|
Rubytree is a simple implementation of the generic Tree data structure. This
|
||||||
|
implementation is node-centric, where the individual nodes on the tree are the
|
||||||
|
primary objects and drive the structure.
|
||||||
|
|
||||||
|
== INSTALL:
|
||||||
|
|
||||||
|
Rubytree is an open source project and is hosted at:
|
||||||
|
|
||||||
|
http://rubytree.rubyforge.org
|
||||||
|
|
||||||
|
Rubytree can be downloaded as a Rubygem or as a tar/zip file from:
|
||||||
|
|
||||||
|
http://rubyforge.org/frs/?group_id=1215&release_id=8817
|
||||||
|
|
||||||
|
The file-name is one of:
|
||||||
|
|
||||||
|
rubytree-<VERSION>.gem - The Rubygem
|
||||||
|
rubytree-<VERSION>.tgz - GZipped source files
|
||||||
|
rubytree-<VERSION>.zip - Zipped source files
|
||||||
|
|
||||||
|
Download the appropriate file-type for your system.
|
||||||
|
|
||||||
|
It is recommended to install Rubytree as a Ruby Gem, as this is an easy way to
|
||||||
|
keep the version updated, and keep multiple versions of the library available on
|
||||||
|
your system.
|
||||||
|
|
||||||
|
=== Installing the Gem
|
||||||
|
|
||||||
|
To Install the Gem, from a Terminal/CLI command prompt, issue the command:
|
||||||
|
|
||||||
|
gem install rubytree
|
||||||
|
|
||||||
|
This should install the gem file for Rubytree. Note that you may need to be a
|
||||||
|
super-user (root) to successfully install the gem.
|
||||||
|
|
||||||
|
=== Installing from the tgz/zip file
|
||||||
|
|
||||||
|
Extract the archive file (tgz or zip) and run the following command from the
|
||||||
|
top-level source directory:
|
||||||
|
|
||||||
|
ruby ./setup.rb
|
||||||
|
|
||||||
|
You may need administrator/super-user privileges to complete the setup using
|
||||||
|
this method.
|
||||||
|
|
||||||
|
== DOCUMENTATION:
|
||||||
|
|
||||||
|
The primary class for this implementation is Tree::TreeNode. See the
|
||||||
|
class documentation for an usage example.
|
||||||
|
|
||||||
|
From a command line/terminal prompt, you can issue the following command to view
|
||||||
|
the text mode ri documentation:
|
||||||
|
|
||||||
|
ri Tree::TreeNode
|
||||||
|
|
||||||
|
Documentation on the web is available at:
|
||||||
|
|
||||||
|
http://rubytree.rubyforge.org/rdoc
|
||||||
|
|
||||||
|
== 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
|
||||||
|
|
||||||
|
== LICENSE:
|
||||||
|
|
||||||
|
Rubytree is licensed under BSD license.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
(Document Revision: $Revision: 1.16 $ by $Author: anupamsg $)
|
|
@ -0,0 +1,212 @@
|
||||||
|
# Rakefile
|
||||||
|
#
|
||||||
|
# $Revision: 1.27 $ by $Author: anupamsg $
|
||||||
|
# $Name: $
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
require 'rake/gempackagetask'
|
||||||
|
|
||||||
|
require 'rake/clean'
|
||||||
|
require 'rake/packagetask'
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
# General Stuff ####################################################
|
||||||
|
|
||||||
|
$:.insert 0, File.expand_path( File.join( File.dirname(__FILE__), 'lib' ) )
|
||||||
|
require 'tree' # To read the version information.
|
||||||
|
|
||||||
|
PKG_NAME = "rubytree"
|
||||||
|
PKG_VERSION = Tree::VERSION
|
||||||
|
PKG_FULLNAME = PKG_NAME + "-" + PKG_VERSION
|
||||||
|
PKG_SUMMARY = "Ruby implementation of the Tree data structure."
|
||||||
|
PKG_DESCRIPTION = <<-END
|
||||||
|
Provides a generic tree data-structure with ability to
|
||||||
|
store keyed node-elements in the tree. The implementation
|
||||||
|
mixes in the Enumerable module.
|
||||||
|
|
||||||
|
Website: http://rubytree.rubyforge.org/
|
||||||
|
END
|
||||||
|
|
||||||
|
PKG_FILES = FileList[
|
||||||
|
'[A-Z]*',
|
||||||
|
'*.rb',
|
||||||
|
'lib/**/*.rb',
|
||||||
|
'test/**/*.rb'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Default is to create a rubygem.
|
||||||
|
desc "Default Task"
|
||||||
|
task :default => :gem
|
||||||
|
|
||||||
|
begin # Try loading hoe
|
||||||
|
require 'hoe'
|
||||||
|
# If Hoe is found, use it to define tasks
|
||||||
|
# =======================================
|
||||||
|
Hoe.new(PKG_NAME, PKG_VERSION) do |p|
|
||||||
|
p.rubyforge_name = PKG_NAME
|
||||||
|
p.author = "Anupam Sengupta"
|
||||||
|
p.email = "anupamsg@gmail.com"
|
||||||
|
p.summary = PKG_SUMMARY
|
||||||
|
p.description = PKG_DESCRIPTION
|
||||||
|
p.url = "http://rubytree.rubyforge.org/"
|
||||||
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
||||||
|
p.remote_rdoc_dir = 'rdoc'
|
||||||
|
p.need_tar = true
|
||||||
|
p.need_zip = true
|
||||||
|
p.test_globs = ['test/test_*.rb']
|
||||||
|
p.spec_extras = {
|
||||||
|
:has_rdoc => true,
|
||||||
|
:platform => Gem::Platform::RUBY,
|
||||||
|
:has_rdoc => true,
|
||||||
|
:extra_rdoc_files => ['README', 'COPYING', 'ChangeLog', 'History.txt'],
|
||||||
|
:rdoc_options => ['--main', 'README'],
|
||||||
|
:autorequire => 'tree'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue LoadError # If Hoe is not found
|
||||||
|
# If Hoe is not found, then use the usual Gemspec based Rake tasks
|
||||||
|
# ================================================================
|
||||||
|
spec = Gem::Specification.new do |s|
|
||||||
|
s.name = PKG_NAME
|
||||||
|
s.version = PKG_VERSION
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.author = "Anupam Sengupta"
|
||||||
|
s.email = "anupamsg@gmail.com"
|
||||||
|
s.homepage = "http://rubytree.rubyforge.org/"
|
||||||
|
s.rubyforge_project = 'rubytree'
|
||||||
|
s.summary = PKG_SUMMARY
|
||||||
|
s.add_dependency('rake', '>= 0.7.2')
|
||||||
|
s.description = PKG_DESCRIPTION
|
||||||
|
s.has_rdoc = true
|
||||||
|
s.extra_rdoc_files = ['README', 'COPYING', 'ChangeLog']
|
||||||
|
s.autorequire = "tree"
|
||||||
|
s.files = PKG_FILES.to_a
|
||||||
|
s.test_files = Dir.glob('test/test*.rb')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Prepares for installation"
|
||||||
|
task :prepare do
|
||||||
|
ruby "setup.rb config"
|
||||||
|
ruby "setup.rb setup"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Installs the package #{PKG_NAME}"
|
||||||
|
task :install => [:prepare] do
|
||||||
|
ruby "setup.rb install"
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::GemPackageTask.new(spec) do |pkg|
|
||||||
|
pkg.need_zip = true
|
||||||
|
pkg.need_tar = true
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::TestTask.new do |t|
|
||||||
|
t.libs << "test"
|
||||||
|
t.test_files = FileList['test/test*.rb']
|
||||||
|
t.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
end # End loading Hoerc
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
|
||||||
|
# The following tasks are loaded independently of Hoe
|
||||||
|
|
||||||
|
# Hoe's rdoc task is ugly.
|
||||||
|
Rake::RDocTask.new(:docs) do |rd|
|
||||||
|
rd.rdoc_files.include("README", "COPYING", "ChangeLog", "lib/**/*.rb")
|
||||||
|
rd.rdoc_dir = 'doc'
|
||||||
|
rd.title = "#{PKG_FULLNAME} Documentation"
|
||||||
|
|
||||||
|
# Use the template only if it is present, otherwise, the standard template is
|
||||||
|
# used.
|
||||||
|
template = "../allison/allison.rb"
|
||||||
|
rd.template = template if File.file?(template)
|
||||||
|
|
||||||
|
rd.options << '--line-numbers' << '--inline-source'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Optional TAGS Task.
|
||||||
|
# Needs https://rubyforge.org/projects/rtagstask/
|
||||||
|
begin
|
||||||
|
require 'rtagstask'
|
||||||
|
RTagsTask.new do |rd|
|
||||||
|
rd.vi = false
|
||||||
|
end
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Optional RCOV Task
|
||||||
|
# Needs http://eigenclass.org/hiki/rcov
|
||||||
|
begin
|
||||||
|
require 'rcov/rcovtask'
|
||||||
|
Rcov::RcovTask.new do |t|
|
||||||
|
t.test_files = FileList['test/test*.rb']
|
||||||
|
t.rcov_opts << "--exclude 'rcov.rb'" # rcov itself should not be profiled
|
||||||
|
# t.verbose = true # uncomment to see the executed commands
|
||||||
|
end
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
|
||||||
|
#Rakefile,v $
|
||||||
|
# Revision 1.21 2007/07/21 05:14:43 anupamsg
|
||||||
|
# Added a VERSION constant to the Tree module,
|
||||||
|
# and using the same in the Rakefile.
|
||||||
|
#
|
||||||
|
# Revision 1.20 2007/07/21 03:24:25 anupamsg
|
||||||
|
# Minor edits to parameter names. User visible functionality does not change.
|
||||||
|
#
|
||||||
|
# Revision 1.19 2007/07/19 02:16:01 anupamsg
|
||||||
|
# Release 0.4.0 (and minor fix in Rakefile).
|
||||||
|
#
|
||||||
|
# Revision 1.18 2007/07/18 20:15:06 anupamsg
|
||||||
|
# Added two predicate methods in BinaryTreeNode to determine whether a node
|
||||||
|
# is a left or a right node.
|
||||||
|
#
|
||||||
|
# Revision 1.17 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.16 2007/07/17 05:34:03 anupamsg
|
||||||
|
# Added an optional tags Rake-task for generating the TAGS file for Emacs.
|
||||||
|
#
|
||||||
|
# Revision 1.15 2007/07/17 04:42:45 anupamsg
|
||||||
|
# Minor fixes to the Rakefile.
|
||||||
|
#
|
||||||
|
# Revision 1.14 2007/07/17 03:39:28 anupamsg
|
||||||
|
# Moved the CVS Log keyword to end of the files.
|
||||||
|
#
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- mode: outline; coding: utf-8-unix; -*-
|
||||||
|
|
||||||
|
* Add logic in Rakefile to read the file list from Manifest.txt file.
|
||||||
|
|
||||||
|
* Add a YAML export method to the TreeNode class.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
#
|
|
@ -0,0 +1,131 @@
|
||||||
|
# binarytree.rb
|
||||||
|
#
|
||||||
|
# $Revision: 1.5 $ by $Author: anupamsg $
|
||||||
|
# $Name: $
|
||||||
|
#
|
||||||
|
# = binarytree.rb - Binary 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) 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'tree'
|
||||||
|
|
||||||
|
module Tree
|
||||||
|
|
||||||
|
# Provides a Binary tree implementation. This tree node allows only two child
|
||||||
|
# nodes (left and right childs). It also provides direct access to the left
|
||||||
|
# and right children, including assignment to the same.
|
||||||
|
class BinaryTreeNode < TreeNode
|
||||||
|
|
||||||
|
# Adds the specified child node to the receiver node. The child node's
|
||||||
|
# parent is set to be the receiver. The child nodes are added in the order
|
||||||
|
# of addition, i.e., the first child added becomes the left node, and the
|
||||||
|
# second child will be the second node.
|
||||||
|
# If only one child is present, then this will be the left child.
|
||||||
|
def add(child)
|
||||||
|
raise "Already has two child nodes" if @children.size == 2
|
||||||
|
|
||||||
|
super(child)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the left child node. Note that
|
||||||
|
# left Child == first Child
|
||||||
|
def leftChild
|
||||||
|
children.first
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the right child node. Note that
|
||||||
|
# right child == last child unless there is only one child.
|
||||||
|
# Returns nil if the right child does not exist.
|
||||||
|
def rightChild
|
||||||
|
children[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the left child. If a previous child existed, it is replaced.
|
||||||
|
def leftChild=(child)
|
||||||
|
@children[0] = child
|
||||||
|
@childrenHash[child.name] = child if child # Assign the name mapping
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the right child. If a previous child existed, it is replaced.
|
||||||
|
def rightChild=(child)
|
||||||
|
@children[1] = child
|
||||||
|
@childrenHash[child.name] = child if child # Assign the name mapping
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns true if this is the left child of its parent. Always returns false
|
||||||
|
# if this is the root node.
|
||||||
|
def isLeftChild?
|
||||||
|
return nil if isRoot?
|
||||||
|
self == parent.leftChild
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns true if this is the right child of its parent. Always returns false
|
||||||
|
# if this is the root node.
|
||||||
|
def isRightChild?
|
||||||
|
return nil if isRoot?
|
||||||
|
self == parent.rightChild
|
||||||
|
end
|
||||||
|
|
||||||
|
# Swaps the left and right children with each other
|
||||||
|
def swap_children
|
||||||
|
tempChild = leftChild
|
||||||
|
self.leftChild= rightChild
|
||||||
|
self.rightChild= tempChild
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# $Log: binarytree.rb,v $
|
||||||
|
# Revision 1.5 2007/12/18 23:11:29 anupamsg
|
||||||
|
# Minor documentation changes in the binarytree class.
|
||||||
|
#
|
||||||
|
# Revision 1.4 2007/08/30 22:08:58 anupamsg
|
||||||
|
# Added a new swap_children method for Binary Tree. Also added minor
|
||||||
|
# documentation and test updates.
|
||||||
|
#
|
||||||
|
# Revision 1.3 2007/07/21 03:24:25 anupamsg
|
||||||
|
# Minor edits to parameter names. User visible functionality does not change.
|
||||||
|
#
|
||||||
|
# Revision 1.2 2007/07/18 20:15:06 anupamsg
|
||||||
|
# Added two predicate methods in BinaryTreeNode to determine whether a node
|
||||||
|
# is a left or a right node.
|
||||||
|
#
|
||||||
|
# Revision 1.1 2007/07/18 19:33:27 anupamsg
|
||||||
|
# Added a new binary tree implementation.
|
||||||
|
#
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,204 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# test_binarytree.rb
|
||||||
|
#
|
||||||
|
# $Revision: 1.5 $ by $Author: anupamsg $
|
||||||
|
# $Name: $
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'tree/binarytree'
|
||||||
|
|
||||||
|
module TestTree
|
||||||
|
# Test class for the Tree node.
|
||||||
|
class TestBinaryTreeNode < Test::Unit::TestCase
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@root = Tree::BinaryTreeNode.new("ROOT", "Root Node")
|
||||||
|
|
||||||
|
@left_child1 = Tree::BinaryTreeNode.new("A Child at Left", "Child Node @ left")
|
||||||
|
@right_child1 = Tree::BinaryTreeNode.new("B Child at Right", "Child Node @ right")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
@root.remove!(@left_child1)
|
||||||
|
@root.remove!(@right_child1)
|
||||||
|
@root = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_initialize
|
||||||
|
assert_not_nil(@root, "Binary tree's Root should have been created")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add
|
||||||
|
@root.add @left_child1
|
||||||
|
assert_same(@left_child1, @root.leftChild, "The left node should be left_child1")
|
||||||
|
assert_same(@left_child1, @root.firstChild, "The first node should be left_child1")
|
||||||
|
|
||||||
|
@root.add @right_child1
|
||||||
|
assert_same(@right_child1, @root.rightChild, "The right node should be right_child1")
|
||||||
|
assert_same(@right_child1, @root.lastChild, "The first node should be right_child1")
|
||||||
|
|
||||||
|
assert_raise RuntimeError do
|
||||||
|
@root.add Tree::BinaryTreeNode.new("The third child!")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise RuntimeError do
|
||||||
|
@root << Tree::BinaryTreeNode.new("The third child!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_leftChild
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
assert_same(@left_child1, @root.leftChild, "The left child should be 'left_child1")
|
||||||
|
assert_not_same(@right_child1, @root.leftChild, "The right_child1 is not the left child")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rightChild
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
assert_same(@right_child1, @root.rightChild, "The right child should be 'right_child1")
|
||||||
|
assert_not_same(@left_child1, @root.rightChild, "The left_child1 is not the left child")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_leftChild_equals
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
assert_same(@left_child1, @root.leftChild, "The left child should be 'left_child1")
|
||||||
|
|
||||||
|
@root.leftChild = Tree::BinaryTreeNode.new("New Left Child")
|
||||||
|
assert_equal("New Left Child", @root.leftChild.name, "The left child should now be the new child")
|
||||||
|
assert_equal("B Child at Right", @root.lastChild.name, "The last child should now be the right child")
|
||||||
|
|
||||||
|
# Now set the left child as nil, and retest
|
||||||
|
@root.leftChild = nil
|
||||||
|
assert_nil(@root.leftChild, "The left child should now be nil")
|
||||||
|
assert_nil(@root.firstChild, "The first child is now nil")
|
||||||
|
assert_equal("B Child at Right", @root.lastChild.name, "The last child should now be the right child")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rightChild_equals
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
assert_same(@right_child1, @root.rightChild, "The right child should be 'right_child1")
|
||||||
|
|
||||||
|
@root.rightChild = Tree::BinaryTreeNode.new("New Right Child")
|
||||||
|
assert_equal("New Right Child", @root.rightChild.name, "The right child should now be the new child")
|
||||||
|
assert_equal("A Child at Left", @root.firstChild.name, "The first child should now be the left child")
|
||||||
|
assert_equal("New Right Child", @root.lastChild.name, "The last child should now be the right child")
|
||||||
|
|
||||||
|
# Now set the right child as nil, and retest
|
||||||
|
@root.rightChild = nil
|
||||||
|
assert_nil(@root.rightChild, "The right child should now be nil")
|
||||||
|
assert_equal("A Child at Left", @root.firstChild.name, "The first child should now be the left child")
|
||||||
|
assert_nil(@root.lastChild, "The first child is now nil")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isLeftChild_eh
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
|
||||||
|
assert(@left_child1.isLeftChild?, "left_child1 should be the left child")
|
||||||
|
assert(!@right_child1.isLeftChild?, "left_child1 should be the left child")
|
||||||
|
|
||||||
|
# Now set the right child as nil, and retest
|
||||||
|
@root.rightChild = nil
|
||||||
|
assert(@left_child1.isLeftChild?, "left_child1 should be the left child")
|
||||||
|
|
||||||
|
assert(!@root.isLeftChild?, "Root is neither left child nor right")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isRightChild_eh
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
|
||||||
|
assert(@right_child1.isRightChild?, "right_child1 should be the right child")
|
||||||
|
assert(!@left_child1.isRightChild?, "right_child1 should be the right child")
|
||||||
|
|
||||||
|
# Now set the left child as nil, and retest
|
||||||
|
@root.leftChild = nil
|
||||||
|
assert(@right_child1.isRightChild?, "right_child1 should be the right child")
|
||||||
|
assert(!@root.isRightChild?, "Root is neither left child nor right")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_swap_children
|
||||||
|
@root << @left_child1
|
||||||
|
@root << @right_child1
|
||||||
|
|
||||||
|
assert(@right_child1.isRightChild?, "right_child1 should be the right child")
|
||||||
|
assert(!@left_child1.isRightChild?, "right_child1 should be the right child")
|
||||||
|
|
||||||
|
@root.swap_children
|
||||||
|
|
||||||
|
assert(@right_child1.isLeftChild?, "right_child1 should now be the left child")
|
||||||
|
assert(@left_child1.isRightChild?, "left_child1 should now be the right child")
|
||||||
|
assert_equal(@right_child1, @root.firstChild, "right_child1 should now be the first child")
|
||||||
|
assert_equal(@left_child1, @root.lastChild, "left_child1 should now be the last child")
|
||||||
|
assert_equal(@right_child1, @root[0], "right_child1 should now be the first child")
|
||||||
|
assert_equal(@left_child1, @root[1], "left_child1 should now be the last child")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# $Log: test_binarytree.rb,v $
|
||||||
|
# Revision 1.5 2007/12/22 00:28:59 anupamsg
|
||||||
|
# Added more test cases, and enabled ZenTest compatibility.
|
||||||
|
#
|
||||||
|
# Revision 1.4 2007/12/18 23:11:29 anupamsg
|
||||||
|
# Minor documentation changes in the binarytree class.
|
||||||
|
#
|
||||||
|
# Revision 1.3 2007/10/02 03:07:30 anupamsg
|
||||||
|
# * Rakefile: Added an optional task for rcov code coverage.
|
||||||
|
#
|
||||||
|
# * test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
|
||||||
|
#
|
||||||
|
# * test/test_tree.rb: Removed dependency on the redundant "Person" class.
|
||||||
|
#
|
||||||
|
# Revision 1.2 2007/08/30 22:06:13 anupamsg
|
||||||
|
# Added a new swap_children method for the Binary Tree class.
|
||||||
|
# Also made minor documentation updates and test additions.
|
||||||
|
#
|
||||||
|
# Revision 1.1 2007/07/21 04:52:37 anupamsg
|
||||||
|
# Renamed the test files.
|
||||||
|
#
|
||||||
|
# Revision 1.4 2007/07/19 02:03:57 anupamsg
|
||||||
|
# Minor syntax correction.
|
||||||
|
#
|
||||||
|
# Revision 1.3 2007/07/19 02:02:12 anupamsg
|
||||||
|
# Removed useless files (including rdoc, which should be generated for each release.
|
||||||
|
#
|
||||||
|
# Revision 1.2 2007/07/18 20:15:06 anupamsg
|
||||||
|
# Added two predicate methods in BinaryTreeNode to determine whether a node
|
||||||
|
# is a left or a right node.
|
||||||
|
#
|
|
@ -0,0 +1,718 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# testtree.rb
|
||||||
|
#
|
||||||
|
# $Revision: 1.6 $ by $Author: anupamsg $
|
||||||
|
# $Name: $
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'test/unit'
|
||||||
|
require 'tree'
|
||||||
|
|
||||||
|
module TestTree
|
||||||
|
# Test class for the Tree node.
|
||||||
|
class TestTreeNode < Test::Unit::TestCase
|
||||||
|
|
||||||
|
Person = Struct::new(:First, :last)
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@root = Tree::TreeNode.new("ROOT", "Root Node")
|
||||||
|
|
||||||
|
@child1 = Tree::TreeNode.new("Child1", "Child Node 1")
|
||||||
|
@child2 = Tree::TreeNode.new("Child2", "Child Node 2")
|
||||||
|
@child3 = Tree::TreeNode.new("Child3", "Child Node 3")
|
||||||
|
@child4 = Tree::TreeNode.new("Child31", "Grand Child 1")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create this structure for the tests
|
||||||
|
#
|
||||||
|
# +----------+
|
||||||
|
# | ROOT |
|
||||||
|
# +-+--------+
|
||||||
|
# |
|
||||||
|
# | +---------------+
|
||||||
|
# +----+ CHILD1 |
|
||||||
|
# | +---------------+
|
||||||
|
# |
|
||||||
|
# | +---------------+
|
||||||
|
# +----+ CHILD2 |
|
||||||
|
# | +---------------+
|
||||||
|
# |
|
||||||
|
# | +---------------+ +------------------+
|
||||||
|
# +----+ CHILD3 +---+ CHILD4 |
|
||||||
|
# +---------------+ +------------------+
|
||||||
|
#
|
||||||
|
def loadChildren
|
||||||
|
@root << @child1
|
||||||
|
@root << @child2
|
||||||
|
@root << @child3 << @child4
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
@root = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_root_setup
|
||||||
|
assert_not_nil(@root, "Root cannot be nil")
|
||||||
|
assert_nil(@root.parent, "Parent of root node should be nil")
|
||||||
|
assert_not_nil(@root.name, "Name should not be nil")
|
||||||
|
assert_equal("ROOT", @root.name, "Name should be 'ROOT'")
|
||||||
|
assert_equal("Root Node", @root.content, "Content should be 'Root Node'")
|
||||||
|
assert(@root.isRoot?, "Should identify as root")
|
||||||
|
assert(!@root.hasChildren?, "Cannot have any children")
|
||||||
|
assert(@root.hasContent?, "This root should have content")
|
||||||
|
assert_equal(1, @root.size, "Number of nodes should be one")
|
||||||
|
assert_nil(@root.siblings, "Root cannot have any children")
|
||||||
|
|
||||||
|
assert_raise(RuntimeError) { Tree::TreeNode.new(nil) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_root
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_same(@root, @root.root, "Root's root is self")
|
||||||
|
assert_same(@root, @child1.root, "Root should be ROOT")
|
||||||
|
assert_same(@root, @child4.root, "Root should be ROOT")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hasContent_eh
|
||||||
|
aNode = Tree::TreeNode.new("A Node")
|
||||||
|
assert_nil(aNode.content, "The node should not have content")
|
||||||
|
assert(!aNode.hasContent?, "The node should not have content")
|
||||||
|
|
||||||
|
aNode.content = "Something"
|
||||||
|
assert_not_nil(aNode.content, "The node should now have content")
|
||||||
|
assert(aNode.hasContent?, "The node should now have content")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_length
|
||||||
|
loadChildren
|
||||||
|
assert_equal(@root.size, @root.length, "Length and size methods should return the same result")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_spaceship # Test the <=> operator.
|
||||||
|
firstNode = Tree::TreeNode.new(1)
|
||||||
|
secondNode = Tree::TreeNode.new(2)
|
||||||
|
|
||||||
|
assert_equal(firstNode <=> nil, +1)
|
||||||
|
assert_equal(firstNode <=> secondNode, -1)
|
||||||
|
|
||||||
|
secondNode = Tree::TreeNode.new(1)
|
||||||
|
assert_equal(firstNode <=> secondNode, 0)
|
||||||
|
|
||||||
|
firstNode = Tree::TreeNode.new("ABC")
|
||||||
|
secondNode = Tree::TreeNode.new("XYZ")
|
||||||
|
|
||||||
|
assert_equal(firstNode <=> nil, +1)
|
||||||
|
assert_equal(firstNode <=> secondNode, -1)
|
||||||
|
|
||||||
|
secondNode = Tree::TreeNode.new("ABC")
|
||||||
|
assert_equal(firstNode <=> secondNode, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_s
|
||||||
|
aNode = Tree::TreeNode.new("A Node", "Some Content")
|
||||||
|
|
||||||
|
expectedString = "Node Name: A Node Content: Some Content Parent: <None> Children: 0 Total Nodes: 1"
|
||||||
|
|
||||||
|
assert_equal(expectedString, aNode.to_s, "The string representation should be same")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_firstSibling
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_same(@root, @root.firstSibling, "Root's first sibling is itself")
|
||||||
|
assert_same(@child1, @child1.firstSibling, "Child1's first sibling is itself")
|
||||||
|
assert_same(@child1, @child2.firstSibling, "Child2's first sibling should be child1")
|
||||||
|
assert_same(@child1, @child3.firstSibling, "Child3's first sibling should be child1")
|
||||||
|
assert_not_same(@child1, @child4.firstSibling, "Child4's first sibling is itself")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isFirstSibling_eh
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert(@root.isFirstSibling?, "Root's first sibling is itself")
|
||||||
|
assert( @child1.isFirstSibling?, "Child1's first sibling is itself")
|
||||||
|
assert(!@child2.isFirstSibling?, "Child2 is not the first sibling")
|
||||||
|
assert(!@child3.isFirstSibling?, "Child3 is not the first sibling")
|
||||||
|
assert( @child4.isFirstSibling?, "Child4's first sibling is itself")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isLastSibling_eh
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert(@root.isLastSibling?, "Root's last sibling is itself")
|
||||||
|
assert(!@child1.isLastSibling?, "Child1 is not the last sibling")
|
||||||
|
assert(!@child2.isLastSibling?, "Child2 is not the last sibling")
|
||||||
|
assert( @child3.isLastSibling?, "Child3's last sibling is itself")
|
||||||
|
assert( @child4.isLastSibling?, "Child4's last sibling is itself")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lastSibling
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_same(@root, @root.lastSibling, "Root's last sibling is itself")
|
||||||
|
assert_same(@child3, @child1.lastSibling, "Child1's last sibling should be child3")
|
||||||
|
assert_same(@child3, @child2.lastSibling, "Child2's last sibling should be child3")
|
||||||
|
assert_same(@child3, @child3.lastSibling, "Child3's last sibling should be itself")
|
||||||
|
assert_not_same(@child3, @child4.lastSibling, "Child4's last sibling is itself")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_siblings
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
siblings = []
|
||||||
|
@child1.siblings { |sibling| siblings << sibling}
|
||||||
|
assert_equal(2, siblings.length, "Should have two siblings")
|
||||||
|
assert(siblings.include?(@child2), "Should have 2nd child as sibling")
|
||||||
|
assert(siblings.include?(@child3), "Should have 3rd child as sibling")
|
||||||
|
|
||||||
|
siblings.clear
|
||||||
|
siblings = @child1.siblings
|
||||||
|
assert_equal(2, siblings.length, "Should have two siblings")
|
||||||
|
|
||||||
|
siblings.clear
|
||||||
|
@child4.siblings {|sibling| siblings << sibling}
|
||||||
|
assert(siblings.empty?, "Should not have any children")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isOnlyChild_eh
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert(!@child1.isOnlyChild?, "Child1 is not the only child")
|
||||||
|
assert(!@child2.isOnlyChild?, "Child2 is not the only child")
|
||||||
|
assert(!@child3.isOnlyChild?, "Child3 is not the only child")
|
||||||
|
assert( @child4.isOnlyChild?, "Child4 is not the only child")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nextSibling
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_equal(@child2, @child1.nextSibling, "Child1's next sibling is Child2")
|
||||||
|
assert_equal(@child3, @child2.nextSibling, "Child2's next sibling is Child3")
|
||||||
|
assert_nil(@child3.nextSibling, "Child3 does not have a next sibling")
|
||||||
|
assert_nil(@child4.nextSibling, "Child4 does not have a next sibling")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_previousSibling
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_nil(@child1.previousSibling, "Child1 does not have previous sibling")
|
||||||
|
assert_equal(@child1, @child2.previousSibling, "Child2's previous sibling is Child1")
|
||||||
|
assert_equal(@child2, @child3.previousSibling, "Child3's previous sibling is Child2")
|
||||||
|
assert_nil(@child4.previousSibling, "Child4 does not have a previous sibling")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add
|
||||||
|
assert(!@root.hasChildren?, "Should not have any children")
|
||||||
|
|
||||||
|
@root.add(@child1)
|
||||||
|
|
||||||
|
@root << @child2
|
||||||
|
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert_equal(3, @root.size, "Should have three nodes")
|
||||||
|
|
||||||
|
@root << @child3 << @child4
|
||||||
|
|
||||||
|
assert_equal(5, @root.size, "Should have five nodes")
|
||||||
|
assert_equal(2, @child3.size, "Should have two nodes")
|
||||||
|
|
||||||
|
assert_raise(RuntimeError) { @root.add(Tree::TreeNode.new(@child1.name)) }
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_remove_bang
|
||||||
|
@root << @child1
|
||||||
|
@root << @child2
|
||||||
|
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert_equal(3, @root.size, "Should have three nodes")
|
||||||
|
|
||||||
|
@root.remove!(@child1)
|
||||||
|
assert_equal(2, @root.size, "Should have two nodes")
|
||||||
|
@root.remove!(@child2)
|
||||||
|
|
||||||
|
assert(!@root.hasChildren?, "Should have no children")
|
||||||
|
assert_equal(1, @root.size, "Should have one node")
|
||||||
|
|
||||||
|
@root << @child1
|
||||||
|
@root << @child2
|
||||||
|
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert_equal(3, @root.size, "Should have three nodes")
|
||||||
|
|
||||||
|
@root.removeAll!
|
||||||
|
|
||||||
|
assert(!@root.hasChildren?, "Should have no children")
|
||||||
|
assert_equal(1, @root.size, "Should have one node")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_removeAll_bang
|
||||||
|
loadChildren
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
@root.removeAll!
|
||||||
|
|
||||||
|
assert(!@root.hasChildren?, "Should have no children")
|
||||||
|
assert_equal(1, @root.size, "Should have one node")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_removeFromParent_bang
|
||||||
|
loadChildren
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert(!@root.isLeaf?, "Root is not a leaf here")
|
||||||
|
|
||||||
|
child1 = @root[0]
|
||||||
|
assert_not_nil(child1, "Child 1 should exist")
|
||||||
|
assert_same(@root, child1.root, "Child 1's root should be ROOT")
|
||||||
|
assert(@root.include?(child1), "root should have child1")
|
||||||
|
child1.removeFromParent!
|
||||||
|
assert_same(child1, child1.root, "Child 1's root should be self")
|
||||||
|
assert(!@root.include?(child1), "root should not have child1")
|
||||||
|
|
||||||
|
child1.removeFromParent!
|
||||||
|
assert_same(child1, child1.root, "Child 1's root should still be self")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_children
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert_equal(5, @root.size, "Should have four nodes")
|
||||||
|
assert(@child3.hasChildren?, "Should have children")
|
||||||
|
assert(!@child3.isLeaf?, "Should not be a leaf")
|
||||||
|
|
||||||
|
children = []
|
||||||
|
for child in @root.children
|
||||||
|
children << child
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal(3, children.length, "Should have three direct children")
|
||||||
|
assert(!children.include?(@root), "Should not have root")
|
||||||
|
assert(children.include?(@child1), "Should have child 1")
|
||||||
|
assert(children.include?(@child2), "Should have child 2")
|
||||||
|
assert(children.include?(@child3), "Should have child 3")
|
||||||
|
assert(!children.include?(@child4), "Should not have child 4")
|
||||||
|
|
||||||
|
children.clear
|
||||||
|
children = @root.children
|
||||||
|
assert_equal(3, children.length, "Should have three children")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_firstChild
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_equal(@child1, @root.firstChild, "Root's first child is Child1")
|
||||||
|
assert_nil(@child1.firstChild, "Child1 does not have any children")
|
||||||
|
assert_equal(@child4, @child3.firstChild, "Child3's first child is Child4")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lastChild
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_equal(@child3, @root.lastChild, "Root's last child is Child3")
|
||||||
|
assert_nil(@child1.lastChild, "Child1 does not have any children")
|
||||||
|
assert_equal(@child4, @child3.lastChild, "Child3's last child is Child4")
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_find
|
||||||
|
loadChildren
|
||||||
|
foundNode = @root.find { |node| node == @child2}
|
||||||
|
assert_same(@child2, foundNode, "The node should be Child 2")
|
||||||
|
|
||||||
|
foundNode = @root.find { |node| node == @child4}
|
||||||
|
assert_same(@child4, foundNode, "The node should be Child 4")
|
||||||
|
|
||||||
|
foundNode = @root.find { |node| node.name == "Child31" }
|
||||||
|
assert_same(@child4, foundNode, "The node should be Child 4")
|
||||||
|
foundNode = @root.find { |node| node.name == "NOT PRESENT" }
|
||||||
|
assert_nil(foundNode, "The node should not be found")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parentage
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert_nil(@root.parentage, "Root does not have any parentage")
|
||||||
|
assert_equal([@root], @child1.parentage, "Child1 has Root as its parent")
|
||||||
|
assert_equal([@child3, @root], @child4.parentage, "Child4 has Child3 and Root as ancestors")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_each
|
||||||
|
loadChildren
|
||||||
|
assert(@root.hasChildren?, "Should have children")
|
||||||
|
assert_equal(5, @root.size, "Should have five nodes")
|
||||||
|
assert(@child3.hasChildren?, "Should have children")
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
@root.each { |node| nodes << node }
|
||||||
|
|
||||||
|
assert_equal(5, nodes.length, "Should have FIVE NODES")
|
||||||
|
assert(nodes.include?(@root), "Should have root")
|
||||||
|
assert(nodes.include?(@child1), "Should have child 1")
|
||||||
|
assert(nodes.include?(@child2), "Should have child 2")
|
||||||
|
assert(nodes.include?(@child3), "Should have child 3")
|
||||||
|
assert(nodes.include?(@child4), "Should have child 4")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_each_leaf
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
@root.each_leaf { |node| nodes << node }
|
||||||
|
|
||||||
|
assert_equal(3, nodes.length, "Should have THREE LEAF NODES")
|
||||||
|
assert(!nodes.include?(@root), "Should not have root")
|
||||||
|
assert(nodes.include?(@child1), "Should have child 1")
|
||||||
|
assert(nodes.include?(@child2), "Should have child 2")
|
||||||
|
assert(!nodes.include?(@child3), "Should not have child 3")
|
||||||
|
assert(nodes.include?(@child4), "Should have child 4")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parent
|
||||||
|
loadChildren
|
||||||
|
assert_nil(@root.parent, "Root's parent should be nil")
|
||||||
|
assert_equal(@root, @child1.parent, "Parent should be root")
|
||||||
|
assert_equal(@root, @child3.parent, "Parent should be root")
|
||||||
|
assert_equal(@child3, @child4.parent, "Parent should be child3")
|
||||||
|
assert_equal(@root, @child4.parent.parent, "Parent should be root")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_indexed_access
|
||||||
|
loadChildren
|
||||||
|
assert_equal(@child1, @root[0], "Should be the first child")
|
||||||
|
assert_equal(@child4, @root[2][0], "Should be the grandchild")
|
||||||
|
assert_nil(@root["TEST"], "Should be nil")
|
||||||
|
assert_raise(RuntimeError) { @root[nil] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_printTree
|
||||||
|
loadChildren
|
||||||
|
#puts
|
||||||
|
#@root.printTree
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tests the binary dumping mechanism with an Object content node
|
||||||
|
def test_marshal_dump
|
||||||
|
# Setup Test Data
|
||||||
|
test_root = Tree::TreeNode.new("ROOT", "Root Node")
|
||||||
|
test_content = {"KEY1" => "Value1", "KEY2" => "Value2" }
|
||||||
|
test_child = Tree::TreeNode.new("Child", test_content)
|
||||||
|
test_content2 = ["AValue1", "AValue2", "AValue3"]
|
||||||
|
test_grand_child = Tree::TreeNode.new("Grand Child 1", test_content2)
|
||||||
|
test_root << test_child << test_grand_child
|
||||||
|
|
||||||
|
# Perform the test operation
|
||||||
|
data = Marshal.dump(test_root) # Marshal
|
||||||
|
new_root = Marshal.load(data) # And unmarshal
|
||||||
|
|
||||||
|
# Test the root node
|
||||||
|
assert_equal(test_root.name, new_root.name, "Must identify as ROOT")
|
||||||
|
assert_equal(test_root.content, new_root.content, "Must have root's content")
|
||||||
|
assert(new_root.isRoot?, "Must be the ROOT node")
|
||||||
|
assert(new_root.hasChildren?, "Must have a child node")
|
||||||
|
|
||||||
|
# Test the child node
|
||||||
|
new_child = new_root[test_child.name]
|
||||||
|
assert_equal(test_child.name, new_child.name, "Must have child 1")
|
||||||
|
assert(new_child.hasContent?, "Child must have content")
|
||||||
|
assert(new_child.isOnlyChild?, "Child must be the only child")
|
||||||
|
|
||||||
|
new_child_content = new_child.content
|
||||||
|
assert_equal(Hash, new_child_content.class, "Class of child's content should be a hash")
|
||||||
|
assert_equal(test_child.content.size, new_child_content.size, "The content should have same size")
|
||||||
|
|
||||||
|
# Test the grand-child node
|
||||||
|
new_grand_child = new_child[test_grand_child.name]
|
||||||
|
assert_equal(test_grand_child.name, new_grand_child.name, "Must have grand child")
|
||||||
|
assert(new_grand_child.hasContent?, "Grand-child must have content")
|
||||||
|
assert(new_grand_child.isOnlyChild?, "Grand-child must be the only child")
|
||||||
|
|
||||||
|
new_grand_child_content = new_grand_child.content
|
||||||
|
assert_equal(Array, new_grand_child_content.class, "Class of grand-child's content should be an Array")
|
||||||
|
assert_equal(test_grand_child.content.size, new_grand_child_content.size, "The content should have same size")
|
||||||
|
end
|
||||||
|
|
||||||
|
# marshal_load and marshal_dump are symmetric methods
|
||||||
|
# This alias is for satisfying ZenTest
|
||||||
|
alias test_marshal_load test_marshal_dump
|
||||||
|
|
||||||
|
# Test the collect method from the mixed-in Enumerable functionality.
|
||||||
|
def test_collect
|
||||||
|
loadChildren
|
||||||
|
collectArray = @root.collect do |node|
|
||||||
|
node.content = "abc"
|
||||||
|
node
|
||||||
|
end
|
||||||
|
collectArray.each {|node| assert_equal("abc", node.content, "Should be 'abc'")}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test freezing the tree
|
||||||
|
def test_freezeTree_bang
|
||||||
|
loadChildren
|
||||||
|
@root.content = "ABC"
|
||||||
|
assert_equal("ABC", @root.content, "Content should be 'ABC'")
|
||||||
|
@root.freezeTree!
|
||||||
|
assert_raise(TypeError) {@root.content = "123"}
|
||||||
|
assert_raise(TypeError) {@root[0].content = "123"}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test whether the content is accesible
|
||||||
|
def test_content
|
||||||
|
pers = Person::new("John", "Doe")
|
||||||
|
@root.content = pers
|
||||||
|
assert_same(pers, @root.content, "Content should be the same")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test the depth computation algorithm
|
||||||
|
def test_depth
|
||||||
|
assert_equal(1, @root.depth, "A single node's depth is 1")
|
||||||
|
|
||||||
|
@root << @child1
|
||||||
|
assert_equal(2, @root.depth, "This should be of depth 2")
|
||||||
|
|
||||||
|
@root << @child2
|
||||||
|
assert_equal(2, @root.depth, "This should be of depth 2")
|
||||||
|
|
||||||
|
@child2 << @child3
|
||||||
|
assert_equal(3, @root.depth, "This should be of depth 3")
|
||||||
|
assert_equal(2, @child2.depth, "This should be of depth 2")
|
||||||
|
|
||||||
|
@child3 << @child4
|
||||||
|
assert_equal(4, @root.depth, "This should be of depth 4")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test the breadth computation algorithm
|
||||||
|
def test_breadth
|
||||||
|
assert_equal(1, @root.breadth, "A single node's breadth is 1")
|
||||||
|
|
||||||
|
@root << @child1
|
||||||
|
assert_equal(1, @root.breadth, "This should be of breadth 1")
|
||||||
|
|
||||||
|
@root << @child2
|
||||||
|
assert_equal(2, @child1.breadth, "This should be of breadth 2")
|
||||||
|
assert_equal(2, @child2.breadth, "This should be of breadth 2")
|
||||||
|
|
||||||
|
@root << @child3
|
||||||
|
assert_equal(3, @child1.breadth, "This should be of breadth 3")
|
||||||
|
assert_equal(3, @child2.breadth, "This should be of breadth 3")
|
||||||
|
|
||||||
|
@child3 << @child4
|
||||||
|
assert_equal(1, @child4.breadth, "This should be of breadth 1")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test the breadth for each
|
||||||
|
def test_breadth_each
|
||||||
|
j = Tree::TreeNode.new("j")
|
||||||
|
f = Tree::TreeNode.new("f")
|
||||||
|
k = Tree::TreeNode.new("k")
|
||||||
|
a = Tree::TreeNode.new("a")
|
||||||
|
d = Tree::TreeNode.new("d")
|
||||||
|
h = Tree::TreeNode.new("h")
|
||||||
|
z = Tree::TreeNode.new("z")
|
||||||
|
|
||||||
|
# The expected order of response
|
||||||
|
expected_array = [j,
|
||||||
|
f, k,
|
||||||
|
a, h, z,
|
||||||
|
d]
|
||||||
|
|
||||||
|
# Create the following Tree
|
||||||
|
# j <-- level 0 (Root)
|
||||||
|
# / \
|
||||||
|
# f k <-- level 1
|
||||||
|
# / \ \
|
||||||
|
# a h z <-- level 2
|
||||||
|
# \
|
||||||
|
# d <-- level 3
|
||||||
|
j << f << a << d
|
||||||
|
f << h
|
||||||
|
j << k << z
|
||||||
|
|
||||||
|
# Create the response
|
||||||
|
result_array = Array.new
|
||||||
|
j.breadth_each { |node| result_array << node.detached_copy }
|
||||||
|
|
||||||
|
expected_array.each_index do |i|
|
||||||
|
assert_equal(expected_array[i].name, result_array[i].name) # Match only the names.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_preordered_each
|
||||||
|
j = Tree::TreeNode.new("j")
|
||||||
|
f = Tree::TreeNode.new("f")
|
||||||
|
k = Tree::TreeNode.new("k")
|
||||||
|
a = Tree::TreeNode.new("a")
|
||||||
|
d = Tree::TreeNode.new("d")
|
||||||
|
h = Tree::TreeNode.new("h")
|
||||||
|
z = Tree::TreeNode.new("z")
|
||||||
|
|
||||||
|
# The expected order of response
|
||||||
|
expected_array = [j, f, a, d, h, k, z]
|
||||||
|
|
||||||
|
# Create the following Tree
|
||||||
|
# j <-- level 0 (Root)
|
||||||
|
# / \
|
||||||
|
# f k <-- level 1
|
||||||
|
# / \ \
|
||||||
|
# a h z <-- level 2
|
||||||
|
# \
|
||||||
|
# d <-- level 3
|
||||||
|
j << f << a << d
|
||||||
|
f << h
|
||||||
|
j << k << z
|
||||||
|
|
||||||
|
result_array = []
|
||||||
|
j.preordered_each { |node| result_array << node.detached_copy}
|
||||||
|
|
||||||
|
expected_array.each_index do |i|
|
||||||
|
# Match only the names.
|
||||||
|
assert_equal(expected_array[i].name, result_array[i].name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_detached_copy
|
||||||
|
loadChildren
|
||||||
|
|
||||||
|
assert(@root.hasChildren?, "The root should have children")
|
||||||
|
copy_of_root = @root.detached_copy
|
||||||
|
assert(!copy_of_root.hasChildren?, "The copy should not have children")
|
||||||
|
assert_equal(@root.name, copy_of_root.name, "The names should be equal")
|
||||||
|
|
||||||
|
# Try the same test with a child node
|
||||||
|
assert(!@child3.isRoot?, "Child 3 is not a root")
|
||||||
|
assert(@child3.hasChildren?, "Child 3 has children")
|
||||||
|
copy_of_child3 = @child3.detached_copy
|
||||||
|
assert(copy_of_child3.isRoot?, "Child 3's copy is a root")
|
||||||
|
assert(!copy_of_child3.hasChildren?, "Child 3's copy does not have children")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hasChildren_eh
|
||||||
|
loadChildren
|
||||||
|
assert(@root.hasChildren?, "The Root node MUST have children")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isLeaf_eh
|
||||||
|
loadChildren
|
||||||
|
assert(!@child3.isLeaf?, "Child 3 is not a leaf node")
|
||||||
|
assert(@child4.isLeaf?, "Child 4 is a leaf node")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_isRoot_eh
|
||||||
|
loadChildren
|
||||||
|
assert(@root.isRoot?, "The ROOT node must respond as the root node")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_content_equals
|
||||||
|
@root.content = nil
|
||||||
|
assert_nil(@root.content, "Root's content should be nil")
|
||||||
|
@root.content = "ABCD"
|
||||||
|
assert_equal("ABCD", @root.content, "Root's content should now be 'ABCD'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_size
|
||||||
|
assert_equal(1, @root.size, "Root's size should be 1")
|
||||||
|
loadChildren
|
||||||
|
assert_equal(5, @root.size, "Root's size should be 5")
|
||||||
|
assert_equal(2, @child3.size, "Child 3's size should be 2")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lt2 # Test the << method
|
||||||
|
@root << @child1
|
||||||
|
@root << @child2
|
||||||
|
@root << @child3 << @child4
|
||||||
|
assert_not_nil(@root['Child1'], "Child 1 should have been added to Root")
|
||||||
|
assert_not_nil(@root['Child2'], "Child 2 should have been added to Root")
|
||||||
|
assert_not_nil(@root['Child3'], "Child 3 should have been added to Root")
|
||||||
|
assert_not_nil(@child3['Child31'], "Child 31 should have been added to Child3")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_index # Test the [] method
|
||||||
|
assert_raise(RuntimeError) {@root[nil]}
|
||||||
|
|
||||||
|
@root << @child1
|
||||||
|
@root << @child2
|
||||||
|
assert_equal(@child1.name, @root['Child1'].name, "Child 1 should be returned")
|
||||||
|
assert_equal(@child1.name, @root[0].name, "Child 1 should be returned")
|
||||||
|
assert_equal(@child2.name, @root['Child2'].name, "Child 2 should be returned")
|
||||||
|
assert_equal(@child2.name, @root[1].name, "Child 2 should be returned")
|
||||||
|
|
||||||
|
assert_nil(@root['Some Random Name'], "Should return nil")
|
||||||
|
assert_nil(@root[99], "Should return nil")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
# $Log: test_tree.rb,v $
|
||||||
|
# Revision 1.6 2007/12/22 00:28:59 anupamsg
|
||||||
|
# Added more test cases, and enabled ZenTest compatibility.
|
||||||
|
#
|
||||||
|
# Revision 1.5 2007/12/19 02:24:18 anupamsg
|
||||||
|
# Updated the marshalling logic to handle non-string contents on the nodes.
|
||||||
|
#
|
||||||
|
# Revision 1.4 2007/10/02 03:38:11 anupamsg
|
||||||
|
# Removed dependency on the redundant "Person" class.
|
||||||
|
# (TC_TreeTest::test_comparator): Added a new test for the spaceship operator.
|
||||||
|
# (TC_TreeTest::test_hasContent): Added tests for hasContent? and length methods.
|
||||||
|
#
|
||||||
|
# Revision 1.3 2007/10/02 03:07:30 anupamsg
|
||||||
|
# * Rakefile: Added an optional task for rcov code coverage.
|
||||||
|
#
|
||||||
|
# * test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
|
||||||
|
#
|
||||||
|
# * test/test_tree.rb: Removed dependency on the redundant "Person" class.
|
||||||
|
#
|
||||||
|
# Revision 1.2 2007/08/31 01:16:28 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.1 2007/07/21 04:52:38 anupamsg
|
||||||
|
# Renamed the test files.
|
||||||
|
#
|
||||||
|
# Revision 1.13 2007/07/18 22:11:50 anupamsg
|
||||||
|
# Added depth and breadth methods for the TreeNode.
|
||||||
|
#
|
||||||
|
# Revision 1.12 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.11 2007/07/17 03:39:29 anupamsg
|
||||||
|
# Moved the CVS Log keyword to end of the files.
|
||||||
|
#
|
Loading…
Reference in New Issue