File Coverage

blib/lib/Pod/Elemental/Transformer/Nester.pm
Criterion Covered Total %
statement 43 44 97.7
branch 11 14 78.5
condition 2 6 33.3
subroutine 7 7 100.0
pod 0 1 0.0
total 63 72 87.5


line stmt bran cond sub pod time code
1             package Pod::Elemental::Transformer::Nester;
2             # ABSTRACT: group the document into sections
3             $Pod::Elemental::Transformer::Nester::VERSION = '0.103005';
4 2     2   3367 use Moose;
  2         5  
  2         14  
5             with 'Pod::Elemental::Transformer';
6              
7             #pod =head1 OVERVIEW
8             #pod
9             #pod The Nester transformer is meant to find potential container elements and make
10             #pod them into actual containers. It works by being told what elements may be made
11             #pod into containers and what subsequent elements they should allow to be stuffed
12             #pod into them.
13             #pod
14             #pod For example, given the following nester:
15             #pod
16             #pod use Pod::Elemental::Selectors qw(s_command s_flat);
17             #pod
18             #pod my $nester = Pod::Elemental::Transformer::Nester->new({
19             #pod top_selector => s_command('head1'),
20             #pod content_selectors => [
21             #pod s_command([ qw(head2 head3 head4) ]),
22             #pod s_flat,
23             #pod ],
24             #pod });
25             #pod
26             #pod ..then when we apply the transformation:
27             #pod
28             #pod $nester->transform_node($document);
29             #pod
30             #pod ...the nester will find all C<=head1> elements in the top-level of the
31             #pod document. It will ensure that they are represented by objects that perform the
32             #pod Pod::Elemental::Node role, and then it will move all subsequent elements
33             #pod matching the C<content_selectors> into the container.
34             #pod
35             #pod So, if we start with this input:
36             #pod
37             #pod =head1 Header
38             #pod =head2 Subheader
39             #pod Pod5::Ordinary <some content>
40             #pod =head1 New Header
41             #pod
42             #pod The nester will convert its structure to look like this:
43             #pod
44             #pod =head1 Header
45             #pod =head2 Subheader
46             #pod Pod5::Ordinary <some content>
47             #pod =head1 New Header
48             #pod
49             #pod Once an element is reached that does not pass the content selectors, the
50             #pod nesting ceases until the next potential container.
51             #pod
52             #pod =cut
53              
54 2     2   13642 use MooseX::Types::Moose qw(ArrayRef CodeRef);
  2         4  
  2         23  
55              
56 2     2   11832 use Pod::Elemental::Element::Nested;
  2         8  
  2         102  
57 2     2   18 use Pod::Elemental::Selectors -all;
  2         5  
  2         22  
58              
59 2     2   1300 use namespace::autoclean;
  2         6  
  2         15  
60              
61             #pod =attr top_selector
62             #pod
63             #pod This attribute must be a coderef (presumably made from
64             #pod Pod::Elemental::Selectors) that will test elements in the transformed node and
65             #pod return true if the element is a potential new container.
66             #pod
67             #pod =cut
68              
69             has top_selector => (
70             is => 'ro',
71             isa => CodeRef,
72             required => 1,
73             );
74              
75             #pod =attr content_selectors
76             #pod
77             #pod This attribute must be an arrayref of coderefs (again presumably made from
78             #pod Pod::Elemental::Selectors) that will test whether paragraphs subsequent to the
79             #pod top-level container may be moved under the container.
80             #pod
81             #pod =cut
82              
83             has content_selectors => (
84             is => 'ro',
85             isa => ArrayRef[ CodeRef ],
86             required => 1,
87             );
88              
89             sub _is_containable {
90 25     25   55 my ($self, $para) = @_;
91              
92 25         41 for my $sel (@{ $self->content_selectors }) {
  25         766  
93 44 100       1005 return 1 if $sel->($para);
94             }
95              
96 4         16 return;
97             }
98              
99             sub transform_node {
100 2     2 0 21 my ($self, $node) = @_;
101              
102             # We used to say (length -2) because "if we're already at the last element,
103             # we can't nest anything -- there's nothing subsequent to the potential
104             # top-level element to nest!" -- my (rjbs's) reasoning in 2009.
105             #
106             # This was an unneeded optimization, and therefore stupid. Worse, it was a
107             # bug. It meant that a nestable element that was the last element in a
108             # sequence wouldn't be upgraded to a Nested element, so later munging could
109             # barf. In fact, that's what happened in [rt.cpan.org #69189]
110             # -- rjbs, 2012-05-04
111 2         5 PASS: for my $i (0 .. @{ $node->children }- 1) {
  2         72  
112 10 100       782 last PASS if $i >= @{ $node->children };
  10         297  
113              
114 8         211 my $para = $node->children->[ $i ];
115 8 100       263 next unless $self->top_selector->($para);
116              
117 6 50 33     31 if (s_command(undef, $para) and not s_node($para)) {
118 6         500 $para = $node->children->[ $i ] = Pod::Elemental::Element::Nested->new({
119             command => $para->command,
120             content => $para->content,
121             });
122             }
123              
124 6 50 33     29 if (! s_node($para) or @{ $para->children }) {
  6         803  
125 0         0 confess "can't use $para as the top of a nesting";
126             }
127              
128 6         15 my @to_nest;
129 6         13 NEST: for my $j ($i+1 .. @{ $node->children } - 1) {
  6         174  
130 25 100       687 last unless $self->_is_containable($node->children->[ $j ]);
131 21         326 push @to_nest, $j;
132             }
133              
134 6 50       196 if (@to_nest) {
135             my @to_nest_elem =
136 6         15 splice @{ $node->children }, $to_nest[0], scalar(@to_nest);
  6         180  
137              
138 6         13 push @{ $para->children }, @to_nest_elem;
  6         167  
139 6         107 next PASS;
140             }
141             }
142              
143 2         8 return $node;
144             }
145              
146             __PACKAGE__->meta->make_immutable;
147              
148             1;
149              
150             __END__
151              
152             =pod
153              
154             =encoding UTF-8
155              
156             =head1 NAME
157              
158             Pod::Elemental::Transformer::Nester - group the document into sections
159              
160             =head1 VERSION
161              
162             version 0.103005
163              
164             =head1 OVERVIEW
165              
166             The Nester transformer is meant to find potential container elements and make
167             them into actual containers. It works by being told what elements may be made
168             into containers and what subsequent elements they should allow to be stuffed
169             into them.
170              
171             For example, given the following nester:
172              
173             use Pod::Elemental::Selectors qw(s_command s_flat);
174              
175             my $nester = Pod::Elemental::Transformer::Nester->new({
176             top_selector => s_command('head1'),
177             content_selectors => [
178             s_command([ qw(head2 head3 head4) ]),
179             s_flat,
180             ],
181             });
182              
183             ..then when we apply the transformation:
184              
185             $nester->transform_node($document);
186              
187             ...the nester will find all C<=head1> elements in the top-level of the
188             document. It will ensure that they are represented by objects that perform the
189             Pod::Elemental::Node role, and then it will move all subsequent elements
190             matching the C<content_selectors> into the container.
191              
192             So, if we start with this input:
193              
194             =head1 Header
195             =head2 Subheader
196             Pod5::Ordinary <some content>
197             =head1 New Header
198              
199             The nester will convert its structure to look like this:
200              
201             =head1 Header
202             =head2 Subheader
203             Pod5::Ordinary <some content>
204             =head1 New Header
205              
206             Once an element is reached that does not pass the content selectors, the
207             nesting ceases until the next potential container.
208              
209             =head1 ATTRIBUTES
210              
211             =head2 top_selector
212              
213             This attribute must be a coderef (presumably made from
214             Pod::Elemental::Selectors) that will test elements in the transformed node and
215             return true if the element is a potential new container.
216              
217             =head2 content_selectors
218              
219             This attribute must be an arrayref of coderefs (again presumably made from
220             Pod::Elemental::Selectors) that will test whether paragraphs subsequent to the
221             top-level container may be moved under the container.
222              
223             =head1 AUTHOR
224              
225             Ricardo SIGNES <rjbs@cpan.org>
226              
227             =head1 COPYRIGHT AND LICENSE
228              
229             This software is copyright (c) 2020 by Ricardo SIGNES.
230              
231             This is free software; you can redistribute it and/or modify it under
232             the same terms as the Perl 5 programming language system itself.
233              
234             =cut