File Coverage

blib/lib/PPI/Xref.pm
Criterion Covered Total %
statement 751 887 84.6
branch 266 380 70.0
condition 90 147 61.2
subroutine 111 131 84.7
pod 24 34 70.5
total 1242 1579 78.6


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