##// END OF EJS Templates
Converted Menus to a Tree structure to allow submenus....
Eric Davis -
r2976:1f06cf889990
parent child
Show More
@@ -0,0 +1,166
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../../test_helper'
19
20 class Redmine::MenuManager::MapperTest < Test::Unit::TestCase
21 context "Mapper#initialize" do
22 should "be tested"
23 end
24
25 def test_push_onto_root
26 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
27 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
28
29 menu_mapper.exists?(:test_overview)
30 end
31
32 def test_push_onto_parent
33 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
34 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
35 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview}
36
37 assert menu_mapper.exists?(:test_child)
38 assert_equal :test_child, menu_mapper.find(:test_child).name
39 end
40
41 def test_push_onto_grandparent
42 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
43 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
44 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview}
45 menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_child}
46
47 assert menu_mapper.exists?(:test_grandchild)
48 grandchild = menu_mapper.find(:test_grandchild)
49 assert_equal :test_grandchild, grandchild.name
50 assert_equal :test_child, grandchild.parent_menu
51 end
52
53 def test_push_first
54 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
55 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
56 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
57 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
58 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
59 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true}
60
61 root = menu_mapper.find(:root)
62 assert_equal 5, root.children.size
63 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
64 assert_not_nil root.children[position]
65 assert_equal name, root.children[position].name
66 end
67
68 end
69
70 def test_push_before
71 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
72 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
73 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
74 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
75 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
76 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth}
77
78 root = menu_mapper.find(:root)
79 assert_equal 5, root.children.size
80 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
81 assert_not_nil root.children[position]
82 assert_equal name, root.children[position].name
83 end
84
85 end
86
87 def test_push_after
88 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
89 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
90 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
91 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
92 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
93 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third}
94
95
96 root = menu_mapper.find(:root)
97 assert_equal 5, root.children.size
98 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
99 assert_not_nil root.children[position]
100 assert_equal name, root.children[position].name
101 end
102
103 end
104
105 def test_push_last
106 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
107 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
108 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
109 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
110 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true}
111 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
112
113 root = menu_mapper.find(:root)
114 assert_equal 5, root.children.size
115 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
116 assert_not_nil root.children[position]
117 assert_equal name, root.children[position].name
118 end
119
120 end
121
122 def test_exists_for_child_node
123 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
124 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
125 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent_menu => :test_overview }
126
127 assert menu_mapper.exists?(:test_child)
128 end
129
130 def test_exists_for_invalid_node
131 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
132 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
133
134 assert !menu_mapper.exists?(:nothing)
135 end
136
137 def test_find
138 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
139 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
140
141 item = menu_mapper.find(:test_overview)
142 assert_equal :test_overview, item.name
143 assert_equal({:controller => 'projects', :action => 'show'}, item.url)
144 end
145
146 def test_find_missing
147 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
148 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
149
150 item = menu_mapper.find(:nothing)
151 assert_equal nil, item
152 end
153
154 def test_delete
155 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
156 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
157 assert_not_nil menu_mapper.delete(:test_overview)
158
159 assert_nil menu_mapper.find(:test_overview)
160 end
161
162 def test_delete_missing
163 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
164 assert_nil menu_mapper.delete(:test_missing)
165 end
166 end
@@ -0,0 +1,161
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../../test_helper'
19
20
21
22 class Redmine::MenuManager::MenuHelperTest < HelperTestCase
23 include Redmine::MenuManager::MenuHelper
24 include ActionController::Assertions::SelectorAssertions
25 fixtures :users, :members, :projects, :enabled_modules
26
27 # Used by assert_select
28 def html_document
29 HTML::Document.new(@response.body)
30 end
31
32 def setup
33 super
34 @response = ActionController::TestResponse.new
35 # Stub the current menu item in the controller
36 def @controller.current_menu_item
37 :index
38 end
39 end
40
41
42 context "MenuManager#current_menu_item" do
43 should "be tested"
44 end
45
46 context "MenuManager#render_main_menu" do
47 should "be tested"
48 end
49
50 context "MenuManager#render_menu" do
51 should "be tested"
52 end
53
54 context "MenuManager#menu_item_and_children" do
55 should "be tested"
56 end
57
58 context "MenuManager#extract_node_details" do
59 should "be tested"
60 end
61
62 def test_render_single_menu_node
63 node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { })
64 @response.body = render_single_menu_node(node, 'This is a test', node.url, false)
65
66 assert_select("a.testing", "This is a test")
67 end
68
69 def test_render_menu_node
70 single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { })
71 @response.body = render_menu_node(single_node, nil)
72
73 assert_select("li") do
74 assert_select("a.single-node", "Single node")
75 end
76 end
77
78 def test_render_menu_node_with_nested_items
79 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { })
80 parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { })
81 parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { })
82 parent_node <<
83 Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) <<
84 Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { })
85
86 @response.body = render_menu_node(parent_node, nil)
87
88 assert_select("li") do
89 assert_select("a.parent-node", "Parent node")
90 assert_select("ul") do
91 assert_select("li a.child-one-node", "Child one node")
92 assert_select("li a.child-two-node", "Child two node")
93 assert_select("li") do
94 assert_select("a.child-three-node", "Child three node")
95 assert_select("ul") do
96 assert_select("li a.child-three-inner-node", "Child three inner node")
97 end
98 end
99 end
100 end
101
102 end
103
104 def test_menu_items_for_should_yield_all_items_if_passed_a_block
105 menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block
106 Redmine::MenuManager.map menu_name do |menu|
107 menu.push(:a_menu, '/', { })
108 menu.push(:a_menu_2, '/', { })
109 menu.push(:a_menu_3, '/', { })
110 end
111
112 items_yielded = []
113 menu_items_for(menu_name) do |item|
114 items_yielded << item
115 end
116
117 assert_equal 3, items_yielded.size
118 end
119
120 def test_menu_items_for_should_return_all_items
121 menu_name = :test_menu_items_for_should_return_all_items
122 Redmine::MenuManager.map menu_name do |menu|
123 menu.push(:a_menu, '/', { })
124 menu.push(:a_menu_2, '/', { })
125 menu.push(:a_menu_3, '/', { })
126 end
127
128 items = menu_items_for(menu_name)
129 assert_equal 3, items.size
130 end
131
132 def test_menu_items_for_should_skip_unallowed_items_on_a_project
133 menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project
134 Redmine::MenuManager.map menu_name do |menu|
135 menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
136 menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { })
137 menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { })
138 end
139
140 User.current = User.find(2)
141
142 items = menu_items_for(menu_name, Project.find(1))
143 assert_equal 2, items.size
144 end
145
146 def test_menu_items_for_should_skip_items_that_fail_the_conditions
147 menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions
148 Redmine::MenuManager.map menu_name do |menu|
149 menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { })
150 menu.push(:unallowed,
151 {:controller => 'issues', :action => 'index' },
152 { :if => Proc.new { false }})
153 end
154
155 User.current = User.find(2)
156
157 items = menu_items_for(menu_name, Project.find(1))
158 assert_equal 1, items.size
159 end
160
161 end
@@ -0,0 +1,108
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../../test_helper'
19
20 module RedmineMenuTestHelper
21 # Helpers
22 def get_menu_item(menu_name, item_name)
23 Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
24 end
25 end
26
27 class Redmine::MenuManager::MenuItemTest < Test::Unit::TestCase
28 include RedmineMenuTestHelper
29
30 Redmine::MenuManager.map :test_menu do |menu|
31 menu.push(:parent_menu, '/test', { })
32 menu.push(:child_menu, '/test', { :parent_menu => :parent_menu})
33 menu.push(:child2_menu, '/test', { :parent_menu => :parent_menu})
34 end
35
36 context "MenuItem#caption" do
37 should "be tested"
38 end
39
40 context "MenuItem#html_options" do
41 should "be tested"
42 end
43
44 # context new menu item
45 def test_new_menu_item_should_require_a_name
46 assert_raises ArgumentError do
47 Redmine::MenuManager::MenuItem.new
48 end
49 end
50
51 def test_new_menu_item_should_require_an_url
52 assert_raises ArgumentError do
53 Redmine::MenuManager::MenuItem.new(:test_missing_url)
54 end
55 end
56
57 def test_new_menu_item_should_require_the_options
58 assert_raises ArgumentError do
59 Redmine::MenuManager::MenuItem.new(:test_missing_options, '/test')
60 end
61 end
62
63 def test_new_menu_item_with_all_required_parameters
64 assert Redmine::MenuManager::MenuItem.new(:test_good_menu, '/test', {})
65 end
66
67 def test_new_menu_item_should_require_a_proc_to_use_for_the_if_condition
68 assert_raises ArgumentError do
69 Redmine::MenuManager::MenuItem.new(:test_error, '/test',
70 {
71 :if => ['not_a_proc']
72 })
73 end
74
75 assert Redmine::MenuManager::MenuItem.new(:test_good_if, '/test',
76 {
77 :if => Proc.new{}
78 })
79 end
80
81 def test_new_menu_item_should_allow_a_hash_for_extra_html_options
82 assert_raises ArgumentError do
83 Redmine::MenuManager::MenuItem.new(:test_error, '/test',
84 {
85 :html => ['not_a_hash']
86 })
87 end
88
89 assert Redmine::MenuManager::MenuItem.new(:test_good_html, '/test',
90 {
91 :html => { :onclick => 'doSomething'}
92 })
93 end
94
95 def test_new_should_not_allow_setting_the_parent_menu_item_to_the_current_item
96 assert_raises ArgumentError do
97 Redmine::MenuManager::MenuItem.new(:test_error, '/test', { :parent_menu => :test_error })
98 end
99 end
100
101 def test_has_children
102 parent_item = get_menu_item(:test_menu, :parent_menu)
103 assert parent_item.hasChildren?
104 assert_equal 2, parent_item.children.size
105 assert_equal get_menu_item(:test_menu, :child_menu), parent_item.children[0]
106 assert_equal get_menu_item(:test_menu, :child2_menu), parent_item.children[1]
107 end
108 end
@@ -0,0 +1,28
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../test_helper'
19
20 class Redmine::MenuManagerTest < Test::Unit::TestCase
21 context "MenuManager#map" do
22 should "be tested"
23 end
24
25 context "MenuManager#items" do
26 should "be tested"
27 end
28 end
@@ -0,0 +1,84
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../test_helper'
19
20 module RedmineMenuTestHelper
21 # Assertions
22 def assert_number_of_items_in_menu(menu_name, count)
23 assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items"
24 end
25
26 def assert_menu_contains_item_named(menu_name, item_name)
27 assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}"
28 end
29
30 # Helpers
31 def get_menu_item(menu_name, item_name)
32 Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym}
33 end
34 end
35
36 class RedmineTest < Test::Unit::TestCase
37 include RedmineMenuTestHelper
38
39 def test_top_menu
40 assert_number_of_items_in_menu :top_menu, 5
41 assert_menu_contains_item_named :top_menu, :home
42 assert_menu_contains_item_named :top_menu, :my_page
43 assert_menu_contains_item_named :top_menu, :projects
44 assert_menu_contains_item_named :top_menu, :administration
45 assert_menu_contains_item_named :top_menu, :help
46 end
47
48 def test_account_menu
49 assert_number_of_items_in_menu :account_menu, 4
50 assert_menu_contains_item_named :account_menu, :login
51 assert_menu_contains_item_named :account_menu, :register
52 assert_menu_contains_item_named :account_menu, :my_account
53 assert_menu_contains_item_named :account_menu, :logout
54 end
55
56 def test_application_menu
57 assert_number_of_items_in_menu :application_menu, 0
58 end
59
60 def test_admin_menu
61 assert_number_of_items_in_menu :admin_menu, 0
62 end
63
64 def test_project_menu
65 assert_number_of_items_in_menu :project_menu, 12
66 assert_menu_contains_item_named :project_menu, :overview
67 assert_menu_contains_item_named :project_menu, :activity
68 assert_menu_contains_item_named :project_menu, :roadmap
69 assert_menu_contains_item_named :project_menu, :issues
70 assert_menu_contains_item_named :project_menu, :new_issue
71 assert_menu_contains_item_named :project_menu, :news
72 assert_menu_contains_item_named :project_menu, :documents
73 assert_menu_contains_item_named :project_menu, :wiki
74 assert_menu_contains_item_named :project_menu, :boards
75 assert_menu_contains_item_named :project_menu, :files
76 assert_menu_contains_item_named :project_menu, :repository
77 assert_menu_contains_item_named :project_menu, :settings
78 end
79
80 def test_new_issue_should_have_root_as_a_parent
81 new_issue = get_menu_item(:project_menu, :new_issue)
82 assert_equal :root, new_issue.parent.name
83 end
84 end
@@ -0,0 +1,80
1 --- !ruby/object:Gem::Specification
2 name: rubytree
3 version: !ruby/object:Gem::Version
4 version: 0.5.2
5 platform: ruby
6 authors:
7 - Anupam Sengupta
8 autorequire: tree
9 bindir: bin
10 cert_chain: []
11
12 date: 2007-12-20 00:00:00 -08:00
13 default_executable:
14 dependencies:
15 - !ruby/object:Gem::Dependency
16 name: hoe
17 type: :runtime
18 version_requirement:
19 version_requirements: !ruby/object:Gem::Requirement
20 requirements:
21 - - ">="
22 - !ruby/object:Gem::Version
23 version: 1.3.0
24 version:
25 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/"
26 email: anupamsg@gmail.com
27 executables: []
28
29 extensions: []
30
31 extra_rdoc_files:
32 - README
33 - COPYING
34 - ChangeLog
35 - History.txt
36 files:
37 - COPYING
38 - ChangeLog
39 - History.txt
40 - Manifest.txt
41 - README
42 - Rakefile
43 - TODO
44 - lib/tree.rb
45 - lib/tree/binarytree.rb
46 - setup.rb
47 - test/test_binarytree.rb
48 - test/test_tree.rb
49 has_rdoc: true
50 homepage: http://rubytree.rubyforge.org/
51 licenses: []
52
53 post_install_message:
54 rdoc_options:
55 - --main
56 - README
57 require_paths:
58 - lib
59 required_ruby_version: !ruby/object:Gem::Requirement
60 requirements:
61 - - ">="
62 - !ruby/object:Gem::Version
63 version: "0"
64 version:
65 required_rubygems_version: !ruby/object:Gem::Requirement
66 requirements:
67 - - ">="
68 - !ruby/object:Gem::Version
69 version: "0"
70 version:
71 requirements: []
72
73 rubyforge_project: rubytree
74 rubygems_version: 1.3.5
75 signing_key:
76 specification_version: 2
77 summary: Ruby implementation of the Tree data structure.
78 test_files:
79 - test/test_binarytree.rb
80 - test/test_tree.rb
@@ -0,0 +1,31
1 RUBYTREE - http://rubytree.rubyforge.org
2 ========================================
3
4 Copyright (c) 2006, 2007 Anupam Sengupta
5
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
10
11 - Redistributions of source code must retain the above copyright notice, this
12 list of conditions and the following disclaimer.
13
14 - Redistributions in binary form must reproduce the above copyright notice, this
15 list of conditions and the following disclaimer in the documentation and/or
16 other materials provided with the distribution.
17
18 - Neither the name of the organization nor the names of its contributors may
19 be used to endorse or promote products derived from this software without
20 specific prior written permission.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,163
1 2007-12-21 Anupam Sengupta <anupamsg@gmail.com>
2
3 * Rakefile: Added the rcov option to exclude rcov itself from
4 coverage reports.
5
6 * lib/tree.rb: Minor comment changes.
7
8 * test/test_tree.rb: Added the TestTree enclosing module, and
9 renamed tests to meet ZenTest requirements. Also added a few
10 missing test cases.
11
12 * test/test_binarytree.rb: Added the TestTree enclosing Module,
13 and renamed the tests to meet ZenTest requirements.
14
15 2007-12-19 Anupam Sengupta <anupamsg@gmail.com>
16
17 * README (Module): Modified the install instructions from source.
18
19 * lib/tree.rb (Tree::TreeNode::initialize): Removed the
20 unnecessary self_initialize method.
21 (Tree::TreeNode): Removed the spurious self_initialize from the
22 protected list.
23 (Module): Updated the minor version number.
24
25 * Rakefile: Fixed a problem with reading the Tree::VERSION for the
26 gem packaging, if any prior version of the gem is already installed.
27
28 2007-12-18 Anupam Sengupta <anupamsg@gmail.com>
29
30 * lib/tree.rb: Updated the marshalling logic to correctly handle
31 non-string content.
32 (Tree::TreeNode::createDumpRep): Minor code change to use symbols
33 instead of string key names.
34 (Tree): Version number change to 0.5.0
35 (Tree::TreeNode::hasContent): Minor fix to the comments.
36
37 * test/test_tree.rb (TC_TreeTest::test_breadth_each): Updated test
38 cases for the marshalling logic.
39
40 2007-11-12 Anupam Sengupta <anupamsg@gmail.com>
41
42 * test/test_binarytree.rb: Minor documentation correction.
43
44 * lib/tree/binarytree.rb (Tree::BinaryTreeNode::isRightChild):
45 Minor documentation change.
46
47 2007-10-10 Anupam Sengupta <anupamsg@gmail.com>
48
49 * README: Restructured the format.
50
51 * Rakefile: Added Hoe related logic. If not present, the Rakefile
52 will default to old behavior.
53
54 2007-10-09 Anupam Sengupta <anupamsg@gmail.com>
55
56 * Rakefile: Added setup.rb related tasks. Also added the setup.rb in the PKG_FILES list.
57
58 2007-10-01 Anupam Sengupta <anupamsg@gmail.com>
59
60 * Rakefile: Added an optional task for rcov code coverage.
61 Added a dependency for rake in the Gem Specification.
62
63 * test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
64
65 * test/test_tree.rb: Removed dependency on the redundant "Person" class.
66 (TC_TreeTest::test_comparator): Added a new test for the spaceship operator.
67 (TC_TreeTest::test_hasContent): Added tests for hasContent? and length methods.
68
69 2007-08-30 Anupam Sengupta <anupamsg@gmail.com>
70
71 * test/test_tree.rb (TC_TreeTest::test_preordered_each, TC_TreeTest::test_breadth_each, TC_TreeTest::test_detached_copy):
72 Added new tests for the new functions added to tree.rb.
73
74 * lib/tree.rb (Tree::TreeNode::detached_copy, Tree::TreeNode::preordered_each, Tree::TreeNode::breadth_each):
75 Added new functions for returning a detached copy of the node and
76 for performing breadth first traversal. Also added the pre-ordered
77 traversal function which is an alias of the existing 'each' method.
78
79 * test/test_binarytree.rb (TC_BinaryTreeTest::test_swap_children):
80 Added a test case for the children swap function.
81
82 * lib/tree/binarytree.rb (Tree::BinaryTreeNode::swap_children):
83 Added new function to swap the children. Other minor changes in
84 comments and code.
85
86 2007-07-18 Anupam Sengupta <anupamsg@gmail.com>
87
88 * lib/tree/binarytree.rb (Tree::BinaryTreeNode::leftChild /
89 rightChild): Minor cosmetic change on the parameter name.
90
91 * test/testbinarytree.rb (TC_BinaryTreeTest::test_isLeftChild):
92 Minor syntax correction.
93
94 * lib/tree.rb (Tree::TreeNode::depth): Added a tree depth
95 computation method.
96 (Tree::TreeNode::breadth): Added a tree breadth method.
97
98 * test/testtree.rb (TC_TreeTest::test_depth/test_breadth): Added a
99 test for the depth and breadth method.
100
101 * lib/tree/binarytree.rb (Tree::BinaryTreeNode::is*Child):
102 Added tests for determining whether a node is a left or right
103 child.
104
105 * test/testbinarytree.rb: Added the test cases for the binary tree
106 implementation.
107 (TC_BinaryTreeTest::test_is*Child): Added tests for right or left
108 childs.
109
110 * lib/tree/binarytree.rb: Added the binary tree implementation.
111
112 2007-07-17 Anupam Sengupta <anupamsg@gmail.com>
113
114 * lib/tree.rb (Tree::TreeNode::parentage): Renamed 'ancestors'
115 method to 'parentage' to avoid clobbering Module.ancestors
116
117 2007-07-16 Anupam Sengupta <anupamsg@gmail.com>
118
119 * Rakefile: Added an optional rtags task to generate TAGS file for
120 Emacs.
121
122 * lib/tree.rb (Tree::TreeNode): Added navigation methods for
123 siblings and children. Also added some convenience methods.
124
125 2007-07-08 Anupam Sengupta <anupamsg@gmail.com>
126
127 * Rakefile: Added a developer target for generating rdoc for the
128 website.
129
130 2007-06-24 Anupam Sengupta <anupamsg@gmail.com>
131
132 * test/testtree.rb, lib/tree.rb: Added the each_leaf traversal method.
133
134 * README: Replaced all occurrances of LICENSE with COPYING
135 and lowercased all instances of the word 'RubyTree'.
136
137 * Rakefile: Replaced all occurrances of LICENSE with COPYING
138
139 2007-06-23 Anupam Sengupta <anupamsg@gmail.com>
140
141 * lib/tree.rb (Tree::TreeNode::isLeaf): Added a isLeaf? method.
142
143 * test/testtree.rb (TC_TreeTest::test_removeFromParent): Added
144 test for isLeaf? method
145
146 * Rakefile: Added the LICENSE and ChangeLog to the extra RDoc files.
147
148 * lib/tree.rb: Minor updates to the comments.
149
150 * test/testtree.rb: Added the Copyright and License header.
151
152 * test/person.rb: Added the Copyright and License header.
153
154 * lib/tree.rb: Added the Copyright and License header.
155
156 * Rakefile: Added the LICENSE and Changelog to be part of the RDoc task.
157
158 * README: Added documentation in the README, including install
159 instructions and an example.
160
161 * LICENSE: Added the BSD LICENSE file.
162
163 * Changelog: Added the Changelog file.
@@ -0,0 +1,20
1 = 0.5.2 / 2007-12-21
2
3 * Added more test cases and enabled ZenTest compatibility for the test case
4 names.
5
6 = 0.5.1 / 2007-12-20
7
8 * Minor code refactoring.
9
10 = 0.5.0 / 2007-12-18
11
12 * Fixed the marshalling code to correctly handle non-string content.
13
14 = 0.4.3 / 2007-10-09
15
16 * Changes to the build mechanism (now uses Hoe).
17
18 = 0.4.2 / 2007-10-01
19
20 * Minor code refactoring. Changes in the Rakefile.
@@ -0,0 +1,12
1 COPYING
2 ChangeLog
3 History.txt
4 Manifest.txt
5 README
6 Rakefile
7 TODO
8 lib/tree.rb
9 lib/tree/binarytree.rb
10 setup.rb
11 test/test_binarytree.rb
12 test/test_tree.rb
@@ -0,0 +1,147
1
2 __ _ _
3 /__\_ _| |__ _ _| |_ _ __ ___ ___
4 / \// | | | '_ \| | | | __| '__/ _ \/ _ \
5 / _ \ |_| | |_) | |_| | |_| | | __/ __/
6 \/ \_/\__,_|_.__/ \__, |\__|_| \___|\___|
7 |___/
8
9 (c) 2006, 2007 Anupam Sengupta
10 http://rubytree.rubyforge.org
11
12 Rubytree is a simple implementation of the generic Tree data structure. This
13 implementation is node-centric, where the individual nodes on the tree are the
14 primary objects and drive the structure.
15
16 == INSTALL:
17
18 Rubytree is an open source project and is hosted at:
19
20 http://rubytree.rubyforge.org
21
22 Rubytree can be downloaded as a Rubygem or as a tar/zip file from:
23
24 http://rubyforge.org/frs/?group_id=1215&release_id=8817
25
26 The file-name is one of:
27
28 rubytree-<VERSION>.gem - The Rubygem
29 rubytree-<VERSION>.tgz - GZipped source files
30 rubytree-<VERSION>.zip - Zipped source files
31
32 Download the appropriate file-type for your system.
33
34 It is recommended to install Rubytree as a Ruby Gem, as this is an easy way to
35 keep the version updated, and keep multiple versions of the library available on
36 your system.
37
38 === Installing the Gem
39
40 To Install the Gem, from a Terminal/CLI command prompt, issue the command:
41
42 gem install rubytree
43
44 This should install the gem file for Rubytree. Note that you may need to be a
45 super-user (root) to successfully install the gem.
46
47 === Installing from the tgz/zip file
48
49 Extract the archive file (tgz or zip) and run the following command from the
50 top-level source directory:
51
52 ruby ./setup.rb
53
54 You may need administrator/super-user privileges to complete the setup using
55 this method.
56
57 == DOCUMENTATION:
58
59 The primary class for this implementation is Tree::TreeNode. See the
60 class documentation for an usage example.
61
62 From a command line/terminal prompt, you can issue the following command to view
63 the text mode ri documentation:
64
65 ri Tree::TreeNode
66
67 Documentation on the web is available at:
68
69 http://rubytree.rubyforge.org/rdoc
70
71 == EXAMPLE:
72
73 The following code-snippet implements this tree structure:
74
75 +------------+
76 | ROOT |
77 +-----+------+
78 +-------------+------------+
79 | |
80 +-------+-------+ +-------+-------+
81 | CHILD 1 | | CHILD 2 |
82 +-------+-------+ +---------------+
83 |
84 |
85 +-------+-------+
86 | GRANDCHILD 1 |
87 +---------------+
88
89 require 'tree'
90
91 myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content")
92
93 myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content")
94
95 myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content")
96
97 myTreeRoot.printTree
98
99 child1 = myTreeRoot["CHILD1"]
100
101 grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"]
102
103 siblingsOfChild1Array = child1.siblings
104
105 immediateChildrenArray = myTreeRoot.children
106
107 # Process all nodes
108
109 myTreeRoot.each { |node| node.content.reverse }
110
111 myTreeRoot.remove!(child1) # Remove the child
112
113 == LICENSE:
114
115 Rubytree is licensed under BSD license.
116
117 Copyright (c) 2006, 2007 Anupam Sengupta
118
119 All rights reserved.
120
121 Redistribution and use in source and binary forms, with or without modification,
122 are permitted provided that the following conditions are met:
123
124 - Redistributions of source code must retain the above copyright notice, this
125 list of conditions and the following disclaimer.
126
127 - Redistributions in binary form must reproduce the above copyright notice, this
128 list of conditions and the following disclaimer in the documentation and/or
129 other materials provided with the distribution.
130
131 - Neither the name of the organization nor the names of its contributors may
132 be used to endorse or promote products derived from this software without
133 specific prior written permission.
134
135 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
136 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
137 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
138 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
139 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
140 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
141 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
142 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
143 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
144 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
145
146
147 (Document Revision: $Revision: 1.16 $ by $Author: anupamsg $)
@@ -0,0 +1,212
1 # Rakefile
2 #
3 # $Revision: 1.27 $ by $Author: anupamsg $
4 # $Name: $
5 #
6 # Copyright (c) 2006, 2007 Anupam Sengupta
7 #
8 # All rights reserved.
9 #
10 # Redistribution and use in source and binary forms, with or without modification,
11 # are permitted provided that the following conditions are met:
12 #
13 # - Redistributions of source code must retain the above copyright notice, this
14 # list of conditions and the following disclaimer.
15 #
16 # - Redistributions in binary form must reproduce the above copyright notice, this
17 # list of conditions and the following disclaimer in the documentation and/or
18 # other materials provided with the distribution.
19 #
20 # - Neither the name of the organization nor the names of its contributors may
21 # be used to endorse or promote products derived from this software without
22 # specific prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
28 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
31 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #
35
36 require 'rubygems'
37 require 'rake/gempackagetask'
38
39 require 'rake/clean'
40 require 'rake/packagetask'
41 require 'rake/testtask'
42 require 'rake/rdoctask'
43
44 require 'fileutils'
45
46 # General Stuff ####################################################
47
48 $:.insert 0, File.expand_path( File.join( File.dirname(__FILE__), 'lib' ) )
49 require 'tree' # To read the version information.
50
51 PKG_NAME = "rubytree"
52 PKG_VERSION = Tree::VERSION
53 PKG_FULLNAME = PKG_NAME + "-" + PKG_VERSION
54 PKG_SUMMARY = "Ruby implementation of the Tree data structure."
55 PKG_DESCRIPTION = <<-END
56 Provides a generic tree data-structure with ability to
57 store keyed node-elements in the tree. The implementation
58 mixes in the Enumerable module.
59
60 Website: http://rubytree.rubyforge.org/
61 END
62
63 PKG_FILES = FileList[
64 '[A-Z]*',
65 '*.rb',
66 'lib/**/*.rb',
67 'test/**/*.rb'
68 ]
69
70 # Default is to create a rubygem.
71 desc "Default Task"
72 task :default => :gem
73
74 begin # Try loading hoe
75 require 'hoe'
76 # If Hoe is found, use it to define tasks
77 # =======================================
78 Hoe.new(PKG_NAME, PKG_VERSION) do |p|
79 p.rubyforge_name = PKG_NAME
80 p.author = "Anupam Sengupta"
81 p.email = "anupamsg@gmail.com"
82 p.summary = PKG_SUMMARY
83 p.description = PKG_DESCRIPTION
84 p.url = "http://rubytree.rubyforge.org/"
85 p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
86 p.remote_rdoc_dir = 'rdoc'
87 p.need_tar = true
88 p.need_zip = true
89 p.test_globs = ['test/test_*.rb']
90 p.spec_extras = {
91 :has_rdoc => true,
92 :platform => Gem::Platform::RUBY,
93 :has_rdoc => true,
94 :extra_rdoc_files => ['README', 'COPYING', 'ChangeLog', 'History.txt'],
95 :rdoc_options => ['--main', 'README'],
96 :autorequire => 'tree'
97 }
98 end
99
100 rescue LoadError # If Hoe is not found
101 # If Hoe is not found, then use the usual Gemspec based Rake tasks
102 # ================================================================
103 spec = Gem::Specification.new do |s|
104 s.name = PKG_NAME
105 s.version = PKG_VERSION
106 s.platform = Gem::Platform::RUBY
107 s.author = "Anupam Sengupta"
108 s.email = "anupamsg@gmail.com"
109 s.homepage = "http://rubytree.rubyforge.org/"
110 s.rubyforge_project = 'rubytree'
111 s.summary = PKG_SUMMARY
112 s.add_dependency('rake', '>= 0.7.2')
113 s.description = PKG_DESCRIPTION
114 s.has_rdoc = true
115 s.extra_rdoc_files = ['README', 'COPYING', 'ChangeLog']
116 s.autorequire = "tree"
117 s.files = PKG_FILES.to_a
118 s.test_files = Dir.glob('test/test*.rb')
119 end
120
121 desc "Prepares for installation"
122 task :prepare do
123 ruby "setup.rb config"
124 ruby "setup.rb setup"
125 end
126
127 desc "Installs the package #{PKG_NAME}"
128 task :install => [:prepare] do
129 ruby "setup.rb install"
130 end
131
132 Rake::GemPackageTask.new(spec) do |pkg|
133 pkg.need_zip = true
134 pkg.need_tar = true
135 end
136
137 Rake::TestTask.new do |t|
138 t.libs << "test"
139 t.test_files = FileList['test/test*.rb']
140 t.verbose = true
141 end
142
143 end # End loading Hoerc
144 # ================================================================
145
146
147 # The following tasks are loaded independently of Hoe
148
149 # Hoe's rdoc task is ugly.
150 Rake::RDocTask.new(:docs) do |rd|
151 rd.rdoc_files.include("README", "COPYING", "ChangeLog", "lib/**/*.rb")
152 rd.rdoc_dir = 'doc'
153 rd.title = "#{PKG_FULLNAME} Documentation"
154
155 # Use the template only if it is present, otherwise, the standard template is
156 # used.
157 template = "../allison/allison.rb"
158 rd.template = template if File.file?(template)
159
160 rd.options << '--line-numbers' << '--inline-source'
161 end
162
163 # Optional TAGS Task.
164 # Needs https://rubyforge.org/projects/rtagstask/
165 begin
166 require 'rtagstask'
167 RTagsTask.new do |rd|
168 rd.vi = false
169 end
170 rescue LoadError
171 end
172
173 # Optional RCOV Task
174 # Needs http://eigenclass.org/hiki/rcov
175 begin
176 require 'rcov/rcovtask'
177 Rcov::RcovTask.new do |t|
178 t.test_files = FileList['test/test*.rb']
179 t.rcov_opts << "--exclude 'rcov.rb'" # rcov itself should not be profiled
180 # t.verbose = true # uncomment to see the executed commands
181 end
182 rescue LoadError
183 end
184
185 #Rakefile,v $
186 # Revision 1.21 2007/07/21 05:14:43 anupamsg
187 # Added a VERSION constant to the Tree module,
188 # and using the same in the Rakefile.
189 #
190 # Revision 1.20 2007/07/21 03:24:25 anupamsg
191 # Minor edits to parameter names. User visible functionality does not change.
192 #
193 # Revision 1.19 2007/07/19 02:16:01 anupamsg
194 # Release 0.4.0 (and minor fix in Rakefile).
195 #
196 # Revision 1.18 2007/07/18 20:15:06 anupamsg
197 # Added two predicate methods in BinaryTreeNode to determine whether a node
198 # is a left or a right node.
199 #
200 # Revision 1.17 2007/07/18 07:17:34 anupamsg
201 # Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method
202 # has been renamed to TreeNode.parentage.
203 #
204 # Revision 1.16 2007/07/17 05:34:03 anupamsg
205 # Added an optional tags Rake-task for generating the TAGS file for Emacs.
206 #
207 # Revision 1.15 2007/07/17 04:42:45 anupamsg
208 # Minor fixes to the Rakefile.
209 #
210 # Revision 1.14 2007/07/17 03:39:28 anupamsg
211 # Moved the CVS Log keyword to end of the files.
212 #
@@ -0,0 +1,7
1 # -*- mode: outline; coding: utf-8-unix; -*-
2
3 * Add logic in Rakefile to read the file list from Manifest.txt file.
4
5 * Add a YAML export method to the TreeNode class.
6
7
This diff has been collapsed as it changes many lines, (539 lines changed) Show them Hide them
@@ -0,0 +1,539
1 # tree.rb
2 #
3 # $Revision: 1.29 $ by $Author: anupamsg $
4 # $Name: $
5 #
6 # = tree.rb - Generic Multi-way Tree implementation
7 #
8 # Provides a generic tree data structure with ability to
9 # store keyed node elements in the tree. The implementation
10 # mixes in the Enumerable module.
11 #
12 # Author:: Anupam Sengupta (anupamsg@gmail.com)
13 #
14
15 # Copyright (c) 2006, 2007 Anupam Sengupta
16 #
17 # All rights reserved.
18 #
19 # Redistribution and use in source and binary forms, with or without modification,
20 # are permitted provided that the following conditions are met:
21 #
22 # - Redistributions of source code must retain the above copyright notice, this
23 # list of conditions and the following disclaimer.
24 #
25 # - Redistributions in binary form must reproduce the above copyright notice, this
26 # list of conditions and the following disclaimer in the documentation and/or
27 # other materials provided with the distribution.
28 #
29 # - Neither the name of the organization nor the names of its contributors may
30 # be used to endorse or promote products derived from this software without
31 # specific prior written permission.
32 #
33 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
37 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
40 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
41 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
42 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 #
44
45 # This module provides a TreeNode class which is the primary class for all
46 # nodes represented in the Tree.
47 # This module mixes in the Enumerable module.
48 module Tree
49
50 # Rubytree Package Version
51 VERSION = '0.5.2'
52
53 # == TreeNode Class Description
54 #
55 # The node class for the tree representation. the nodes are named and have a
56 # place-holder for the node data (i.e., the `content' of the node). The node
57 # names are expected to be unique. In addition, the node provides navigation
58 # methods to traverse the tree.
59 #
60 # The nodes can have any number of child nodes attached to it. Note that while
61 # this implementation does not support directed graphs, the class itself makes
62 # no restrictions on associating a node's CONTENT with multiple parent nodes.
63 #
64 #
65 # == Example
66 #
67 # The following code-snippet implements this tree structure:
68 #
69 # +------------+
70 # | ROOT |
71 # +-----+------+
72 # +-------------+------------+
73 # | |
74 # +-------+-------+ +-------+-------+
75 # | CHILD 1 | | CHILD 2 |
76 # +-------+-------+ +---------------+
77 # |
78 # |
79 # +-------+-------+
80 # | GRANDCHILD 1 |
81 # +---------------+
82 #
83 # require 'tree'
84 #
85 # myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content")
86 #
87 # myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content")
88 #
89 # myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content")
90 #
91 # myTreeRoot.printTree
92 #
93 # child1 = myTreeRoot["CHILD1"]
94 #
95 # grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"]
96 #
97 # siblingsOfChild1Array = child1.siblings
98 #
99 # immediateChildrenArray = myTreeRoot.children
100 #
101 # # Process all nodes
102 #
103 # myTreeRoot.each { |node| node.content.reverse }
104 #
105 # myTreeRoot.remove!(child1) # Remove the child
106 class TreeNode
107 include Enumerable
108
109 attr_reader :content, :name, :parent
110 attr_writer :content
111
112 # Constructor which expects the name of the node
113 #
114 # Name of the node is expected to be unique across the
115 # tree.
116 #
117 # The content can be of any type, and is defaulted to _nil_.
118 def initialize(name, content = nil)
119 raise "Node name HAS to be provided" if name == nil
120 @name = name
121 @content = content
122 self.setAsRoot!
123
124 @childrenHash = Hash.new
125 @children = []
126 end
127
128 # Returns a copy of this node, with the parent and children links removed.
129 def detached_copy
130 Tree::TreeNode.new(@name, @content ? @content.clone : nil)
131 end
132
133 # Print the string representation of this node.
134 def to_s
135 "Node Name: #{@name}" +
136 " Content: " + (@content || "<Empty>") +
137 " Parent: " + (isRoot?() ? "<None>" : @parent.name) +
138 " Children: #{@children.length}" +
139 " Total Nodes: #{size()}"
140 end
141
142 # Returns an array of ancestors in reversed order (the first element is the
143 # immediate parent). Returns nil if this is a root node.
144 def parentage
145 return nil if isRoot?
146
147 parentageArray = []
148 prevParent = self.parent
149 while (prevParent)
150 parentageArray << prevParent
151 prevParent = prevParent.parent
152 end
153
154 parentageArray
155 end
156
157 # Protected method to set the parent node.
158 # This method should NOT be invoked by client code.
159 def parent=(parent)
160 @parent = parent
161 end
162
163 # Convenience synonym for TreeNode#add method. This method allows a convenient
164 # method to add children hierarchies in the tree.
165 #
166 # E.g. root << child << grand_child
167 def <<(child)
168 add(child)
169 end
170
171 # Adds the specified child node to the receiver node. The child node's
172 # parent is set to be the receiver. The child is added as the last child in
173 # the current list of children for the receiver node.
174 def add(child)
175 raise "Child already added" if @childrenHash.has_key?(child.name)
176
177 @childrenHash[child.name] = child
178 @children << child
179 child.parent = self
180 return child
181
182 end
183
184 # Removes the specified child node from the receiver node. The removed
185 # children nodes are orphaned but available if an alternate reference
186 # exists.
187 #
188 # Returns the child node.
189 def remove!(child)
190 @childrenHash.delete(child.name)
191 @children.delete(child)
192 child.setAsRoot! unless child == nil
193 return child
194 end
195
196 # Removes this node from its parent. If this is the root node, then does
197 # nothing.
198 def removeFromParent!
199 @parent.remove!(self) unless isRoot?
200 end
201
202 # Removes all children from the receiver node.
203 def removeAll!
204 for child in @children
205 child.setAsRoot!
206 end
207 @childrenHash.clear
208 @children.clear
209 self
210 end
211
212 # Indicates whether this node has any associated content.
213 def hasContent?
214 @content != nil
215 end
216
217 # Protected method which sets this node as a root node.
218 def setAsRoot!
219 @parent = nil
220 end
221
222 # Indicates whether this node is a root node. Note that
223 # orphaned children will also be reported as root nodes.
224 def isRoot?
225 @parent == nil
226 end
227
228 # Indicates whether this node has any immediate child nodes.
229 def hasChildren?
230 @children.length != 0
231 end
232
233 # Indicates whether this node is a 'leaf' - i.e., one without
234 # any children
235 def isLeaf?
236 !hasChildren?
237 end
238
239 # Returns an array of all the immediate children. If a block is given,
240 # yields each child node to the block.
241 def children
242 if block_given?
243 @children.each {|child| yield child}
244 else
245 @children
246 end
247 end
248
249 # Returns the first child of this node. Will return nil if no children are
250 # present.
251 def firstChild
252 children.first
253 end
254
255 # Returns the last child of this node. Will return nil if no children are
256 # present.
257 def lastChild
258 children.last
259 end
260
261 # Returns every node (including the receiver node) from the tree to the
262 # specified block. The traversal is depth first and from left to right in
263 # pre-ordered sequence.
264 def each &block
265 yield self
266 children { |child| child.each(&block) }
267 end
268
269 # Traverses the tree in a pre-ordered sequence. This is equivalent to
270 # TreeNode#each
271 def preordered_each &block
272 each(&block)
273 end
274
275 # Performs breadth first traversal of the tree rooted at this node. The
276 # traversal in a given level is from left to right.
277 def breadth_each &block
278 node_queue = [self] # Create a queue with self as the initial entry
279
280 # Use a queue to do breadth traversal
281 until node_queue.empty?
282 node_to_traverse = node_queue.shift
283 yield node_to_traverse
284 # Enqueue the children from left to right.
285 node_to_traverse.children { |child| node_queue.push child }
286 end
287 end
288
289 # Yields all leaf nodes from this node to the specified block. May yield
290 # this node as well if this is a leaf node. Leaf traversal depth first and
291 # left to right.
292 def each_leaf &block
293 self.each { |node| yield(node) if node.isLeaf? }
294 end
295
296 # Returns the requested node from the set of immediate children.
297 #
298 # If the parameter is _numeric_, then the in-sequence array of children is
299 # accessed (see Tree#children). If the parameter is not _numeric_, then it
300 # is assumed to be the *name* of the child node to be returned.
301 def [](name_or_index)
302 raise "Name_or_index needs to be provided" if name_or_index == nil
303
304 if name_or_index.kind_of?(Integer)
305 @children[name_or_index]
306 else
307 @childrenHash[name_or_index]
308 end
309 end
310
311 # Returns the total number of nodes in this tree, rooted at the receiver
312 # node.
313 def size
314 @children.inject(1) {|sum, node| sum + node.size}
315 end
316
317 # Convenience synonym for Tree#size
318 def length
319 size()
320 end
321
322 # Pretty prints the tree starting with the receiver node.
323 def printTree(level = 0)
324
325 if isRoot?
326 print "*"
327 else
328 print "|" unless parent.isLastSibling?
329 print(' ' * (level - 1) * 4)
330 print(isLastSibling? ? "+" : "|")
331 print "---"
332 print(hasChildren? ? "+" : ">")
333 end
334
335 puts " #{name}"
336
337 children { |child| child.printTree(level + 1)}
338 end
339
340 # Returns the root for this tree. Root's root is itself.
341 def root
342 root = self
343 root = root.parent while !root.isRoot?
344 root
345 end
346
347 # Returns the first sibling for this node. If this is the root node, returns
348 # itself.
349 def firstSibling
350 if isRoot?
351 self
352 else
353 parent.children.first
354 end
355 end
356
357 # Returns true if this node is the first sibling.
358 def isFirstSibling?
359 firstSibling == self
360 end
361
362 # Returns the last sibling for this node. If this node is the root, returns
363 # itself.
364 def lastSibling
365 if isRoot?
366 self
367 else
368 parent.children.last
369 end
370 end
371
372 # Returns true if his node is the last sibling
373 def isLastSibling?
374 lastSibling == self
375 end
376
377 # Returns an array of siblings for this node.
378 # If a block is provided, yields each of the sibling
379 # nodes to the block. The root always has nil siblings.
380 def siblings
381 return nil if isRoot?
382 if block_given?
383 for sibling in parent.children
384 yield sibling if sibling != self
385 end
386 else
387 siblings = []
388 parent.children {|sibling| siblings << sibling if sibling != self}
389 siblings
390 end
391 end
392
393 # Returns true if this node is the only child of its parent
394 def isOnlyChild?
395 parent.children.size == 1
396 end
397
398 # Returns the next sibling for this node. Will return nil if no subsequent
399 # node is present.
400 def nextSibling
401 if myidx = parent.children.index(self)
402 parent.children.at(myidx + 1)
403 end
404 end
405
406 # Returns the previous sibling for this node. Will return nil if no
407 # subsequent node is present.
408 def previousSibling
409 if myidx = parent.children.index(self)
410 parent.children.at(myidx - 1) if myidx > 0
411 end
412 end
413
414 # Provides a comparision operation for the nodes. Comparision
415 # is based on the natural character-set ordering for the
416 # node names.
417 def <=>(other)
418 return +1 if other == nil
419 self.name <=> other.name
420 end
421
422 # Freezes all nodes in the tree
423 def freezeTree!
424 each {|node| node.freeze}
425 end
426
427 # Creates the marshal-dump represention of the tree rooted at this node.
428 def marshal_dump
429 self.collect { |node| node.createDumpRep }
430 end
431
432 # Creates a dump representation and returns the same as a hash.
433 def createDumpRep
434 { :name => @name, :parent => (isRoot? ? nil : @parent.name), :content => Marshal.dump(@content)}
435 end
436
437 # Loads a marshalled dump of the tree and returns the root node of the
438 # reconstructed tree. See the Marshal class for additional details.
439 def marshal_load(dumped_tree_array)
440 nodes = { }
441 for node_hash in dumped_tree_array do
442 name = node_hash[:name]
443 parent_name = node_hash[:parent]
444 content = Marshal.load(node_hash[:content])
445
446 if parent_name then
447 nodes[name] = current_node = Tree::TreeNode.new(name, content)
448 nodes[parent_name].add current_node
449 else
450 # This is the root node, hence initialize self.
451 initialize(name, content)
452
453 nodes[name] = self # Add self to the list of nodes
454 end
455 end
456 end
457
458 # Returns depth of the tree from this node. A single leaf node has a
459 # depth of 1.
460 def depth
461 return 1 if isLeaf?
462 1 + @children.collect { |child| child.depth }.max
463 end
464
465 # Returns breadth of the tree at this node level. A single node has a
466 # breadth of 1.
467 def breadth
468 return 1 if isRoot?
469 parent.children.size
470 end
471
472 protected :parent=, :setAsRoot!, :createDumpRep
473
474 end
475 end
476
477 # $Log: tree.rb,v $
478 # Revision 1.29 2007/12/22 00:28:59 anupamsg
479 # Added more test cases, and enabled ZenTest compatibility.
480 #
481 # Revision 1.28 2007/12/20 03:19:33 anupamsg
482 # * README (Module): Modified the install instructions from source.
483 # (Module): Updated the minor version number.
484 #
485 # Revision 1.27 2007/12/20 03:00:03 anupamsg
486 # Minor code changes. Removed self_initialize from the protected methods' list.
487 #
488 # Revision 1.26 2007/12/20 02:50:04 anupamsg
489 # (Tree::TreeNode): Removed the spurious self_initialize from the protected list.
490 #
491 # Revision 1.25 2007/12/19 20:28:05 anupamsg
492 # Removed the unnecesary self_initialize method.
493 #
494 # Revision 1.24 2007/12/19 06:39:17 anupamsg
495 # Removed the unnecessary field and record separator constants. Also updated the
496 # history.txt file.
497 #
498 # Revision 1.23 2007/12/19 06:25:00 anupamsg
499 # (Tree::TreeNode): Minor fix to the comments. Also fixed the private/protected
500 # scope issue with the createDumpRep method.
501 #
502 # Revision 1.22 2007/12/19 06:22:03 anupamsg
503 # Updated the marshalling logic to correctly handle non-string content. This
504 # should fix the bug # 15614 ("When dumping with an Object as the content, you get
505 # a delimiter collision")
506 #
507 # Revision 1.21 2007/12/19 02:24:17 anupamsg
508 # Updated the marshalling logic to handle non-string contents on the nodes.
509 #
510 # Revision 1.20 2007/10/10 08:42:57 anupamsg
511 # Release 0.4.3
512 #
513 # Revision 1.19 2007/08/31 01:16:27 anupamsg
514 # Added breadth and pre-order traversals for the tree. Also added a method
515 # to return the detached copy of a node from the tree.
516 #
517 # Revision 1.18 2007/07/21 05:14:44 anupamsg
518 # Added a VERSION constant to the Tree module,
519 # and using the same in the Rakefile.
520 #
521 # Revision 1.17 2007/07/21 03:24:25 anupamsg
522 # Minor edits to parameter names. User visible functionality does not change.
523 #
524 # Revision 1.16 2007/07/18 23:38:55 anupamsg
525 # Minor updates to tree.rb
526 #
527 # Revision 1.15 2007/07/18 22:11:50 anupamsg
528 # Added depth and breadth methods for the TreeNode.
529 #
530 # Revision 1.14 2007/07/18 19:33:27 anupamsg
531 # Added a new binary tree implementation.
532 #
533 # Revision 1.13 2007/07/18 07:17:34 anupamsg
534 # Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method
535 # has been renamed to TreeNode.parentage.
536 #
537 # Revision 1.12 2007/07/17 03:39:28 anupamsg
538 # Moved the CVS Log keyword to end of the files.
539 #
@@ -0,0 +1,131
1 # binarytree.rb
2 #
3 # $Revision: 1.5 $ by $Author: anupamsg $
4 # $Name: $
5 #
6 # = binarytree.rb - Binary Tree implementation
7 #
8 # Provides a generic tree data structure with ability to
9 # store keyed node elements in the tree. The implementation
10 # mixes in the Enumerable module.
11 #
12 # Author:: Anupam Sengupta (anupamsg@gmail.com)
13 #
14
15 # Copyright (c) 2007 Anupam Sengupta
16 #
17 # All rights reserved.
18 #
19 # Redistribution and use in source and binary forms, with or without modification,
20 # are permitted provided that the following conditions are met:
21 #
22 # - Redistributions of source code must retain the above copyright notice, this
23 # list of conditions and the following disclaimer.
24 #
25 # - Redistributions in binary form must reproduce the above copyright notice, this
26 # list of conditions and the following disclaimer in the documentation and/or
27 # other materials provided with the distribution.
28 #
29 # - Neither the name of the organization nor the names of its contributors may
30 # be used to endorse or promote products derived from this software without
31 # specific prior written permission.
32 #
33 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
37 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
40 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
41 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
42 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 #
44
45 require 'tree'
46
47 module Tree
48
49 # Provides a Binary tree implementation. This tree node allows only two child
50 # nodes (left and right childs). It also provides direct access to the left
51 # and right children, including assignment to the same.
52 class BinaryTreeNode < TreeNode
53
54 # Adds the specified child node to the receiver node. The child node's
55 # parent is set to be the receiver. The child nodes are added in the order
56 # of addition, i.e., the first child added becomes the left node, and the
57 # second child will be the second node.
58 # If only one child is present, then this will be the left child.
59 def add(child)
60 raise "Already has two child nodes" if @children.size == 2
61
62 super(child)
63 end
64
65 # Returns the left child node. Note that
66 # left Child == first Child
67 def leftChild
68 children.first
69 end
70
71 # Returns the right child node. Note that
72 # right child == last child unless there is only one child.
73 # Returns nil if the right child does not exist.
74 def rightChild
75 children[1]
76 end
77
78 # Sets the left child. If a previous child existed, it is replaced.
79 def leftChild=(child)
80 @children[0] = child
81 @childrenHash[child.name] = child if child # Assign the name mapping
82 end
83
84 # Sets the right child. If a previous child existed, it is replaced.
85 def rightChild=(child)
86 @children[1] = child
87 @childrenHash[child.name] = child if child # Assign the name mapping
88 end
89
90 # Returns true if this is the left child of its parent. Always returns false
91 # if this is the root node.
92 def isLeftChild?
93 return nil if isRoot?
94 self == parent.leftChild
95 end
96
97 # Returns true if this is the right child of its parent. Always returns false
98 # if this is the root node.
99 def isRightChild?
100 return nil if isRoot?
101 self == parent.rightChild
102 end
103
104 # Swaps the left and right children with each other
105 def swap_children
106 tempChild = leftChild
107 self.leftChild= rightChild
108 self.rightChild= tempChild
109 end
110 end
111
112 end
113
114 # $Log: binarytree.rb,v $
115 # Revision 1.5 2007/12/18 23:11:29 anupamsg
116 # Minor documentation changes in the binarytree class.
117 #
118 # Revision 1.4 2007/08/30 22:08:58 anupamsg
119 # Added a new swap_children method for Binary Tree. Also added minor
120 # documentation and test updates.
121 #
122 # Revision 1.3 2007/07/21 03:24:25 anupamsg
123 # Minor edits to parameter names. User visible functionality does not change.
124 #
125 # Revision 1.2 2007/07/18 20:15:06 anupamsg
126 # Added two predicate methods in BinaryTreeNode to determine whether a node
127 # is a left or a right node.
128 #
129 # Revision 1.1 2007/07/18 19:33:27 anupamsg
130 # Added a new binary tree implementation.
131 #
This diff has been collapsed as it changes many lines, (1585 lines changed) Show them Hide them
@@ -0,0 +1,1585
1 #
2 # setup.rb
3 #
4 # Copyright (c) 2000-2005 Minero Aoki
5 #
6 # This program is free software.
7 # You can distribute/modify this program under the terms of
8 # the GNU LGPL, Lesser General Public License version 2.1.
9 #
10
11 unless Enumerable.method_defined?(:map) # Ruby 1.4.6
12 module Enumerable
13 alias map collect
14 end
15 end
16
17 unless File.respond_to?(:read) # Ruby 1.6
18 def File.read(fname)
19 open(fname) {|f|
20 return f.read
21 }
22 end
23 end
24
25 unless Errno.const_defined?(:ENOTEMPTY) # Windows?
26 module Errno
27 class ENOTEMPTY
28 # We do not raise this exception, implementation is not needed.
29 end
30 end
31 end
32
33 def File.binread(fname)
34 open(fname, 'rb') {|f|
35 return f.read
36 }
37 end
38
39 # for corrupted Windows' stat(2)
40 def File.dir?(path)
41 File.directory?((path[-1,1] == '/') ? path : path + '/')
42 end
43
44
45 class ConfigTable
46
47 include Enumerable
48
49 def initialize(rbconfig)
50 @rbconfig = rbconfig
51 @items = []
52 @table = {}
53 # options
54 @install_prefix = nil
55 @config_opt = nil
56 @verbose = true
57 @no_harm = false
58 end
59
60 attr_accessor :install_prefix
61 attr_accessor :config_opt
62
63 attr_writer :verbose
64
65 def verbose?
66 @verbose
67 end
68
69 attr_writer :no_harm
70
71 def no_harm?
72 @no_harm
73 end
74
75 def [](key)
76 lookup(key).resolve(self)
77 end
78
79 def []=(key, val)
80 lookup(key).set val
81 end
82
83 def names
84 @items.map {|i| i.name }
85 end
86
87 def each(&block)
88 @items.each(&block)
89 end
90
91 def key?(name)
92 @table.key?(name)
93 end
94
95 def lookup(name)
96 @table[name] or setup_rb_error "no such config item: #{name}"
97 end
98
99 def add(item)
100 @items.push item
101 @table[item.name] = item
102 end
103
104 def remove(name)
105 item = lookup(name)
106 @items.delete_if {|i| i.name == name }
107 @table.delete_if {|name, i| i.name == name }
108 item
109 end
110
111 def load_script(path, inst = nil)
112 if File.file?(path)
113 MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
114 end
115 end
116
117 def savefile
118 '.config'
119 end
120
121 def load_savefile
122 begin
123 File.foreach(savefile()) do |line|
124 k, v = *line.split(/=/, 2)
125 self[k] = v.strip
126 end
127 rescue Errno::ENOENT
128 setup_rb_error $!.message + "\n#{File.basename($0)} config first"
129 end
130 end
131
132 def save
133 @items.each {|i| i.value }
134 File.open(savefile(), 'w') {|f|
135 @items.each do |i|
136 f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
137 end
138 }
139 end
140
141 def load_standard_entries
142 standard_entries(@rbconfig).each do |ent|
143 add ent
144 end
145 end
146
147 def standard_entries(rbconfig)
148 c = rbconfig
149
150 rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
151
152 major = c['MAJOR'].to_i
153 minor = c['MINOR'].to_i
154 teeny = c['TEENY'].to_i
155 version = "#{major}.#{minor}"
156
157 # ruby ver. >= 1.4.4?
158 newpath_p = ((major >= 2) or
159 ((major == 1) and
160 ((minor >= 5) or
161 ((minor == 4) and (teeny >= 4)))))
162
163 if c['rubylibdir']
164 # V > 1.6.3
165 libruby = "#{c['prefix']}/lib/ruby"
166 librubyver = c['rubylibdir']
167 librubyverarch = c['archdir']
168 siteruby = c['sitedir']
169 siterubyver = c['sitelibdir']
170 siterubyverarch = c['sitearchdir']
171 elsif newpath_p
172 # 1.4.4 <= V <= 1.6.3
173 libruby = "#{c['prefix']}/lib/ruby"
174 librubyver = "#{c['prefix']}/lib/ruby/#{version}"
175 librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
176 siteruby = c['sitedir']
177 siterubyver = "$siteruby/#{version}"
178 siterubyverarch = "$siterubyver/#{c['arch']}"
179 else
180 # V < 1.4.4
181 libruby = "#{c['prefix']}/lib/ruby"
182 librubyver = "#{c['prefix']}/lib/ruby/#{version}"
183 librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
184 siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
185 siterubyver = siteruby
186 siterubyverarch = "$siterubyver/#{c['arch']}"
187 end
188 parameterize = lambda {|path|
189 path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
190 }
191
192 if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
193 makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
194 else
195 makeprog = 'make'
196 end
197
198 [
199 ExecItem.new('installdirs', 'std/site/home',
200 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
201 {|val, table|
202 case val
203 when 'std'
204 table['rbdir'] = '$librubyver'
205 table['sodir'] = '$librubyverarch'
206 when 'site'
207 table['rbdir'] = '$siterubyver'
208 table['sodir'] = '$siterubyverarch'
209 when 'home'
210 setup_rb_error '$HOME was not set' unless ENV['HOME']
211 table['prefix'] = ENV['HOME']
212 table['rbdir'] = '$libdir/ruby'
213 table['sodir'] = '$libdir/ruby'
214 end
215 },
216 PathItem.new('prefix', 'path', c['prefix'],
217 'path prefix of target environment'),
218 PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
219 'the directory for commands'),
220 PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
221 'the directory for libraries'),
222 PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
223 'the directory for shared data'),
224 PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
225 'the directory for man pages'),
226 PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
227 'the directory for system configuration files'),
228 PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
229 'the directory for local state data'),
230 PathItem.new('libruby', 'path', libruby,
231 'the directory for ruby libraries'),
232 PathItem.new('librubyver', 'path', librubyver,
233 'the directory for standard ruby libraries'),
234 PathItem.new('librubyverarch', 'path', librubyverarch,
235 'the directory for standard ruby extensions'),
236 PathItem.new('siteruby', 'path', siteruby,
237 'the directory for version-independent aux ruby libraries'),
238 PathItem.new('siterubyver', 'path', siterubyver,
239 'the directory for aux ruby libraries'),
240 PathItem.new('siterubyverarch', 'path', siterubyverarch,
241 'the directory for aux ruby binaries'),
242 PathItem.new('rbdir', 'path', '$siterubyver',
243 'the directory for ruby scripts'),
244 PathItem.new('sodir', 'path', '$siterubyverarch',
245 'the directory for ruby extentions'),
246 PathItem.new('rubypath', 'path', rubypath,
247 'the path to set to #! line'),
248 ProgramItem.new('rubyprog', 'name', rubypath,
249 'the ruby program using for installation'),
250 ProgramItem.new('makeprog', 'name', makeprog,
251 'the make program to compile ruby extentions'),
252 SelectItem.new('shebang', 'all/ruby/never', 'ruby',
253 'shebang line (#!) editing mode'),
254 BoolItem.new('without-ext', 'yes/no', 'no',
255 'does not compile/install ruby extentions')
256 ]
257 end
258 private :standard_entries
259
260 def load_multipackage_entries
261 multipackage_entries().each do |ent|
262 add ent
263 end
264 end
265
266 def multipackage_entries
267 [
268 PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
269 'package names that you want to install'),
270 PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
271 'package names that you do not want to install')
272 ]
273 end
274 private :multipackage_entries
275
276 ALIASES = {
277 'std-ruby' => 'librubyver',
278 'stdruby' => 'librubyver',
279 'rubylibdir' => 'librubyver',
280 'archdir' => 'librubyverarch',
281 'site-ruby-common' => 'siteruby', # For backward compatibility
282 'site-ruby' => 'siterubyver', # For backward compatibility
283 'bin-dir' => 'bindir',
284 'bin-dir' => 'bindir',
285 'rb-dir' => 'rbdir',
286 'so-dir' => 'sodir',
287 'data-dir' => 'datadir',
288 'ruby-path' => 'rubypath',
289 'ruby-prog' => 'rubyprog',
290 'ruby' => 'rubyprog',
291 'make-prog' => 'makeprog',
292 'make' => 'makeprog'
293 }
294
295 def fixup
296 ALIASES.each do |ali, name|
297 @table[ali] = @table[name]
298 end
299 @items.freeze
300 @table.freeze
301 @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
302 end
303
304 def parse_opt(opt)
305 m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
306 m.to_a[1,2]
307 end
308
309 def dllext
310 @rbconfig['DLEXT']
311 end
312
313 def value_config?(name)
314 lookup(name).value?
315 end
316
317 class Item
318 def initialize(name, template, default, desc)
319 @name = name.freeze
320 @template = template
321 @value = default
322 @default = default
323 @description = desc
324 end
325
326 attr_reader :name
327 attr_reader :description
328
329 attr_accessor :default
330 alias help_default default
331
332 def help_opt
333 "--#{@name}=#{@template}"
334 end
335
336 def value?
337 true
338 end
339
340 def value
341 @value
342 end
343
344 def resolve(table)
345 @value.gsub(%r<\$([^/]+)>) { table[$1] }
346 end
347
348 def set(val)
349 @value = check(val)
350 end
351
352 private
353
354 def check(val)
355 setup_rb_error "config: --#{name} requires argument" unless val
356 val
357 end
358 end
359
360 class BoolItem < Item
361 def config_type
362 'bool'
363 end
364
365 def help_opt
366 "--#{@name}"
367 end
368
369 private
370
371 def check(val)
372 return 'yes' unless val
373 case val
374 when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
375 when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
376 else
377 setup_rb_error "config: --#{@name} accepts only yes/no for argument"
378 end
379 end
380 end
381
382 class PathItem < Item
383 def config_type
384 'path'
385 end
386
387 private
388
389 def check(path)
390 setup_rb_error "config: --#{@name} requires argument" unless path
391 path[0,1] == '$' ? path : File.expand_path(path)
392 end
393 end
394
395 class ProgramItem < Item
396 def config_type
397 'program'
398 end
399 end
400
401 class SelectItem < Item
402 def initialize(name, selection, default, desc)
403 super
404 @ok = selection.split('/')
405 end
406
407 def config_type
408 'select'
409 end
410
411 private
412
413 def check(val)
414 unless @ok.include?(val.strip)
415 setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
416 end
417 val.strip
418 end
419 end
420
421 class ExecItem < Item
422 def initialize(name, selection, desc, &block)
423 super name, selection, nil, desc
424 @ok = selection.split('/')
425 @action = block
426 end
427
428 def config_type
429 'exec'
430 end
431
432 def value?
433 false
434 end
435
436 def resolve(table)
437 setup_rb_error "$#{name()} wrongly used as option value"
438 end
439
440 undef set
441
442 def evaluate(val, table)
443 v = val.strip.downcase
444 unless @ok.include?(v)
445 setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
446 end
447 @action.call v, table
448 end
449 end
450
451 class PackageSelectionItem < Item
452 def initialize(name, template, default, help_default, desc)
453 super name, template, default, desc
454 @help_default = help_default
455 end
456
457 attr_reader :help_default
458
459 def config_type
460 'package'
461 end
462
463 private
464
465 def check(val)
466 unless File.dir?("packages/#{val}")
467 setup_rb_error "config: no such package: #{val}"
468 end
469 val
470 end
471 end
472
473 class MetaConfigEnvironment
474 def initialize(config, installer)
475 @config = config
476 @installer = installer
477 end
478
479 def config_names
480 @config.names
481 end
482
483 def config?(name)
484 @config.key?(name)
485 end
486
487 def bool_config?(name)
488 @config.lookup(name).config_type == 'bool'
489 end
490
491 def path_config?(name)
492 @config.lookup(name).config_type == 'path'
493 end
494
495 def value_config?(name)
496 @config.lookup(name).config_type != 'exec'
497 end
498
499 def add_config(item)
500 @config.add item
501 end
502
503 def add_bool_config(name, default, desc)
504 @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
505 end
506
507 def add_path_config(name, default, desc)
508 @config.add PathItem.new(name, 'path', default, desc)
509 end
510
511 def set_config_default(name, default)
512 @config.lookup(name).default = default
513 end
514
515 def remove_config(name)
516 @config.remove(name)
517 end
518
519 # For only multipackage
520 def packages
521 raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
522 @installer.packages
523 end
524
525 # For only multipackage
526 def declare_packages(list)
527 raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
528 @installer.packages = list
529 end
530 end
531
532 end # class ConfigTable
533
534
535 # This module requires: #verbose?, #no_harm?
536 module FileOperations
537
538 def mkdir_p(dirname, prefix = nil)
539 dirname = prefix + File.expand_path(dirname) if prefix
540 $stderr.puts "mkdir -p #{dirname}" if verbose?
541 return if no_harm?
542
543 # Does not check '/', it's too abnormal.
544 dirs = File.expand_path(dirname).split(%r<(?=/)>)
545 if /\A[a-z]:\z/i =~ dirs[0]
546 disk = dirs.shift
547 dirs[0] = disk + dirs[0]
548 end
549 dirs.each_index do |idx|
550 path = dirs[0..idx].join('')
551 Dir.mkdir path unless File.dir?(path)
552 end
553 end
554
555 def rm_f(path)
556 $stderr.puts "rm -f #{path}" if verbose?
557 return if no_harm?
558 force_remove_file path
559 end
560
561 def rm_rf(path)
562 $stderr.puts "rm -rf #{path}" if verbose?
563 return if no_harm?
564 remove_tree path
565 end
566
567 def remove_tree(path)
568 if File.symlink?(path)
569 remove_file path
570 elsif File.dir?(path)
571 remove_tree0 path
572 else
573 force_remove_file path
574 end
575 end
576
577 def remove_tree0(path)
578 Dir.foreach(path) do |ent|
579 next if ent == '.'
580 next if ent == '..'
581 entpath = "#{path}/#{ent}"
582 if File.symlink?(entpath)
583 remove_file entpath
584 elsif File.dir?(entpath)
585 remove_tree0 entpath
586 else
587 force_remove_file entpath
588 end
589 end
590 begin
591 Dir.rmdir path
592 rescue Errno::ENOTEMPTY
593 # directory may not be empty
594 end
595 end
596
597 def move_file(src, dest)
598 force_remove_file dest
599 begin
600 File.rename src, dest
601 rescue
602 File.open(dest, 'wb') {|f|
603 f.write File.binread(src)
604 }
605 File.chmod File.stat(src).mode, dest
606 File.unlink src
607 end
608 end
609
610 def force_remove_file(path)
611 begin
612 remove_file path
613 rescue
614 end
615 end
616
617 def remove_file(path)
618 File.chmod 0777, path
619 File.unlink path
620 end
621
622 def install(from, dest, mode, prefix = nil)
623 $stderr.puts "install #{from} #{dest}" if verbose?
624 return if no_harm?
625
626 realdest = prefix ? prefix + File.expand_path(dest) : dest
627 realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
628 str = File.binread(from)
629 if diff?(str, realdest)
630 verbose_off {
631 rm_f realdest if File.exist?(realdest)
632 }
633 File.open(realdest, 'wb') {|f|
634 f.write str
635 }
636 File.chmod mode, realdest
637
638 File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
639 if prefix
640 f.puts realdest.sub(prefix, '')
641 else
642 f.puts realdest
643 end
644 }
645 end
646 end
647
648 def diff?(new_content, path)
649 return true unless File.exist?(path)
650 new_content != File.binread(path)
651 end
652
653 def command(*args)
654 $stderr.puts args.join(' ') if verbose?
655 system(*args) or raise RuntimeError,
656 "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
657 end
658
659 def ruby(*args)
660 command config('rubyprog'), *args
661 end
662
663 def make(task = nil)
664 command(*[config('makeprog'), task].compact)
665 end
666
667 def extdir?(dir)
668 File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
669 end
670
671 def files_of(dir)
672 Dir.open(dir) {|d|
673 return d.select {|ent| File.file?("#{dir}/#{ent}") }
674 }
675 end
676
677 DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
678
679 def directories_of(dir)
680 Dir.open(dir) {|d|
681 return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
682 }
683 end
684
685 end
686
687
688 # This module requires: #srcdir_root, #objdir_root, #relpath
689 module HookScriptAPI
690
691 def get_config(key)
692 @config[key]
693 end
694
695 alias config get_config
696
697 # obsolete: use metaconfig to change configuration
698 def set_config(key, val)
699 @config[key] = val
700 end
701
702 #
703 # srcdir/objdir (works only in the package directory)
704 #
705
706 def curr_srcdir
707 "#{srcdir_root()}/#{relpath()}"
708 end
709
710 def curr_objdir
711 "#{objdir_root()}/#{relpath()}"
712 end
713
714 def srcfile(path)
715 "#{curr_srcdir()}/#{path}"
716 end
717
718 def srcexist?(path)
719 File.exist?(srcfile(path))
720 end
721
722 def srcdirectory?(path)
723 File.dir?(srcfile(path))
724 end
725
726 def srcfile?(path)
727 File.file?(srcfile(path))
728 end
729
730 def srcentries(path = '.')
731 Dir.open("#{curr_srcdir()}/#{path}") {|d|
732 return d.to_a - %w(. ..)
733 }
734 end
735
736 def srcfiles(path = '.')
737 srcentries(path).select {|fname|
738 File.file?(File.join(curr_srcdir(), path, fname))
739 }
740 end
741
742 def srcdirectories(path = '.')
743 srcentries(path).select {|fname|
744 File.dir?(File.join(curr_srcdir(), path, fname))
745 }
746 end
747
748 end
749
750
751 class ToplevelInstaller
752
753 Version = '3.4.1'
754 Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
755
756 TASKS = [
757 [ 'all', 'do config, setup, then install' ],
758 [ 'config', 'saves your configurations' ],
759 [ 'show', 'shows current configuration' ],
760 [ 'setup', 'compiles ruby extentions and others' ],
761 [ 'install', 'installs files' ],
762 [ 'test', 'run all tests in test/' ],
763 [ 'clean', "does `make clean' for each extention" ],
764 [ 'distclean',"does `make distclean' for each extention" ]
765 ]
766
767 def ToplevelInstaller.invoke
768 config = ConfigTable.new(load_rbconfig())
769 config.load_standard_entries
770 config.load_multipackage_entries if multipackage?
771 config.fixup
772 klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
773 klass.new(File.dirname($0), config).invoke
774 end
775
776 def ToplevelInstaller.multipackage?
777 File.dir?(File.dirname($0) + '/packages')
778 end
779
780 def ToplevelInstaller.load_rbconfig
781 if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
782 ARGV.delete(arg)
783 load File.expand_path(arg.split(/=/, 2)[1])
784 $".push 'rbconfig.rb'
785 else
786 require 'rbconfig'
787 end
788 ::Config::CONFIG
789 end
790
791 def initialize(ardir_root, config)
792 @ardir = File.expand_path(ardir_root)
793 @config = config
794 # cache
795 @valid_task_re = nil
796 end
797
798 def config(key)
799 @config[key]
800 end
801
802 def inspect
803 "#<#{self.class} #{__id__()}>"
804 end
805
806 def invoke
807 run_metaconfigs
808 case task = parsearg_global()
809 when nil, 'all'
810 parsearg_config
811 init_installers
812 exec_config
813 exec_setup
814 exec_install
815 else
816 case task
817 when 'config', 'test'
818 ;
819 when 'clean', 'distclean'
820 @config.load_savefile if File.exist?(@config.savefile)
821 else
822 @config.load_savefile
823 end
824 __send__ "parsearg_#{task}"
825 init_installers
826 __send__ "exec_#{task}"
827 end
828 end
829
830 def run_metaconfigs
831 @config.load_script "#{@ardir}/metaconfig"
832 end
833
834 def init_installers
835 @installer = Installer.new(@config, @ardir, File.expand_path('.'))
836 end
837
838 #
839 # Hook Script API bases
840 #
841
842 def srcdir_root
843 @ardir
844 end
845
846 def objdir_root
847 '.'
848 end
849
850 def relpath
851 '.'
852 end
853
854 #
855 # Option Parsing
856 #
857
858 def parsearg_global
859 while arg = ARGV.shift
860 case arg
861 when /\A\w+\z/
862 setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
863 return arg
864 when '-q', '--quiet'
865 @config.verbose = false
866 when '--verbose'
867 @config.verbose = true
868 when '--help'
869 print_usage $stdout
870 exit 0
871 when '--version'
872 puts "#{File.basename($0)} version #{Version}"
873 exit 0
874 when '--copyright'
875 puts Copyright
876 exit 0
877 else
878 setup_rb_error "unknown global option '#{arg}'"
879 end
880 end
881 nil
882 end
883
884 def valid_task?(t)
885 valid_task_re() =~ t
886 end
887
888 def valid_task_re
889 @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
890 end
891
892 def parsearg_no_options
893 unless ARGV.empty?
894 task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
895 setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
896 end
897 end
898
899 alias parsearg_show parsearg_no_options
900 alias parsearg_setup parsearg_no_options
901 alias parsearg_test parsearg_no_options
902 alias parsearg_clean parsearg_no_options
903 alias parsearg_distclean parsearg_no_options
904
905 def parsearg_config
906 evalopt = []
907 set = []
908 @config.config_opt = []
909 while i = ARGV.shift
910 if /\A--?\z/ =~ i
911 @config.config_opt = ARGV.dup
912 break
913 end
914 name, value = *@config.parse_opt(i)
915 if @config.value_config?(name)
916 @config[name] = value
917 else
918 evalopt.push [name, value]
919 end
920 set.push name
921 end
922 evalopt.each do |name, value|
923 @config.lookup(name).evaluate value, @config
924 end
925 # Check if configuration is valid
926 set.each do |n|
927 @config[n] if @config.value_config?(n)
928 end
929 end
930
931 def parsearg_install
932 @config.no_harm = false
933 @config.install_prefix = ''
934 while a = ARGV.shift
935 case a
936 when '--no-harm'
937 @config.no_harm = true
938 when /\A--prefix=/
939 path = a.split(/=/, 2)[1]
940 path = File.expand_path(path) unless path[0,1] == '/'
941 @config.install_prefix = path
942 else
943 setup_rb_error "install: unknown option #{a}"
944 end
945 end
946 end
947
948 def print_usage(out)
949 out.puts 'Typical Installation Procedure:'
950 out.puts " $ ruby #{File.basename $0} config"
951 out.puts " $ ruby #{File.basename $0} setup"
952 out.puts " # ruby #{File.basename $0} install (may require root privilege)"
953 out.puts
954 out.puts 'Detailed Usage:'
955 out.puts " ruby #{File.basename $0} <global option>"
956 out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
957
958 fmt = " %-24s %s\n"
959 out.puts
960 out.puts 'Global options:'
961 out.printf fmt, '-q,--quiet', 'suppress message outputs'
962 out.printf fmt, ' --verbose', 'output messages verbosely'
963 out.printf fmt, ' --help', 'print this message'
964 out.printf fmt, ' --version', 'print version and quit'
965 out.printf fmt, ' --copyright', 'print copyright and quit'
966 out.puts
967 out.puts 'Tasks:'
968 TASKS.each do |name, desc|
969 out.printf fmt, name, desc
970 end
971
972 fmt = " %-24s %s [%s]\n"
973 out.puts
974 out.puts 'Options for CONFIG or ALL:'
975 @config.each do |item|
976 out.printf fmt, item.help_opt, item.description, item.help_default
977 end
978 out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
979 out.puts
980 out.puts 'Options for INSTALL:'
981 out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
982 out.printf fmt, '--prefix=path', 'install path prefix', ''
983 out.puts
984 end
985
986 #
987 # Task Handlers
988 #
989
990 def exec_config
991 @installer.exec_config
992 @config.save # must be final
993 end
994
995 def exec_setup
996 @installer.exec_setup
997 end
998
999 def exec_install
1000 @installer.exec_install
1001 end
1002
1003 def exec_test
1004 @installer.exec_test
1005 end
1006
1007 def exec_show
1008 @config.each do |i|
1009 printf "%-20s %s\n", i.name, i.value if i.value?
1010 end
1011 end
1012
1013 def exec_clean
1014 @installer.exec_clean
1015 end
1016
1017 def exec_distclean
1018 @installer.exec_distclean
1019 end
1020
1021 end # class ToplevelInstaller
1022
1023
1024 class ToplevelInstallerMulti < ToplevelInstaller
1025
1026 include FileOperations
1027
1028 def initialize(ardir_root, config)
1029 super
1030 @packages = directories_of("#{@ardir}/packages")
1031 raise 'no package exists' if @packages.empty?
1032 @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1033 end
1034
1035 def run_metaconfigs
1036 @config.load_script "#{@ardir}/metaconfig", self
1037 @packages.each do |name|
1038 @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
1039 end
1040 end
1041
1042 attr_reader :packages
1043
1044 def packages=(list)
1045 raise 'package list is empty' if list.empty?
1046 list.each do |name|
1047 raise "directory packages/#{name} does not exist"\
1048 unless File.dir?("#{@ardir}/packages/#{name}")
1049 end
1050 @packages = list
1051 end
1052
1053 def init_installers
1054 @installers = {}
1055 @packages.each do |pack|
1056 @installers[pack] = Installer.new(@config,
1057 "#{@ardir}/packages/#{pack}",
1058 "packages/#{pack}")
1059 end
1060 with = extract_selection(config('with'))
1061 without = extract_selection(config('without'))
1062 @selected = @installers.keys.select {|name|
1063 (with.empty? or with.include?(name)) \
1064 and not without.include?(name)
1065 }
1066 end
1067
1068 def extract_selection(list)
1069 a = list.split(/,/)
1070 a.each do |name|
1071 setup_rb_error "no such package: #{name}" unless @installers.key?(name)
1072 end
1073 a
1074 end
1075
1076 def print_usage(f)
1077 super
1078 f.puts 'Inluded packages:'
1079 f.puts ' ' + @packages.sort.join(' ')
1080 f.puts
1081 end
1082
1083 #
1084 # Task Handlers
1085 #
1086
1087 def exec_config
1088 run_hook 'pre-config'
1089 each_selected_installers {|inst| inst.exec_config }
1090 run_hook 'post-config'
1091 @config.save # must be final
1092 end
1093
1094 def exec_setup
1095 run_hook 'pre-setup'
1096 each_selected_installers {|inst| inst.exec_setup }
1097 run_hook 'post-setup'
1098 end
1099
1100 def exec_install
1101 run_hook 'pre-install'
1102 each_selected_installers {|inst| inst.exec_install }
1103 run_hook 'post-install'
1104 end
1105
1106 def exec_test
1107 run_hook 'pre-test'
1108 each_selected_installers {|inst| inst.exec_test }
1109 run_hook 'post-test'
1110 end
1111
1112 def exec_clean
1113 rm_f @config.savefile
1114 run_hook 'pre-clean'
1115 each_selected_installers {|inst| inst.exec_clean }
1116 run_hook 'post-clean'
1117 end
1118
1119 def exec_distclean
1120 rm_f @config.savefile
1121 run_hook 'pre-distclean'
1122 each_selected_installers {|inst| inst.exec_distclean }
1123 run_hook 'post-distclean'
1124 end
1125
1126 #
1127 # lib
1128 #
1129
1130 def each_selected_installers
1131 Dir.mkdir 'packages' unless File.dir?('packages')
1132 @selected.each do |pack|
1133 $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1134 Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1135 Dir.chdir "packages/#{pack}"
1136 yield @installers[pack]
1137 Dir.chdir '../..'
1138 end
1139 end
1140
1141 def run_hook(id)
1142 @root_installer.run_hook id
1143 end
1144
1145 # module FileOperations requires this
1146 def verbose?
1147 @config.verbose?
1148 end
1149
1150 # module FileOperations requires this
1151 def no_harm?
1152 @config.no_harm?
1153 end
1154
1155 end # class ToplevelInstallerMulti
1156
1157
1158 class Installer
1159
1160 FILETYPES = %w( bin lib ext data conf man )
1161
1162 include FileOperations
1163 include HookScriptAPI
1164
1165 def initialize(config, srcroot, objroot)
1166 @config = config
1167 @srcdir = File.expand_path(srcroot)
1168 @objdir = File.expand_path(objroot)
1169 @currdir = '.'
1170 end
1171
1172 def inspect
1173 "#<#{self.class} #{File.basename(@srcdir)}>"
1174 end
1175
1176 def noop(rel)
1177 end
1178
1179 #
1180 # Hook Script API base methods
1181 #
1182
1183 def srcdir_root
1184 @srcdir
1185 end
1186
1187 def objdir_root
1188 @objdir
1189 end
1190
1191 def relpath
1192 @currdir
1193 end
1194
1195 #
1196 # Config Access
1197 #
1198
1199 # module FileOperations requires this
1200 def verbose?
1201 @config.verbose?
1202 end
1203
1204 # module FileOperations requires this
1205 def no_harm?
1206 @config.no_harm?
1207 end
1208
1209 def verbose_off
1210 begin
1211 save, @config.verbose = @config.verbose?, false
1212 yield
1213 ensure
1214 @config.verbose = save
1215 end
1216 end
1217
1218 #
1219 # TASK config
1220 #
1221
1222 def exec_config
1223 exec_task_traverse 'config'
1224 end
1225
1226 alias config_dir_bin noop
1227 alias config_dir_lib noop
1228
1229 def config_dir_ext(rel)
1230 extconf if extdir?(curr_srcdir())
1231 end
1232
1233 alias config_dir_data noop
1234 alias config_dir_conf noop
1235 alias config_dir_man noop
1236
1237 def extconf
1238 ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1239 end
1240
1241 #
1242 # TASK setup
1243 #
1244
1245 def exec_setup
1246 exec_task_traverse 'setup'
1247 end
1248
1249 def setup_dir_bin(rel)
1250 files_of(curr_srcdir()).each do |fname|
1251 update_shebang_line "#{curr_srcdir()}/#{fname}"
1252 end
1253 end
1254
1255 alias setup_dir_lib noop
1256
1257 def setup_dir_ext(rel)
1258 make if extdir?(curr_srcdir())
1259 end
1260
1261 alias setup_dir_data noop
1262 alias setup_dir_conf noop
1263 alias setup_dir_man noop
1264
1265 def update_shebang_line(path)
1266 return if no_harm?
1267 return if config('shebang') == 'never'
1268 old = Shebang.load(path)
1269 if old
1270 $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
1271 new = new_shebang(old)
1272 return if new.to_s == old.to_s
1273 else
1274 return unless config('shebang') == 'all'
1275 new = Shebang.new(config('rubypath'))
1276 end
1277 $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1278 open_atomic_writer(path) {|output|
1279 File.open(path, 'rb') {|f|
1280 f.gets if old # discard
1281 output.puts new.to_s
1282 output.print f.read
1283 }
1284 }
1285 end
1286
1287 def new_shebang(old)
1288 if /\Aruby/ =~ File.basename(old.cmd)
1289 Shebang.new(config('rubypath'), old.args)
1290 elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1291 Shebang.new(config('rubypath'), old.args[1..-1])
1292 else
1293 return old unless config('shebang') == 'all'
1294 Shebang.new(config('rubypath'))
1295 end
1296 end
1297
1298 def open_atomic_writer(path, &block)
1299 tmpfile = File.basename(path) + '.tmp'
1300 begin
1301 File.open(tmpfile, 'wb', &block)
1302 File.rename tmpfile, File.basename(path)
1303 ensure
1304 File.unlink tmpfile if File.exist?(tmpfile)
1305 end
1306 end
1307
1308 class Shebang
1309 def Shebang.load(path)
1310 line = nil
1311 File.open(path) {|f|
1312 line = f.gets
1313 }
1314 return nil unless /\A#!/ =~ line
1315 parse(line)
1316 end
1317
1318 def Shebang.parse(line)
1319 cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1320 new(cmd, args)
1321 end
1322
1323 def initialize(cmd, args = [])
1324 @cmd = cmd
1325 @args = args
1326 end
1327
1328 attr_reader :cmd
1329 attr_reader :args
1330
1331 def to_s
1332 "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1333 end
1334 end
1335
1336 #
1337 # TASK install
1338 #
1339
1340 def exec_install
1341 rm_f 'InstalledFiles'
1342 exec_task_traverse 'install'
1343 end
1344
1345 def install_dir_bin(rel)
1346 install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1347 end
1348
1349 def install_dir_lib(rel)
1350 install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1351 end
1352
1353 def install_dir_ext(rel)
1354 return unless extdir?(curr_srcdir())
1355 install_files rubyextentions('.'),
1356 "#{config('sodir')}/#{File.dirname(rel)}",
1357 0555
1358 end
1359
1360 def install_dir_data(rel)
1361 install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1362 end
1363
1364 def install_dir_conf(rel)
1365 # FIXME: should not remove current config files
1366 # (rename previous file to .old/.org)
1367 install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1368 end
1369
1370 def install_dir_man(rel)
1371 install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1372 end
1373
1374 def install_files(list, dest, mode)
1375 mkdir_p dest, @config.install_prefix
1376 list.each do |fname|
1377 install fname, dest, mode, @config.install_prefix
1378 end
1379 end
1380
1381 def libfiles
1382 glob_reject(%w(*.y *.output), targetfiles())
1383 end
1384
1385 def rubyextentions(dir)
1386 ents = glob_select("*.#{@config.dllext}", targetfiles())
1387 if ents.empty?
1388 setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1389 end
1390 ents
1391 end
1392
1393 def targetfiles
1394 mapdir(existfiles() - hookfiles())
1395 end
1396
1397 def mapdir(ents)
1398 ents.map {|ent|
1399 if File.exist?(ent)
1400 then ent # objdir
1401 else "#{curr_srcdir()}/#{ent}" # srcdir
1402 end
1403 }
1404 end
1405
1406 # picked up many entries from cvs-1.11.1/src/ignore.c
1407 JUNK_FILES = %w(
1408 core RCSLOG tags TAGS .make.state
1409 .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1410 *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1411
1412 *.org *.in .*
1413 )
1414
1415 def existfiles
1416 glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1417 end
1418
1419 def hookfiles
1420 %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1421 %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1422 }.flatten
1423 end
1424
1425 def glob_select(pat, ents)
1426 re = globs2re([pat])
1427 ents.select {|ent| re =~ ent }
1428 end
1429
1430 def glob_reject(pats, ents)
1431 re = globs2re(pats)
1432 ents.reject {|ent| re =~ ent }
1433 end
1434
1435 GLOB2REGEX = {
1436 '.' => '\.',
1437 '$' => '\$',
1438 '#' => '\#',
1439 '*' => '.*'
1440 }
1441
1442 def globs2re(pats)
1443 /\A(?:#{
1444 pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1445 })\z/
1446 end
1447
1448 #
1449 # TASK test
1450 #
1451
1452 TESTDIR = 'test'
1453
1454 def exec_test
1455 unless File.directory?('test')
1456 $stderr.puts 'no test in this package' if verbose?
1457 return
1458 end
1459 $stderr.puts 'Running tests...' if verbose?
1460 begin
1461 require 'test/unit'
1462 rescue LoadError
1463 setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
1464 end
1465 runner = Test::Unit::AutoRunner.new(true)
1466 runner.to_run << TESTDIR
1467 runner.run
1468 end
1469
1470 #
1471 # TASK clean
1472 #
1473
1474 def exec_clean
1475 exec_task_traverse 'clean'
1476 rm_f @config.savefile
1477 rm_f 'InstalledFiles'
1478 end
1479
1480 alias clean_dir_bin noop
1481 alias clean_dir_lib noop
1482 alias clean_dir_data noop
1483 alias clean_dir_conf noop
1484 alias clean_dir_man noop
1485
1486 def clean_dir_ext(rel)
1487 return unless extdir?(curr_srcdir())
1488 make 'clean' if File.file?('Makefile')
1489 end
1490
1491 #
1492 # TASK distclean
1493 #
1494
1495 def exec_distclean
1496 exec_task_traverse 'distclean'
1497 rm_f @config.savefile
1498 rm_f 'InstalledFiles'
1499 end
1500
1501 alias distclean_dir_bin noop
1502 alias distclean_dir_lib noop
1503
1504 def distclean_dir_ext(rel)
1505 return unless extdir?(curr_srcdir())
1506 make 'distclean' if File.file?('Makefile')
1507 end
1508
1509 alias distclean_dir_data noop
1510 alias distclean_dir_conf noop
1511 alias distclean_dir_man noop
1512
1513 #
1514 # Traversing
1515 #
1516
1517 def exec_task_traverse(task)
1518 run_hook "pre-#{task}"
1519 FILETYPES.each do |type|
1520 if type == 'ext' and config('without-ext') == 'yes'
1521 $stderr.puts 'skipping ext/* by user option' if verbose?
1522 next
1523 end
1524 traverse task, type, "#{task}_dir_#{type}"
1525 end
1526 run_hook "post-#{task}"
1527 end
1528
1529 def traverse(task, rel, mid)
1530 dive_into(rel) {
1531 run_hook "pre-#{task}"
1532 __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1533 directories_of(curr_srcdir()).each do |d|
1534 traverse task, "#{rel}/#{d}", mid
1535 end
1536 run_hook "post-#{task}"
1537 }
1538 end
1539
1540 def dive_into(rel)
1541 return unless File.dir?("#{@srcdir}/#{rel}")
1542
1543 dir = File.basename(rel)
1544 Dir.mkdir dir unless File.dir?(dir)
1545 prevdir = Dir.pwd
1546 Dir.chdir dir
1547 $stderr.puts '---> ' + rel if verbose?
1548 @currdir = rel
1549 yield
1550 Dir.chdir prevdir
1551 $stderr.puts '<--- ' + rel if verbose?
1552 @currdir = File.dirname(rel)
1553 end
1554
1555 def run_hook(id)
1556 path = [ "#{curr_srcdir()}/#{id}",
1557 "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1558 return unless path
1559 begin
1560 instance_eval File.read(path), path, 1
1561 rescue
1562 raise if $DEBUG
1563 setup_rb_error "hook #{path} failed:\n" + $!.message
1564 end
1565 end
1566
1567 end # class Installer
1568
1569
1570 class SetupError < StandardError; end
1571
1572 def setup_rb_error(msg)
1573 raise SetupError, msg
1574 end
1575
1576 if $0 == __FILE__
1577 begin
1578 ToplevelInstaller.invoke
1579 rescue SetupError
1580 raise if $DEBUG
1581 $stderr.puts $!.message
1582 $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1583 exit 1
1584 end
1585 end
@@ -0,0 +1,204
1 #!/usr/bin/env ruby
2
3 # test_binarytree.rb
4 #
5 # $Revision: 1.5 $ by $Author: anupamsg $
6 # $Name: $
7 #
8 # Copyright (c) 2006, 2007 Anupam Sengupta
9 #
10 # All rights reserved.
11 #
12 # Redistribution and use in source and binary forms, with or without modification,
13 # are permitted provided that the following conditions are met:
14 #
15 # - Redistributions of source code must retain the above copyright notice, this
16 # list of conditions and the following disclaimer.
17 #
18 # - Redistributions in binary form must reproduce the above copyright notice, this
19 # list of conditions and the following disclaimer in the documentation and/or
20 # other materials provided with the distribution.
21 #
22 # - Neither the name of the organization nor the names of its contributors may
23 # be used to endorse or promote products derived from this software without
24 # specific prior written permission.
25 #
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
33 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 #
37
38 require 'test/unit'
39 require 'tree/binarytree'
40
41 module TestTree
42 # Test class for the Tree node.
43 class TestBinaryTreeNode < Test::Unit::TestCase
44
45 def setup
46 @root = Tree::BinaryTreeNode.new("ROOT", "Root Node")
47
48 @left_child1 = Tree::BinaryTreeNode.new("A Child at Left", "Child Node @ left")
49 @right_child1 = Tree::BinaryTreeNode.new("B Child at Right", "Child Node @ right")
50
51 end
52
53 def teardown
54 @root.remove!(@left_child1)
55 @root.remove!(@right_child1)
56 @root = nil
57 end
58
59 def test_initialize
60 assert_not_nil(@root, "Binary tree's Root should have been created")
61 end
62
63 def test_add
64 @root.add @left_child1
65 assert_same(@left_child1, @root.leftChild, "The left node should be left_child1")
66 assert_same(@left_child1, @root.firstChild, "The first node should be left_child1")
67
68 @root.add @right_child1
69 assert_same(@right_child1, @root.rightChild, "The right node should be right_child1")
70 assert_same(@right_child1, @root.lastChild, "The first node should be right_child1")
71
72 assert_raise RuntimeError do
73 @root.add Tree::BinaryTreeNode.new("The third child!")
74 end
75
76 assert_raise RuntimeError do
77 @root << Tree::BinaryTreeNode.new("The third child!")
78 end
79 end
80
81 def test_leftChild
82 @root << @left_child1
83 @root << @right_child1
84 assert_same(@left_child1, @root.leftChild, "The left child should be 'left_child1")
85 assert_not_same(@right_child1, @root.leftChild, "The right_child1 is not the left child")
86 end
87
88 def test_rightChild
89 @root << @left_child1
90 @root << @right_child1
91 assert_same(@right_child1, @root.rightChild, "The right child should be 'right_child1")
92 assert_not_same(@left_child1, @root.rightChild, "The left_child1 is not the left child")
93 end
94
95 def test_leftChild_equals
96 @root << @left_child1
97 @root << @right_child1
98 assert_same(@left_child1, @root.leftChild, "The left child should be 'left_child1")
99
100 @root.leftChild = Tree::BinaryTreeNode.new("New Left Child")
101 assert_equal("New Left Child", @root.leftChild.name, "The left child should now be the new child")
102 assert_equal("B Child at Right", @root.lastChild.name, "The last child should now be the right child")
103
104 # Now set the left child as nil, and retest
105 @root.leftChild = nil
106 assert_nil(@root.leftChild, "The left child should now be nil")
107 assert_nil(@root.firstChild, "The first child is now nil")
108 assert_equal("B Child at Right", @root.lastChild.name, "The last child should now be the right child")
109 end
110
111 def test_rightChild_equals
112 @root << @left_child1
113 @root << @right_child1
114 assert_same(@right_child1, @root.rightChild, "The right child should be 'right_child1")
115
116 @root.rightChild = Tree::BinaryTreeNode.new("New Right Child")
117 assert_equal("New Right Child", @root.rightChild.name, "The right child should now be the new child")
118 assert_equal("A Child at Left", @root.firstChild.name, "The first child should now be the left child")
119 assert_equal("New Right Child", @root.lastChild.name, "The last child should now be the right child")
120
121 # Now set the right child as nil, and retest
122 @root.rightChild = nil
123 assert_nil(@root.rightChild, "The right child should now be nil")
124 assert_equal("A Child at Left", @root.firstChild.name, "The first child should now be the left child")
125 assert_nil(@root.lastChild, "The first child is now nil")
126 end
127
128 def test_isLeftChild_eh
129 @root << @left_child1
130 @root << @right_child1
131
132 assert(@left_child1.isLeftChild?, "left_child1 should be the left child")
133 assert(!@right_child1.isLeftChild?, "left_child1 should be the left child")
134
135 # Now set the right child as nil, and retest
136 @root.rightChild = nil
137 assert(@left_child1.isLeftChild?, "left_child1 should be the left child")
138
139 assert(!@root.isLeftChild?, "Root is neither left child nor right")
140 end
141
142 def test_isRightChild_eh
143 @root << @left_child1
144 @root << @right_child1
145
146 assert(@right_child1.isRightChild?, "right_child1 should be the right child")
147 assert(!@left_child1.isRightChild?, "right_child1 should be the right child")
148
149 # Now set the left child as nil, and retest
150 @root.leftChild = nil
151 assert(@right_child1.isRightChild?, "right_child1 should be the right child")
152 assert(!@root.isRightChild?, "Root is neither left child nor right")
153 end
154
155 def test_swap_children
156 @root << @left_child1
157 @root << @right_child1
158
159 assert(@right_child1.isRightChild?, "right_child1 should be the right child")
160 assert(!@left_child1.isRightChild?, "right_child1 should be the right child")
161
162 @root.swap_children
163
164 assert(@right_child1.isLeftChild?, "right_child1 should now be the left child")
165 assert(@left_child1.isRightChild?, "left_child1 should now be the right child")
166 assert_equal(@right_child1, @root.firstChild, "right_child1 should now be the first child")
167 assert_equal(@left_child1, @root.lastChild, "left_child1 should now be the last child")
168 assert_equal(@right_child1, @root[0], "right_child1 should now be the first child")
169 assert_equal(@left_child1, @root[1], "left_child1 should now be the last child")
170 end
171 end
172 end
173
174 # $Log: test_binarytree.rb,v $
175 # Revision 1.5 2007/12/22 00:28:59 anupamsg
176 # Added more test cases, and enabled ZenTest compatibility.
177 #
178 # Revision 1.4 2007/12/18 23:11:29 anupamsg
179 # Minor documentation changes in the binarytree class.
180 #
181 # Revision 1.3 2007/10/02 03:07:30 anupamsg
182 # * Rakefile: Added an optional task for rcov code coverage.
183 #
184 # * test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
185 #
186 # * test/test_tree.rb: Removed dependency on the redundant "Person" class.
187 #
188 # Revision 1.2 2007/08/30 22:06:13 anupamsg
189 # Added a new swap_children method for the Binary Tree class.
190 # Also made minor documentation updates and test additions.
191 #
192 # Revision 1.1 2007/07/21 04:52:37 anupamsg
193 # Renamed the test files.
194 #
195 # Revision 1.4 2007/07/19 02:03:57 anupamsg
196 # Minor syntax correction.
197 #
198 # Revision 1.3 2007/07/19 02:02:12 anupamsg
199 # Removed useless files (including rdoc, which should be generated for each release.
200 #
201 # Revision 1.2 2007/07/18 20:15:06 anupamsg
202 # Added two predicate methods in BinaryTreeNode to determine whether a node
203 # is a left or a right node.
204 #
This diff has been collapsed as it changes many lines, (718 lines changed) Show them Hide them
@@ -0,0 +1,718
1 #!/usr/bin/env ruby
2
3 # testtree.rb
4 #
5 # $Revision: 1.6 $ by $Author: anupamsg $
6 # $Name: $
7 #
8 # Copyright (c) 2006, 2007 Anupam Sengupta
9 #
10 # All rights reserved.
11 #
12 # Redistribution and use in source and binary forms, with or without modification,
13 # are permitted provided that the following conditions are met:
14 #
15 # - Redistributions of source code must retain the above copyright notice, this
16 # list of conditions and the following disclaimer.
17 #
18 # - Redistributions in binary form must reproduce the above copyright notice, this
19 # list of conditions and the following disclaimer in the documentation and/or
20 # other materials provided with the distribution.
21 #
22 # - Neither the name of the organization nor the names of its contributors may
23 # be used to endorse or promote products derived from this software without
24 # specific prior written permission.
25 #
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
30 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
33 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 #
37
38 require 'test/unit'
39 require 'tree'
40
41 module TestTree
42 # Test class for the Tree node.
43 class TestTreeNode < Test::Unit::TestCase
44
45 Person = Struct::new(:First, :last)
46
47 def setup
48 @root = Tree::TreeNode.new("ROOT", "Root Node")
49
50 @child1 = Tree::TreeNode.new("Child1", "Child Node 1")
51 @child2 = Tree::TreeNode.new("Child2", "Child Node 2")
52 @child3 = Tree::TreeNode.new("Child3", "Child Node 3")
53 @child4 = Tree::TreeNode.new("Child31", "Grand Child 1")
54
55 end
56
57 # Create this structure for the tests
58 #
59 # +----------+
60 # | ROOT |
61 # +-+--------+
62 # |
63 # | +---------------+
64 # +----+ CHILD1 |
65 # | +---------------+
66 # |
67 # | +---------------+
68 # +----+ CHILD2 |
69 # | +---------------+
70 # |
71 # | +---------------+ +------------------+
72 # +----+ CHILD3 +---+ CHILD4 |
73 # +---------------+ +------------------+
74 #
75 def loadChildren
76 @root << @child1
77 @root << @child2
78 @root << @child3 << @child4
79 end
80
81 def teardown
82 @root = nil
83 end
84
85 def test_root_setup
86 assert_not_nil(@root, "Root cannot be nil")
87 assert_nil(@root.parent, "Parent of root node should be nil")
88 assert_not_nil(@root.name, "Name should not be nil")
89 assert_equal("ROOT", @root.name, "Name should be 'ROOT'")
90 assert_equal("Root Node", @root.content, "Content should be 'Root Node'")
91 assert(@root.isRoot?, "Should identify as root")
92 assert(!@root.hasChildren?, "Cannot have any children")
93 assert(@root.hasContent?, "This root should have content")
94 assert_equal(1, @root.size, "Number of nodes should be one")
95 assert_nil(@root.siblings, "Root cannot have any children")
96
97 assert_raise(RuntimeError) { Tree::TreeNode.new(nil) }
98 end
99
100 def test_root
101 loadChildren
102
103 assert_same(@root, @root.root, "Root's root is self")
104 assert_same(@root, @child1.root, "Root should be ROOT")
105 assert_same(@root, @child4.root, "Root should be ROOT")
106 end
107
108 def test_hasContent_eh
109 aNode = Tree::TreeNode.new("A Node")
110 assert_nil(aNode.content, "The node should not have content")
111 assert(!aNode.hasContent?, "The node should not have content")
112
113 aNode.content = "Something"
114 assert_not_nil(aNode.content, "The node should now have content")
115 assert(aNode.hasContent?, "The node should now have content")
116 end
117
118 def test_length
119 loadChildren
120 assert_equal(@root.size, @root.length, "Length and size methods should return the same result")
121 end
122
123 def test_spaceship # Test the <=> operator.
124 firstNode = Tree::TreeNode.new(1)
125 secondNode = Tree::TreeNode.new(2)
126
127 assert_equal(firstNode <=> nil, +1)
128 assert_equal(firstNode <=> secondNode, -1)
129
130 secondNode = Tree::TreeNode.new(1)
131 assert_equal(firstNode <=> secondNode, 0)
132
133 firstNode = Tree::TreeNode.new("ABC")
134 secondNode = Tree::TreeNode.new("XYZ")
135
136 assert_equal(firstNode <=> nil, +1)
137 assert_equal(firstNode <=> secondNode, -1)
138
139 secondNode = Tree::TreeNode.new("ABC")
140 assert_equal(firstNode <=> secondNode, 0)
141 end
142
143 def test_to_s
144 aNode = Tree::TreeNode.new("A Node", "Some Content")
145
146 expectedString = "Node Name: A Node Content: Some Content Parent: <None> Children: 0 Total Nodes: 1"
147
148 assert_equal(expectedString, aNode.to_s, "The string representation should be same")
149 end
150
151 def test_firstSibling
152 loadChildren
153
154 assert_same(@root, @root.firstSibling, "Root's first sibling is itself")
155 assert_same(@child1, @child1.firstSibling, "Child1's first sibling is itself")
156 assert_same(@child1, @child2.firstSibling, "Child2's first sibling should be child1")
157 assert_same(@child1, @child3.firstSibling, "Child3's first sibling should be child1")
158 assert_not_same(@child1, @child4.firstSibling, "Child4's first sibling is itself")
159 end
160
161 def test_isFirstSibling_eh
162 loadChildren
163
164 assert(@root.isFirstSibling?, "Root's first sibling is itself")
165 assert( @child1.isFirstSibling?, "Child1's first sibling is itself")
166 assert(!@child2.isFirstSibling?, "Child2 is not the first sibling")
167 assert(!@child3.isFirstSibling?, "Child3 is not the first sibling")
168 assert( @child4.isFirstSibling?, "Child4's first sibling is itself")
169 end
170
171 def test_isLastSibling_eh
172 loadChildren
173
174 assert(@root.isLastSibling?, "Root's last sibling is itself")
175 assert(!@child1.isLastSibling?, "Child1 is not the last sibling")
176 assert(!@child2.isLastSibling?, "Child2 is not the last sibling")
177 assert( @child3.isLastSibling?, "Child3's last sibling is itself")
178 assert( @child4.isLastSibling?, "Child4's last sibling is itself")
179 end
180
181 def test_lastSibling
182 loadChildren
183
184 assert_same(@root, @root.lastSibling, "Root's last sibling is itself")
185 assert_same(@child3, @child1.lastSibling, "Child1's last sibling should be child3")
186 assert_same(@child3, @child2.lastSibling, "Child2's last sibling should be child3")
187 assert_same(@child3, @child3.lastSibling, "Child3's last sibling should be itself")
188 assert_not_same(@child3, @child4.lastSibling, "Child4's last sibling is itself")
189 end
190
191 def test_siblings
192 loadChildren
193
194 siblings = []
195 @child1.siblings { |sibling| siblings << sibling}
196 assert_equal(2, siblings.length, "Should have two siblings")
197 assert(siblings.include?(@child2), "Should have 2nd child as sibling")
198 assert(siblings.include?(@child3), "Should have 3rd child as sibling")
199
200 siblings.clear
201 siblings = @child1.siblings
202 assert_equal(2, siblings.length, "Should have two siblings")
203
204 siblings.clear
205 @child4.siblings {|sibling| siblings << sibling}
206 assert(siblings.empty?, "Should not have any children")
207
208 end
209
210 def test_isOnlyChild_eh
211 loadChildren
212
213 assert(!@child1.isOnlyChild?, "Child1 is not the only child")
214 assert(!@child2.isOnlyChild?, "Child2 is not the only child")
215 assert(!@child3.isOnlyChild?, "Child3 is not the only child")
216 assert( @child4.isOnlyChild?, "Child4 is not the only child")
217 end
218
219 def test_nextSibling
220 loadChildren
221
222 assert_equal(@child2, @child1.nextSibling, "Child1's next sibling is Child2")
223 assert_equal(@child3, @child2.nextSibling, "Child2's next sibling is Child3")
224 assert_nil(@child3.nextSibling, "Child3 does not have a next sibling")
225 assert_nil(@child4.nextSibling, "Child4 does not have a next sibling")
226 end
227
228 def test_previousSibling
229 loadChildren
230
231 assert_nil(@child1.previousSibling, "Child1 does not have previous sibling")
232 assert_equal(@child1, @child2.previousSibling, "Child2's previous sibling is Child1")
233 assert_equal(@child2, @child3.previousSibling, "Child3's previous sibling is Child2")
234 assert_nil(@child4.previousSibling, "Child4 does not have a previous sibling")
235 end
236
237 def test_add
238 assert(!@root.hasChildren?, "Should not have any children")
239
240 @root.add(@child1)
241
242 @root << @child2
243
244 assert(@root.hasChildren?, "Should have children")
245 assert_equal(3, @root.size, "Should have three nodes")
246
247 @root << @child3 << @child4
248
249 assert_equal(5, @root.size, "Should have five nodes")
250 assert_equal(2, @child3.size, "Should have two nodes")
251
252 assert_raise(RuntimeError) { @root.add(Tree::TreeNode.new(@child1.name)) }
253
254 end
255
256 def test_remove_bang
257 @root << @child1
258 @root << @child2
259
260 assert(@root.hasChildren?, "Should have children")
261 assert_equal(3, @root.size, "Should have three nodes")
262
263 @root.remove!(@child1)
264 assert_equal(2, @root.size, "Should have two nodes")
265 @root.remove!(@child2)
266
267 assert(!@root.hasChildren?, "Should have no children")
268 assert_equal(1, @root.size, "Should have one node")
269
270 @root << @child1
271 @root << @child2
272
273 assert(@root.hasChildren?, "Should have children")
274 assert_equal(3, @root.size, "Should have three nodes")
275
276 @root.removeAll!
277
278 assert(!@root.hasChildren?, "Should have no children")
279 assert_equal(1, @root.size, "Should have one node")
280
281 end
282
283 def test_removeAll_bang
284 loadChildren
285 assert(@root.hasChildren?, "Should have children")
286 @root.removeAll!
287
288 assert(!@root.hasChildren?, "Should have no children")
289 assert_equal(1, @root.size, "Should have one node")
290 end
291
292 def test_removeFromParent_bang
293 loadChildren
294 assert(@root.hasChildren?, "Should have children")
295 assert(!@root.isLeaf?, "Root is not a leaf here")
296
297 child1 = @root[0]
298 assert_not_nil(child1, "Child 1 should exist")
299 assert_same(@root, child1.root, "Child 1's root should be ROOT")
300 assert(@root.include?(child1), "root should have child1")
301 child1.removeFromParent!
302 assert_same(child1, child1.root, "Child 1's root should be self")
303 assert(!@root.include?(child1), "root should not have child1")
304
305 child1.removeFromParent!
306 assert_same(child1, child1.root, "Child 1's root should still be self")
307 end
308
309 def test_children
310 loadChildren
311
312 assert(@root.hasChildren?, "Should have children")
313 assert_equal(5, @root.size, "Should have four nodes")
314 assert(@child3.hasChildren?, "Should have children")
315 assert(!@child3.isLeaf?, "Should not be a leaf")
316
317 children = []
318 for child in @root.children
319 children << child
320 end
321
322 assert_equal(3, children.length, "Should have three direct children")
323 assert(!children.include?(@root), "Should not have root")
324 assert(children.include?(@child1), "Should have child 1")
325 assert(children.include?(@child2), "Should have child 2")
326 assert(children.include?(@child3), "Should have child 3")
327 assert(!children.include?(@child4), "Should not have child 4")
328
329 children.clear
330 children = @root.children
331 assert_equal(3, children.length, "Should have three children")
332
333 end
334
335 def test_firstChild
336 loadChildren
337
338 assert_equal(@child1, @root.firstChild, "Root's first child is Child1")
339 assert_nil(@child1.firstChild, "Child1 does not have any children")
340 assert_equal(@child4, @child3.firstChild, "Child3's first child is Child4")
341
342 end
343
344 def test_lastChild
345 loadChildren
346
347 assert_equal(@child3, @root.lastChild, "Root's last child is Child3")
348 assert_nil(@child1.lastChild, "Child1 does not have any children")
349 assert_equal(@child4, @child3.lastChild, "Child3's last child is Child4")
350
351 end
352
353 def test_find
354 loadChildren
355 foundNode = @root.find { |node| node == @child2}
356 assert_same(@child2, foundNode, "The node should be Child 2")
357
358 foundNode = @root.find { |node| node == @child4}
359 assert_same(@child4, foundNode, "The node should be Child 4")
360
361 foundNode = @root.find { |node| node.name == "Child31" }
362 assert_same(@child4, foundNode, "The node should be Child 4")
363 foundNode = @root.find { |node| node.name == "NOT PRESENT" }
364 assert_nil(foundNode, "The node should not be found")
365 end
366
367 def test_parentage
368 loadChildren
369
370 assert_nil(@root.parentage, "Root does not have any parentage")
371 assert_equal([@root], @child1.parentage, "Child1 has Root as its parent")
372 assert_equal([@child3, @root], @child4.parentage, "Child4 has Child3 and Root as ancestors")
373 end
374
375 def test_each
376 loadChildren
377 assert(@root.hasChildren?, "Should have children")
378 assert_equal(5, @root.size, "Should have five nodes")
379 assert(@child3.hasChildren?, "Should have children")
380
381 nodes = []
382 @root.each { |node| nodes << node }
383
384 assert_equal(5, nodes.length, "Should have FIVE NODES")
385 assert(nodes.include?(@root), "Should have root")
386 assert(nodes.include?(@child1), "Should have child 1")
387 assert(nodes.include?(@child2), "Should have child 2")
388 assert(nodes.include?(@child3), "Should have child 3")
389 assert(nodes.include?(@child4), "Should have child 4")
390 end
391
392 def test_each_leaf
393 loadChildren
394
395 nodes = []
396 @root.each_leaf { |node| nodes << node }
397
398 assert_equal(3, nodes.length, "Should have THREE LEAF NODES")
399 assert(!nodes.include?(@root), "Should not have root")
400 assert(nodes.include?(@child1), "Should have child 1")
401 assert(nodes.include?(@child2), "Should have child 2")
402 assert(!nodes.include?(@child3), "Should not have child 3")
403 assert(nodes.include?(@child4), "Should have child 4")
404 end
405
406 def test_parent
407 loadChildren
408 assert_nil(@root.parent, "Root's parent should be nil")
409 assert_equal(@root, @child1.parent, "Parent should be root")
410 assert_equal(@root, @child3.parent, "Parent should be root")
411 assert_equal(@child3, @child4.parent, "Parent should be child3")
412 assert_equal(@root, @child4.parent.parent, "Parent should be root")
413 end
414
415 def test_indexed_access
416 loadChildren
417 assert_equal(@child1, @root[0], "Should be the first child")
418 assert_equal(@child4, @root[2][0], "Should be the grandchild")
419 assert_nil(@root["TEST"], "Should be nil")
420 assert_raise(RuntimeError) { @root[nil] }
421 end
422
423 def test_printTree
424 loadChildren
425 #puts
426 #@root.printTree
427 end
428
429 # Tests the binary dumping mechanism with an Object content node
430 def test_marshal_dump
431 # Setup Test Data
432 test_root = Tree::TreeNode.new("ROOT", "Root Node")
433 test_content = {"KEY1" => "Value1", "KEY2" => "Value2" }
434 test_child = Tree::TreeNode.new("Child", test_content)
435 test_content2 = ["AValue1", "AValue2", "AValue3"]
436 test_grand_child = Tree::TreeNode.new("Grand Child 1", test_content2)
437 test_root << test_child << test_grand_child
438
439 # Perform the test operation
440 data = Marshal.dump(test_root) # Marshal
441 new_root = Marshal.load(data) # And unmarshal
442
443 # Test the root node
444 assert_equal(test_root.name, new_root.name, "Must identify as ROOT")
445 assert_equal(test_root.content, new_root.content, "Must have root's content")
446 assert(new_root.isRoot?, "Must be the ROOT node")
447 assert(new_root.hasChildren?, "Must have a child node")
448
449 # Test the child node
450 new_child = new_root[test_child.name]
451 assert_equal(test_child.name, new_child.name, "Must have child 1")
452 assert(new_child.hasContent?, "Child must have content")
453 assert(new_child.isOnlyChild?, "Child must be the only child")
454
455 new_child_content = new_child.content
456 assert_equal(Hash, new_child_content.class, "Class of child's content should be a hash")
457 assert_equal(test_child.content.size, new_child_content.size, "The content should have same size")
458
459 # Test the grand-child node
460 new_grand_child = new_child[test_grand_child.name]
461 assert_equal(test_grand_child.name, new_grand_child.name, "Must have grand child")
462 assert(new_grand_child.hasContent?, "Grand-child must have content")
463 assert(new_grand_child.isOnlyChild?, "Grand-child must be the only child")
464
465 new_grand_child_content = new_grand_child.content
466 assert_equal(Array, new_grand_child_content.class, "Class of grand-child's content should be an Array")
467 assert_equal(test_grand_child.content.size, new_grand_child_content.size, "The content should have same size")
468 end
469
470 # marshal_load and marshal_dump are symmetric methods
471 # This alias is for satisfying ZenTest
472 alias test_marshal_load test_marshal_dump
473
474 # Test the collect method from the mixed-in Enumerable functionality.
475 def test_collect
476 loadChildren
477 collectArray = @root.collect do |node|
478 node.content = "abc"
479 node
480 end
481 collectArray.each {|node| assert_equal("abc", node.content, "Should be 'abc'")}
482 end
483
484 # Test freezing the tree
485 def test_freezeTree_bang
486 loadChildren
487 @root.content = "ABC"
488 assert_equal("ABC", @root.content, "Content should be 'ABC'")
489 @root.freezeTree!
490 assert_raise(TypeError) {@root.content = "123"}
491 assert_raise(TypeError) {@root[0].content = "123"}
492 end
493
494 # Test whether the content is accesible
495 def test_content
496 pers = Person::new("John", "Doe")
497 @root.content = pers
498 assert_same(pers, @root.content, "Content should be the same")
499 end
500
501 # Test the depth computation algorithm
502 def test_depth
503 assert_equal(1, @root.depth, "A single node's depth is 1")
504
505 @root << @child1
506 assert_equal(2, @root.depth, "This should be of depth 2")
507
508 @root << @child2
509 assert_equal(2, @root.depth, "This should be of depth 2")
510
511 @child2 << @child3
512 assert_equal(3, @root.depth, "This should be of depth 3")
513 assert_equal(2, @child2.depth, "This should be of depth 2")
514
515 @child3 << @child4
516 assert_equal(4, @root.depth, "This should be of depth 4")
517 end
518
519 # Test the breadth computation algorithm
520 def test_breadth
521 assert_equal(1, @root.breadth, "A single node's breadth is 1")
522
523 @root << @child1
524 assert_equal(1, @root.breadth, "This should be of breadth 1")
525
526 @root << @child2
527 assert_equal(2, @child1.breadth, "This should be of breadth 2")
528 assert_equal(2, @child2.breadth, "This should be of breadth 2")
529
530 @root << @child3
531 assert_equal(3, @child1.breadth, "This should be of breadth 3")
532 assert_equal(3, @child2.breadth, "This should be of breadth 3")
533
534 @child3 << @child4
535 assert_equal(1, @child4.breadth, "This should be of breadth 1")
536 end
537
538 # Test the breadth for each
539 def test_breadth_each
540 j = Tree::TreeNode.new("j")
541 f = Tree::TreeNode.new("f")
542 k = Tree::TreeNode.new("k")
543 a = Tree::TreeNode.new("a")
544 d = Tree::TreeNode.new("d")
545 h = Tree::TreeNode.new("h")
546 z = Tree::TreeNode.new("z")
547
548 # The expected order of response
549 expected_array = [j,
550 f, k,
551 a, h, z,
552 d]
553
554 # Create the following Tree
555 # j <-- level 0 (Root)
556 # / \
557 # f k <-- level 1
558 # / \ \
559 # a h z <-- level 2
560 # \
561 # d <-- level 3
562 j << f << a << d
563 f << h
564 j << k << z
565
566 # Create the response
567 result_array = Array.new
568 j.breadth_each { |node| result_array << node.detached_copy }
569
570 expected_array.each_index do |i|
571 assert_equal(expected_array[i].name, result_array[i].name) # Match only the names.
572 end
573 end
574
575
576 def test_preordered_each
577 j = Tree::TreeNode.new("j")
578 f = Tree::TreeNode.new("f")
579 k = Tree::TreeNode.new("k")
580 a = Tree::TreeNode.new("a")
581 d = Tree::TreeNode.new("d")
582 h = Tree::TreeNode.new("h")
583 z = Tree::TreeNode.new("z")
584
585 # The expected order of response
586 expected_array = [j, f, a, d, h, k, z]
587
588 # Create the following Tree
589 # j <-- level 0 (Root)
590 # / \
591 # f k <-- level 1
592 # / \ \
593 # a h z <-- level 2
594 # \
595 # d <-- level 3
596 j << f << a << d
597 f << h
598 j << k << z
599
600 result_array = []
601 j.preordered_each { |node| result_array << node.detached_copy}
602
603 expected_array.each_index do |i|
604 # Match only the names.
605 assert_equal(expected_array[i].name, result_array[i].name)
606 end
607 end
608
609 def test_detached_copy
610 loadChildren
611
612 assert(@root.hasChildren?, "The root should have children")
613 copy_of_root = @root.detached_copy
614 assert(!copy_of_root.hasChildren?, "The copy should not have children")
615 assert_equal(@root.name, copy_of_root.name, "The names should be equal")
616
617 # Try the same test with a child node
618 assert(!@child3.isRoot?, "Child 3 is not a root")
619 assert(@child3.hasChildren?, "Child 3 has children")
620 copy_of_child3 = @child3.detached_copy
621 assert(copy_of_child3.isRoot?, "Child 3's copy is a root")
622 assert(!copy_of_child3.hasChildren?, "Child 3's copy does not have children")
623 end
624
625 def test_hasChildren_eh
626 loadChildren
627 assert(@root.hasChildren?, "The Root node MUST have children")
628 end
629
630 def test_isLeaf_eh
631 loadChildren
632 assert(!@child3.isLeaf?, "Child 3 is not a leaf node")
633 assert(@child4.isLeaf?, "Child 4 is a leaf node")
634 end
635
636 def test_isRoot_eh
637 loadChildren
638 assert(@root.isRoot?, "The ROOT node must respond as the root node")
639 end
640
641 def test_content_equals
642 @root.content = nil
643 assert_nil(@root.content, "Root's content should be nil")
644 @root.content = "ABCD"
645 assert_equal("ABCD", @root.content, "Root's content should now be 'ABCD'")
646 end
647
648 def test_size
649 assert_equal(1, @root.size, "Root's size should be 1")
650 loadChildren
651 assert_equal(5, @root.size, "Root's size should be 5")
652 assert_equal(2, @child3.size, "Child 3's size should be 2")
653 end
654
655 def test_lt2 # Test the << method
656 @root << @child1
657 @root << @child2
658 @root << @child3 << @child4
659 assert_not_nil(@root['Child1'], "Child 1 should have been added to Root")
660 assert_not_nil(@root['Child2'], "Child 2 should have been added to Root")
661 assert_not_nil(@root['Child3'], "Child 3 should have been added to Root")
662 assert_not_nil(@child3['Child31'], "Child 31 should have been added to Child3")
663 end
664
665 def test_index # Test the [] method
666 assert_raise(RuntimeError) {@root[nil]}
667
668 @root << @child1
669 @root << @child2
670 assert_equal(@child1.name, @root['Child1'].name, "Child 1 should be returned")
671 assert_equal(@child1.name, @root[0].name, "Child 1 should be returned")
672 assert_equal(@child2.name, @root['Child2'].name, "Child 2 should be returned")
673 assert_equal(@child2.name, @root[1].name, "Child 2 should be returned")
674
675 assert_nil(@root['Some Random Name'], "Should return nil")
676 assert_nil(@root[99], "Should return nil")
677 end
678 end
679 end
680
681 __END__
682
683 # $Log: test_tree.rb,v $
684 # Revision 1.6 2007/12/22 00:28:59 anupamsg
685 # Added more test cases, and enabled ZenTest compatibility.
686 #
687 # Revision 1.5 2007/12/19 02:24:18 anupamsg
688 # Updated the marshalling logic to handle non-string contents on the nodes.
689 #
690 # Revision 1.4 2007/10/02 03:38:11 anupamsg
691 # Removed dependency on the redundant "Person" class.
692 # (TC_TreeTest::test_comparator): Added a new test for the spaceship operator.
693 # (TC_TreeTest::test_hasContent): Added tests for hasContent? and length methods.
694 #
695 # Revision 1.3 2007/10/02 03:07:30 anupamsg
696 # * Rakefile: Added an optional task for rcov code coverage.
697 #
698 # * test/test_binarytree.rb: Removed the unnecessary dependency on "Person" class.
699 #
700 # * test/test_tree.rb: Removed dependency on the redundant "Person" class.
701 #
702 # Revision 1.2 2007/08/31 01:16:28 anupamsg
703 # Added breadth and pre-order traversals for the tree. Also added a method
704 # to return the detached copy of a node from the tree.
705 #
706 # Revision 1.1 2007/07/21 04:52:38 anupamsg
707 # Renamed the test files.
708 #
709 # Revision 1.13 2007/07/18 22:11:50 anupamsg
710 # Added depth and breadth methods for the TreeNode.
711 #
712 # Revision 1.12 2007/07/18 07:17:34 anupamsg
713 # Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method
714 # has been renamed to TreeNode.parentage.
715 #
716 # Revision 1.11 2007/07/17 03:39:29 anupamsg
717 # Moved the CVS Log keyword to end of the files.
718 #
@@ -50,6 +50,8 Rails::Initializer.run do |config|
50 # It will automatically turn deliveries on
50 # It will automatically turn deliveries on
51 config.action_mailer.perform_deliveries = false
51 config.action_mailer.perform_deliveries = false
52
52
53 config.gem 'rubytree', :lib => 'tree'
54
53 # Load any local configuration that is kept out of source control
55 # Load any local configuration that is kept out of source control
54 # (e.g. gems, patches).
56 # (e.g. gems, patches).
55 if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
57 if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
@@ -15,6 +15,84
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'tree' # gem install rubytree
19
20 # Monkey patch the TreeNode to add on a few more methods :nodoc:
21 module TreeNodePatch
22 def self.included(base)
23 base.class_eval do
24 attr_reader :last_items_count
25
26 alias :old_initilize :initialize
27 def initialize(name, content = nil)
28 old_initilize(name, content)
29 @last_items_count = 0
30 extend(InstanceMethods)
31 end
32 end
33 end
34
35 module InstanceMethods
36 # Adds the specified child node to the receiver node. The child node's
37 # parent is set to be the receiver. The child is added as the first child in
38 # the current list of children for the receiver node.
39 def prepend(child)
40 raise "Child already added" if @childrenHash.has_key?(child.name)
41
42 @childrenHash[child.name] = child
43 @children = [child] + @children
44 child.parent = self
45 return child
46
47 end
48
49 # Adds the specified child node to the receiver node. The child node's
50 # parent is set to be the receiver. The child is added at the position
51 # into the current list of children for the receiver node.
52 def add_at(child, position)
53 raise "Child already added" if @childrenHash.has_key?(child.name)
54
55 @childrenHash[child.name] = child
56 @children = @children.insert(position, child)
57 child.parent = self
58 return child
59
60 end
61
62 def add_last(child)
63 raise "Child already added" if @childrenHash.has_key?(child.name)
64
65 @childrenHash[child.name] = child
66 @children << child
67 @last_items_count += 1
68 child.parent = self
69 return child
70
71 end
72
73 # Adds the specified child node to the receiver node. The child node's
74 # parent is set to be the receiver. The child is added as the last child in
75 # the current list of children for the receiver node.
76 def add(child)
77 raise "Child already added" if @childrenHash.has_key?(child.name)
78
79 @childrenHash[child.name] = child
80 position = @children.size - @last_items_count
81 @children.insert(position, child)
82 child.parent = self
83 return child
84
85 end
86
87 # Will return the position (zero-based) of the current child in
88 # it's parent
89 def position
90 self.parent.children.index(self)
91 end
92 end
93 end
94 Tree::TreeNode.send(:include, TreeNodePatch)
95
18 module Redmine
96 module Redmine
19 module MenuManager
97 module MenuManager
20 module MenuController
98 module MenuController
@@ -79,35 +157,80 module Redmine
79
157
80 def render_menu(menu, project=nil)
158 def render_menu(menu, project=nil)
81 links = []
159 links = []
82 menu_items_for(menu, project) do |item, caption, url, selected|
160 menu_items_for(menu, project) do |node|
83 links << content_tag('li',
161 links << render_menu_node(node, project)
84 link_to(h(caption), url, item.html_options(:selected => selected)))
85 end
162 end
86 links.empty? ? nil : content_tag('ul', links.join("\n"))
163 links.empty? ? nil : content_tag('ul', links.join("\n"))
87 end
164 end
88
165
166 def render_menu_node(node, project=nil)
167 caption, url, selected = extract_node_details(node, project)
168 if node.hasChildren?
169 html = []
170 html << '<li>'
171 html << render_single_menu_node(node, caption, url, selected) # parent
172 html << ' <ul>'
173 node.children.each do |child|
174 html << render_menu_node(child, project)
175 end
176 html << ' </ul>'
177 html << '</li>'
178 return html.join("\n")
179 else
180 return content_tag('li',
181 render_single_menu_node(node, caption, url, selected))
182 end
183 end
184
185 def render_single_menu_node(item, caption, url, selected)
186 link_to(h(caption), url, item.html_options(:selected => selected))
187 end
188
89 def menu_items_for(menu, project=nil)
189 def menu_items_for(menu, project=nil)
90 items = []
190 items = []
91 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
191 Redmine::MenuManager.items(menu).root.children.each do |node|
92 unless item.condition && !item.condition.call(project)
192 if allowed_node?(node, User.current, project)
93 url = case item.url
94 when Hash
95 project.nil? ? item.url : {item.param => project}.merge(item.url)
96 when Symbol
97 send(item.url)
98 else
99 item.url
100 end
101 caption = item.caption(project)
102 if block_given?
193 if block_given?
103 yield item, caption, url, (current_menu_item == item.name)
194 yield node
104 else
195 else
105 items << [item, caption, url, (current_menu_item == item.name)]
196 items << node # TODO: not used?
106 end
197 end
107 end
198 end
108 end
199 end
109 return block_given? ? nil : items
200 return block_given? ? nil : items
110 end
201 end
202
203 def extract_node_details(node, project=nil)
204 item = node
205 url = case item.url
206 when Hash
207 project.nil? ? item.url : {item.param => project}.merge(item.url)
208 when Symbol
209 send(item.url)
210 else
211 item.url
212 end
213 caption = item.caption(project)
214 return [caption, url, (current_menu_item == item.name)]
215 end
216
217 # Checks if a user is allowed to access the menu item by:
218 #
219 # * Checking the conditions of the item
220 # * Checking the url target (project only)
221 def allowed_node?(node, user, project)
222 if node.condition && !node.condition.call(project)
223 # Condition that doesn't pass
224 return false
225 end
226
227 if project
228 return user && user.allowed_to?(node.url, project)
229 else
230 # outside a project, all menu items allowed
231 return true
232 end
233 end
111 end
234 end
112
235
113 class << self
236 class << self
@@ -122,17 +245,13 module Redmine
122 end
245 end
123
246
124 def items(menu_name)
247 def items(menu_name)
125 @items[menu_name.to_sym] || []
248 @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {})
126 end
127
128 def allowed_items(menu_name, user, project)
129 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
130 end
249 end
131 end
250 end
132
251
133 class Mapper
252 class Mapper
134 def initialize(menu, items)
253 def initialize(menu, items)
135 items[menu] ||= []
254 items[menu] ||= Tree::TreeNode.new(:root, {})
136 @menu = menu
255 @menu = menu
137 @menu_items = items[menu]
256 @menu_items = items[menu]
138 end
257 end
@@ -151,36 +270,78 module Redmine
151 # * html_options: a hash of html options that are passed to link_to
270 # * html_options: a hash of html options that are passed to link_to
152 def push(name, url, options={})
271 def push(name, url, options={})
153 options = options.dup
272 options = options.dup
154
273
274 if options[:parent_menu]
275 subtree = self.find(options[:parent_menu])
276 if subtree
277 target_root = subtree
278 else
279 target_root = @menu_items.root
280 end
281
282 else
283 target_root = @menu_items.root
284 end
285
155 # menu item position
286 # menu item position
156 if before = options.delete(:before)
287 if first = options.delete(:first)
157 position = @menu_items.collect(&:name).index(before)
288 target_root.prepend(MenuItem.new(name, url, options))
289 elsif before = options.delete(:before)
290
291 if exists?(before)
292 target_root.add_at(MenuItem.new(name, url, options), position_of(before))
293 else
294 target_root.add(MenuItem.new(name, url, options))
295 end
296
158 elsif after = options.delete(:after)
297 elsif after = options.delete(:after)
159 position = @menu_items.collect(&:name).index(after)
298
160 position += 1 unless position.nil?
299 if exists?(after)
300 target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
301 else
302 target_root.add(MenuItem.new(name, url, options))
303 end
304
161 elsif options.delete(:last)
305 elsif options.delete(:last)
162 position = @menu_items.size
306 target_root.add_last(MenuItem.new(name, url, options))
163 @@last_items_count[@menu] += 1
307 else
308 target_root.add(MenuItem.new(name, url, options))
164 end
309 end
165 # default position
166 position ||= @menu_items.size - @@last_items_count[@menu]
167
168 @menu_items.insert(position, MenuItem.new(name, url, options))
169 end
310 end
170
311
171 # Removes a menu item
312 # Removes a menu item
172 def delete(name)
313 def delete(name)
173 @menu_items.delete_if {|i| i.name == name}
314 if found = self.find(name)
315 @menu_items.remove!(found)
316 end
317 end
318
319 # Checks if a menu item exists
320 def exists?(name)
321 @menu_items.any? {|node| node.name == name}
322 end
323
324 def find(name)
325 @menu_items.find {|node| node.name == name}
326 end
327
328 def position_of(name)
329 @menu_items.each do |node|
330 if node.name == name
331 return node.position
332 end
333 end
174 end
334 end
175 end
335 end
176
336
177 class MenuItem
337 class MenuItem < Tree::TreeNode
178 include Redmine::I18n
338 include Redmine::I18n
179 attr_reader :name, :url, :param, :condition
339 attr_reader :name, :url, :param, :condition, :parent_menu
180
340
181 def initialize(name, url, options)
341 def initialize(name, url, options)
182 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
342 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
183 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
343 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
344 raise ArgumentError, "Cannot set the :parent_menu to be the same as this item" if options[:parent_menu] == name.to_sym
184 @name = name
345 @name = name
185 @url = url
346 @url = url
186 @condition = options[:if]
347 @condition = options[:if]
@@ -189,6 +350,8 module Redmine
189 @html_options = options[:html] || {}
350 @html_options = options[:html] || {}
190 # Adds a unique class to each menu item based on its name
351 # Adds a unique class to each menu item based on its name
191 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
352 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
353 @parent_menu = options[:parent_menu]
354 super @name.to_sym
192 end
355 end
193
356
194 def caption(project=nil)
357 def caption(project=nil)
General Comments 0
You need to be logged in to leave comments. Login now