line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package IRC::Server::Tree; |
2
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
## Array-type object representing a network map. |
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
42853
|
use strictures 1; |
|
2
|
|
|
|
|
1338
|
|
|
2
|
|
|
|
|
60
|
|
7
|
2
|
|
|
2
|
|
95
|
use Carp; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
173
|
|
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
10
|
use Scalar::Util 'blessed'; |
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
189
|
|
10
|
2
|
|
|
2
|
|
1939
|
use Storable 'dclone' ; |
|
2
|
|
|
|
|
12434
|
|
|
2
|
|
|
|
|
3119
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub new { |
13
|
9
|
|
|
9
|
1
|
653
|
my $class = shift; |
14
|
|
|
|
|
|
|
|
15
|
9
|
|
|
|
|
12
|
my $self; |
16
|
|
|
|
|
|
|
|
17
|
9
|
100
|
|
|
|
30
|
BUILD: { |
18
|
9
|
|
|
|
|
13
|
last BUILD unless @_; |
19
|
|
|
|
|
|
|
|
20
|
4
|
50
|
|
|
|
13
|
if (@_ > 1) { |
21
|
|
|
|
|
|
|
## Got a tree as a list |
22
|
|
|
|
|
|
|
## (or the user did something dumb and will regret it later) |
23
|
0
|
|
|
|
|
0
|
$self = [ @_ ]; |
24
|
|
|
|
|
|
|
last BUILD |
25
|
0
|
|
|
|
|
0
|
} |
26
|
|
|
|
|
|
|
|
27
|
4
|
|
|
|
|
6
|
my ($opt) = @_; |
28
|
|
|
|
|
|
|
|
29
|
4
|
100
|
66
|
|
|
28
|
if (blessed $opt && $opt->isa('IRC::Server::Tree') ) { |
30
|
|
|
|
|
|
|
## Got a Tree. Clone it to break refs. |
31
|
1
|
|
|
|
|
142
|
$self = dclone($opt); |
32
|
|
|
|
|
|
|
last BUILD |
33
|
1
|
|
|
|
|
4
|
} |
34
|
|
|
|
|
|
|
|
35
|
3
|
50
|
|
|
|
11
|
if (ref $opt eq 'ARRAY') { |
36
|
|
|
|
|
|
|
## Got a Tree as a raw ARRAY. |
37
|
|
|
|
|
|
|
## No clone; keep refs to allow darker forms of magic |
38
|
3
|
|
|
|
|
3
|
$self = $opt; |
39
|
|
|
|
|
|
|
last BUILD |
40
|
3
|
|
|
|
|
8
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
9
|
100
|
|
|
|
25
|
$self = [] unless $self; |
45
|
9
|
|
|
|
|
53
|
bless $self, $class |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub add_node_to_parent_ref { |
49
|
18
|
|
|
18
|
1
|
32
|
my ($self, $parent_ref, $name, $arrayref) = @_; |
50
|
|
|
|
|
|
|
|
51
|
18
|
100
|
|
|
|
60
|
$arrayref = [@$arrayref] if blessed $arrayref; |
52
|
|
|
|
|
|
|
|
53
|
18
|
|
100
|
|
|
58
|
push @$parent_ref, $name, ($arrayref||=[]); |
54
|
|
|
|
|
|
|
|
55
|
18
|
|
|
|
|
81
|
$arrayref |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub add_node_to_top { |
59
|
5
|
|
|
5
|
1
|
638
|
my ($self, $name, $arrayref) = @_; |
60
|
|
|
|
|
|
|
|
61
|
5
|
|
|
|
|
16
|
$self->add_node_to_parent_ref( $self, $name, $arrayref ) |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub add_node_to_name { |
65
|
13
|
|
|
13
|
1
|
484
|
my ($self, $parent_name, $name, $arrayref) = @_; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
## Can be passed $self like add_node_to_parent_ref |
68
|
|
|
|
|
|
|
## Should just use add_node_to_top instead, though |
69
|
13
|
50
|
|
|
|
45
|
if ($parent_name eq $self) { |
70
|
0
|
|
|
|
|
0
|
return $self->add_node_to_top($name, $arrayref) |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
13
|
50
|
0
|
|
|
32
|
my $index_route = |
74
|
|
|
|
|
|
|
$self->trace_indexes($parent_name) |
75
|
|
|
|
|
|
|
or carp "Cannot add node to nonexistant parent $parent_name" |
76
|
|
|
|
|
|
|
and return; |
77
|
|
|
|
|
|
|
|
78
|
13
|
|
|
|
|
18
|
my $cur_ref = $self; |
79
|
|
|
|
|
|
|
|
80
|
13
|
|
|
|
|
34
|
while (my $idx = shift @$index_route) { |
81
|
16
|
|
|
|
|
123
|
$cur_ref = $cur_ref->[$idx] |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
## Now in the ref belonging to our named parent. |
85
|
13
|
|
100
|
|
|
54
|
$self->add_node_to_parent_ref($cur_ref, $name, $arrayref || [] ) |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub __t_add_to_hash { |
89
|
13
|
|
|
13
|
|
49
|
my ($parent_hash, $name, $node_ref) = @_; |
90
|
|
|
|
|
|
|
|
91
|
13
|
50
|
|
|
|
37
|
$parent_hash->{$name} = {} |
92
|
|
|
|
|
|
|
unless exists $parent_hash->{$name}; |
93
|
|
|
|
|
|
|
|
94
|
13
|
|
|
|
|
22
|
my @list = @$node_ref; |
95
|
|
|
|
|
|
|
|
96
|
13
|
|
|
|
|
57
|
while (my ($nextname, $nextref) = splice @list, 0, 2 ) { |
97
|
9
|
|
|
|
|
29
|
__t_add_to_hash( $parent_hash->{$name}, $nextname, $nextref ) |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub as_hash { |
102
|
2
|
|
|
2
|
1
|
6
|
my ($self, $parent_ref) = @_; |
103
|
|
|
|
|
|
|
|
104
|
2
|
50
|
|
|
|
9
|
$parent_ref = $self unless defined $parent_ref; |
105
|
|
|
|
|
|
|
|
106
|
2
|
|
|
|
|
5
|
my $mapref = {}; |
107
|
|
|
|
|
|
|
|
108
|
2
|
|
|
|
|
7
|
my @list = @$parent_ref; |
109
|
|
|
|
|
|
|
|
110
|
2
|
|
|
|
|
9
|
while (my ($name, $node_ref) = splice @list, 0, 2 ) { |
111
|
4
|
|
|
|
|
9
|
__t_add_to_hash( $mapref, $name, $node_ref ) |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
$mapref |
115
|
2
|
|
|
|
|
25
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub as_list { |
118
|
1
|
|
|
1
|
1
|
2
|
my ($self, $parent_ref) = @_; |
119
|
1
|
|
33
|
|
|
9
|
$parent_ref ||= $self; |
120
|
1
|
|
|
|
|
1
|
@{ $parent_ref } |
|
1
|
|
|
|
|
4
|
|
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub child_node_for { |
124
|
3
|
|
|
3
|
1
|
5
|
my ($self, $server_name, $parent_ref) = @_; |
125
|
|
|
|
|
|
|
|
126
|
3
|
50
|
|
|
|
9
|
$parent_ref = $self unless defined $parent_ref; |
127
|
|
|
|
|
|
|
|
128
|
3
|
50
|
|
|
|
7
|
my $index_route = |
129
|
|
|
|
|
|
|
$self->trace_indexes($server_name, $parent_ref) |
130
|
|
|
|
|
|
|
or return; |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
## Recurse the list indexes. |
133
|
3
|
|
|
|
|
4
|
my $cur_ref = $parent_ref; |
134
|
|
|
|
|
|
|
|
135
|
3
|
|
|
|
|
8
|
while (my $idx = shift @$index_route) { |
136
|
6
|
|
|
|
|
14
|
$cur_ref = $cur_ref->[$idx] |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
$cur_ref |
140
|
3
|
|
|
|
|
10
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub del_node_by_name { |
143
|
4
|
|
|
4
|
1
|
8
|
my ($self, $name, $parent_ref) = @_; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
## Returns deleted node. |
146
|
|
|
|
|
|
|
|
147
|
4
|
50
|
0
|
|
|
11
|
my $index_route = |
148
|
|
|
|
|
|
|
$self->trace_indexes($name, $parent_ref) |
149
|
|
|
|
|
|
|
or carp "Cannot del nonexistant node $name" |
150
|
|
|
|
|
|
|
and return; |
151
|
|
|
|
|
|
|
|
152
|
4
|
|
|
|
|
9
|
my $idx_for_ref = pop @$index_route; |
153
|
4
|
|
|
|
|
47
|
my $idx_for_name = $idx_for_ref - 1; |
154
|
|
|
|
|
|
|
|
155
|
4
|
|
33
|
|
|
25
|
my $cur_ref = $parent_ref || $self; |
156
|
4
|
|
|
|
|
11
|
while (my $idx = shift @$index_route) { |
157
|
2
|
|
|
|
|
6
|
$cur_ref = $cur_ref->[$idx] |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
## Should now be in top-level container and have index values |
161
|
|
|
|
|
|
|
## for the name/ref that we're deleting. |
162
|
4
|
|
|
|
|
15
|
my ($del_name, $del_ref) = splice @$cur_ref, $idx_for_name, 2; |
163
|
|
|
|
|
|
|
|
164
|
4
|
|
|
|
|
19
|
$del_ref |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub names_beneath { |
168
|
48
|
|
|
48
|
1
|
58
|
my ($self, $ref_or_name) = @_; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
## Given either a ref (such as from del_node_by_name) |
171
|
|
|
|
|
|
|
## or a name (ref is retrived), get the names of |
172
|
|
|
|
|
|
|
## all the nodes in the tree under us. |
173
|
|
|
|
|
|
|
|
174
|
48
|
|
|
|
|
48
|
my $ref; |
175
|
48
|
50
|
|
|
|
106
|
if (!$ref_or_name) { |
|
|
100
|
|
|
|
|
|
176
|
0
|
|
|
|
|
0
|
$ref = $self |
177
|
|
|
|
|
|
|
} elsif (ref $ref_or_name) { |
178
|
47
|
|
33
|
|
|
82
|
$ref = $ref_or_name || $self |
179
|
|
|
|
|
|
|
} else { |
180
|
1
|
|
|
|
|
3
|
$ref = $self->child_node_for($ref_or_name) |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
48
|
50
|
|
|
|
90
|
return unless $ref; |
184
|
|
|
|
|
|
|
|
185
|
48
|
|
|
|
|
73
|
my @list = @$ref; |
186
|
48
|
|
|
|
|
41
|
my @names; |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
## Recurse and accumulate names. |
189
|
48
|
|
|
|
|
127
|
while (my ($node_name, $node_ref) = splice @list, 0, 2) { |
190
|
36
|
|
|
|
|
44
|
push(@names, $node_name); |
191
|
36
|
50
|
|
|
|
34
|
push(@names, @{ $self->names_beneath($node_ref) || [] }); |
|
36
|
|
|
|
|
71
|
|
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
\@names |
195
|
48
|
|
|
|
|
201
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub trace { |
198
|
4
|
|
|
4
|
1
|
9
|
my ($self, $server_name, $parent_ref) = @_; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
## A list of named hops to the target. |
201
|
|
|
|
|
|
|
## The last hop is the target's name. |
202
|
|
|
|
|
|
|
|
203
|
4
|
100
|
|
|
|
12
|
$parent_ref = $self unless defined $parent_ref; |
204
|
|
|
|
|
|
|
|
205
|
4
|
50
|
|
|
|
11
|
my $index_route = |
206
|
|
|
|
|
|
|
$self->trace_indexes($server_name, $parent_ref) |
207
|
|
|
|
|
|
|
or return; |
208
|
|
|
|
|
|
|
|
209
|
4
|
|
|
|
|
12
|
$self->path_by_indexes($index_route, $parent_ref) |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub path_by_indexes { |
213
|
12
|
|
|
12
|
1
|
20
|
my ($self, $index_array, $parent_ref) = @_; |
214
|
|
|
|
|
|
|
## Walk a trace_indexes array and retrieve names. |
215
|
|
|
|
|
|
|
## Used by ->trace() |
216
|
|
|
|
|
|
|
|
217
|
12
|
|
|
|
|
24
|
my @indexes = @$index_array; |
218
|
|
|
|
|
|
|
|
219
|
12
|
|
|
|
|
22
|
my @names; |
220
|
12
|
|
66
|
|
|
39
|
my $cur_ref = $parent_ref || $self; |
221
|
12
|
|
|
|
|
31
|
while (my $idx = shift @indexes) { |
222
|
31
|
|
|
|
|
59
|
push @names, $cur_ref->[ $idx - 1 ]; |
223
|
31
|
|
|
|
|
81
|
$cur_ref = $cur_ref->[$idx]; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
\@names |
227
|
12
|
|
|
|
|
56
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub trace_indexes { |
230
|
31
|
|
|
31
|
1
|
43
|
my ($self, $server_name, $parent_ref) = @_; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
## An example of breadth-first search. |
233
|
|
|
|
|
|
|
## |
234
|
|
|
|
|
|
|
## We explore each path in the current node, and as we find new paths, |
235
|
|
|
|
|
|
|
## we queue them to be explored after the current iteration. |
236
|
|
|
|
|
|
|
## (This is in contrast to depth-first techniques, where you recursively |
237
|
|
|
|
|
|
|
## explore each deeper reference as you hit it, with the path-so-far |
238
|
|
|
|
|
|
|
## included in the call, until you have the path desired.) |
239
|
|
|
|
|
|
|
## |
240
|
|
|
|
|
|
|
## This is useful for cases like an IRC server tree, where there is |
241
|
|
|
|
|
|
|
## an essentially arbitrary structure to the tree; any node may have |
242
|
|
|
|
|
|
|
## any arbitrary number of child nodes (ad infinitum) and we have no |
243
|
|
|
|
|
|
|
## actual hints as to the possible path. |
244
|
|
|
|
|
|
|
## |
245
|
|
|
|
|
|
|
## (Hmm. Considering running networked maze-solver races...) |
246
|
|
|
|
|
|
|
## |
247
|
|
|
|
|
|
|
## Defaults to operating on $self |
248
|
|
|
|
|
|
|
## Return indexes into arrays describing the path |
249
|
|
|
|
|
|
|
## Return value is the full list of indexes to get to the array |
250
|
|
|
|
|
|
|
## belonging to the named server |
251
|
|
|
|
|
|
|
## i.e.: |
252
|
|
|
|
|
|
|
## 1, 3, 1 |
253
|
|
|
|
|
|
|
## $parent_ref->[1] is a ref belonging to an intermediate hop |
254
|
|
|
|
|
|
|
## $parent_ref->[1]->[3] is a ref belonging to an intermediate hop |
255
|
|
|
|
|
|
|
## $parent_ref->[1]->[3]->[1] is the ref belonging to the target hop |
256
|
|
|
|
|
|
|
## Subtracting one from an index will get you the NAME value. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
## A start-point. |
259
|
31
|
|
66
|
|
|
132
|
my @queue = ( PARENT => ($parent_ref || $self) ); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
## Our seen routes. |
262
|
31
|
|
|
|
|
38
|
my %route; |
263
|
|
|
|
|
|
|
|
264
|
31
|
|
|
|
|
46
|
my $parent_idx = 0; |
265
|
31
|
|
|
|
|
92
|
PARENT: while (my ($parent_name, $parent_ref) = splice @queue, 0, 2) { |
266
|
|
|
|
|
|
|
|
267
|
74
|
50
|
|
|
|
132
|
return [ $parent_idx+1 ] if $parent_name eq $server_name; |
268
|
|
|
|
|
|
|
|
269
|
74
|
|
|
|
|
119
|
my @leaf_list = @$parent_ref; |
270
|
74
|
|
|
|
|
77
|
my $child_idx = 0; |
271
|
|
|
|
|
|
|
|
272
|
74
|
|
|
|
|
168
|
CHILD: while (my ($child_name, $child_ref) = splice @leaf_list, 0, 2) { |
273
|
|
|
|
|
|
|
|
274
|
101
|
50
|
|
|
|
195
|
unless ( $route{$child_name} ) { |
275
|
101
|
100
|
|
|
|
919
|
$route{$child_name} = |
276
|
101
|
|
|
|
|
101
|
[ @{ $route{$parent_name}||[] }, $child_idx+1 ]; |
277
|
|
|
|
|
|
|
|
278
|
101
|
100
|
|
|
|
212
|
return \@{$route{$child_name}} if $child_name eq $server_name; |
|
30
|
|
|
|
|
181
|
|
279
|
|
|
|
|
|
|
|
280
|
71
|
|
|
|
|
100
|
push @queue, $child_name, $child_ref; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
71
|
|
|
|
|
206
|
$child_idx += 2; |
284
|
|
|
|
|
|
|
} ## CHILD |
285
|
|
|
|
|
|
|
|
286
|
44
|
|
|
|
|
116
|
$parent_idx += 2; |
287
|
|
|
|
|
|
|
} ## PARENT |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
return |
290
|
1
|
|
|
|
|
4
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub print_map { |
293
|
1
|
|
|
1
|
1
|
1496
|
my ($self, $parent_ref) = @_; |
294
|
|
|
|
|
|
|
|
295
|
1
|
50
|
|
|
|
4
|
$parent_ref = $self unless defined $parent_ref; |
296
|
|
|
|
|
|
|
|
297
|
1
|
|
|
|
|
2
|
my $indent = 1; |
298
|
|
|
|
|
|
|
|
299
|
1
|
|
|
|
|
1
|
my $recurse_print; |
300
|
|
|
|
|
|
|
$recurse_print = sub { |
301
|
7
|
|
|
7
|
|
9
|
my ($name, $ref) = @_; |
302
|
7
|
|
|
|
|
18
|
my @nodes = @$ref; |
303
|
|
|
|
|
|
|
|
304
|
7
|
100
|
100
|
|
|
23
|
if ($indent == 1 || scalar @nodes) { |
305
|
3
|
|
|
|
|
5
|
$name = "* $name"; |
306
|
|
|
|
|
|
|
} else { |
307
|
4
|
|
|
|
|
7
|
$name = "` $name"; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
7
|
|
|
|
|
9
|
print {*STDOUT} ( (' ' x $indent) . "$name\n" ); |
|
7
|
|
|
|
|
25
|
|
311
|
|
|
|
|
|
|
|
312
|
7
|
|
|
|
|
24
|
while (my ($next_name, $next_ref) = splice @nodes, 0, 2) { |
313
|
5
|
|
|
|
|
6
|
$indent += 3; |
314
|
5
|
|
|
|
|
14
|
$recurse_print->($next_name, $next_ref); |
315
|
5
|
|
|
|
|
13
|
$indent -= 3; |
316
|
|
|
|
|
|
|
} |
317
|
1
|
|
|
|
|
7
|
}; |
318
|
|
|
|
|
|
|
|
319
|
1
|
|
|
|
|
3
|
my @list = @$parent_ref; |
320
|
1
|
50
|
|
|
|
3
|
warn "No refs found\n" unless @list; |
321
|
1
|
|
|
|
|
5
|
while (my ($parent_name, $parent_ref) = splice @list, 0, 2) { |
322
|
2
|
|
|
|
|
3
|
$recurse_print->($parent_name, $parent_ref); |
323
|
2
|
|
|
|
|
6
|
$indent = 1; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
1
|
|
|
|
|
4
|
return 1 |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
1; |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=pod |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=head1 NAME |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
IRC::Server::Tree - Manipulate an IRC "spanning tree" |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=head1 SYNOPSIS |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
## Basic path-tracing usage: |
340
|
|
|
|
|
|
|
my $tree = IRC::Server::Tree->new; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
$tree->add_node_to_top($_) for qw/ peerA peerB /; |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
$tree->add_node_to_name('peerA', 'leafA'); |
345
|
|
|
|
|
|
|
$tree->add_node_to_name('peerA', 'leafB'); |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
$tree->add_node_to_name('peerB', 'hubA'); |
348
|
|
|
|
|
|
|
$tree->add_node_to_name('hubA', 'peerB'); |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
## ARRAY of hop names between root and peerB: |
351
|
|
|
|
|
|
|
my $hop_names = $tree->trace( 'peerB' ); |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
See L for a simpler and more specialized |
354
|
|
|
|
|
|
|
interface to the tree. |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
See the DESCRIPTION for a complete method list. |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head1 DESCRIPTION |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
This piece was split out of a pending project because it may prove |
361
|
|
|
|
|
|
|
otherwise useful. See L for higher-level |
362
|
|
|
|
|
|
|
(and simpler) methods pertaining to manipulation of an IRC network |
363
|
|
|
|
|
|
|
specifically; a Network instance also provides an optional |
364
|
|
|
|
|
|
|
memory-for-speed tradeoff via memoization of traced paths. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
IRC servers are linked to form a network. |
367
|
|
|
|
|
|
|
An IRC network is defined as a 'spanning tree' per RFC1459; this module |
368
|
|
|
|
|
|
|
is an array-type object representing such a tree, with convenient path |
369
|
|
|
|
|
|
|
resolution methods for determining route "hops" and extending or shrinking |
370
|
|
|
|
|
|
|
the tree. |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
An IRC network tree is essentially unordered; any node can have any |
373
|
|
|
|
|
|
|
number of child nodes, with the only rules being that: |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=over |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item * |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
The tree remains a tree (it is acyclic; there is only one route between |
380
|
|
|
|
|
|
|
any two nodes, and no node has more than one parent) |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=item * |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
No two nodes can share the same name. |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=back |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
Currently, this module doesn't enforce the listed rules for performance |
389
|
|
|
|
|
|
|
reasons, but things will break if you add non-uniquely-named nodes. Be |
390
|
|
|
|
|
|
|
warned. In fact, this module doesn't sanity |
391
|
|
|
|
|
|
|
check very much of anything; an L does much |
392
|
|
|
|
|
|
|
more to validate the tree and passed arguments. |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
A new Tree can be created from an existing Tree: |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
my $new_tree = IRC::Server::Tree->new( $old_tree ); |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
In principle, the general structure of the tree is your average deep |
399
|
|
|
|
|
|
|
array-of-arrays: |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
$self => [ |
402
|
|
|
|
|
|
|
hubA => [ |
403
|
|
|
|
|
|
|
leafA => [], |
404
|
|
|
|
|
|
|
leafB => [], |
405
|
|
|
|
|
|
|
], |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
hubB => [ |
408
|
|
|
|
|
|
|
leafC => [], |
409
|
|
|
|
|
|
|
leafD => [], |
410
|
|
|
|
|
|
|
], |
411
|
|
|
|
|
|
|
], |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
The methods provided below can be used to manipulate the tree and |
414
|
|
|
|
|
|
|
determine hops in a path to an arbitrary node using a breadth-first |
415
|
|
|
|
|
|
|
search. |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Currently routes are not memoized; that's left to a higher layer or |
418
|
|
|
|
|
|
|
subclass. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head2 new |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Create a new network tree: |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
my $tree = IRC::Server::Tree->new; |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
Create a new network tree from an old one or part of one (see |
427
|
|
|
|
|
|
|
L and L): |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
my $tree = IRC::Server::Tree->new( $old_tree ); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
(Note that this will clone the old Tree object.) |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
Optionally create a tree from an ARRAY, if you really know what |
434
|
|
|
|
|
|
|
you're doing: |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
my $tree = IRC::Server::Tree->new( |
437
|
|
|
|
|
|
|
[ |
438
|
|
|
|
|
|
|
hubA => [ |
439
|
|
|
|
|
|
|
hubB => [ |
440
|
|
|
|
|
|
|
hubBleaf1 => [], |
441
|
|
|
|
|
|
|
], |
442
|
|
|
|
|
|
|
leaf1 => [], |
443
|
|
|
|
|
|
|
leaf2 => [], |
444
|
|
|
|
|
|
|
], |
445
|
|
|
|
|
|
|
], |
446
|
|
|
|
|
|
|
); |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=head2 add_node_to_parent_ref |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
## Add empty node to parent ref: |
451
|
|
|
|
|
|
|
$tree->add_node_to_parent_ref( $parent_ref, $new_name ); |
452
|
|
|
|
|
|
|
## Add existing node to parent ref: |
453
|
|
|
|
|
|
|
$tree->add_node_to_parent_ref( $parent_ref, $new_name, $new_ref ); |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Adds an empty or preexisting node to a specified parent reference. |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
Also see L, L |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=head2 add_node_to_top |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
$tree->add_node_to_top( $new_name ); |
462
|
|
|
|
|
|
|
$tree->add_node_to_top( $new_name, $new_ref ); |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
Also see L, L |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=head2 add_node_to_name |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
$tree->add_node_to_name( $parent_name, $name ); |
469
|
|
|
|
|
|
|
$tree->add_node_to_name( $parent_name, $name, $new_ref ); |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
Adds an empty or specified node to the specified parent name. |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
For example: |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
$tree->add_node_to_top( 'MyHub1' ); |
476
|
|
|
|
|
|
|
$tree->add_node_to_name( 'MyHub1', 'MyLeafA' ); |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
## Existing nodes under our new node |
479
|
|
|
|
|
|
|
my $new_node = [ 'MyLeafB' => [] ]; |
480
|
|
|
|
|
|
|
$tree->add_node_to_name( 'MyHub1', 'MyHub2', $new_node ); |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=head2 as_hash |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
my $hash_ref = $tree->as_hash; |
485
|
|
|
|
|
|
|
my $hash_ref = $tree->as_hash( $parent_ref ); |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Get a (possibly deep) HASH describing the state of the tree underneath |
488
|
|
|
|
|
|
|
the specified parent reference, or the entire tree if none is specified. |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
For example: |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
my $hash_ref = $tree->as_hash( $self->child_node_for('MyHub1') ); |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
Also see L |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head2 as_list |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
my @tree = $tree->as_list; |
499
|
|
|
|
|
|
|
my @tree = $tree->as_list( $parent_ref ); |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Returns the tree in list format. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
Not useful for most purposes and may be removed. |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head2 child_node_for |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
my $child_node = $tree->child_node_for( $parent_name ); |
508
|
|
|
|
|
|
|
my $child_node = $tree->child_node_for( $parent_name, $start_ref ); |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
Finds and returns the named child node from the tree. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Starts at the root of the tree or the specified parent reference. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=head2 del_node_by_name |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
$tree->del_node_by_name( $parent_name ); |
517
|
|
|
|
|
|
|
$tree->del_node_by_name( $parent_name, $start_ref ); |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
Finds and deletes the named child from the tree. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
Returns the deleted node. |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
=head2 names_beneath |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
my $names = $tree->names_beneath( $parent_name ); |
526
|
|
|
|
|
|
|
my $names = $tree->names_beneath( $parent_ref ); |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
Return an arrayref of all names in the tree beneath the specified parent |
529
|
|
|
|
|
|
|
node. |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Takes either the name of a node in the tree or a reference to a node. |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=head2 path_by_indexes |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
my $names = $tree->path_by_indexes( $index_route ); |
536
|
|
|
|
|
|
|
my $names = $tree->path_by_indexes( $index_route, $parent_ref ); |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
Given an array of index hops as retrieved by L, retrieve |
539
|
|
|
|
|
|
|
the name for each hop. |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
This is mostly used internally by L. |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
=head2 print_map |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
$tree->print_map; |
546
|
|
|
|
|
|
|
$tree->print_map( $start_ref ); |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
Prints a visualization of the network map to STDOUT. |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=head2 trace |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
my $names = $tree->trace( $parent_name ); |
553
|
|
|
|
|
|
|
my $names = $tree->trace( $parent_name, $start_ref ); |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
Returns an arrayref of the names of every hop in the path to the |
556
|
|
|
|
|
|
|
specified parent name. |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
Starts tracing from the root of the tree unless a parent node reference |
559
|
|
|
|
|
|
|
is also specified. |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
The last hop returned is the target's name. |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head2 trace_indexes |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Primarily intended for internal use. This is the breadth-first search |
566
|
|
|
|
|
|
|
that other methods use to find a node. There is nothing very useful you |
567
|
|
|
|
|
|
|
can do with this externally except count hops; it is documented here to |
568
|
|
|
|
|
|
|
show how path resolution works. |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
Returns an arrayref consisting of the index of every hop taken to get to |
571
|
|
|
|
|
|
|
the node reference belonging to the specified node name starting from |
572
|
|
|
|
|
|
|
the root of the tree or the specified parent node reference. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Given a network: |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
hubA |
577
|
|
|
|
|
|
|
leafA |
578
|
|
|
|
|
|
|
leafB |
579
|
|
|
|
|
|
|
hubB |
580
|
|
|
|
|
|
|
leafC |
581
|
|
|
|
|
|
|
leafD |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
C<< trace_indexes(B<'leafD'>) >> would return: |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
[ 1, 5, 1 ] |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
These are the indexes into the node references (arrays) owned by each |
588
|
|
|
|
|
|
|
hop, including the last hop. Retrieving their names requires |
589
|
|
|
|
|
|
|
subtracting one from each index; L handles this. |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=head1 AUTHOR |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Jon Portnoy |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=cut |