@@ -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 | |
|
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. |
|
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 |
|
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