File Coverage

blib/lib/PPI/Xref.pm
Criterion Covered Total %
statement 755 896 84.2
branch 267 386 69.1
condition 92 150 61.3
subroutine 111 131 84.7
pod 24 34 70.5
total 1249 1597 78.2


line stmt bran cond sub pod time code
1             package PPI::Xref;
2              
3             require v5.14; # 5.10: defined-or; 5.14: package Sub { ... }
4              
5             our $VERSION = '0.008';
6              
7 32     32   665775 use strict;
  32         77  
  32         860  
8 32     32   179 use warnings;
  32         100  
  32         815  
9              
10 32     32   26365 use PPI;
  32         4945452  
  32         9972  
11              
12             # We will use file ids (integers) instead of filenames, mainly for
13             # space savings, but also speed, and only convert back to filenames
14             # on leaving the API boundary.
15             my $FILE_ID = 0;
16             my %FILE_BY_ID;
17              
18             my %CTOR_OPTS =
19             map { $_ => 1} qw/process_verbose cache_verbose recurse_verbose
20             recurse INC
21             cache_directory
22             __allow_relative/;
23              
24             my $HASHALGO = 'sha1';
25              
26             package Sub { # For getting the current sub name.
27 32     32   192 sub TIESCALAR { bless \$_[1], $_[0] }
28 119     119   2942 sub FETCH { (caller(1))[3] }
29             }
30             tie my $Sub, 'Sub';
31              
32             sub __is_readwrite_directory {
33 72     72   123 my ($self, $dir) = @_;
34 72   33     3302 return -d $dir && -r $dir && -w $dir;
35             }
36              
37             # Our constructor.
38             sub new {
39 37     37 0 39440 my ($class, $opt) = @_;
40 37   100     187 $opt //= {};
41             # In the opt you can specify:
42             # - process_verbose: for process progress
43             # - cache_verbose: for cache activity
44             # - recurse_verbose: for recurse, show revisits
45             # - INC: an aref for a custom @INC
46             # - recurse: or not (default: yes)
47             # - cache_directory: directory where to cache the results
48              
49 37   100     275 $opt->{recurse} //= 1;
50              
51 37         195 my %unexpopt = %$opt;
52 37         229 delete @unexpopt{keys %CTOR_OPTS};
53              
54 37         199 for my $k (sort keys %unexpopt) {
55 0         0 warn "$Sub: unexpected option: $k\n";
56             }
57              
58 37         189 my $self = { opt => $opt };
59              
60 37         113 my $cache_directory = $opt->{cache_directory};
61 37 100       194 if (defined $cache_directory) {
62 8 50       45 unless (PPI::Xref->__is_readwrite_directory($cache_directory)) {
63 0         0 warn "$Sub: cache_directory '$cache_directory': not a read-write directory\n";
64             }
65              
66 8         30 $self->{__cache_prefix_length} = length($cache_directory) + 1;
67              
68 32     32   310 use Sereal::Encoder;
  32         76  
  32         1530  
69 32     32   174 use Sereal::Decoder;
  32         64  
  32         8836  
70 8         139 $self->{encoder} = Sereal::Encoder->new;
71 8         92 $self->{decoder} = Sereal::Decoder->new;
72             }
73              
74 37         171 bless $self, $class;
75             }
76              
77             # Unless $self->{inc_dirs} is set, set it from either opt INC, if set,
78             # or from the system @INC.
79             sub __inc_dirs {
80 180     180   288 my $self = shift;
81 180 100       722 unless (defined $self->{inc_dirs}) {
82 36 100       167 for my $d ($self->{opt}{INC} ? @{ $self->{opt}{INC}} : @INC) {
  35         149  
83 76 50       207 next if ref $d;
84 76 50 33     2563 next unless -d $d && -x $d;
85 76         112 push @{ $self->{inc_dirs} }, $d;
  76         289  
86             }
87             }
88             }
89              
90             sub PPI::Xref::INC {
91 36     36 1 211 my $self = shift;
92 36         147 $self->__inc_dirs;
93 36         222 return $self->{inc_dirs};
94             }
95              
96             # Given a file, look for it in @INC.
97             sub __find_file {
98 144     144   256 my ($self, $file) = @_;
99 144         375 $self->__inc_dirs;
100 144 50       454 unless (exists $self->{inc_file}{$file}) {
101 144         208 for my $d (@{ $self->{inc_dirs}}) {
  144         361  
102 295 50       808 unless ($self->{opt}{__allow_relative}) { # For testing.
103 32     32   178 use File::Spec;
  32         67  
  32         28429  
104 0 0       0 $d = File::Spec->rel2abs($d) unless
105             File::Spec->file_name_is_absolute($d);
106             }
107 295         633 my $f = "$d/$file";
108 295 100       6668 if (-f $f) {
109 140         440 $self->{inc_file}{$file} = $f;
110 140         274 last;
111             }
112             }
113 144 100       472 unless (exists $self->{inc_file}{$file}) {
114 4         10 $self->{inc_file}{$file} = undef;
115             }
116             }
117 144         464 return $self->{inc_file}{$file};
118             }
119              
120             # Given a module name, look for its file in @INC.
121             sub __find_module {
122 173     173   302 my ($self, $module_name) = @_;
123 173 100       538 unless (exists $self->{module_file}{$module_name}) {
124 94         158 my $m = $module_name;
125 94         180 $m =~ s{::}{/}g;
126 94         138 $m .= '.pm';
127 94         265 $self->{module_file}{$module_name} = $self->__find_file($m);
128             }
129 173         436 return $self->{module_file}{$module_name};
130             }
131              
132             # Remove comments and tokens, and squeeze
133             # multiple whitespaces into one.
134             sub __normalize_whitespace {
135 2230     2230   2732 my @n;
136             my $prev_ws;
137 0         0 my $curr_ws;
138 2230         3897 for my $n (@_) {
139 12226 100       47332 next if $n->isa('PPI::Token::Comment');
140 12096 100       44775 next if $n->isa('PPI::Token::Pod');
141 12083         33171 $curr_ws = $n->isa('PPI::Token::Whitespace');
142 12083 100 100     34540 next if $prev_ws && $curr_ws;
143 11456         13823 push @n, $n;
144 11456         18002 $prev_ws = $curr_ws;
145             }
146 2230         6664 return @n;
147             }
148              
149             # For a filename, assign it a file id (an integer) if it does not
150             # have one, and in any case return its file id.
151             sub __assign_file_id {
152 200     200   339 my ($self, $filename) = @_;
153 200         452 my $file_id = $self->{file_id}{$filename};
154 200 100       559 unless (defined $file_id) {
155 182         453 $file_id = $self->{file_id}{$filename} = $FILE_ID++;
156 182         519 $FILE_BY_ID{$file_id} = $filename;
157             }
158 200         503 return $file_id;
159             }
160              
161             # Close the current package, if any.
162             sub __close_package {
163 330     330   651 my ($self, $file_id, $package, $elem) = @_;
164 330 100 100     1900 if (exists $self->{file_packages} &&
      66        
165             ref $self->{file_packages}{$file_id} &&
166 273         1130 @{ $self->{file_packages}{$file_id} }) {
167 273         428 push @{ $self->{file_packages}{$file_id}[-1] },
  273         1036  
168             $elem->line_number,
169             $elem->column_number;
170             }
171             }
172              
173             # Open a new package.
174             sub __open_package {
175 273     273   590 my ($self, $file_id, $package, $elem) = @_;
176 273         323 push @{ $self->{file_packages}{$file_id} },
  273         1149  
177             [
178             $package, # 0
179             $elem->line_number, # 1
180             $elem->column_number, # 2
181             ];
182             }
183              
184             # Close the current package, if any, and open a new one.
185             sub __close_open_package {
186 156     156   349 my ($self, $file_id, $old_package, $old_elem,
187             $new_package, $new_elem, $fileloc) = @_;
188 156 100 66     750 if (defined $old_package && $old_package ne 'main') {
189 154         367 $self->__close_package($file_id, $old_package, $old_elem, $fileloc);
190             }
191 156         3779 $self->__open_package($file_id, $new_package, $new_elem);
192             }
193              
194             # Function for portably turning the directory portion of a pathname
195             # into a directory name. If the $flatten_volume is true, loses
196             # information in platforms that have a volume name in pathnames, but
197             # the main idea is to safely split the argument into a new directory
198             # name (possibly modified by prepending the volume name as a
199             # directory), and the filename. E.g. '/a/b/c' -> ('/a/b', 'c')
200             # 'c:/d/e' -> ('/c/d', 'e')
201             sub __safe_vol_dir_file {
202 100     100   160 my ($self, $path, $flatten_volume) = @_;
203 100         1433 my ($vol, $dirs, $file) = File::Spec->splitpath($path);
204 100 50 66     516 if ($flatten_volume && $^O eq 'MSWin32') {
205 0         0 $vol =~ s/:$//; # splitpath() leaves the $vol as e.g. "c:"
206             }
207 100         758 return (File::Spec->catpath($vol, $dirs), $file);
208             }
209              
210             # Returns the directory part and the file part. Note that this will
211             # convert the volume name (if any) in the $path into a directory name,
212             # e.g. 'c:/d/e' -> ('/c/d', 'e'). This is useful for re-rooting
213             # a pathname under a new directory.
214             sub __safe_dir_and_file_flatten_volume {
215 64     64   104 my ($self, $path) = @_;
216 64         150 return $self->__safe_vol_dir_file($path, 1);
217             }
218              
219             # Returns the directory part (with the possible volume part prepended)
220             # and the file part. Kind of like a safe dirname().
221             sub __safe_dir_and_file_same_volume {
222 36     36   61 my ($self, $path) = @_;
223 36         89 return $self->__safe_vol_dir_file($path, 0);
224             }
225              
226             my $CACHE_EXT = '.cache';
227              
228             # "shadow file" is a filename rooted into a new, "shadow", directory.
229             sub __shadow_cache_filename {
230 64     64   120 my ($self, $shadowdir, $filename) = @_;
231              
232             # Paranoia check. (Either absolute or relative is fine, though.)
233 64 50       181 if ($filename =~ m{\.\.}) {
234 0         0 warn "$Sub: Skipping unexpected file: '$filename'\n";
235 0         0 return;
236             }
237              
238 32     32   189 use File::Spec;
  32         62  
  32         6500  
239 64 50       609 my $absfile =
240             File::Spec->file_name_is_absolute($filename) ?
241             $filename :
242             File::Spec->rel2abs($filename);
243 64         188 my ($redir, $file) = $self->__safe_dir_and_file_flatten_volume($absfile);
244              
245             # For portable filenames, we cannot just keep on
246             # appending filename extensions with dots, and we
247             # are going to append the cache filename extension.
248             # So we mangle the .pm or .pl as _pm and _pl.
249 64         410 $file =~ s{\.(p[ml])$}{_$1};
250              
251 64         929 return File::Spec->catfile($shadowdir, $redir, $file . $CACHE_EXT);
252             }
253              
254             # The hash checksum for the file, and the mtime timestamp.
255             sub __current_filehash_and_mtime {
256 188     188   286 my ($self, $origfilename) = @_;
257 188 100       4298 return unless -f $origfilename;
258 187         262 my $origfilefh;
259 187 50       7725 unless (open($origfilefh, $origfilename)) {
260 0         0 warn qq[$Sub: Failed to open "$origfilename": $!\n];
261 0         0 return;
262             }
263 32     32   28502 use Digest::SHA;
  32         123310  
  32         5404  
264 187         1456 my $sha = Digest::SHA->new($HASHALGO);
265 187         3890 $sha->addfile($origfilefh);
266             return (
267 187         20724 "$HASHALGO:". $sha->hexdigest,
268             (stat($origfilefh))[9], # mtime
269             );
270             }
271              
272             # Create the directory of the filename.
273             sub __make_path_file {
274 36     36   73 my ($self, $base) = @_;
275 32     32   220 use File::Path qw[make_path];
  32         65  
  32         9308  
276 36         101 my ($dir, $file) = $self->__safe_dir_and_file_same_volume($base);
277 36 100       77 return eval { make_path($dir) unless -d $dir; 1; };
  36         5606  
  36         139  
278             }
279              
280             # The attributes that are written to and read from cache.
281             my @CACHE_FIELDS =
282             qw[
283             file_incs
284             file_lines
285             file_modules
286             file_packages
287             file_subs
288             file_missing_modules
289             file_parse_errors
290             ];
291              
292             # Error fields are cache fields but they should not be cleared
293             # since they accumulate and are hrefs as opposed to arefs.
294             my %CACHE_FIELDS_KEEP =
295             map { $_ => 1 }
296             qw[
297             file_missing_modules
298             file_parse_errors
299             ];
300              
301             my @CACHE_FIELDS_CLEAR =
302             grep { ! exists $CACHE_FIELDS_KEEP{$_} } @CACHE_FIELDS;
303              
304             # Given the href, serialize it to the file.
305             sub __encode_to_file {
306 36     36   73 my ($self, $file, $cached) = @_;
307              
308 36         46 my $success = 0;
309              
310 36         157 my $temp = "$file.$$"; # For atomic renaming.
311              
312             # If anything goes wrong, abort the commit.
313             COMMIT: {
314 36         53 my $blob = $self->{encoder}->encode($cached);
  36         1878  
315 36 50       114 unless (defined $blob) {
316 0         0 warn "$Sub: Failed to encode into '$temp'\n";
317 0         0 last COMMIT;
318             }
319              
320 36 50       113 unless ($self->__make_path_file($temp)) {
321 0         0 warn "$Sub: Failed to create path for '$temp'\n";
322 0         0 last COMMIT;
323             }
324              
325 36         60 my $fh;
326 32     32   535 use Fcntl qw[O_CREAT O_WRONLY];
  32         68  
  32         20321  
327 36 50       3365 unless (sysopen($fh, $temp, O_CREAT|O_WRONLY, 0644)) {
328 0         0 warn "$Sub: Failed to open '$temp' for writing: $!\n";
329 0         0 last COMMIT;
330             }
331              
332 36         89 my $size = length($blob);
333 36         1370 my $wrote = syswrite($fh, $blob);
334              
335 36 50 33     231 unless (defined $wrote && $wrote == $size) {
336 0         0 warn "$Sub: Failed to write $size bytes to '$temp': $!\n";
337 0         0 last COMMIT;
338             }
339              
340 36 50       532 unless (close($fh)) {
341 0         0 warn "$Sub: Failed to close '$temp': $!\n";
342 0         0 last COMMIT;
343             }
344              
345 36 50       2475 unless (rename($temp, $file)) {
346 0         0 warn "$Sub: Failed to rename '$temp' as '$file': !$\n";
347 0         0 last COMMIT;
348             }
349              
350             # Finally we are happy.
351 36         150 $success = 1;
352              
353             } # COMMIT
354              
355 36 50       832 if (-f $temp) {
356 0         0 warn "$Sub: Cleaning temporary file '$temp'\n";
357             }
358 36         612 unlink $temp; # In any case.
359              
360 36         200 return $success;
361             }
362              
363             # Write the results to the file.
364             sub __write_cachefile {
365 36     36   73 my ($self, $cache_filename, $hash_current, $file_id, $file_mtime) = @_;
366              
367 36 100       146 if ($self->{opt}{cache_verbose}) {
368 20         62 print "$Sub: writing $cache_filename\n";
369             }
370              
371 36         68 my $cached; # Re-root the data we care about.
372 36         82 for my $k (@CACHE_FIELDS) {
373 252 100       766 if (defined $self->{$k}{$file_id}) {
374 152         438 $cached->{$k} = $self->{$k}{$file_id};
375             }
376             }
377 36         85 $cached->{file_hash} = $hash_current;
378              
379             # The mtime is in UTC, and should only be used for
380             # maintenance / statistics. In other words, it should
381             # NOT be used for uptodateness.
382 36         65 $cached->{file_mtime} = $file_mtime;
383              
384             # Mark also in the object that we have processed this one.
385 36         90 $self->{file_hash}{$file_id} = $hash_current;
386              
387 36         112 return $self->__encode_to_file($cache_filename, $cached);
388             }
389              
390             # Compose a cache filename, given an original filename.
391             # The filenames are re-rooted in the cache_directory.
392             sub __cache_filename {
393 64     64   117 my ($self, $path) = @_;
394 64 50       170 return if $path eq '-';
395              
396 64         126 my $cache_directory = $self->{opt}{cache_directory};
397 64 50       169 return unless defined $cache_directory;
398              
399 64 50       177 unless ($self->__is_readwrite_directory($cache_directory)) {
400 0         0 warn "$Sub: Not a read-write directory '$cache_directory'\n";
401 0         0 return;
402             }
403              
404 64 50       327 if ($path !~ /\.p[ml]$/) {
405 0         0 warn "$Sub: Unexpected filename: '$path'\n";
406 0         0 return;
407             }
408              
409 64         172 return $self->__shadow_cache_filename($cache_directory, $path);
410             }
411              
412             # Deserialize from the file.
413             sub __decode_from_file {
414 57     57   1261 my ($self, $file) = @_;
415              
416 57         72 my $fh;
417 32     32   170 use Fcntl qw[O_RDONLY];
  32         57  
  32         61388  
418 57 100       2012 unless (sysopen($fh, $file, O_RDONLY)) {
419             # warn "$Sub: Failed to open '$file' for reading: $!\n";
420 35         120 return;
421             }
422              
423 22         154 my $size = -s $fh;
424 22         119 my $read = sysread($fh, my $blob, $size);
425 22 50       57 unless ($read == $size) {
426 0         0 warn "$Sub: Failed to read $size bytes from '$file': $!\n";
427 0         0 return;
428             }
429              
430 22         689 return $self->{decoder}->decode($blob);
431             }
432              
433             # Check if we have the results for this file cached.
434             sub __check_cached {
435 187     187   302 my ($self, $origfile) = @_;
436 187 50       470 return if $origfile eq '-';
437              
438 187         545 my ($hash_current, $file_mtime) =
439             $self->__current_filehash_and_mtime($origfile);
440 187         575 my $cache_directory = $self->{opt}{cache_directory};
441 187         283 my $cache_filename;
442             my $cached;
443 0         0 my $hash_previous;
444 0         0 my $hash_match;
445              
446 187 100       551 if (defined $cache_directory) {
447 56         165 $cache_filename = $self->__cache_filename($origfile);
448 56 50       194 if (defined $cache_filename) {
449 56 100       154 if ($self->{opt}{cache_verbose}) {
450 25         76 print "$Sub: reading $cache_filename\n";
451             }
452 56         174 $cached = $self->__decode_from_file($cache_filename);
453 56 100       149 if (defined $cached) {
454 21 100       61 if ($self->{opt}{cache_verbose}) {
455 5         16 print "$Sub: reading $cache_filename SUCCESS\n";
456             }
457 21         41 $hash_previous = $cached->{file_hash};
458 21   66     156 $hash_match =
459             defined $hash_previous &&
460             defined $hash_current &&
461             $hash_previous eq $hash_current;
462             } else {
463 35 100       116 if ($self->{opt}{cache_verbose}) {
464 20         52 print "$Sub: reading $cache_filename FAILURE\n";
465             }
466             }
467             }
468             }
469              
470 187         709 return ($cache_filename,
471             $cached,
472             $hash_current,
473             $hash_match,
474             $file_mtime);
475             }
476              
477             # Write to the cache and tick various counters.
478             sub __to_cache {
479 36     36   95 my ($self, $cache_filename, $hash_current, $file_id, $file_mtime) = @_;
480              
481 36         978 my $had_cache = -f $cache_filename;
482 36 50       121 if ($self->__write_cachefile($cache_filename, $hash_current,
483             $file_id, $file_mtime)) {
484 36 100       124 if ($self->{opt}{cache_verbose}) {
485 20         67 print "$Sub: writing $cache_filename SUCCESS\n";
486             }
487 36         89 $self->{__cachewrites}++;
488 36 100       185 unless ($had_cache) {
489 35         226 $self->{__cachecreates}++;
490             }
491             } else {
492 0 0       0 if ($self->{opt}{cache_verbose}) {
493 0         0 print "$Sub: writing $cache_filename FAILURE\n";
494             }
495             }
496             }
497              
498             # Import the fields we care about from the cached data.
499             sub __import_cached {
500 20     20   38 my ($self, $file_id, $cached) = @_;
501              
502 20         32 for my $k (@CACHE_FIELDS) {
503 140         334 $self->{$k}{$file_id} = $cached->{$k};
504             }
505              
506 20         33 return 1;
507             }
508              
509             # Clear the cached fields. Used especially in preparation of import.
510             sub __clear_cached {
511 186     186   349 my ($self, $file_id) = @_;
512              
513 186         393 for my $k (@CACHE_FIELDS_CLEAR) {
514 930         2168 delete $self->{$k}{$file_id};
515             }
516              
517 186         338 return 1;
518             }
519              
520             sub __parse_error {
521 5     5   15 my ($self, $file_id, $file, $fileloc, $error) = @_;
522 5 50       20 if (defined $fileloc) {
523 5         25 warn qq[$Sub: $error in $fileloc\n];
524             } else {
525 0         0 warn qq[$Sub: $error\n];
526             }
527 5   33     369 $self->{file_parse_errors}{$file_id}{$fileloc // $file} = $error;
528             }
529              
530             sub __doc_create {
531 177     177   372 my ($self, $arg, $file, $file_id) = @_;
532 177         254 my $doc;
533 177         269 eval { $doc = PPI::Document->new($arg) };
  177         1193  
534 177 100       4884197 unless (defined $doc) {
535 1         5 $self->__parse_error($file_id, $file, $file,
536             "PPI::Document creation failed");
537             } else {
538 176         275 my $complete;
539 176         269 eval { $complete = $doc->complete };
  176         680  
540 176 100       568272 unless ($complete) {
541 4         9 my $pseudo = $file eq '-';
542 4 50 33     45 if (!$pseudo && ! -f $file) {
    50 33        
543 0         0 $self->__parse_error($file_id, $file, undef,
544             "Missing file");
545             } elsif (!$pseudo && ! -s $file) {
546 0         0 $self->__parse_error($file_id, $file, undef,
547             "Empty file");
548             } else {
549 4         14 $self->__parse_error($file_id, $file, $file,
550             "PPI::Document incomplete");
551             }
552             }
553             }
554 177         444 return $doc;
555             }
556              
557             # Process a given filename.
558             sub __process_file {
559 288     288   583 my ($self, $arg, $file, $process_depth) = @_;
560 288   66     1076 $file //= $arg;
561 288         714 $self->{file_counts}{$file}++;
562 288 100       1500 if ($file eq '-') { # Pseudofile.
    100          
    50          
563 10 50       38 if ($self->{opt}{process_verbose}) {
564 0         0 printf "$Sub: %*s%s\n", $process_depth + 1, ' ', $file;
565             }
566 10         38 my $file_id = $self->__assign_file_id($file);
567 10         42 my $doc = $self->__doc_create($arg, $file, $file_id);
568 10 50       37 return unless defined $doc;
569 10         33 $self->{__docscreated}++;
570 10         39 $self->__process_id($doc, $file_id, $process_depth);
571             } elsif ($self->{seen_file}{$file}) {
572 91 50 66     347 if ($self->{opt}{process_verbose} && $self->{opt}{recurse_verbose}) {
573 0         0 printf "$Sub: %*s%s [seen]\n", $process_depth + 1, ' ', $file;
574             }
575             } elsif (! $self->{seen_file}{$file}++) {
576 187 100       564 if ($self->{opt}{process_verbose}) {
577 18         62 printf "$Sub: %*s%s\n", $process_depth + 1, ' ', $file;
578             }
579 187         549 my $file_id = $self->__assign_file_id($file);
580 187         598 my ($cache_filename, $cached, $hash_current,
581             $hash_match, $file_mtime) =
582             $self->__check_cached($file);
583 187 100       484 if ($hash_match) {
584 20         52 $self->__clear_cached($file_id);
585 20         50 $self->__import_cached($file_id, $cached);
586 20         71 $self->__process_cached_incs($file_id, $process_depth);
587 20         37 $self->{__cachereads}++;
588             } else {
589 167         507 my $doc = $self->__doc_create($arg, $file, $file_id);
590 167 100       446 return unless defined $doc;
591 166         557 $self->__clear_cached($file_id);
592 166         1204 $self->__process_id($doc, $file_id, $process_depth);
593 166         738 $self->{__docscreated}++;
594             }
595 186 100 66     335638 if (defined $cache_filename &&
      100        
596             defined $hash_current &&
597             !$hash_match) {
598 36 100       132 if ($self->__to_cache($cache_filename, $hash_current,
599             $file_id, $file_mtime)) {
600 30 100 66     200 if (!$hash_match && defined $cached) {
601 1         6 $self->{__cacheupdates}++;
602             }
603             }
604             }
605             }
606 287         1572 return $self->{file_id}{$file};
607             }
608              
609             # Counter getters.
610              
611             sub docs_created {
612 10     10 1 24 my ($self) = @_;
613 10   100     120 return $self->{__docscreated} // 0;
614             }
615              
616             sub cache_reads {
617 9     9 1 18 my ($self) = @_;
618 9   100     75 return $self->{__cachereads} // 0;
619             }
620              
621             sub cache_writes {
622 12     12 1 24 my ($self) = @_;
623 12   100     86 return $self->{__cachewrites} // 0;
624             }
625              
626             sub cache_creates {
627 6     6 0 13 my ($self) = @_;
628 6   100     51 return $self->{__cachecreates} // 0;
629             }
630              
631             sub cache_updates {
632 6     6 0 16 my ($self) = @_;
633 6   100     43 return $self->{__cacheupdates} // 0;
634             }
635              
636             sub cache_deletes {
637 0     0 0 0 my ($self) = @_;
638 0   0     0 return $self->{__cachedeletes} // 0;
639             }
640              
641             # For results imported from cache, process any cached inclusions.
642             # The [6] is the include file, the [7] will become its (new) file id.
643             sub __process_cached_incs {
644 20     20   35 my ($self, $file_id, $process_depth) = @_;
645 20         26 for my $inc (@{ $self->{file_incs}{$file_id} }) {
  20         60  
646 31         48 my $include_file = $inc->[6];
647 31         82 $self->__process_file($include_file, undef,
648             $process_depth + 1);
649 31         79 $inc->[7] = $self->{file_id}{$include_file};
650             }
651             }
652              
653             # For freshly computed results, process any cached inclusions.
654             # The [6] is the include file, the [7] will become its (new) file id.
655             sub __process_pending_incs {
656 176     176   404 my ($self, $file_id, $process_depth) = @_;
657 176 50       559 if ($self->{__incs_flush}{$file_id}) {
658             $self->{file_incs}{$file_id} =
659 176         490 delete $self->{__incs_pending}{$file_id};
660 176         248 for my $inc (@{ $self->{file_incs}{$file_id} }) {
  176         497  
661 215         452 my $include_file = $inc->[6];
662 215         1048 $self->__process_file($include_file, undef,
663             $process_depth + 1);
664 215         712 $inc->[7] = $self->{file_id}{$include_file};
665             }
666 176         849 delete $self->{__incs_flush}{$file_id}; # Defuse the trigger.
667             }
668             }
669              
670             # Process a given PPI document, that has a given file id.
671             sub __process_id {
672 176     176   409 my ($self, $doc, $file_id, $process_depth) = @_;
673 176         251 my @queue = @{$doc->{children}};
  176         670  
674 176         282 my $scope_depth = 0;
675 176         603 my %package = ( 0 => 'main' );
676 176         395 my $package = $package{$scope_depth};
677 176         246 my $prev_package;
678             my $elem;
679 0         0 my $prev_elem;
680 176         346 my $filename = $FILE_BY_ID{$file_id};
681 176         231 my $fileloc;
682 176         504 while (@queue) {
683 9812         13010 $elem = shift @queue;
684 9812         25801 my $linenumber = $elem->line_number;
685 9812         1212186 $fileloc = "$filename:$linenumber";
686 9812         10025 if (0) {
687             printf("$fileloc elem = %s[%s]\n",
688             $filename, ref $elem, $elem->content);
689             }
690             my @children = exists $elem->{children} ?
691 9812 100       22030 __normalize_whitespace(@{$elem->{children}}) : ();
  2230         5692  
692 9812 100       39219 if ($elem->isa('PPI::Token::Structure')) {
693             # { ... }
694 1583 100       3876 if ($elem->content eq '{') {
    100          
695 558         2308 $scope_depth++;
696             } elsif ($elem->content eq '}') {
697 558 50       4491 if ($scope_depth <= 0) {
698 0         0 $self->__parse_error($file_id, $filename, $fileloc,
699             "scope pop underflow");
700             } else {
701 558         691 $scope_depth--;
702 558         1233 delete @package{ grep { $_ > $scope_depth } keys %package };
  661         2057  
703 32     32   191 use List::Util qw[first];
  32         62  
  32         52411  
704             $package =
705             $package{$scope_depth} //
706 558   66 102   2220 first { defined } @package{reverse 0..$scope_depth};
  102         288  
707 558 100 66     2803 if (defined $prev_package && $package ne $prev_package) {
708 26         52 if (0) {
709             print "$fileloc: package change: $prev_package -> $package\n";
710             }
711             $self->__close_open_package(
712 26         85 $file_id, $prev_package, $prev_elem,
713             $package, $elem);
714             }
715             }
716             }
717             }
718 9812 100       24282 if (@children) {
719 1922 100 66     18108 if ($elem->isa('PPI::Statement::Package') && @children >= 2) {
    100 66        
    100 66        
720             # package ...
721             #
722             # Remember to test 'use mro' and look for next::can().
723 247         778 $package = $children[2]->content;
724 247         1116 $package{$scope_depth} = $package;
725 247 50 33     1139 if (defined $package && length $package) {
726             # Okay, keep going.
727             } else {
728 0         0 $self->__parse_error($file_id,
729             $filename,
730             $fileloc, "missing package");
731             }
732 247 100       482 if (defined $prev_package) {
733 130 50       436 if ($package ne $prev_package) {
734 130         143 if (0) {
735             print "$fileloc: package change: $prev_package -> $package\n";
736             }
737             $self->__close_open_package(
738 130         377 $file_id, $prev_package, $prev_elem,
739             $package, $elem);
740             }
741             } else {
742 117         135 if (0) {
743             print "$fileloc: package first: $package\n";
744             }
745 117         408 $self->__open_package($file_id, $package, $elem);
746             }
747             } elsif ($elem->isa('PPI::Statement::Sub') &&
748             defined $elem->name &&
749             !$elem->forward # Not a forward declaration.
750             ) {
751             # sub ...
752 457         21462 my $sub = $elem->name;
753 457 100       10259 unless ($sub =~ /::/) { # sub x::y::z { ... }
754 455   50     943 $package //= 'main';
755 455         955 $sub = $package . '::' . $sub;
756             }
757 457         1168 my $finish = $elem->block->finish;
758 457 50       8492 unless (defined $finish) {
759             # E.g. Devel::Peek:debug_flags() fails to have a finish.
760 0         0 $finish = $elem; # Fake it.
761 0         0 $self->__parse_error($file_id,
762             $filename,
763             $fileloc, "missing finish");
764             }
765 457         538 push @{ $self->{file_subs}{$file_id} },
  457         1732  
766             [
767             $sub, # 0
768             $elem->line_number, # 1
769             $elem->column_number, # 2
770             $finish->line_number, # 3
771             $finish->column_number, # 4
772             ];
773             } elsif ($elem->isa('PPI::Statement')) {
774             # use, no, require
775 968         2734 my $stmt_content = $children[0]->content;
776 968         34267 my $include = $children[2];
777 968 100       2258 next unless defined $include;
778 851         2214 my $include_content = $include->content;
779 851         8813 my $including_module;
780             my $including_file;
781 851 100 66     8038 if ($elem->isa('PPI::Statement::Include') &&
    100 100        
      66        
      100        
782             # use/no/require Module
783             $stmt_content =~ /^(?:use|no|require)$/ &&
784             $include->isa('PPI::Token::Word') &&
785             $include_content !~ /^v?5/) {
786 173         262 $including_module = 1;
787             } elsif ($stmt_content =~ /^(?:require|do)$/ &&
788             $include->isa('PPI::Token::Quote')) {
789             # require/do "file"
790 50         92 $including_file = 1;
791             } else {
792             # Not a use/no/require/do, quietly exit stage left.
793 628         2298 next;
794             }
795 223 50       506 unless (defined $include) {
796 0         0 $self->__parse_error($file_id, $fileloc, "missing include");
797 0         0 next;
798             }
799 223         318 my $last = $children[-1];
800 223         301 my $include_file;
801             my $include_string;
802 223 100       496 if ($including_module) {
    50          
803 173         308 $include_string = $include_content;
804 173         488 $include_file = $self->__find_module($include_content);
805 173         543 $self->{file_modules}{$file_id}{$include_content}++;
806 173 100       530 unless (defined $include_file) {
807 5         14 $self->{file_missing_modules}{$file_id}{$include_content}{$fileloc}++;
808 5         18 warn "$Sub: warning: Failed to find module '$include_string' in $fileloc\n";
809             }
810             } elsif ($including_file) {
811 50         231 $include_string = $include->string;
812 50         394 $include_file = $self->__find_file($include_string);
813 50 50       166 unless (defined $include_file) {
814 0         0 warn "$Sub: warning: Failed to find file '$include_string' in $fileloc\n";
815             }
816             }
817 223 100       688 if (defined $include_file) {
818 218 100       580 if ($self->{opt}{recurse}) {
819 215         268 push @{ $self->{__incs_pending}{$file_id} },
  215         932  
820             [
821             $stmt_content, # 0
822             $elem->line_number, # 1
823             $elem->column_number, # 2
824             $last->line_number, # 3
825             $last->column_number, # 4
826             $include_string, # 5
827             $include_file, # 6
828             # 7 will be the file_id of include_file
829             ];
830             } else {
831 3         7 $self->__assign_file_id($include_file);
832             }
833             }
834             }
835             }
836 9067 100       74025 if ($elem->isa('PPI::Structure')) {
837             # { ... }
838 558 50       1506 unshift @queue, $elem->finish if $elem->finish;
839 558         4703 unshift @queue, @children;
840 558 50       1380 unshift @queue, $elem->start if $elem->start;
841             } else {
842 8509         11662 unshift @queue, @children;
843             }
844 9067         14237 $prev_elem = $elem;
845 9067         23288 $prev_package = $package;
846             }
847 176 50       476 if (defined $elem) {
    0          
848 176         556 $self->{file_lines}{$file_id} = $elem->line_number;
849 176         2562 $self->__close_package($file_id, $package, $elem);
850 0         0 } elsif (@{ $doc->{children} }) {
851 0         0 $self->__parse_error($file_id, $filename,
852             "Undefined token when leaving");
853             }
854              
855             # Mark the __incs_pending as ready to be recursed into.
856 176         3159 $self->{__incs_flush}{$file_id}++;
857              
858 176         658 $self->__process_pending_incs($file_id, $process_depth);
859             }
860              
861             sub __trash_cache {
862 43     43   81 my $self = shift;
863 43         147 delete $self->{result_cache};
864 43         104 delete $self->{seen_file};
865             }
866              
867             # Parse the given filenames (or if a scalar ref, a string of code,
868             # in which case filename is assumed to be '-').
869             sub process {
870 43     43 0 1416 my $self = shift;
871 43         181 $self->__trash_cache;
872 43         72 my $success = 1;
873 43         122 for my $arg (@_) {
874 43         67 my $file;
875 43         95 my $ref = ref $arg;
876 43 100       156 if ($ref eq '') {
    100          
877 32         68 $file = $arg;
878             } elsif ($ref eq 'SCALAR') {
879 10         23 $file = '-';
880             } else {
881 1         7 warn "$Sub: Unexpected argument '$arg' (ref: $ref)\n";
882 1         163 $success = 0;
883 1         4 next;
884             }
885 42         190 my $file_id = $self->__process_file($arg, $file, 0);
886 42 100       179 unless (defined $file_id) {
887 1         2 $success = 0;
888 1         4 next;
889             }
890 41         243 $self->{__process}{ $file_id }++;
891             }
892 43         289 return $success;
893             }
894              
895             sub process_files_from_cache {
896 0     0 0 0 my $self = shift;
897 0         0 my %files;
898 0         0 $self->find_cache_files(\%files);
899 0 0 0     0 if ($self->{opt}{process_verbose} || $self->{opt}{cache_verbose}) {
900 0         0 my $cache_directory = $self->{opt}{cache_directory};
901 0         0 printf("$Sub: found %d cache files from %s\n",
902             scalar keys %files, $cache_directory);
903             }
904 0         0 $self->process(sort keys %files);
905             }
906              
907             sub process_files_from_system {
908 0     0 0 0 my $self = shift;
909 0         0 my %files;
910 0         0 $self->find_system_files(\%files);
911 0 0       0 if ($self->{opt}{process_verbose}) {
912 0         0 my $cache_directory = $self->{opt}{cache_directory};
913 0         0 printf("$Sub: found %d system files from @INC\n",
914             scalar keys %files);
915             }
916 0         0 $self->process(sort keys %files);
917             }
918              
919             # Returns the seen filenames.
920             sub files {
921 8     8 1 18 my $self = shift;
922 8 50       43 unless (defined $self->{result_cache}{files}) {
923 8 50       32 return unless $self->{file_id};
924 8         18 $self->{result_cache}{files} = [ sort keys %{ $self->{file_id} } ];
  8         120  
925             }
926 8         26 return @{ $self->{result_cache}{files} };
  8         62  
927             }
928              
929             # Computes the total number of lines.
930             sub total_lines {
931 1     1 1 3 my $self = shift;
932 1 50       6 unless (defined $self->{result_cache}{total_lines}) {
933 1 50       4 return unless $self->{file_lines};
934 32     32   193 use List::Util qw[sum];
  32         71  
  32         61777  
935 1         2 $self->{result_cache}{total_lines} = sum grep { defined } values %{ $self->{file_lines} };
  6         24  
  1         4  
936             }
937 1         7 return $self->{result_cache}{total_lines};
938             }
939              
940             # Lines in a file.
941             sub file_lines {
942 6     6 1 27 my ($self, $file) = @_;
943 6         26 return $self->{file_lines}{$self->{file_id}{$file}};
944             }
945              
946             # Returns the known file ids.
947             sub __file_ids {
948 46     46   77 my $self = shift;
949 46 100       169 unless (defined $self->{result_cache}{__file_ids}) {
950 30 50       96 return unless $FILE_ID;
951 30         159 $self->{result_cache}{__file_ids} = [ 0..$FILE_ID-1 ];
952             }
953 46         80 return @{ $self->{result_cache}{__file_ids} };
  46         155  
954             }
955              
956             # Returns the reference count of a filename.
957             sub file_count {
958 6     6 1 26 my ($self, $file) = @_;
959 6 50       16 return unless $self->{file_counts};
960 6         21 return $self->{file_counts}->{$file};
961             }
962              
963             # Computes the seen modules.
964             sub __modules {
965 12     12   19 my $self = shift;
966 12 100       43 unless (defined $self->{result_cache}{modules}) {
967 8 50       28 return unless $self->{file_modules};
968 8         15 delete $self->{modules};
969 8         25 for my $f ($self->__file_ids) {
970 100         112 for my $m (keys %{ $self->{file_modules}{$f} }) {
  100         341  
971 63         167 $self->{modules}{$m} += $self->{file_modules}{$f}{$m};
972             }
973             }
974 8         105 $self->{result_cache}{modules} = [ sort keys %{ $self->{modules} } ];
  8         58  
975             }
976             }
977              
978             # Returns the seen modules.
979             sub modules {
980 9     9 1 19 my $self = shift;
981 9         30 $self->__modules;
982 9         17 return @{ $self->{result_cache}{modules} };
  9         54  
983             }
984              
985             # Computes the missing modules.
986             sub __missing_modules {
987 9     9   12 my $self = shift;
988 9 100       30 unless (defined $self->{result_cache}{missing_modules}) {
989 4   50     21 $self->{result_cache}{missing_modules} //= [];
990 4   50     18 $self->{result_cache}{missing_modules_files} //= {};
991 4   50     18 $self->{result_cache}{missing_modules_count} //= {};
992 4 100       10 return unless $self->{file_missing_modules};
993 3         5 delete $self->{missing_modules};
994 3         9 delete $self->{missing_modules_files};
995 3         6 delete $self->{missing_modules_lines};
996 3         6 delete $self->{missing_modules_count};
997 3         9 for my $f ($self->__file_ids) {
998 3         7 my $file = $FILE_BY_ID{$f};
999 3         5 for my $m (keys %{ $self->{file_missing_modules}{$f} }) {
  3         10  
1000 5         4 for my $l (keys %{ $self->{file_missing_modules}{$f}{$m} }) {
  5         14  
1001 5         8 my $c = $self->{file_missing_modules}{$f}{$m}{$l};
1002 5         12 $self->{missing_modules_files}{$m}{$file} += $c;
1003 5         10 $self->{missing_modules_lines}{$m}{$l} += $c;
1004 5         16 $self->{missing_modules_count}{$m} += $c;
1005             }
1006             }
1007             }
1008             $self->{result_cache}{missing_modules} =
1009 3         6 [ sort keys %{ $self->{missing_modules_files} } ];
  3         15  
1010             }
1011             }
1012              
1013             # Returns the missing modules.
1014             sub missing_modules {
1015 3     3 1 8 my $self = shift;
1016 3         9 $self->__missing_modules;
1017 3         5 return @{ $self->{result_cache}{missing_modules} };
  3         17  
1018             }
1019              
1020             # Returns the total reference count of a module name.
1021             sub module_count {
1022 3     3 1 14 my ($self, $module) = @_;
1023 3         7 $self->__modules;
1024 3 50       8 return 0 unless $self->{modules};
1025 3   50     14 return $self->{modules}->{$module} || 0;
1026             }
1027              
1028             # Returns the files referring a missing module.
1029             sub missing_module_files {
1030 1     1 1 3 my ($self, $module) = @_;
1031 1         3 $self->__missing_modules;
1032 1 50       4 return unless $self->{missing_modules_files}{$module};
1033 1         2 return sort keys %{ $self->{missing_modules_files}{$module} };
  1         8  
1034             }
1035              
1036             # Returns the lines referring a missing module.
1037             sub missing_module_lines {
1038 1     1 1 3 my ($self, $module) = @_;
1039 1         3 $self->__missing_modules;
1040 1 50       5 return unless $self->{missing_modules_lines}{$module};
1041 1         7 return map { "$_->[0]:$_->[1]" }
1042 0 0       0 sort { $a->[0] cmp $b->[0] || $a->[1] <=> $b->[1] }
1043 1 50       10 map { /^(.+):(\d+)$/ ? [ $1, $2 ] : [ $_, 0 ] }
1044 1         2 keys %{ $self->{missing_modules_lines}{$module} };
  1         4  
1045             }
1046              
1047             # Returns the times a missing module was referred.
1048             sub missing_module_count {
1049 4     4 1 9 my ($self, $module) = @_;
1050 4         12 $self->__missing_modules;
1051 4 100       19 return 0 unless $self->{missing_modules_count}{$module};
1052 2   50     23 return $self->{missing_modules_count}{$module} || 0;
1053             }
1054              
1055             # Computes the parse errors.
1056             sub __parse_errors {
1057 3     3   5 my $self = shift;
1058 3 100       13 unless (defined $self->{result_cache}{parse_errors_files}) {
1059 2   50     13 $self->{result_cache}{parse_errors_files} //= [];
1060 2 100       6 return unless exists $self->{file_parse_errors};
1061 1         3 delete $self->{parse_errors_files};
1062 1         5 for my $f ($self->__file_ids) {
1063 1         2 for my $l (keys %{ $self->{file_parse_errors}{$f} }) {
  1         4  
1064 1         5 $self->{parse_errors_files}{$FILE_BY_ID{$f}}++;
1065             }
1066             }
1067             $self->{result_cache}{parse_errors_files} =
1068 1         2 [ sort keys %{ $self->{parse_errors_files} } ];
  1         6  
1069             }
1070             }
1071              
1072             # Return the files with parse errors.
1073             sub parse_errors_files {
1074 2     2 1 13 my $self = shift;
1075 2         6 $self->__parse_errors;
1076 2         4 return @{ $self->{result_cache}{parse_errors_files} };
  2         22  
1077             }
1078              
1079             # Return the parse errors in a file, as a hash of filelocation -> error.
1080             sub file_parse_errors {
1081 1     1 1 4 my ($self, $file) = @_;
1082 1         5 $self->__parse_errors;
1083 1 50       4 return unless exists $self->{file_parse_errors};
1084 1 50       4 return unless defined $file;
1085 1         2 my $file_id = $self->{file_id}{$file};
1086 1 50       3 return unless defined $file_id;
1087 1 50       5 return unless exists $self->{file_parse_errors}{$file_id};
1088 1         2 return %{ $self->{file_parse_errors}{$file_id} };
  1         7  
1089             }
1090              
1091             # Generates the subs or packages.
1092             sub __subs_or_packages {
1093 21     21   41 my ($self, $key, $cache) = @_;
1094 21 100       92 unless (defined $self->{result_cache}{$cache}) {
1095 19 50       61 return unless $self->{$key};
1096 19         32 my %uniq;
1097 19         65 for my $f ($self->__file_ids) {
1098 202         247 @uniq{ map { $_->[0] } @{ $self->{$key}{$f} } } = ();
  338         2488  
  202         628  
1099             }
1100 19         318 $self->{result_cache}{$cache} = [ sort keys %uniq ];
1101             }
1102 21         51 return @{ $self->{result_cache}{$cache} };
  21         243  
1103             }
1104              
1105             # Returns the subs.
1106             sub subs {
1107 12     12 1 25 my $self = shift;
1108 12         45 return $self->__subs_or_packages(file_subs => 'subs');
1109             }
1110              
1111             # Returns the packages.
1112             sub packages {
1113 9     9 1 17 my $self = shift;
1114 9         32 return $self->__subs_or_packages(file_packages => 'packages');
1115             }
1116              
1117             # Generates the subs' or packages' files.
1118             sub __subs_or_packages_and_files {
1119 6     6   15 my ($self, $key, $cache) = @_;
1120 6 50       33 unless (defined $self->{result_cache}{$cache}) {
1121 6 50       26 return [] unless $self->{$key};
1122 6         10 my @cache;
1123 6         29 for my $f ($self->__file_ids) {
1124 36         46 for my $s (@{ $self->{$key}{$f} }) {
  36         103  
1125             push @cache,
1126 70         105 [ $s->[0], $f, @{$s}[1..$#$s] ];
  70         228  
1127             }
1128             }
1129             $self->{result_cache}{$cache} = [
1130 6         44 sort { $a->[0] cmp $b->[0] ||
1131 172 50 66     465 $FILE_BY_ID{$a->[1]} cmp $FILE_BY_ID{$b->[1]} ||
      66        
1132             $a->[2] <=> $b->[2] ||
1133             $a->[3] <=> $b->[3] }
1134             @cache ];
1135             }
1136 6         33 return $self->{result_cache}{$cache};
1137             }
1138              
1139             # Returns the subs' files (an aref to iterate through).
1140             sub __subs_files_full {
1141 5     5   12 my $self = shift;
1142 5         23 $self->__subs_or_packages_and_files(file_subs => '__subs_files');
1143             }
1144              
1145             # Returns the packages' files (an aref to iterate through).
1146             sub __packages_files_full {
1147 1     1   2 my $self = shift;
1148 1         5 $self->__subs_or_packages_and_files(file_packages => '__packages_files');
1149             }
1150              
1151             # Base class for returning iterator results.
1152             package PPI::Xref::IterResultBase {
1153             # Result as a string.
1154             # The desired fields are selected, and concatenated.
1155             sub string {
1156 85     85   392 my $self = shift;
1157 85         183 return $self->{cb}->($self->{it});
1158             }
1159             # Result as an array.
1160             sub array {
1161 0     0   0 my $self = shift;
1162 0         0 return @{ $self->{it} };
  0         0  
1163             }
1164             }
1165              
1166             package PPI::Xref::FileIterResult {
1167 32     32   25304 use parent -norequire, 'PPI::Xref::IterResultBase';
  32         9105  
  32         211  
1168             }
1169              
1170             package PPI::Xref::FileIterBase {
1171             # Base class method for stepping through the subs' and packages' files.
1172             # Converts the file id into filename, and returns iterator result.
1173             sub next {
1174             # NOTE: while this is an iterator in the sense of returning the next
1175             # result, this does not compute the next result because all the results
1176             # have already been computed when the iterator was constructed.
1177 76     76   2046 my $self = shift;
1178 76 100       104 if ($self->{ix} < @{$self->{it}}) {
  76         206  
1179 70         82 my @it = @{ $self->{it}->[$self->{ix}++] };
  70         223  
1180 70         131 $it[1] = $FILE_BY_ID{$it[1]};
1181             return bless { it => \@it, cb => $self->{cb} },
1182 70         277 'PPI::Xref::FileIterResult'
1183             }
1184 6         15 return;
1185             }
1186             }
1187              
1188             package PPI::Xref::SubsFilesIter {
1189 32     32   5414 use parent -norequire, 'PPI::Xref::FileIterBase';
  32         58  
  32         155  
1190             }
1191              
1192             package PPI::Xref::PackagesFilesIter {
1193 32     32   1685 use parent -norequire, 'PPI::Xref::FileIterBase';
  32         52  
  32         143  
1194             }
1195              
1196             # Callback generator for subs' and packages' files.
1197             # Selects the desired fields, and concatenates the fields.
1198             sub __file_iter_callback {
1199 6     6   11 my $opt = shift;
1200             sub {
1201 70     70   84 my $self = shift;
1202             join($opt->{separator} // "\t",
1203 70         206 @{$self}[0, 1, 2],
1204 70         557 @{$self}[$opt->{column} ?
1205             ($opt->{finish} ? (3, 4, 5) : (3)) :
1206 70 100 100     254 ($opt->{finish} ? (4) : ())]
    100          
    100          
1207             );
1208             }
1209 6         67 }
1210              
1211             # Constructor for iterating through the subs' files.
1212             sub subs_files_iter {
1213 5     5 1 12 my ($self, $opt) = @_;
1214 5         25 bless {
1215             it => $self->__subs_files_full,
1216             ix => 0,
1217             cb => __file_iter_callback($opt),
1218             }, 'PPI::Xref::SubsFilesIter';
1219             }
1220              
1221             # Constructor for iterating through the packages' files.
1222             sub packages_files_iter {
1223 1     1 1 2 my ($self, $opt) = @_;
1224 1         5 bless {
1225             it => $self->__packages_files_full,
1226             ix => 0,
1227             cb => __file_iter_callback($opt),
1228             }, 'PPI::Xref::PackagesFilesIter';
1229             }
1230              
1231             # Generates all the inclusion files and caches them.
1232             # The inclusion files are the file id, followed all its inclusions.
1233             # Sorting is by filename, line, column, and include_string (e.g. Data::Dumper).
1234             sub __incs_files_full {
1235 1     1   3 my ($self) = @_;
1236 1 50       7 unless (defined $self->{result_cache}{__incs_files}) {
1237 1         2 my @cache;
1238 1         6 for my $f ($self->__file_ids) {
1239 6         10 for my $i (@{ $self->{file_incs}{$f} }) {
  6         20  
1240             push @cache, [ $f, # 0: fileid1
1241 7         12 @{$i}[1, # 1: line
  7         32  
1242             7, # 2: fileid2
1243             0, # 3: stmt
1244             5, # 4: include_string
1245             2, # 5: col
1246             3, # 6: line
1247             4, # 7: col
1248             ] ],
1249             }
1250             }
1251             $self->{result_cache}{__incs_files} = [
1252 1 50 66     8 sort { $FILE_BY_ID{$a->[0]} cmp $FILE_BY_ID{$b->[0]} ||
  11   33     60  
1253             $a->[1] <=> $b->[1] || # line
1254             $a->[2] <=> $b->[2] || # column
1255             $a->[5] cmp $b->[5] } # include_string
1256             @cache ];
1257             }
1258 1         7 return $self->{result_cache}{__incs_files};
1259             }
1260              
1261             # Callback generator for inclusion files.
1262             # Selects the desired fields, and concatenates the fields with the separator.
1263             sub __incs_files_iter_callback {
1264 1     1   3 my $opt = shift;
1265             sub {
1266 7     7   12 my $self = shift;
1267             join($opt->{separator} // "\t",
1268 7         30 @{$self}[0..4],
1269 7         84 @{$self}[$opt->{column} ?
1270             ($opt->{finish} ? (5, 6, 7) : (5)) :
1271 7 0 50     34 ($opt->{finish} ? (6) : ())],
    50          
    50          
1272             );
1273             }
1274 1         13 }
1275              
1276             package PPI::Xref::IncsFilesIterResult {
1277 32     32   18526 use parent -norequire, 'PPI::Xref::IterResultBase';
  32         55  
  32         162  
1278             }
1279              
1280             package PPI::Xref::IncsFilesIter {
1281             # Iterator stepper for iterating through the inclusion files.
1282             # Converts the file ids to filenames, and returns iterator result.
1283             sub next {
1284             # NOTE: while this is an iterator in the sense of returning the next
1285             # result, this does not compute the next result because all the results
1286             # have already been computed when the iterator was constructed.
1287 8     8   373 my $self = shift;
1288 8 100       14 if ($self->{ix} < @{$self->{it}}) {
  8         32  
1289 7         12 my @it = @{ $self->{it}->[$self->{ix}++] };
  7         26  
1290 7         17 $it[0] = $FILE_BY_ID{$it[0]};
1291 7         17 $it[2] = $FILE_BY_ID{$it[2]};
1292             return bless { it => \@it, cb => $self->{cb} },
1293 7         39 'PPI::Xref::IncsFilesIterResult'
1294             }
1295 1         4 return;
1296             }
1297             }
1298              
1299             # Constructor for iterating through the inclusion files.
1300             sub incs_files_iter {
1301 1     1 1 2 my ($self, $opt) = @_;
1302 1         5 bless {
1303             it => $self->__incs_files_full,
1304             ix => 0,
1305             cb => __incs_files_iter_callback($opt),
1306             }, 'PPI::Xref::IncsFilesIter';
1307             }
1308              
1309             # Recursive generator for inclusion chains. If there are inclusions
1310             # from this file, recurse for them; if not, aggregate into the result.
1311             sub __incs_chains_recurse {
1312 0     0   0 my ($self, $file_id, $path, $seen, $result) = @_;
1313             my @s =
1314             exists $self->{file_incs}{$file_id} ?
1315 0 0       0 @{ $self->{file_incs}{$file_id} } : ();
  0         0  
1316 0         0 $seen->{$file_id}++;
1317             # print "recurse: $FILE_BY_ID{$file_id} path: [@{[map { $FILE_BY_ID{$_} // $_ } @$path]}] \n";
1318 0         0 my $s = 0;
1319 0         0 for my $i (@s) {
1320 0         0 my ($line, $next_file_id) = ($i->[1], $i->[-1]);
1321             # print "recurse: $FILE_BY_ID{$file_id}:$line -> $FILE_BY_ID{$next_file_id} path: [@{[map { $FILE_BY_ID{$_} // $_ } @$path]}] seen: [@{[sort map { $FILE_BY_ID{$_} } keys %$seen]}]\n";
1322             # E.g. Carp uses strict, strict requires Carp.
1323 0 0       0 unless ($seen->{$next_file_id}++) {
1324 0         0 $self->__incs_chains_recurse($next_file_id, [ @$path, $line, $next_file_id ], $seen, $result);
1325 0         0 $s++;
1326             }
1327             }
1328 0 0       0 if ($s == 0) { # If this was a leaf (no paths leading out), aggregrate result.
1329 0         0 push @{$result}, [ @$path ];
  0         0  
1330             }
1331 0         0 delete $seen->{$file_id};
1332             }
1333              
1334             sub __incs_deps {
1335 4     4   9 my ($self) = @_;
1336 4 50       22 unless (defined $self->{result_cache}{__incs_deps}) {
1337 4         7 my %pred;
1338             my %succ;
1339 0         0 my %line;
1340 4         21 for my $fi ($self->__file_ids) {
1341 25 100       81 if (exists $self->{file_incs}{$fi}) {
1342 19         25 for my $g (@{ $self->{file_incs}{$fi} }) {
  19         73  
1343 21         31 my ($gl, $gi) = @{ $g }[ 1, 7 ];
  21         47  
1344 21         60 $succ{$fi}{$gi}{$gl}++;
1345 21         50 $pred{$gi}{$fi}{$gl}++;
1346 21         69 $line{$fi}{$gl}{$gi}++;
1347             }
1348             }
1349             }
1350 4         13 my %singleton;
1351             my %leaf;
1352 0         0 my %root;
1353 0         0 my %branch;
1354 4         15 for my $s ($self->__file_ids) {
1355 25 100       76 my @s = exists $succ{$s} ? keys %{$succ{$s}} : ();
  9         35  
1356 25 100       65 my @p = exists $pred{$s} ? keys %{$pred{$s}} : ();
  15         47  
1357 25 100       69 if (@s == 0) {
    100          
1358 16 100       38 if (@p == 0) {
1359 7         18 $singleton{$s}++;
1360             } else {
1361 9         27 $leaf{$s}++;
1362             }
1363             } elsif (@p == 0) {
1364 3         11 $root{$s}++;
1365             } else {
1366 6         18 $branch{$s}++;
1367             }
1368             }
1369             $self->{result_cache}{__incs_deps} = {
1370 4         47 pred => \%pred,
1371             succ => \%succ,
1372             line => \%line,
1373             singleton => \%singleton,
1374             leaf => \%leaf,
1375             root => \%root,
1376             branch => \%branch,
1377             parent => $self,
1378             };
1379             }
1380 4         31 return $self->{result_cache}{__incs_deps};
1381             }
1382              
1383             package PPI::Xref::IncsDeps {
1384             sub files {
1385 1     1   286 my ($self) = @_;
1386 1         5 return $self->{parent}->files;
1387             }
1388             sub __file_id {
1389 8     8   11 my ($self, $file) = @_;
1390 8         22 return $self->{parent}{file_id}{$file};
1391             }
1392             sub __by_file {
1393 0     0   0 my ($self, $key, $file) = @_;
1394 0         0 my $file_id = $self->__file_id($file);
1395 0 0 0     0 return unless defined $file_id && exists $self->{$key}{$file_id};
1396 0         0 return keys %{ $self->{$key}{$file_id} };
  0         0  
1397             }
1398             sub __filenames {
1399 0     0   0 my $self = shift;
1400 0         0 return map { $FILE_BY_ID{$_} } @_;
  0         0  
1401             }
1402             sub __predecessors {
1403 0     0   0 my ($self, $file) = @_;
1404 0         0 return _$self->__by_file(pred => $file);
1405             }
1406             sub __successors {
1407 0     0   0 my ($self, $file) = @_;
1408 0         0 return $self->__by_file(succ => $file);
1409             }
1410             sub predecessors {
1411 0     0   0 my ($self, $file) = @_;
1412 0         0 return $self->__filenames(_$self->__predecessors($file));
1413             }
1414             sub successors {
1415 0     0   0 my ($self, $file) = @_;
1416 0         0 return $self->__filenames($self->__successors($file));
1417             }
1418             sub __files {
1419 0     0   0 my ($self, $key) = @_;
1420             return exists $self->{$key} ?
1421 0 0       0 map { $FILE_BY_ID{$_} } keys %{ $self->{$key} } : ();
  0         0  
  0         0  
1422             }
1423             sub __roots {
1424 2     2   5 my ($self) = @_;
1425 2 50       21 return exists $self->{root} ? keys %{ $self->{root} } : ();
  2         16  
1426             }
1427             sub __singletons {
1428 2     2   5 my ($self) = @_;
1429 2 50       9 return exists $self->{singleton} ? keys %{ $self->{singleton} } : ();
  2         10  
1430             }
1431             sub roots {
1432 0     0   0 my ($self) = @_;
1433 0         0 return $self->__files('root');
1434             }
1435             sub leaves {
1436 0     0   0 my ($self) = @_;
1437 0         0 return $self->__files('leaf');
1438             }
1439             sub singletons {
1440 0     0   0 my ($self) = @_;
1441 0         0 return $self->__files('singleton');
1442             }
1443             sub branches {
1444 0     0   0 my ($self) = @_;
1445 0         0 return $self->__files('branch');
1446             }
1447             sub __file_kind {
1448 10     10   16 my ($self, $file_id) = @_;
1449 10 100       33 return unless defined $file_id;
1450 9 100       27 return 'branch' if exists $self->{branch} {$file_id};
1451 7 100       26 return 'leaf' if exists $self->{leaf} {$file_id};
1452 4 100       19 return 'root' if exists $self->{root} {$file_id};
1453 1 50       7 return 'singleton' if exists $self->{singleton}{$file_id};
1454 0         0 return;
1455             }
1456             sub file_kind {
1457 8     8   648 my ($self, $file) = @_;
1458 8         18 return $self->__file_kind($self->__file_id($file));
1459             }
1460             }
1461              
1462             sub incs_deps {
1463 4     4 1 10 my ($self) = @_;
1464 4         18 bless $self->__incs_deps, 'PPI::Xref::IncsDeps';
1465             }
1466              
1467             sub __incs_chains_iter {
1468 2     2   5 my ($self, $opt) = @_;
1469 2         4 my %iter;
1470 2         9 my $deps = $self->incs_deps;
1471 2 50       9 if (defined $deps) {
1472             $iter{next} = sub {
1473 10     10   18 my ($iterself) = @_;
1474 10         37 until ($iterself->{done}) {
1475 12 100 100     40 unless (defined $iterself->{path} && @{ $iterself->{path} }) {
  10         37  
1476 4 100       16 unless (defined $iterself->{roots}) {
1477 2         10 my @roots = (
1478             $deps->__roots,
1479             $deps->__singletons,
1480             );
1481 2         6 my %roots;
1482 2         6 @roots{@roots} = ();
1483 2 50       11 if (exists $self->{__process}) {
1484 2         6 for my $id (keys %{ $self->{__process} }) {
  2         8  
1485 2 50       12 push @roots, $id unless exists $roots{$id};
1486             }
1487             }
1488 2         9 $iterself->{roots} = \@roots;
1489             }
1490 4         8 my $root = shift @{ $iterself->{roots} };
  4         9  
1491 4 100       13 unless (defined $root) {
1492 2         5 $iterself->{done}++;
1493 2         8 return;
1494             }
1495 2         8 $iterself->{path} = [ $root ];
1496             # E.g. Carp uses strict, strict requires Carp, and also
1497             # the dependency trees are very probably not clean DAGs.
1498 2         12 $iterself->{seen} = { $root => { 0 => 1 } };
1499             };
1500 10         18 while (@{ $iterself->{path} }) {
  32         84  
1501 30         51 my $curr = $iterself->{path}[-1];
1502 30         37 my $pushed = 0;
1503             SUCC: {
1504 30 100       34 if (exists $deps->{line}{$curr}) {
  30         78  
1505 22         30 for my $line (sort { $a <=> $b }
  38         82  
1506 22         84 keys %{ $deps->{line}{$curr} }) {
1507 42         50 for my $succ (sort { $a cmp $b }
  0         0  
1508 42         115 keys %{ $deps->{line}{$curr}{$line} }) {
1509 42 100       141 unless ($iterself->{seen}{$succ}{$line}++) {
1510 14         16 push @{ $iterself->{path} }, $line, $succ;
  14         34  
1511 14         19 $pushed++;
1512 14         31 last SUCC;
1513             }
1514             }
1515             }
1516             }
1517             }
1518 30 100       66 unless ($pushed) {
1519 16 50       19 if (my @path = @{ $iterself->{path} }) {
  16         63  
1520 16 100       36 @path = reverse @path if $opt->{reverse_chains};
1521 16 100       35 if (@path > 1) {
1522 14         17 splice @{ $iterself->{path} }, -2; # Double-pop.
  14         26  
1523 14 100       39 if ($self->{lastpush}) {
1524 8         11 $self->{lastpush} = $pushed;
1525 8         47 return @path;
1526             }
1527             } else {
1528 2         7 $iterself->{path} = [];
1529 2         8 my $kind = $deps->__file_kind($curr);
1530 2 50 33     17 if (defined $kind && $kind eq 'singleton') {
1531 0         0 return @path;
1532             }
1533             }
1534             }
1535             }
1536 22         43 $self->{lastpush} = $pushed;
1537             } # while
1538             }
1539 2         20 };
1540             }
1541 2         12 return \%iter;
1542             }
1543              
1544             # Callback generator for inclusion chains.
1545             # Simply concatenates the fields with the separator.
1546             sub __incs_chains_iter_callback {
1547 2     2   5 my $opt = shift;
1548             sub {
1549 8     8   12 my $self = shift;
1550 8   50     42 join($opt->{separator} // "\t", @{$self} );
  8         122  
1551             }
1552 2         22 }
1553              
1554             package PPI::Xref::IncsChainsIterResult {
1555 32     32   68192 use parent -norequire, 'PPI::Xref::IterResultBase';
  32         67  
  32         210  
1556             }
1557              
1558             package PPI::Xref::IncsChainsIter {
1559             # Iterator stepper for iterating through the inclusion chains.
1560             # Converts the file ids to filenames, and returns iterator result.
1561             sub next {
1562 10     10   606 my $self = shift;
1563 10 100       23 if (my @it = $self->{it}{next}->($self)) {
1564 8         22 for (my $i = 0; $i < @it; $i += 2) {
1565 26         74 $it[$i] = $FILE_BY_ID{$it[$i]};
1566             }
1567             return bless { it => \@it, cb => $self->{cb} },
1568 8         43 'PPI::Xref::IncsChainsIterResult';
1569             }
1570 2         6 return;
1571             }
1572             }
1573              
1574             # Constructor for iterating through the inclusion chains.
1575             sub incs_chains_iter {
1576 2     2 1 6 my ($self, $opt) = @_;
1577 2         12 bless {
1578             it => $self->__incs_chains_iter($opt),
1579             ix => 0,
1580             cb => __incs_chains_iter_callback($opt),
1581             }, 'PPI::Xref::IncsChainsIter';
1582             }
1583              
1584             sub looks_like_cache_file {
1585 3     3 0 6 my ($self, $file) = @_;
1586              
1587 3         7 my $cache_directory = $self->{opt}{cache_directory};
1588 3 50       8 return unless defined $cache_directory;
1589              
1590 3 50       8 return 0 if $file =~ m{\.\.};
1591              
1592 3         31 return $file =~ m{^\Q$cache_directory\E/.+\Q$CACHE_EXT\E$};
1593             }
1594              
1595             sub cache_delete {
1596 3     3 1 2381 my $self = shift;
1597 3         7 my $cache_directory = $self->{opt}{cache_directory};
1598 3 50       10 unless (defined $cache_directory) {
1599 0         0 warn "$Sub: cache_directory undefined\n";
1600 0         0 return;
1601             }
1602 3         5 my $delete_count = 0;
1603 3         6 for my $file (@_) {
1604 3 50 33     82 if (!File::Spec->file_name_is_absolute($file) ||
      66        
      33        
1605             $file =~ m{\.\.} ||
1606             ($file !~ m{_p[ml](?:\Q$CACHE_EXT\E)?$} &&
1607             $file !~ m{.p[ml]$})) {
1608             # Paranoia check one.
1609 0         0 warn "$Sub: Skipping unexpected file: '$file'\n";
1610 0         0 next;
1611             }
1612 3 100       23 my $cache_file =
1613             $file =~ /\Q$CACHE_EXT\E$/ ?
1614             $file : $self->__cache_filename($file);
1615             # Paranoia check two. Both paranoia checks are needed.
1616 3 50       11 unless ($self->looks_like_cache_file($cache_file)) {
1617 0         0 warn "$Sub: Skipping unexpected cache file: '$cache_file'\n";
1618 0         0 next;
1619             }
1620 3 50       12 if ($self->{opt}{cache_verbose}) {
1621 3         15 print "cache_delete: deleting $cache_file\n";
1622             }
1623 3 100       227 if (unlink $cache_file) {
1624 2         4 $delete_count++;
1625 2         8 $self->{__cachedeletes}++;
1626             }
1627             }
1628 3         15 return $delete_count;
1629             }
1630              
1631             sub __unparse_cache_filename {
1632 1     1   1157 my ($self, $cache_filename) = @_;
1633              
1634 1         4 my $cache_directory = $self->{opt}{cache_directory};
1635 1 50       4 return unless defined $cache_directory;
1636              
1637 1 50       16 return unless $cache_filename =~ s{\Q$CACHE_EXT\E$}{};
1638              
1639 1         4 my $cache_prefix_length = $self->{__cache_prefix_length};
1640 1 50       3 return unless length($cache_filename) > $cache_prefix_length;
1641              
1642 1         3 my $prefix = substr($cache_filename, 0, $cache_prefix_length);
1643 1 50       65 return unless $prefix =~ m{^\Q$cache_directory\E(?:/|\\)$};
1644              
1645 1         3 my $path = substr($cache_filename, $cache_prefix_length - 1);
1646              
1647 1         8 $path =~ s{_(p[ml])$}{\.$1}; # _pm -> .pm, _pl -> .pl
1648              
1649 1 50       4 if ($^O eq 'MSWin32') {
1650             # \c\a\b -> c:/a/b
1651 0         0 $path =~ s{\\}{/}g;
1652 0 0       0 if ($path =~ m{^/([A-Z])(/.+)}) {
1653 0         0 my $volpath = "$1:$2";
1654 0 0       0 if (-f $volpath) {
1655 0         0 $path = $volpath;
1656             }
1657             }
1658             }
1659              
1660 1         8 return $path;
1661             }
1662              
1663             # Given an xref, find all the cache files under its cache directory,
1664             # and add their filenames to href.
1665             sub find_cache_files {
1666 0     0 0   my ($self, $files) = @_;
1667              
1668 0           my $cache_directory = $self->{opt}{cache_directory};
1669 0 0         unless (defined $cache_directory) {
1670 0           warn "$Sub: cache_directory undefined\n";
1671 0           return;
1672             }
1673              
1674 32     32   28738 use File::Find qw[find];
  32         57  
  32         6050  
1675              
1676             find(
1677             sub {
1678 0 0   0     if (/\.p[ml]\Q$CACHE_EXT\E$/) {
1679 0           my $name = $self->__unparse_cache_filename($File::Find::name);
1680 0           $files->{$name} = $File::Find::name;
1681             }
1682             },
1683 0           $cache_directory);
1684             }
1685              
1686             # Given an xref, find all the pm files under its INC,
1687             # and add their filenames to href.
1688             sub find_system_files {
1689 0     0 0   my ($self, $files) = @_;
1690              
1691 32     32   191 use File::Find qw[find];
  32         73  
  32         4878  
1692              
1693 0           for my $d (@{ $self->INC }) {
  0            
1694             find(
1695             sub {
1696 0 0   0     if (/\.p[ml]$/) {
1697 0           $files->{$File::Find::name} = $File::Find::name;
1698             }
1699             },
1700 0           $d);
1701             }
1702             }
1703              
1704             1;
1705             __DATA__