File Coverage

blib/lib/PPI/Xref.pm
Criterion Covered Total %
statement 757 898 84.3
branch 270 392 68.8
condition 94 153 61.4
subroutine 111 131 84.7
pod 24 34 70.5
total 1256 1608 78.1


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