File Coverage

blib/lib/PPIx/Utilities/Node.pm
Criterion Covered Total %
statement 86 86 100.0
branch 29 32 90.6
condition 20 25 80.0
subroutine 13 13 100.0
pod 1 1 100.0
total 149 157 94.9


line stmt bran cond sub pod time code
1             package PPIx::Utilities::Node;
2              
3 1     1   226504 use 5.006001;
  1         5  
  1         48  
4 1     1   7 use strict;
  1         3  
  1         39  
5 1     1   6 use warnings;
  1         10  
  1         56  
6              
7             our $VERSION = '1.001000';
8              
9 1     1   6 use Readonly;
  1         2  
  1         73  
10              
11              
12 1     1   8 use PPI::Document::Fragment 1.208 qw< >;
  1         35  
  1         31  
13 1     1   6 use Scalar::Util qw< refaddr >;
  1         2  
  1         57  
14              
15              
16 1     1   3148 use PPIx::Utilities::Exception::Bug qw< >;
  1         3  
  1         29  
17              
18              
19 1     1   12 use base 'Exporter';
  1         2  
  1         1042  
20              
21             Readonly::Array our @EXPORT_OK => qw<
22             split_ppi_node_by_namespace
23             >;
24              
25              
26             sub split_ppi_node_by_namespace {
27 10     10 1 295387 my ($node) = @_;
28              
29             # Ensure we don't screw up the original.
30 10         67 $node = $node->clone();
31              
32             # We want to make sure that we have locations prior to things being split
33             # up, if we can, but don't worry about it if we don't.
34 10         12107 eval { $node->location(); }; ## no critic (RequireCheckingReturnValueOfEval)
  10         58  
35              
36 10 100       21149 if ( my $single_namespace = _split_ppi_node_by_namespace_single($node) ) {
37 2         61 return $single_namespace;
38             } # end if
39              
40 8         17 my %nodes_by_namespace;
41 8         37 _split_ppi_node_by_namespace_in_lexical_scope(
42             $node, 'main', undef, \%nodes_by_namespace,
43             );
44              
45 8         49 return \%nodes_by_namespace;
46             } # end split_ppi_node_by_namespace()
47              
48              
49             # Handle the case where there's only one.
50             sub _split_ppi_node_by_namespace_single {
51 10     10   23 my ($node) = @_;
52              
53 10         69 my $package_statements = $node->find('PPI::Statement::Package');
54              
55 10 100 66     41302 if ( not $package_statements or not @{$package_statements} ) {
  9         44  
56 1         6 return { main => [$node] };
57             } # end if
58              
59 9 100       17 if (@{$package_statements} == 1) {
  9         38  
60 6         15 my $package_statement = $package_statements->[0];
61 6         28 my $package_address = refaddr $package_statement;
62              
63             # Yes, child and not schild.
64 6         40 my $first_child = $node->child(0);
65 6 100 66     108 if (
      33        
66             $package_address == refaddr $node
67             or $first_child and $package_address == refaddr $first_child
68             ) {
69 1         8 return { $package_statement->namespace() => [$node] };
70             } # end if
71             } # end if
72              
73 8         38 return;
74             } # end _split_ppi_node_by_namespace_single()
75              
76              
77             sub _split_ppi_node_by_namespace_in_lexical_scope {
78 18     18   42 my ($node, $initial_namespace, $initial_fragment, $nodes_by_namespace)
79             = @_;
80              
81 18         24 my %scope_fragments_by_namespace;
82              
83             # I certainly hope a value isn't going to exist at address 0.
84 18   100     94 my $initial_fragment_address = refaddr $initial_fragment || 0;
85 18         34 my ($namespace, $fragment) = ($initial_namespace, $initial_fragment);
86              
87 18 100       60 if ($initial_fragment) {
88 10         24 $scope_fragments_by_namespace{$namespace} = $initial_fragment;
89             } # end if
90              
91 18         83 foreach my $child ( $node->children() ) {
92 151 100 100     4598 if ( $child->isa('PPI::Statement::Package') ) {
    100 100        
93 20 100       67 if ($fragment) {
94 19         38 _push_fragment($nodes_by_namespace, $namespace, $fragment);
95              
96 19         30 undef $fragment;
97             } # end if
98              
99 20         79 $namespace = $child->namespace();
100             } elsif (
101             $child->isa('PPI::Statement::Compound')
102             or $child->isa('PPI::Statement::Given')
103             or $child->isa('PPI::Statement::When')
104             ) {
105 10         18 my $block;
106 10         54 my @components = $child->children();
107 10   66     129 while (not $block and my $component = shift @components) {
108 44 100       360 if ( $component->isa('PPI::Structure::Block') ) {
109 10         41 $block = $component;
110             } # end if
111             } # end while
112              
113 10 50       36 if ($block) {
114 10 100       34 if (not $fragment) {
115 1         4 $fragment = _get_fragment_for_split_ppi_node(
116             $nodes_by_namespace,
117             \%scope_fragments_by_namespace,
118             $namespace,
119             );
120             } # end if
121              
122             _split_ppi_node_by_namespace_in_lexical_scope(
123 10         46 $block, $namespace, $fragment, $nodes_by_namespace,
124             );
125             } # end if
126             } # end if
127              
128 151         15050 $fragment = _get_fragment_for_split_ppi_node(
129             $nodes_by_namespace, \%scope_fragments_by_namespace, $namespace,
130             );
131              
132 151 100       618 if ($initial_fragment_address != refaddr $fragment) {
133             # Need to fix these to use exceptions. Thankfully the P::C tests
134             # will insist that this happens.
135 125 50       383 $child->remove()
136             or PPIx::Utilities::Exception::Bug->throw(
137             'Could not remove child from parent.'
138             );
139 125 50       4245 $fragment->add_element($child)
140             or PPIx::Utilities::Exception::Bug->throw(
141             'Could not add child to fragment.'
142             );
143             } # end if
144             } # end foreach
145              
146 18         304 return;
147             } # end _split_ppi_node_by_namespace_in_lexical_scope()
148              
149              
150             sub _get_fragment_for_split_ppi_node {
151 152     152   272 my ($nodes_by_namespace, $scope_fragments_by_namespace, $namespace) = @_;
152              
153 152         151 my $fragment;
154 152 100       507 if ( not $fragment = $scope_fragments_by_namespace->{$namespace} ) {
155 22         99 $fragment = PPI::Document::Fragment->new();
156 22         603 $scope_fragments_by_namespace->{$namespace} = $fragment;
157 22         52 _push_fragment($nodes_by_namespace, $namespace, $fragment);
158             } # end if
159              
160 152         279 return $fragment;
161             } # end _get_fragment_for_split_ppi_node()
162              
163              
164             # Due to $fragment being passed into recursive calls to
165             # _split_ppi_node_by_namespace_in_lexical_scope(), we can end up attempting to
166             # put the same fragment into a namespace's nodes multiple times.
167             sub _push_fragment {
168 41     41   72 my ($nodes_by_namespace, $namespace, $fragment) = @_;
169              
170 41   100     152 my $nodes = $nodes_by_namespace->{$namespace} ||= [];
171              
172 41 100 100     47 if (not @{$nodes} or refaddr $nodes->[-1] != refaddr $fragment) {
  41         201  
173 22         23 push @{$nodes}, $fragment;
  22         50  
174             } # end if
175              
176 41         79 return;
177             } # end _push_fragment()
178              
179              
180             1;
181              
182             __END__
183              
184             =head1 NAME
185              
186             PPIx::Utilities::Node - Extensions to L<PPI::Node|PPI::Node>.
187              
188              
189             =head1 VERSION
190              
191             This document describes PPIx::Utilities::Node version 1.1.0.
192              
193              
194             =head1 SYNOPSIS
195              
196             use PPIx::Utilities::Node qw< split_ppi_node_by_namespace >;
197              
198             my $dom = PPI::Document->new("...");
199              
200             while (
201             my ($namespace, $sub_doms) = each split_ppi_node_by_namespace($dom)
202             ) {
203             foreach my $sub_dom ( @{$sub_doms} ) {
204             ...
205             }
206             }
207              
208              
209             =head1 DESCRIPTION
210              
211             This is a collection of functions for dealing with L<PPI::Node|PPI::Node>s.
212              
213              
214             =head1 INTERFACE
215              
216             Nothing is exported by default.
217              
218              
219             =head2 split_ppi_node_by_namespace($node)
220              
221             Returns the sub-trees for each namespace in the node as a reference to a hash
222             of references to arrays of L<PPI::Node|PPI::Node>s. Say we've got the
223             following code:
224              
225             #!perl
226              
227             my $x = blah();
228              
229             package Foo;
230              
231             my $y = blah_blah();
232              
233             {
234             say 'Whee!';
235              
236             package Bar;
237              
238             something();
239             }
240              
241             thingy();
242              
243             package Baz;
244              
245             da_da_da();
246              
247             package Foo;
248              
249             foreach ( blrfl() ) {
250             ...
251             }
252              
253             Calling this function on a L<PPI::Document|PPI::Document> for the above
254             returns a value that looks like this, using multi-line string literals for the
255             actual code parts instead of PPI trees to make this easier to read:
256              
257             {
258             main => [
259             q<
260             #!perl
261              
262             my $x = blah();
263             >,
264             ],
265             Foo => [
266             q<
267             package Foo;
268              
269             my $y = blah_blah();
270              
271             {
272             say 'Whee!';
273              
274             }
275              
276             thingy();
277             >,
278             q<
279             package Foo;
280              
281             foreach ( blrfl() ) {
282             ...
283             }
284             >,
285             ],
286             Bar => [
287             q<
288             package Bar;
289              
290             something();
291             >,
292             ],
293             Baz => [
294             q<
295             package Baz;
296              
297             da_da_da();
298             >,
299             ],
300             }
301              
302             Note that the return value contains copies of the original nodes, and not the
303             original nodes themselves due to the need to handle namespaces that are not
304             file-scoped. (Notice how the first element for "Foo" above differs from the
305             original code.)
306              
307              
308             =head1 BUGS AND LIMITATIONS
309              
310             Please report any bugs or feature requests to
311             C<bug-ppix-utilities@rt.cpan.org>, or through the web interface at
312             L<http://rt.cpan.org>.
313              
314              
315             =head1 AUTHOR
316              
317             Elliot Shank C<< <perl@galumph.com> >>
318              
319              
320             =head1 COPYRIGHT
321              
322             Copyright (c)2009-2010, Elliot Shank C<< <perl@galumph.com> >>.
323              
324             This program is free software; you can redistribute it and/or modify
325             it under the same terms as Perl itself. The full text of this license
326             can be found in the LICENSE file included with this module.
327              
328              
329             =cut
330              
331             ##############################################################################
332             # $URL: http://perlcritic.tigris.org/svn/perlcritic/trunk/distributions/PPIx-Utilities/lib/PPIx/Utilities/Node.pm $
333             # $Date: 2010-12-01 20:31:47 -0600 (Wed, 01 Dec 2010) $
334             # $Author: clonezone $
335             # $Revision: 4001 $
336             ##############################################################################
337              
338             # Local Variables:
339             # mode: cperl
340             # cperl-indent-level: 4
341             # fill-column: 70
342             # indent-tabs-mode: nil
343             # c-indentation-style: bsd
344             # End:
345             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround: