File Coverage

blib/lib/File/PackageIndexer.pm
Criterion Covered Total %
statement 149 166 89.7
branch 50 58 86.2
condition 14 33 42.4
subroutine 14 15 93.3
pod 5 6 83.3
total 232 278 83.4


line stmt bran cond sub pod time code
1             package File::PackageIndexer;
2              
3 9     9   252760 use 5.008001;
  9         32  
  9         705  
4 9     9   52 use strict;
  9         17  
  9         460  
5 9     9   45 use warnings;
  9         29  
  9         428  
6              
7             our $VERSION = '0.02';
8              
9 9     9   10008 use PPI;
  9         1612295  
  9         380  
10 9     9   106 use Carp;
  9         20  
  9         1229  
11             require File::PackageIndexer::PPI::Util;
12             require File::PackageIndexer::PPI::ClassXSAccessor;
13             require File::PackageIndexer::PPI::Inheritance;
14              
15             use Class::XSAccessor
16 9         102 accessors => {
17             default_package => 'default_package',
18             clean => 'clean',
19 9     9   9674 };
  9         31428  
20              
21             sub new {
22 8     8 1 168 my $class = shift;
23 8         42 my $self = bless {
24             clean => 1,
25             @_
26             } => $class;
27 8         32 return $self;
28             }
29              
30             sub parse {
31 85     85 1 196775 my $self = shift;
32 85         429 my $def_pkg = $self->default_package;
33 85 100       401 $def_pkg = 'main', $self->default_package('main')
34             if not defined $def_pkg;
35              
36 85         149 my $doc = shift;
37 85 50 33     334 if (not ref($doc) or not $doc->isa("PPI::Node")) {
38 85         583 $doc = PPI::Document->new(\$doc);
39             }
40 85 50       869850 if (not ref($doc)) {
41 0         0 return();
42             }
43            
44 85         160 my $curpkg;
45 85         179 my $pkgs = {};
46              
47             # TODO: More accessor generators et al
48             # TODO: More inheritance
49             # TODO: package statement scopes
50              
51 85         171 my $in_scheduled_block = 0;
52 85         122 my $finder;
53 9     9   37529 use Data::Dumper;
  9         76052  
  9         17468  
54             $finder = sub {
55 6911 100   6911   105710 return(0) unless $_[1]->isa("PPI::Statement");
56 858         1569 my $statement = $_[1];
57              
58 858         2367 my $class = $statement->class;
59             # BEGIN/CHECK/INIT/UNITCHECK/END:
60             # Recurse and set the block state, then break outer
61             # recursion so we don't process twice
62 858 100       7213 if ( $class eq 'PPI::Statement::Scheduled' ) {
    100          
    100          
    100          
    100          
63 15         29 my $temp_copy = $in_scheduled_block;
64 15         68 $in_scheduled_block = $statement->type;
65 15         391 $statement->find($finder);
66 15         167 $in_scheduled_block = $temp_copy;
67 15         45 return undef;
68             }
69             # new sub declaration
70             elsif ( $class eq 'PPI::Statement::Sub' ) {
71 61         267 my $subname = $statement->name;
72 61 100       1658 if (not defined $curpkg) {
73 22         74 $curpkg = $self->lazy_create_pkg($def_pkg, $pkgs);
74             }
75 61         268 $curpkg->{subs}->{$subname} = 1;
76             }
77             # new package statement
78             elsif ( $class eq 'PPI::Statement::Package' ) {
79 74         336 my $namespace = $statement->namespace;
80 74         1933 $curpkg = $self->lazy_create_pkg($namespace, $pkgs);
81             }
82             # use()
83 11573 100       185417 elsif ( $class eq 'PPI::Statement::Include' ) {
84 87         291 $self->_handle_includes($statement, $curpkg, $pkgs);
85             }
86             elsif ( $statement->find_any(sub {$_[1]->class eq "PPI::Token::Symbol" and $_[1]->content eq '@ISA'}) ) {
87 85         1928 File::PackageIndexer::PPI::Inheritance::handle_isa($self, $statement, $curpkg, $pkgs, $in_scheduled_block);
88             }
89 85         766 };
90              
91             # run it
92 85         407 $doc->find($finder);
93              
94 85         3535 foreach my $token ( $doc->tokens ) {
95             # find Class->method and __PACKAGE__->method
96 7448         62588 my ($callee, $methodname) = File::PackageIndexer::PPI::Util::is_class_method_call($token);
97              
98 7448 100 100     32889 if ($callee and $methodname =~ /^(?:mk_(?:[rw]o_)?accessors)$/) {
99             # resolve __PACKAGE__ to current package
100 9 100       22 if ($callee eq '__PACKAGE__') {
101 3 50       13 $callee = defined($curpkg) ? $curpkg->{name} : $def_pkg;
102             }
103              
104 9         25 my $args = $token->snext_sibling->snext_sibling->snext_sibling; # class->op->method->structure
105 9 50 33     437 if (defined $args and $args->isa("PPI::Structure::List")) {
106 9         85 my $list = File::PackageIndexer::PPI::Util::list_structure_to_array($args);
107 9 100       26 if (@$list) {
108 8         22 my $pkg = $self->lazy_create_pkg($callee, $pkgs);
109 8         56 $pkg->{subs}{$_} = 1 for @$list;
110             }
111             }
112              
113             }
114             }
115              
116              
117             # prepend unshift()d inheritance to the
118             # compile-time ISA, then append the push()d
119             # inheritance
120 85         759 foreach my $pkgname (keys %$pkgs) {
121 101         232 my $pkg = $pkgs->{$pkgname};
122              
123 101         298 my $isa = [ @{$pkg->{begin_isa}} ];
  101         269  
124 101 100       276 if ($pkg->{isa_cleared_at_runtime}) {
125 10         15 $isa = [];
126             }
127              
128 101         139 unshift @$isa, @{ $pkg->{isa_unshift} };
  101         201  
129 101         141 push @$isa, @{ $pkg->{isa_push} };
  101         191  
130              
131 101 100       437 if ($self->clean) {
132 85         210 delete $pkg->{begin_isa};
133 85         199 delete $pkg->{isa_unshift};
134 85         143 delete $pkg->{isa_push};
135 85         122 delete $pkg->{isa_cleared_at_runtime};
136 85         147 delete $pkg->{isa_cleared_at_compiletime};
137             }
138              
139 101         308 $pkg->{isa} = $isa;
140             }
141              
142 85         499 return $pkgs;
143             }
144              
145             # generate empty, new package struct
146             sub lazy_create_pkg {
147 135     135 0 201 my $self = shift;
148 135         216 my $p_name = shift;
149 135         168 my $pkgs = shift;
150 135 100       411 return $pkgs->{$p_name} if exists $pkgs->{$p_name};
151 101         884 $pkgs->{$p_name} = {
152             name => $p_name,
153             subs => {},
154             isa_unshift => [], # usa entries unshifted at run-time
155             isa_push => [], # isa entries pushed at run-time
156             begin_isa => [], # temporary storage for compile-time inheritance, will be deleted before returning from parse()
157             };
158 101         368 return $pkgs->{$p_name};
159             }
160              
161              
162             # try to deduce info from module loads
163             sub _handle_includes {
164 87     87   130 my $self = shift;
165 87         129 my $statement = shift;
166 87         115 my $curpkg = shift;
167 87         123 my $pkgs = shift;
168              
169             return
170 87 100 66     386 if $statement->type ne 'use'
171             or not defined $statement->module;
172              
173 84         3980 my $module = $statement->module;
174              
175 84 100       2060 if ($module =~ /^Class::XSAccessor(?:::Array)?$/) {
    100          
176 31         183 File::PackageIndexer::PPI::ClassXSAccessor::handle_class_xsaccessor($self, $statement, $curpkg, $pkgs);
177             }
178             elsif ($module =~ /^(?:base|parent)$/) {
179 44         166 File::PackageIndexer::PPI::Inheritance::handle_base($self, $statement, $curpkg, $pkgs);
180             }
181              
182             # TODO: handle other generating modules loaded via use
183            
184             # TODO: Elsewhere, we need to handle Class->import()!
185             }
186              
187              
188             sub merge_results {
189 7     7 1 2115 my @results = @_;
190 7   66     131 shift @results while @results and !ref($results[0]) || ref($results[0]) eq 'File::PackageIndexer';
      33        
191 7         25 return merge_results_inplace({}, @results);
192             }
193              
194             sub merge_results_inplace {
195 14     14 1 1726 my @results = @_;
196 14   66     132 shift @results while @results and !ref($results[0]) || ref($results[0]) eq 'File::PackageIndexer';
      33        
197              
198 14 50       33 return @results if @results == 1;
199              
200             # check that the user used things right
201 14         31 foreach my $r (@results) {
202 35         77 foreach my $pkg (values %$r) {
203 58 50       169 if (not exists $pkg->{begin_isa}) {
204 0         0 croak("Can't merge results that have been cleaned. Set the 'clean' option of the parser to a false value to disable cleaning of the result structures. Also RTFM.");
205             }
206             }
207             }
208              
209 14         23 my $res = shift(@results);
210 14         25 foreach my $in (@results) {
211              
212 21         42 foreach my $pkgname (keys %$in) {
213 37         49 my $inpkg = $in->{$pkgname};
214 37 100       69 if (not exists $res->{$pkgname}) {
215 29         71 $res->{$pkgname} = $inpkg;
216             }
217             # merge!
218             else {
219 8         12 my $pkg = $res->{$pkgname};
220              
221             # handle compile time isa
222 8 100       23 if ($inpkg->{isa_cleared_at_compiletime}) {
223 4         6 $pkg->{begin_isa} = [@{$inpkg->{begin_isa}}];
  4         13  
224 4         10 $pkg->{isa_cleared_at_compiletime} = 1;
225             }
226             else {
227 4         5 push @{$pkg->{begin_isa}}, @{$inpkg->{begin_isa}};
  4         7  
  4         10  
228             }
229              
230             # handle run-time isa
231 8 100       23 if ($inpkg->{isa_cleared_at_runtime}) {
232 2         2 $pkg->{isa_unshift} = [@{$inpkg->{isa_unshift}}];
  2         7  
233 2         6 $pkg->{isa_push} = [@{$inpkg->{isa_push}}];
  2         6  
234 2   33     19 $pkg->{isa_cleared_at_runtime} ||= $inpkg->{isa_cleared_at_runtime};
235             }
236             else {
237 6         8 unshift @{$pkg->{isa_unshift}}, @{$inpkg->{isa_unshift}};
  6         11  
  6         11  
238 6         8 push @{$pkg->{isa_push}}, @{$inpkg->{isa_push}};
  6         10  
  6         11  
239             }
240              
241             # finalize isa
242 8         16 my $isa = [];
243 8         10 @$isa = @{$pkg->{begin_isa}};
  8         21  
244 8 100       38 if ($pkg->{isa_cleared_at_runtime}) {
245 2         3 $isa = [];
246             }
247              
248 8         11 unshift @$isa, @{ $pkg->{isa_unshift} };
  8         20  
249 8         10 push @$isa, @{ $pkg->{isa_push} };
  8         17  
250              
251 8         21 $pkg->{isa} = $isa;
252              
253             # merge subs
254 8         18 my $subs = $pkg->{subs};
255 8         13 foreach my $insub (keys %{$inpkg->{subs}}) {
  8         33  
256 4         21 $subs->{$insub} = 1;
257             }
258              
259             } # end merge
260              
261             } # end foreach packages
262             } # end foreach @results
263            
264 14         44 return $res;
265             }
266              
267              
268             sub clean_results {
269 0     0 1   my @results = @_;
270 0   0       shift @results while @results and !ref($results[0]) || ref($results[0]) eq 'File::PackageIndexer';
      0        
271              
272 0 0         return({}) if not @results;
273 0           my $in = $results[0];
274              
275 0           my $res = {};
276 0           foreach my $pkgname (keys %{$in}) {
  0            
277 0           my $inpkg = $in->{$pkgname};
278 0           my $pkg = $res->{$pkgname} = {};
279 0           $pkg->{subs} = {%{$inpkg->{subs}}};
  0            
280 0           $pkg->{isa} = [@{$inpkg->{isa}}];
  0            
281 0           $pkg->{name} = $inpkg->{name};
282             }
283            
284 0           return $res;
285             }
286              
287             1;
288              
289             __END__