File Coverage

blib/lib/Tree.pm
Criterion Covered Total %
statement 177 205 86.3
branch 42 52 80.7
condition 18 24 75.0
subroutine 31 35 88.5
pod 21 21 100.0
total 289 337 85.7


line stmt bran cond sub pod time code
1             package Tree;
2              
3 21     21   87940 use 5.006;
  21         87  
4              
5 21     21   132 use base 'Tree::Fast';
  21         41  
  21         11060  
6 21     21   136 use strict;
  21         42  
  21         626  
7 21     21   106 use warnings;
  21         44  
  21         993  
8              
9             our $VERSION = '1.16';
10              
11 21     21   115 use Scalar::Util qw( blessed refaddr weaken );
  21         47  
  21         33668  
12              
13             # These are the class methods
14              
15             my %error_handlers = (
16             'quiet' => sub {
17             my $node = shift;
18             $node->last_error( join "\n", @_);
19             return;
20             },
21             'warn' => sub {
22             my $node = shift;
23             $node->last_error( join "\n", @_);
24             warn @_;
25             return;
26             },
27             'die' => sub {
28             my $node = shift;
29             $node->last_error( join "\n", @_);
30             die @_;
31             },
32             );
33              
34 5     5 1 869 sub QUIET { return $error_handlers{ 'quiet' } }
35 3     3 1 12 sub WARN { return $error_handlers{ 'warn' } }
36 4     4 1 14 sub DIE { return $error_handlers{ 'die' } }
37              
38             # The default error handler is quiet
39             my $ERROR_HANDLER = $error_handlers{ 'quiet' };
40              
41             sub _init {
42 239     239   342 my $self = shift;
43              
44 239         596 $self->SUPER::_init( @_ );
45              
46             $self->{_height} = 1,
47             $self->{_width} = 1,
48             $self->{_depth} = 0,
49              
50             $self->{_error_handler} = $ERROR_HANDLER,
51 239         774 $self->{_last_error} = undef;
52              
53             $self->{_handlers} = {
54 239         792 add_child => [],
55             remove_child => [],
56             value => [],
57             };
58              
59             $self->{_root} = undef,
60 239         712 $self->_set_root( $self );
61              
62 239         363 return $self;
63             }
64              
65             # These are the behaviors
66              
67             sub add_child {
68 197     197 1 12599 my $self = shift;
69 197         367 my @nodes = @_;
70              
71 197         468 $self->last_error( undef );
72              
73 197         439 my $options = $self->_strip_options( \@nodes );
74              
75 197 100       473 unless ( @nodes ) {
76 1         3 return $self->error( "add_child(): No children passed in" );
77             }
78              
79 196 100       441 if ( defined $options->{at}) {
80 13         40 my $num_children = () = $self->children;
81 13 100       92 unless ( $options->{at} =~ /^-?\d+$/ ) {
82 2         9 return $self->error(
83             "add_child(): '$options->{at}' is not a legal index"
84             );
85             }
86              
87 11 100 100     93 if ( $options->{at} > $num_children ||
88             $num_children + $options->{at} < 0
89             ) {
90 2         8 return $self->error( "add_child(): '$options->{at}' is out-of-bounds" );
91             }
92             }
93              
94 192         320 for my $node ( @nodes ) {
95 221 100 100     1062 unless ( blessed($node) && $node->isa( __PACKAGE__ ) ) {
96 4         20 return $self->error( "add_child(): '$node' is not a " . __PACKAGE__ );
97             }
98              
99 217 100       507 if ( $node->root eq $self->root ) {
100 2         5 return $self->error( "add_child(): Cannot add a node in the tree back into the tree" );
101             }
102              
103 215 100       562 if ( $node->parent ) {
104 1         3 return $self->error( "add_child(): Cannot add a child to another parent" );
105             }
106             }
107              
108 185         553 $self->SUPER::add_child( $options, @nodes );
109              
110 185         283 for my $node ( @nodes ) {
111 213         363 $node->_set_root( $self->root );
112 213         400 $node->_fix_depth;
113             }
114              
115 185         441 $self->_fix_height;
116 185         384 $self->_fix_width;
117              
118 185         474 $self->event( 'add_child', $self, @_ );
119              
120 185         395 return $self;
121             }
122              
123             sub remove_child {
124 27     27 1 5538 my $self = shift;
125 27         59 my @nodes = @_;
126              
127 27         72 $self->last_error( undef );
128              
129 27         63 my $options = $self->_strip_options( \@nodes );
130              
131 27 100       71 unless ( @nodes ) {
132 1         2 return $self->error( "remove_child(): Nothing to remove" );
133             }
134              
135 26         38 my @indices;
136 26         78 my $num_children = () = $self->children;
137 26         57 foreach my $proto (@nodes) {
138 31 100       73 if ( !defined( $proto ) ) {
139 1         3 return $self->error( "remove_child(): 'undef' is out-of-bounds" );
140             }
141              
142 30 100       103 if ( !blessed( $proto ) ) {
143 10 100       70 unless ( $proto =~ /^-?\d+$/ ) {
144 1         4 return $self->error( "remove_child(): '$proto' is not a legal index" );
145             }
146              
147 9 100 100     42 if ( $proto >= $num_children || $num_children + $proto <= 0 ) {
148 2         6 return $self->error( "remove_child(): '$proto' is out-of-bounds" );
149             }
150              
151 7         19 push @indices, $proto;
152             }
153             else {
154 20         47 my ($index) = $self->get_index_for( $proto );
155              
156 20 100       57 unless ( defined $index ) {
157 1         4 return $self->error( "remove_child(): '$proto' not found" );
158             }
159              
160 19         46 push @indices, $index;
161             }
162             }
163              
164 21         92 my @return = $self->SUPER::remove_child( $options, @indices );
165              
166 21         44 for my $node ( @return ) {
167 26         61 $node->_set_root( $node );
168 26         55 $node->_fix_depth;
169             }
170              
171 21         49 $self->_fix_height;
172 21         46 $self->_fix_width;
173              
174 21         57 $self->event( 'remove_child', $self, @_ );
175              
176 21         83 return @return;
177             }
178              
179             sub add_event_handler {
180 2     2 1 670 my $self = shift;
181 2         3 my ($opts) = @_;
182              
183 2         10 while ( my ($type,$handler) = each %$opts ) {
184 3         4 push @{$self->{_handlers}{$type}}, $handler;
  3         11  
185             }
186              
187 2         9 return $self;
188             }
189              
190             sub event {
191 5196     5196 1 6077 my $self = shift;
192 5196         7607 my ( $type, @args ) = @_;
193              
194 5196         5498 foreach my $handler ( @{$self->{_handlers}{$type}} ) {
  5196         8102  
195 4         9 $handler->( @args );
196             }
197              
198 5196         8205 $self->parent->event( @_ );
199              
200 5196         6313 return $self;
201             }
202              
203             # These are the state-queries
204              
205             sub is_root {
206 335     335 1 23781 my $self = shift;
207 335         679 return !$self->parent;
208             }
209              
210             sub is_leaf {
211 41     41 1 11817 my $self = shift;
212 41         92 return $self->height == 1;
213             }
214              
215             sub has_child {
216 14     14 1 585 my $self = shift;
217 14         28 my @nodes = @_;
218              
219 14         39 my @children = $self->children;
220 14         43 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  21         88  
221              
222 14         29 my $rv = 1;
223             $rv &&= exists $temp{refaddr($_)}
224 14   100     86 for @nodes;
225 14         101 return $rv;
226             }
227              
228             sub get_index_for {
229 24     24 1 551 my $self = shift;
230 24         47 my @nodes = @_;
231              
232 24         85 my @children = $self->children;
233 24         88 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  42         162  
234              
235 24         74 return map { $temp{refaddr($_)} } @nodes;
  24         153  
236             }
237              
238             # These are the smart accessors
239              
240             sub root {
241 1229     1229 1 2256 my $self = shift;
242 1229         2525 return $self->{_root};
243             }
244              
245             sub _set_root {
246 532     532   661 my $self = shift;
247              
248 532         735 $self->{_root} = shift;
249 532         1384 weaken( $self->{_root} );
250              
251             # Propagate the root-change down to all children
252             # Because this is called from DESTROY, we need to verify
253             # that the child still exists because destruction in Perl5
254             # is neither ordered nor timely.
255              
256             $_->_set_root( $self->{_root} )
257 532         636 for grep { $_ } @{$self->{_children}};
  180         384  
  532         1053  
258              
259 532         736 return $self;
260             }
261              
262             for my $name ( qw( height width depth ) ) {
263 21     21   183 no strict 'refs';
  21         40  
  21         1586  
264              
265             *{ __PACKAGE__ . "::${name}" } = sub {
266 21     21   145 use strict;
  21         61  
  21         25256  
267 11163     11163   53992 my $self = shift;
268 11163         19921 return $self->{"_${name}"};
269             };
270             }
271              
272             sub size {
273 75     75 1 18635 my $self = shift;
274 75         123 my $size = 1;
275 75         183 $size += $_->size for $self->children;
276 75         275 return $size;
277             }
278              
279             sub set_value {
280 6     6 1 1647 my $self = shift;
281              
282 6         20 my $old_value = $self->value();
283 6         25 $self->SUPER::set_value( @_ );
284              
285 6         25 $self->event( 'value', $self, $old_value, $self->value );
286              
287 6         30 return $self;
288             }
289              
290             # These are the error-handling functions
291              
292             sub error_handler {
293 41     41 1 598 my $self = shift;
294              
295 41 100       127 if ( !blessed( $self ) ) {
296 2         3 my $old = $ERROR_HANDLER;
297 2 100       6 $ERROR_HANDLER = shift if @_;
298 2         38 return $old;
299             }
300              
301 39         81 my $root = $self->root;
302 39         54 my $old = $root->{_error_handler};
303 39 100       79 $root->{_error_handler} = shift if @_;
304 39         93 return $old;
305             }
306              
307             sub error {
308 27     27 1 1892 my $self = shift;
309 27         77 my @args = @_;
310              
311 27         55 return $self->error_handler->( $self, @_ );
312             }
313              
314             sub last_error {
315 275     275 1 371 my $self = shift;
316 275 100       724 $self->root->{_last_error} = shift if @_;
317 275         452 return $self->root->{_last_error};
318             }
319              
320             # These are private convenience methods
321              
322             sub _fix_height {
323 5214     5214   6120 my $self = shift;
324              
325 5214         5703 my $height = 1;
326 5214         7755 for my $child ($self->children) {
327 5341         7536 my $temp_height = $child->height + 1;
328 5341 100       10005 $height = $temp_height if $height < $temp_height;
329             }
330              
331 5214         6426 $self->{_height} = $height;
332              
333 5214         8055 $self->parent->_fix_height;
334              
335 5214         5754 return $self;
336             }
337              
338             sub _fix_width {
339 5214     5214   5821 my $self = shift;
340              
341 5214         5729 my $width = 0;
342 5214         7704 $width += $_->width for $self->children;
343              
344 5214 100       7705 $self->{_width} = $width ? $width : 1;
345              
346 5214         7841 $self->parent->_fix_width;
347              
348 5214         5746 return $self;
349             }
350              
351             sub _fix_depth {
352 293     293   420 my $self = shift;
353              
354 293 100       518 if ( $self->is_root ) {
355 28         62 $self->{_depth} = 0;
356             }
357             else {
358 265         480 $self->{_depth} = $self->parent->depth + 1;
359             }
360              
361 293         644 $_->_fix_depth for $self->children;
362              
363 293         510 return $self;
364             }
365              
366             sub _strip_options {
367 224     224   312 my $self = shift;
368 224         324 my ($params) = @_;
369              
370 224 100 100     1424 if ( @$params && !blessed($params->[0]) && ref($params->[0]) eq 'HASH' ) {
      100        
371 141         314 return shift @$params;
372             }
373             else {
374 83         238 return {};
375             }
376             }
377              
378             # -----------------------------------------------
379              
380             sub format_node
381             {
382 0     0 1   my($self, $options, $node) = @_;
383 0           my($s) = $node -> value;
384 0 0         $s .= '. Attributes: ' . $self -> hashref2string($node -> meta) if (! $$options{no_attributes});
385              
386 0           return $s;
387              
388             } # End of format_node.
389              
390             # -----------------------------------------------
391              
392             sub hashref2string
393             {
394 0     0 1   my($self, $hashref) = @_;
395 0   0       $hashref ||= {};
396              
397 0           return '{' . join(', ', map{qq|$_ => "$$hashref{$_}"|} sort keys %$hashref) . '}';
  0            
398              
399             } # End of hashref2string.
400              
401             # -----------------------------------------------
402              
403             sub node2string
404             {
405 0     0 1   my($self, $options, $node, $vert_dashes) = @_;
406 0           my($depth) = $node -> depth;
407 0           my(@siblings) = $node -> parent -> children;
408 0           my($sibling_count) = scalar @siblings; # Warning: Don't combine this with the previous line.
409 0           my($offset) = ' ' x 4;
410 0 0         my(@indent) = map{$$vert_dashes[$_] || $offset} 0 .. $depth - 1;
  0            
411 0 0         @$vert_dashes =
412             (
413             @indent,
414             ($sibling_count == 0 ? $offset : ' |'),
415             );
416              
417 0           my(@i) = $node -> parent -> get_index_for($node);
418 0           my(@indexes) = $node -> parent -> get_index_for($node);
419 0 0         $$vert_dashes[$depth] = ($offset . ' ') if ($sibling_count == ($indexes[0] + 1) );
420              
421 0 0         return join('', @indent[1 .. $#indent]) . ($depth ? ' |--- ' : '') . $self -> format_node($options, $node);
422              
423             } # End of node2string.
424              
425             # ------------------------------------------------
426              
427             sub tree2string
428             {
429 0     0 1   my($self, $options) = @_;
430 0   0       $options ||= {};
431 0   0       $$options{no_attributes} ||= 0;
432 0           my(@nodes) = $self -> traverse;
433              
434 0           my(@out);
435             my(@vert_dashes);
436              
437 0           for my $i (0 .. $#nodes)
438             {
439 0           push @out, $self -> node2string($options, $nodes[$i], \@vert_dashes);
440             }
441              
442 0           return [@out];
443              
444             } # End of tree2string.
445              
446             # -----------------------------------------------
447              
448             1;
449             __END__