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   72943 use 5.006;
  21         82  
4              
5 21     21   96 use base 'Tree::Fast';
  21         31  
  21         9571  
6 21     21   117 use strict;
  21         34  
  21         524  
7 21     21   87 use warnings;
  21         32  
  21         714  
8              
9             our $VERSION = '1.15';
10              
11 21     21   92 use Scalar::Util qw( blessed refaddr weaken );
  21         31  
  21         28943  
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 739 sub QUIET { return $error_handlers{ 'quiet' } }
35 3     3 1 10 sub WARN { return $error_handlers{ 'warn' } }
36 4     4 1 13 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   270 my $self = shift;
43              
44 239         525 $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         707 $self->{_last_error} = undef;
52              
53             $self->{_handlers} = {
54 239         663 add_child => [],
55             remove_child => [],
56             value => [],
57             };
58              
59             $self->{_root} = undef,
60 239         567 $self->_set_root( $self );
61              
62 239         314 return $self;
63             }
64              
65             # These are the behaviors
66              
67             sub add_child {
68 197     197 1 10788 my $self = shift;
69 197         302 my @nodes = @_;
70              
71 197         409 $self->last_error( undef );
72              
73 197         426 my $options = $self->_strip_options( \@nodes );
74              
75 197 100       367 unless ( @nodes ) {
76 1         2 return $self->error( "add_child(): No children passed in" );
77             }
78              
79 196 100       353 if ( defined $options->{at}) {
80 13         32 my $num_children = () = $self->children;
81 13 100       72 unless ( $options->{at} =~ /^-?\d+$/ ) {
82 2         6 return $self->error(
83             "add_child(): '$options->{at}' is not a legal index"
84             );
85             }
86              
87 11 100 100     53 if ( $options->{at} > $num_children ||
88             $num_children + $options->{at} < 0
89             ) {
90 2         7 return $self->error( "add_child(): '$options->{at}' is out-of-bounds" );
91             }
92             }
93              
94 192         287 for my $node ( @nodes ) {
95 221 100 100     875 unless ( blessed($node) && $node->isa( __PACKAGE__ ) ) {
96 4         17 return $self->error( "add_child(): '$node' is not a " . __PACKAGE__ );
97             }
98              
99 217 100       388 if ( $node->root eq $self->root ) {
100 2         3 return $self->error( "add_child(): Cannot add a node in the tree back into the tree" );
101             }
102              
103 215 100       454 if ( $node->parent ) {
104 1         1 return $self->error( "add_child(): Cannot add a child to another parent" );
105             }
106             }
107              
108 185         479 $self->SUPER::add_child( $options, @nodes );
109              
110 185         240 for my $node ( @nodes ) {
111 213         282 $node->_set_root( $self->root );
112 213         363 $node->_fix_depth;
113             }
114              
115 185         354 $self->_fix_height;
116 185         351 $self->_fix_width;
117              
118 185         391 $self->event( 'add_child', $self, @_ );
119              
120 185         347 return $self;
121             }
122              
123             sub remove_child {
124 27     27 1 4571 my $self = shift;
125 27         53 my @nodes = @_;
126              
127 27         68 $self->last_error( undef );
128              
129 27         65 my $options = $self->_strip_options( \@nodes );
130              
131 27 100       66 unless ( @nodes ) {
132 1         2 return $self->error( "remove_child(): Nothing to remove" );
133             }
134              
135 26         48 my @indices;
136 26         68 my $num_children = () = $self->children;
137 26         53 foreach my $proto (@nodes) {
138 31 100       63 if ( !defined( $proto ) ) {
139 1         2 return $self->error( "remove_child(): 'undef' is out-of-bounds" );
140             }
141              
142 30 100       82 if ( !blessed( $proto ) ) {
143 10 100       53 unless ( $proto =~ /^-?\d+$/ ) {
144 1         4 return $self->error( "remove_child(): '$proto' is not a legal index" );
145             }
146              
147 9 100 100     35 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         14 push @indices, $proto;
152             }
153             else {
154 20         48 my ($index) = $self->get_index_for( $proto );
155              
156 20 100       46 unless ( defined $index ) {
157 1         4 return $self->error( "remove_child(): '$proto' not found" );
158             }
159              
160 19         40 push @indices, $index;
161             }
162             }
163              
164 21         88 my @return = $self->SUPER::remove_child( $options, @indices );
165              
166 21         46 for my $node ( @return ) {
167 26         56 $node->_set_root( $node );
168 26         50 $node->_fix_depth;
169             }
170              
171 21         51 $self->_fix_height;
172 21         45 $self->_fix_width;
173              
174 21         51 $self->event( 'remove_child', $self, @_ );
175              
176 21         72 return @return;
177             }
178              
179             sub add_event_handler {
180 2     2 1 459 my $self = shift;
181 2         4 my ($opts) = @_;
182              
183 2         8 while ( my ($type,$handler) = each %$opts ) {
184 3         5 push @{$self->{_handlers}{$type}}, $handler;
  3         8  
185             }
186              
187 2         8 return $self;
188             }
189              
190             sub event {
191 5196     5196 1 4753 my $self = shift;
192 5196         6319 my ( $type, @args ) = @_;
193              
194 5196         4584 foreach my $handler ( @{$self->{_handlers}{$type}} ) {
  5196         6618  
195 4         9 $handler->( @args );
196             }
197              
198 5196         7258 $self->parent->event( @_ );
199              
200 5196         5514 return $self;
201             }
202              
203             # These are the state-queries
204              
205             sub is_root {
206 335     335 1 16396 my $self = shift;
207 335         593 return !$self->parent;
208             }
209              
210             sub is_leaf {
211 41     41 1 9709 my $self = shift;
212 41         86 return $self->height == 1;
213             }
214              
215             sub has_child {
216 14     14 1 478 my $self = shift;
217 14         25 my @nodes = @_;
218              
219 14         39 my @children = $self->children;
220 14         38 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  21         86  
221              
222 14         25 my $rv = 1;
223             $rv &&= exists $temp{refaddr($_)}
224 14   100     93 for @nodes;
225 14         60 return $rv;
226             }
227              
228             sub get_index_for {
229 24     24 1 438 my $self = shift;
230 24         40 my @nodes = @_;
231              
232 24         49 my @children = $self->children;
233 24         59 my %temp = map { refaddr($children[$_]) => $_ } 0 .. $#children;
  42         145  
234              
235 24         48 return map { $temp{refaddr($_)} } @nodes;
  24         100  
236             }
237              
238             # These are the smart accessors
239              
240             sub root {
241 1229     1229 1 1803 my $self = shift;
242 1229         2086 return $self->{_root};
243             }
244              
245             sub _set_root {
246 532     532   564 my $self = shift;
247              
248 532         617 $self->{_root} = shift;
249 532         1151 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         540 for grep { $_ } @{$self->{_children}};
  180         335  
  532         941  
258              
259 532         611 return $self;
260             }
261              
262             for my $name ( qw( height width depth ) ) {
263 21     21   165 no strict 'refs';
  21         53  
  21         1324  
264              
265             *{ __PACKAGE__ . "::${name}" } = sub {
266 21     21   114 use strict;
  21         55  
  21         21265  
267 11163     11163   44710 my $self = shift;
268 11163         16506 return $self->{"_${name}"};
269             };
270             }
271              
272             sub size {
273 75     75 1 15339 my $self = shift;
274 75         105 my $size = 1;
275 75         196 $size += $_->size for $self->children;
276 75         227 return $size;
277             }
278              
279             sub set_value {
280 6     6 1 6260 my $self = shift;
281              
282 6         20 my $old_value = $self->value();
283 6         28 $self->SUPER::set_value( @_ );
284              
285 6         14 $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 451 my $self = shift;
294              
295 41 100       105 if ( !blessed( $self ) ) {
296 2         3 my $old = $ERROR_HANDLER;
297 2 100       4 $ERROR_HANDLER = shift if @_;
298 2         5 return $old;
299             }
300              
301 39         66 my $root = $self->root;
302 39         46 my $old = $root->{_error_handler};
303 39 100       66 $root->{_error_handler} = shift if @_;
304 39         69 return $old;
305             }
306              
307             sub error {
308 27     27 1 1608 my $self = shift;
309 27         69 my @args = @_;
310              
311 27         51 return $self->error_handler->( $self, @_ );
312             }
313              
314             sub last_error {
315 275     275 1 312 my $self = shift;
316 275 100       622 $self->root->{_last_error} = shift if @_;
317 275         358 return $self->root->{_last_error};
318             }
319              
320             # These are private convenience methods
321              
322             sub _fix_height {
323 5214     5214   4965 my $self = shift;
324              
325 5214         4809 my $height = 1;
326 5214         6318 for my $child ($self->children) {
327 5341         6090 my $temp_height = $child->height + 1;
328 5341 100       7959 $height = $temp_height if $height < $temp_height;
329             }
330              
331 5214         5337 $self->{_height} = $height;
332              
333 5214         7009 $self->parent->_fix_height;
334              
335 5214         4858 return $self;
336             }
337              
338             sub _fix_width {
339 5214     5214   4854 my $self = shift;
340              
341 5214         4580 my $width = 0;
342 5214         6591 $width += $_->width for $self->children;
343              
344 5214 100       6761 $self->{_width} = $width ? $width : 1;
345              
346 5214         6699 $self->parent->_fix_width;
347              
348 5214         4837 return $self;
349             }
350              
351             sub _fix_depth {
352 293     293   329 my $self = shift;
353              
354 293 100       403 if ( $self->is_root ) {
355 28         40 $self->{_depth} = 0;
356             }
357             else {
358 265         379 $self->{_depth} = $self->parent->depth + 1;
359             }
360              
361 293         589 $_->_fix_depth for $self->children;
362              
363 293         420 return $self;
364             }
365              
366             sub _strip_options {
367 224     224   248 my $self = shift;
368 224         284 my ($params) = @_;
369              
370 224 100 100     1207 if ( @$params && !blessed($params->[0]) && ref($params->[0]) eq 'HASH' ) {
      100        
371 141         257 return shift @$params;
372             }
373             else {
374 83         173 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__