@@ -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 | 50 | # It will automatically turn deliveries on |
|
51 | 51 | config.action_mailer.perform_deliveries = false |
|
52 | 52 | |
|
53 | config.gem 'rubytree', :lib => 'tree' | |
|
54 | ||
|
53 | 55 | # Load any local configuration that is kept out of source control |
|
54 | 56 | # (e.g. gems, patches). |
|
55 | 57 | if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) |
@@ -15,6 +15,84 | |||
|
15 | 15 | # along with this program; if not, write to the Free Software |
|
16 | 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 | 96 | module Redmine |
|
19 | 97 | module MenuManager |
|
20 | 98 | module MenuController |
@@ -79,35 +157,80 module Redmine | |||
|
79 | 157 | |
|
80 | 158 | def render_menu(menu, project=nil) |
|
81 | 159 | links = [] |
|
82 |
menu_items_for(menu, project) do | |
|
|
83 | links << content_tag('li', | |
|
84 | link_to(h(caption), url, item.html_options(:selected => selected))) | |
|
160 | menu_items_for(menu, project) do |node| | |
|
161 | links << render_menu_node(node, project) | |
|
85 | 162 | end |
|
86 | 163 | links.empty? ? nil : content_tag('ul', links.join("\n")) |
|
87 | 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 | 189 | def menu_items_for(menu, project=nil) |
|
90 | 190 | items = [] |
|
91 |
Redmine::MenuManager. |
|
|
92 | unless item.condition && !item.condition.call(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) | |
|
191 | Redmine::MenuManager.items(menu).root.children.each do |node| | |
|
192 | if allowed_node?(node, User.current, project) | |
|
102 | 193 | if block_given? |
|
103 | yield item, caption, url, (current_menu_item == item.name) | |
|
194 | yield node | |
|
104 | 195 | else |
|
105 | items << [item, caption, url, (current_menu_item == item.name)] | |
|
196 | items << node # TODO: not used? | |
|
106 | 197 | end |
|
107 | 198 | end |
|
108 | 199 | end |
|
109 | 200 | return block_given? ? nil : items |
|
110 | 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 | 234 | end |
|
112 | 235 | |
|
113 | 236 | class << self |
@@ -122,17 +245,13 module Redmine | |||
|
122 | 245 | end |
|
123 | 246 | |
|
124 | 247 | def items(menu_name) |
|
125 |
@items[menu_name.to_sym] || |
|
|
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) | |
|
248 | @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {}) | |
|
130 | 249 | end |
|
131 | 250 | end |
|
132 | 251 | |
|
133 | 252 | class Mapper |
|
134 | 253 | def initialize(menu, items) |
|
135 |
items[menu] ||= |
|
|
254 | items[menu] ||= Tree::TreeNode.new(:root, {}) | |
|
136 | 255 | @menu = menu |
|
137 | 256 | @menu_items = items[menu] |
|
138 | 257 | end |
@@ -151,36 +270,78 module Redmine | |||
|
151 | 270 | # * html_options: a hash of html options that are passed to link_to |
|
152 | 271 | def push(name, url, options={}) |
|
153 | 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 | 286 | # menu item position |
|
156 |
if |
|
|
157 | position = @menu_items.collect(&:name).index(before) | |
|
287 | if first = options.delete(:first) | |
|
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 | 297 | elsif after = options.delete(:after) |
|
159 | position = @menu_items.collect(&:name).index(after) | |
|
160 | position += 1 unless position.nil? | |
|
298 | ||
|
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 | 305 | elsif options.delete(:last) |
|
162 | position = @menu_items.size | |
|
163 | @@last_items_count[@menu] += 1 | |
|
306 | target_root.add_last(MenuItem.new(name, url, options)) | |
|
307 | else | |
|
308 | target_root.add(MenuItem.new(name, url, options)) | |
|
164 | 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 | 310 | end |
|
170 | 311 | |
|
171 | 312 | # Removes a menu item |
|
172 | 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 | 334 | end |
|
175 | 335 | end |
|
176 | 336 | |
|
177 | class MenuItem | |
|
337 | class MenuItem < Tree::TreeNode | |
|
178 | 338 | include Redmine::I18n |
|
179 | attr_reader :name, :url, :param, :condition | |
|
339 | attr_reader :name, :url, :param, :condition, :parent_menu | |
|
180 | 340 | |
|
181 | 341 | def initialize(name, url, options) |
|
182 | raise "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) | |
|
342 | raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) | |
|
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 | 345 | @name = name |
|
185 | 346 | @url = url |
|
186 | 347 | @condition = options[:if] |
@@ -189,6 +350,8 module Redmine | |||
|
189 | 350 | @html_options = options[:html] || {} |
|
190 | 351 | # Adds a unique class to each menu item based on its name |
|
191 | 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 | 355 | end |
|
193 | 356 | |
|
194 | 357 | def caption(project=nil) |
General Comments 0
You need to be logged in to leave comments.
Login now