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   83742 use 5.006;
  21         78  
4              
5 21     21   107 use base 'Tree::Fast';
  21         35  
  21         9925  
6 21     21   135 use strict;
  21         40  
  21         565  
7 21     21   102 use warnings;
  21         38  
  21         791  
8              
9             our $VERSION = '1.14';
10              
11 21     21   112 use Scalar::Util qw( blessed refaddr weaken );
  21         34  
  21         31112  
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 719 sub QUIET { return $error_handlers{ 'quiet' } }
35 3     3 1 10 sub WARN { return $error_handlers{ 'warn' } }
36 4     4 1 12 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   323 my $self = shift;
43              
44 239         638 $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         840 $self->{_last_error} = undef;
52              
53             $self->{_handlers} = {
54 239         849 add_child => [],
55             remove_child => [],
56             value => [],
57             };
58              
59             $self->{_root} = undef,
60 239         690 $self->_set_root( $self );
61              
62 239         409 return $self;
63             }
64              
65             # These are the behaviors
66              
67             sub add_child {
68 197     197 1 12716 my $self = shift;
69 197         368 my @nodes = @_;
70              
71 197         483 $self->last_error( undef );
72              
73 197         466 my $options = $self->_strip_options( \@nodes );
74              
75 197 100       439 unless ( @nodes ) {
76 1         3 return $self->error( "add_child(): No children passed in" );
77             }
78              
79 196 100       467 if ( defined $options->{at}) {
80 13         39 my $num_children = () = $self->children;
81 13 100       87 unless ( $options->{at} =~ /^-?\d+$/ ) {
82 2         10 return $self->error(
83             "add_child(): '$options->{at}' is not a legal index"
84             );
85             }
86              
87 11 100 100     71 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         326 for my $node ( @nodes ) {
95 221 100 100     1117 unless ( blessed($node) && $node->isa( __PACKAGE__ ) ) {
96 4         20 return $self->error( "add_child(): '$node' is not a " . __PACKAGE__ );
97             }
98              
99 217 100       480 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       535 if ( $node->parent ) {
104 1         3 return $self->error( "add_child(): Cannot add a child to another parent" );
105             }
106             }
107              
108 185         554 $self->SUPER::add_child( $options, @nodes );
109              
110 185         274 for my $node ( @nodes ) {
111 213         356 $node->_set_root( $self->root );
112 213         425 $node->_fix_depth;
113             }
114              
115 185         412 $self->_fix_height;
116 185         400 $self->_fix_width;
117              
118 185         462 $self->event( 'add_child', $self, @_ );
119              
120 185         398 return $self;
121             }
122              
123             sub remove_child {
124 27     27 1 5004 my $self = shift;
125 27         54 my @nodes = @_;
126              
127 27         77 $self->last_error( undef );
128              
129 27         72 my $options = $self->_strip_options( \@nodes );
130              
131 27 100       75 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       66 if ( !defined( $proto ) ) {
139 1         3 return $self->error( "remove_child(): 'undef' is out-of-bounds" );
140             }
141              
142 30 100       93 if ( !blessed( $proto ) ) {
143 10 100       59 unless ( $proto =~ /^-?\d+$/ ) {
144 1         4 return $self->error( "remove_child(): '$proto' is not a legal index" );
145             }
146              
147 9 100 100     43 if ( $proto >= $num_children || $num_children + $proto <= 0 ) {
148 2         5 return $self->error( "remove_child(): '$proto' is out-of-bounds" );
149             }
150              
151 7         16 push @indices, $proto;
152             }
153             else {
154 20         55 my ($index) = $self->get_index_for( $proto );
155              
156 20 100       56 unless ( defined $index ) {
157 1         3 return $self->error( "remove_child(): '$proto' not found" );
158             }
159              
160 19         44 push @indices, $index;
161             }
162             }
163              
164 21         114 my @return = $self->SUPER::remove_child( $options, @indices );
165              
166 21         48 for my $node ( @return ) {
167 26         69 $node->_set_root( $node );
168 26         112 $node->_fix_depth;
169             }
170              
171 21         53 $self->_fix_height;
172 21         50 $self->_fix_width;
173              
174 21         58 $self->event( 'remove_child', $self, @_ );
175              
176 21         76 return @return;
177             }
178              
179             sub add_event_handler {
180 2     2 1 553 my $self = shift;
181 2         4 my ($opts) = @_;
182              
183 2         10 while ( my ($type,$handler) = each %$opts ) {
184 3         4 push @{$self->{_handlers}{$type}}, $handler;
  3         13  
185             }
186              
187 2         6 return $self;
188             }
189              
190             sub event {
191 5196     5196 1 6084 my $self = shift;
192 5196         7649 my ( $type, @args ) = @_;
193              
194 5196         5723 foreach my $handler ( @{$self->{_handlers}{$type}} ) {
  5196         8235  
195 4         18 $handler->( @args );
196             }
197              
198 5196         8152 $self->parent->event( @_ );
199              
200 5196         6470 return $self;
201             }
202              
203             # These are the state-queries
204              
205             sub is_root {
206 335     335 1 19698 my $self = shift;
207 335         676 return !$self->parent;
208             }
209              
210             sub is_leaf {
211 41     41 1 11759 my $self = shift;
212 41         90 return $self->height == 1;
213             }
214              
215             sub has_child {
216 14     14 1 565 my $self = shift;
217 14         27 my @nodes = @_;
218              
219 14         43 my @children = $self->children;
220 14         36 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  21         85  
221              
222 14         29 my $rv = 1;
223             $rv &&= exists $temp{refaddr($_)}
224 14   100     85 for @nodes;
225 14         75 return $rv;
226             }
227              
228             sub get_index_for {
229 24     24 1 500 my $self = shift;
230 24         45 my @nodes = @_;
231              
232 24         57 my @children = $self->children;
233 24         73 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  42         158  
234              
235 24         56 return map { $temp{refaddr($_)} } @nodes;
  24         112  
236             }
237              
238             # These are the smart accessors
239              
240             sub root {
241 1229     1229 1 2219 my $self = shift;
242 1229         2528 return $self->{_root};
243             }
244              
245             sub _set_root {
246 532     532   672 my $self = shift;
247              
248 532         736 $self->{_root} = shift;
249 532         1370 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         633 for grep { $_ } @{$self->{_children}};
  180         395  
  532         1046  
258              
259 532         775 return $self;
260             }
261              
262             for my $name ( qw( height width depth ) ) {
263 21     21   184 no strict 'refs';
  21         41  
  21         1398  
264              
265             *{ __PACKAGE__ . "::${name}" } = sub {
266 21     21   142 use strict;
  21         52  
  21         22761  
267 11163     11163   53475 my $self = shift;
268 11163         20347 return $self->{"_${name}"};
269             };
270             }
271              
272             sub size {
273 75     75 1 18269 my $self = shift;
274 75         122 my $size = 1;
275 75         183 $size += $_->size for $self->children;
276 75         256 return $size;
277             }
278              
279             sub set_value {
280 6     6 1 4605 my $self = shift;
281              
282 6         24 my $old_value = $self->value();
283 6         30 $self->SUPER::set_value( @_ );
284              
285 6         12 $self->event( 'value', $self, $old_value, $self->value );
286              
287 6         19 return $self;
288             }
289              
290             # These are the error-handling functions
291              
292             sub error_handler {
293 41     41 1 496 my $self = shift;
294              
295 41 100       129 if ( !blessed( $self ) ) {
296 2         2 my $old = $ERROR_HANDLER;
297 2 100       5 $ERROR_HANDLER = shift if @_;
298 2         5 return $old;
299             }
300              
301 39         71 my $root = $self->root;
302 39         45 my $old = $root->{_error_handler};
303 39 100       74 $root->{_error_handler} = shift if @_;
304 39         79 return $old;
305             }
306              
307             sub error {
308 27     27 1 1551 my $self = shift;
309 27         68 my @args = @_;
310              
311 27         54 return $self->error_handler->( $self, @_ );
312             }
313              
314             sub last_error {
315 275     275 1 364 my $self = shift;
316 275 100       728 $self->root->{_last_error} = shift if @_;
317 275         451 return $self->root->{_last_error};
318             }
319              
320             # These are private convenience methods
321              
322             sub _fix_height {
323 5214     5214   6050 my $self = shift;
324              
325 5214         5737 my $height = 1;
326 5214         7678 for my $child ($self->children) {
327 5341         7748 my $temp_height = $child->height + 1;
328 5341 100       9474 $height = $temp_height if $height < $temp_height;
329             }
330              
331 5214         6645 $self->{_height} = $height;
332              
333 5214         8356 $self->parent->_fix_height;
334              
335 5214         5955 return $self;
336             }
337              
338             sub _fix_width {
339 5214     5214   6302 my $self = shift;
340              
341 5214         5639 my $width = 0;
342 5214         7648 $width += $_->width for $self->children;
343              
344 5214 100       7898 $self->{_width} = $width ? $width : 1;
345              
346 5214         8120 $self->parent->_fix_width;
347              
348 5214         5883 return $self;
349             }
350              
351             sub _fix_depth {
352 293     293   396 my $self = shift;
353              
354 293 100       490 if ( $self->is_root ) {
355 28         53 $self->{_depth} = 0;
356             }
357             else {
358 265         474 $self->{_depth} = $self->parent->depth + 1;
359             }
360              
361 293         655 $_->_fix_depth for $self->children;
362              
363 293         497 return $self;
364             }
365              
366             sub _strip_options {
367 224     224   313 my $self = shift;
368 224         334 my ($params) = @_;
369              
370 224 100 100     1460 if ( @$params && !blessed($params->[0]) && ref($params->[0]) eq 'HASH' ) {
      100        
371 141         309 return shift @$params;
372             }
373             else {
374 83         207 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__