File Coverage

blib/lib/Pod/Abstract/Tree.pm
Criterion Covered Total %
statement 58 95 61.0
branch 9 20 45.0
condition 3 3 100.0
subroutine 7 10 70.0
pod 8 9 88.8
total 85 137 62.0


line stmt bran cond sub pod time code
1             package Pod::Abstract::Tree;
2 6     6   44 use strict;
  6         12  
  6         7688  
3              
4             our $VERSION = '0.26';
5              
6             =head1 NAME
7              
8             Pod::Abstract::Tree - Manage a level of Pod document tree Nodes.
9              
10             =head1 DESCRIPTION
11              
12             Pod::Abstract::Tree keeps track of a set of Pod::Abstract::Node
13             elements, and allows manipulation of that list of elements. Elements
14             are stored in an ordered set - a single node can appear once only in a
15             single document tree, so inserting a node at a point will also remove
16             it from it's previous location.
17              
18             This is an internal class to Pod::Abstract::Node, and should not
19             generally be used externally.
20              
21             =head1 METHODS
22              
23             =cut
24              
25             sub new {
26 579     579 0 1039 my $class = shift;
27              
28 579         3248 return bless {
29             id_map => { },
30             nodes => [ ],
31             }, $class;
32             }
33              
34             =head2 detach
35              
36             $tree->detach($node);
37              
38             Unparent the C<$node> from C<$tree>. All other elements will be
39             shifted to fill the empty spot.
40              
41             =cut
42              
43             sub detach {
44 24     24 1 41 my $self = shift;
45 24         44 my $node = shift;
46 24         44 my $id_map = $self->{id_map};
47 24         55 my $serial = $node->serial;
48            
49 24         73 my $idx = $id_map->{$node->serial};
50 24 100       101 return 0 unless defined $idx;
51             die "Wrong node ($idx/$serial)! Got: ", $self->{nodes}[$idx]->serial
52 12 50       32 unless $self->{nodes}[$idx]->serial == $serial;
53            
54             # Node is defined, remove it:
55 12         24 splice @{$self->{nodes}},$idx,1;
  12         32  
56 12         32 delete $id_map->{$serial};
57            
58             # Move all following nodes back by 1
59 12         23 my $length = scalar @{$self->{nodes}};
  12         28  
60 12         32 for(my $i = $idx; $i < $length; $i ++) {
61 40         88 my $s = $self->{nodes}[$i]->serial;
62 40         116 $id_map->{$s} --;
63             }
64            
65             # Node now has no parent.
66 12         55 $node->parent(undef);
67 12         27 return $node;
68             }
69              
70             =head2 push
71              
72             Add an element to the end of the node list.
73              
74             =cut
75              
76             sub push {
77 462     462 1 764 my $self = shift;
78 462         765 my $node = shift;
79            
80 462 50       1063 if($node->attached) {
81 0         0 $node->detach;
82 0         0 warn "Implicit detach of node on push";
83             }
84            
85 462         1150 my $s = $node->serial;
86 462         769 push @{$self->{nodes}}, $node;
  462         1204  
87 462         765 $self->{id_map}{$s} = $#{$self->{nodes}};
  462         1648  
88 462         1246 return 1;
89             }
90              
91             =head2 pop
92              
93             Remove an element from the end of the node list.
94              
95             =cut
96              
97             sub pop {
98 0     0 1 0 my $self = shift;
99            
100 0         0 my $node = pop @{$self->{nodes}};
  0         0  
101 0         0 my $s = $node->serial;
102 0         0 delete $self->{id_map}{$s};
103 0         0 $node->parent(undef);
104              
105 0         0 return $node;
106             }
107              
108             =head2 insert_before
109              
110             $tree->insert_before($target,$node);
111              
112             Insert C<$node> before C<$target>. Both must be children of C<$tree>
113              
114             =cut
115              
116             sub insert_before {
117 0     0 1 0 my $self = shift;
118 0         0 my $target = shift;
119 0         0 my $node = shift;
120            
121 0         0 my $idx = $self->{id_map}{$target->serial};
122 0 0       0 return 0 unless defined $idx;
123            
124 0         0 splice(@{$self->{nodes}}, $idx, 0, $node);
  0         0  
125 0         0 $self->{id_map}{$node->serial} = $idx;
126              
127             # Push all following nodes forwards by 1.
128 0         0 my $length = scalar @{$self->{nodes}};
  0         0  
129 0         0 for( my $i = $idx + 1; $i < $length; $i ++) {
130 0         0 my $s = $self->{nodes}[$i]->serial;
131 0         0 $self->{id_map}{$s} ++;
132             }
133 0         0 return 1;
134             }
135              
136             =head2 insert_after
137              
138             $tree->insert_after($target,$node);
139              
140             Insert C<$node> after C<$target>. Both must be children of C<$tree>
141              
142             =cut
143              
144             sub insert_after {
145 0     0 1 0 my $self = shift;
146 0         0 my $target = shift;
147 0         0 my $node = shift;
148            
149 0         0 my $idx = $self->{id_map}{$target->serial};
150 0 0       0 die $target->serial, " not in index ", join(", ", keys %{$self->{id_map}})
  0         0  
151             unless defined $idx;
152 0         0 my $last_idx = $#{$self->{nodes}};
  0         0  
153 0 0       0 if($idx == $last_idx) {
154 0         0 return $self->push($node);
155             } else {
156 0         0 my $before_target = $self->{nodes}[$idx + 1];
157 0         0 return $self->insert_before($before_target, $node);
158             }
159             }
160              
161             =head2 unshift
162              
163             Remove the first node from the node list and return it.
164              
165             Unshift takes linear time - it has to relocate every other element in
166             id_map so that they stay in line.
167              
168             =cut
169              
170             sub unshift {
171 3     3 1 6 my $self = shift;
172 3         7 my $node = shift;
173              
174 3 50       8 if($node->attached) {
175 0         0 $node->detach;
176 0         0 warn "Implicit detach of node on unshift";
177             }
178            
179 3         7 my $s = $node->serial;
180 3         7 foreach my $k (keys %{$self->{id_map}}) {
  3         14  
181 33         59 $self->{id_map}{$k} ++;
182             }
183 3         8 unshift @{$self->{nodes}}, $node;
  3         8  
184 3         9 $self->{id_map}{$s} = 0;
185 3         9 return 1;
186             }
187              
188             =head2 children
189              
190             Returns the in-order node list.
191              
192             =cut
193              
194             sub children {
195 3291     3291 1 5144 my $self = shift;
196 3291         4777 return @{$self->{nodes}};
  3291         9663  
197             }
198              
199             =head2 index_relative
200              
201             my $node = $tree->index_relative($target, $offset);
202              
203             This method will return a node at an offset of $offset (which may be
204             negative) from this tree structure. If there is no such node, undef
205             will be returned. For example, an offset of 1 will give the following
206             element of $node.
207              
208             =cut
209              
210             sub index_relative {
211 13     13 1 44 my $self = shift;
212 13         19 my $node = shift;
213 13         22 my $index = shift;
214 13         34 my $serial = $node->serial;
215            
216 13 50       34 die "index_relative called with unattached node"
217             unless $node->attached;
218 13         67 my $node_idx = $self->{id_map}{$serial};
219 13 50       33 die "index_relative called with node not present in tree"
220             unless defined $node_idx;
221 13         22 my $real_index = $node_idx + $index;
222 13         116 my $n_nodes = scalar @{$self->{nodes}};
  13         29  
223 13 100 100     57 if($real_index >= 0 && $real_index < $n_nodes) {
224 11         40 return $self->{nodes}[$real_index];
225             } else {
226 2         9 return undef;
227             }
228             }
229              
230             =head1 AUTHOR
231              
232             Ben Lilburne
233              
234             =head1 COPYRIGHT AND LICENSE
235              
236             Copyright (C) 2009-2025 Ben Lilburne
237              
238             This program is free software; you can redistribute it and/or modify
239             it under the same terms as Perl itself.
240              
241             =cut
242              
243             1;