File Coverage

blib/lib/Pod/Find.pm
Criterion Covered Total %
statement 0 161 0.0
branch 0 114 0.0
condition 0 34 0.0
subroutine 0 9 0.0
pod 4 4 100.0
total 4 322 1.2


line stmt bran cond sub pod time code
1             #############################################################################
2             # Pod/Find.pm -- finds files containing POD documentation
3             #
4             # Author: Marek Rouchal
5             #
6             # Copyright (C) 1999-2000 by Marek Rouchal (and borrowing code
7             # from Nick Ing-Simmon's PodToHtml). All rights reserved.
8             # This file is part of "PodParser". Pod::Find is free software;
9             # you can redistribute it and/or modify it under the same terms
10             # as Perl itself.
11             #############################################################################
12              
13             package Pod::Find;
14             use strict;
15              
16             use vars qw($VERSION);
17             $VERSION = '1.66'; ## Current version of this package
18             require 5.005; ## requires this Perl version or later
19             use Carp;
20              
21             BEGIN {
22             if ($] < 5.006) {
23             require Symbol;
24             import Symbol;
25             }
26             }
27              
28             #############################################################################
29              
30             =head1 NAME
31              
32             Pod::Find - find POD documents in directory trees
33              
34             =head1 SYNOPSIS
35              
36             use Pod::Find qw(pod_find simplify_name);
37             my %pods = pod_find({ -verbose => 1, -inc => 1 });
38             foreach(keys %pods) {
39             print "found library POD `$pods{$_}' in $_\n";
40             }
41              
42             print "podname=",simplify_name('a/b/c/mymodule.pod'),"\n";
43              
44             $location = pod_where( { -inc => 1 }, "Pod::Find" );
45              
46             =head1 DESCRIPTION
47              
48             B
49             higher) are going to remove Pod-Parser from core and use L
50             for all things POD.>
51              
52             B provides a set of functions to locate POD files. Note that
53             no function is exported by default to avoid pollution of your namespace,
54             so be sure to specify them in the B statement if you need them:
55              
56             use Pod::Find qw(pod_find);
57              
58             From this version on the typical SCM (software configuration management)
59             directories are ignored. These are: RCS, CVS, SCCS, .svn, .hg, .git, .sync
60              
61             =cut
62              
63             #use diagnostics;
64             use Exporter;
65             use File::Spec;
66             use File::Find;
67             use Cwd qw(abs_path cwd);
68              
69             use vars qw(@ISA @EXPORT_OK $VERSION);
70             @ISA = qw(Exporter);
71             @EXPORT_OK = qw(&pod_find &simplify_name &pod_where &contains_pod);
72              
73             # package global variables
74             my $SIMPLIFY_RX;
75              
76             =head2 C
77              
78             The function B searches for POD documents in a given set of
79             files and/or directories. It returns a hash with the file names as keys
80             and the POD name as value. The POD name is derived from the file name
81             and its position in the directory tree.
82              
83             E.g. when searching in F<$HOME/perl5lib>, the file
84             F<$HOME/perl5lib/MyModule.pm> would get the POD name I,
85             whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be
86             I. The name information can be used for POD
87             translators.
88              
89             Only text files containing at least one valid POD command are found.
90              
91             A warning is printed if more than one POD file with the same POD name
92             is found, e.g. F in different directories. This usually
93             indicates duplicate occurrences of modules in the I<@INC> search path.
94              
95             B The first argument for B may be a hash reference
96             with options. The rest are either directories that are searched
97             recursively or files. The POD names of files are the plain basenames
98             with any Perl-like extension (.pm, .pl, .pod) stripped.
99              
100             =over 4
101              
102             =item C<-verbose =E 1>
103              
104             Print progress information while scanning.
105              
106             =item C<-perl =E 1>
107              
108             Apply Perl-specific heuristics to find the correct PODs. This includes
109             stripping Perl-like extensions, omitting subdirectories that are numeric
110             but do I match the current Perl interpreter's version id, suppressing
111             F as a module hierarchy name etc.
112              
113             =item C<-script =E 1>
114              
115             Search for PODs in the current Perl interpreter's installation
116             B. This is taken from the local L module.
117              
118             =item C<-inc =E 1>
119              
120             Search for PODs in the current Perl interpreter's I<@INC> paths. This
121             automatically considers paths specified in the C environment
122             as this is included in I<@INC> by the Perl interpreter itself.
123              
124             =back
125              
126             =cut
127              
128             # return a hash of the POD files found
129             # first argument may be a hashref (options),
130             # rest is a list of directories to search recursively
131             sub pod_find
132             {
133 0     0 1   my %opts;
134 0 0         if(ref $_[0]) {
135 0           %opts = %{shift()};
  0            
136             }
137              
138 0   0       $opts{-verbose} ||= 0;
139 0   0       $opts{-perl} ||= 0;
140              
141 0           my (@search) = @_;
142              
143 0 0         if($opts{-script}) {
144 0           require Config;
145             push(@search, $Config::Config{scriptdir})
146 0 0         if -d $Config::Config{scriptdir};
147 0           $opts{-perl} = 1;
148             }
149              
150 0 0         if($opts{-inc}) {
151 0 0         if ($^O eq 'MacOS') {
152             # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
153 0           my @new_INC = @INC;
154 0           for (@new_INC) {
155 0 0         if ( $_ eq '.' ) {
    0          
156 0           $_ = ':';
157 0           } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
158 0           $_ = ':'. $_;
159             } else {
160 0           $_ =~ s{^\./}{:};
161             }
162             }
163 0           push(@search, grep($_ ne File::Spec->curdir, @new_INC));
164             } else {
165 0           my %seen;
166 0           my $curdir = File::Spec->curdir;
167 0           foreach(@INC) {
168 0 0         next if $_ eq $curdir;
169 0           my $path = abs_path($_);
170 0 0         push(@search, $path) unless $seen{$path}++;
171             }
172             }
173              
174 0           $opts{-perl} = 1;
175             }
176              
177 0 0         if($opts{-perl}) {
178 0           require Config;
179             # this code simplifies the POD name for Perl modules:
180             # * remove "site_perl"
181             # * remove e.g. "i586-linux" (from 'archname')
182             # * remove e.g. 5.00503
183             # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
184              
185             # Mac OS:
186             # * remove ":?site_perl:"
187             # * remove :?pod: if followed by *.pod (e.g. in :pod:perlfunc.pod)
188              
189 0 0         if ($^O eq 'MacOS') {
190 0           $SIMPLIFY_RX =
191             qq!^(?i:\:?site_perl\:|\:?pod\:(?=.*?\\.pod\\z))*!;
192             } else {
193 0           $SIMPLIFY_RX =
194             qq!^(?i:site(_perl)?/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
195             }
196             }
197              
198 0           my %dirs_visited;
199             my %pods;
200 0           my %names;
201 0           my $pwd = cwd();
202              
203 0           foreach my $try (@search) {
204 0 0         unless(File::Spec->file_name_is_absolute($try)) {
205             # make path absolute
206 0           $try = File::Spec->catfile($pwd,$try);
207             }
208             # simplify path
209             # on VMS canonpath will vmsify:[the.path], but File::Find::find
210             # wants /unixy/paths
211 0 0         if ($^O eq 'VMS') {
212 0           $try = VMS::Filespec::unixify($try);
213             }
214             else {
215 0           $try = _unixify(File::Spec->canonpath($try));
216             }
217 0           my $name;
218 0 0         if(-f $try) {
219 0 0         if($name = _check_and_extract_name($try, $opts{-verbose})) {
220 0           _check_for_duplicates($try, $name, \%names, \%pods);
221             }
222 0           next;
223             }
224 0 0         my $root_rx = $^O eq 'MacOS' ? qq!^\Q$try\E! : qq!^\Q$try\E/!;
225 0           $root_rx=~ s|//$|/|; # remove trailing double slash
226             File::Find::find( sub {
227 0     0     my $item = $File::Find::name;
228 0 0         if(-d) {
229 0 0         if($item =~ m{/(?:RCS|CVS|SCCS|\.svn|\.hg|\.git|\.sync)$}) {
    0          
230 0           $File::Find::prune = 1;
231 0           return;
232             }
233             elsif($dirs_visited{$item}) {
234             warn "Directory '$item' already seen, skipping.\n"
235 0 0         if($opts{-verbose});
236 0           $File::Find::prune = 1;
237 0           return;
238             }
239             else {
240 0           $dirs_visited{$item} = 1;
241             }
242 0 0 0       if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
      0        
243 0           $File::Find::prune = 1;
244             warn "Perl $] version mismatch on $_, skipping.\n"
245 0 0         if($opts{-verbose});
246             }
247 0           return;
248             }
249 0 0         if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
250 0           _check_for_duplicates($item, $name, \%names, \%pods);
251             }
252 0           }, $try); # end of File::Find::find
253             }
254 0           chdir $pwd;
255 0           return %pods;
256             }
257              
258             sub _unixify {
259 0     0     my $this = shift;
260 0           $this =~ s/\\/\//g;
261 0           return $this;
262             }
263              
264             sub _check_for_duplicates {
265 0     0     my ($file, $name, $names_ref, $pods_ref) = @_;
266 0 0         if($$names_ref{$name}) {
267 0           warn "Duplicate POD found (shadowing?): $name ($file)\n";
268             warn ' Already seen in ',
269 0           join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
270             }
271             else {
272 0           $$names_ref{$name} = 1;
273             }
274 0           return $$pods_ref{$file} = $name;
275             }
276              
277             sub _check_and_extract_name {
278 0     0     my ($file, $verbose, $root_rx) = @_;
279              
280             # check extension or executable flag
281             # this involves testing the .bat extension on Win32!
282 0 0 0       unless(-f $file && -T $file && ($file =~ /\.(pod|pm|plx?)\z/i || -x $file )) {
      0        
      0        
283 0           return;
284             }
285              
286 0 0         return unless contains_pod($file,$verbose);
287              
288             # strip non-significant path components
289             # TODO what happens on e.g. Win32?
290 0           my $name = $file;
291 0 0         if(defined $root_rx) {
292 0           $name =~ s/$root_rx//is;
293 0 0         $name =~ s/$SIMPLIFY_RX//is if(defined $SIMPLIFY_RX);
294             }
295             else {
296 0 0         if ($^O eq 'MacOS') {
297 0           $name =~ s/^.*://s;
298             } else {
299 0           $name =~ s{^.*/}{}s;
300             }
301             }
302 0           _simplify($name);
303 0           $name =~ s{/+}{::}g;
304 0 0         if ($^O eq 'MacOS') {
305 0           $name =~ s{:+}{::}g; # : -> ::
306             } else {
307 0           $name =~ s{/+}{::}g; # / -> ::
308             }
309 0           return $name;
310             }
311              
312             =head2 C
313              
314             The function B is equivalent to B, but also
315             strips Perl-like extensions (.pm, .pl, .pod) and extensions like
316             F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
317              
318             =cut
319              
320             # basic simplification of the POD name:
321             # basename & strip extension
322             sub simplify_name {
323 0     0 1   my ($str) = @_;
324             # remove all path components
325 0 0         if ($^O eq 'MacOS') {
326 0           $str =~ s/^.*://s;
327             } else {
328 0           $str =~ s{^.*/}{}s;
329             }
330 0           _simplify($str);
331 0           return $str;
332             }
333              
334             # internal sub only
335             sub _simplify {
336             # strip Perl's own extensions
337 0     0     $_[0] =~ s/\.(pod|pm|plx?)\z//i;
338             # strip meaningless extensions on Win32 and OS/2
339 0 0         $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i);
340             # strip meaningless extensions on VMS
341 0 0         $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
342             }
343              
344             # contribution from Tim Jenness
345              
346             =head2 C
347              
348             Returns the location of a pod document given a search directory
349             and a module (e.g. C) or script (e.g. C) name.
350              
351             Options:
352              
353             =over 4
354              
355             =item C<-inc =E 1>
356              
357             Search @INC for the pod and also the C defined in the
358             L module.
359              
360             =item C<-dirs =E [ $dir1, $dir2, ... ]>
361              
362             Reference to an array of search directories. These are searched in order
363             before looking in C<@INC> (if B<-inc>). Current directory is used if
364             none are specified.
365              
366             =item C<-verbose =E 1>
367              
368             List directories as they are searched
369              
370             =back
371              
372             Returns the full path of the first occurrence to the file.
373             Package names (eg 'A::B') are automatically converted to directory
374             names in the selected directory. (eg on unix 'A::B' is converted to
375             'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
376             search automatically if required.
377              
378             A subdirectory F is also checked if it exists in any of the given
379             search directories. This ensures that e.g. L is
380             found.
381              
382             It is assumed that if a module name is supplied, that that name
383             matches the file name. Pods are not opened to check for the 'NAME'
384             entry.
385              
386             A check is made to make sure that the file that is found does
387             contain some pod documentation.
388              
389             =cut
390              
391             sub pod_where {
392              
393             # default options
394 0     0 1   my %options = (
395             '-inc' => 0,
396             '-verbose' => 0,
397             '-dirs' => [ File::Spec->curdir ],
398             );
399              
400             # Check for an options hash as first argument
401 0 0 0       if (defined $_[0] && ref($_[0]) eq 'HASH') {
402 0           my $opt = shift;
403              
404             # Merge default options with supplied options
405 0           %options = (%options, %$opt);
406             }
407              
408             # Check usage
409 0 0         carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
410              
411             # Read argument
412 0           my $pod = shift;
413              
414             # Split on :: and then join the name together using File::Spec
415 0           my @parts = split (/::/, $pod);
416              
417             # Get full directory list
418 0           my @search_dirs = @{ $options{'-dirs'} };
  0            
419              
420 0 0         if ($options{'-inc'}) {
421              
422 0           require Config;
423              
424             # Add @INC
425 0 0 0       if ($^O eq 'MacOS' && $options{'-inc'}) {
    0          
426             # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
427 0           my @new_INC = @INC;
428 0           for (@new_INC) {
429 0 0         if ( $_ eq '.' ) {
    0          
430 0           $_ = ':';
431 0           } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
432 0           $_ = ':'. $_;
433             } else {
434 0           $_ =~ s{^\./}{:};
435             }
436             }
437 0           push (@search_dirs, @new_INC);
438             } elsif ($options{'-inc'}) {
439 0           push (@search_dirs, @INC);
440             }
441              
442             # Add location of pod documentation for perl man pages (eg perlfunc)
443             # This is a pod directory in the private install tree
444             #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'},
445             # 'pod');
446             #push (@search_dirs, $perlpoddir)
447             # if -d $perlpoddir;
448              
449             # Add location of binaries such as pod2text
450             push (@search_dirs, $Config::Config{'scriptdir'})
451 0 0         if -d $Config::Config{'scriptdir'};
452             }
453              
454             warn 'Search path is: '.join(' ', @search_dirs)."\n"
455 0 0         if $options{'-verbose'};
456              
457             # Loop over directories
458 0           Dir: foreach my $dir ( @search_dirs ) {
459              
460             # Don't bother if can't find the directory
461 0 0         if (-d $dir) {
462             warn "Looking in directory $dir\n"
463 0 0         if $options{'-verbose'};
464              
465             # Now concatenate this directory with the pod we are searching for
466 0           my $fullname = File::Spec->catfile($dir, @parts);
467 0 0         $fullname = $^O eq 'VMS' ? VMS::Filespec::unixify($fullname) : _unixify($fullname);
468             warn "Filename is now $fullname\n"
469 0 0         if $options{'-verbose'};
470              
471             # Loop over possible extensions
472 0           foreach my $ext ('', '.pod', '.pm', '.pl') {
473 0           my $fullext = $fullname . $ext;
474 0 0 0       if (-f $fullext &&
475             contains_pod($fullext, $options{'-verbose'}) ) {
476 0 0         warn "FOUND: $fullext\n" if $options{'-verbose'};
477 0           return $fullext;
478             }
479             }
480             } else {
481             warn "Directory $dir does not exist\n"
482 0 0         if $options{'-verbose'};
483 0           next Dir;
484             }
485             # for some strange reason the path on MacOS/darwin/cygwin is
486             # 'pods' not 'pod'
487             # this could be the case also for other systems that
488             # have a case-tolerant file system, but File::Spec
489             # does not recognize 'darwin' yet. And cygwin also has "pods",
490             # but is not case tolerant. Oh well...
491 0 0 0       if((File::Spec->case_tolerant || $^O =~ /macos|darwin|cygwin/i)
      0        
492             && -d File::Spec->catdir($dir,'pods')) {
493 0           $dir = File::Spec->catdir($dir,'pods');
494 0           redo Dir;
495             }
496 0 0         if(-d File::Spec->catdir($dir,'pod')) {
497 0           $dir = File::Spec->catdir($dir,'pod');
498 0           redo Dir;
499             }
500             }
501             # No match;
502 0           return;
503             }
504              
505             =head2 C
506              
507             Returns true if the supplied filename (not POD module) contains some pod
508             information.
509              
510             =cut
511              
512             sub contains_pod {
513 0     0 1   my $file = shift;
514 0           my $verbose = 0;
515 0 0         $verbose = shift if @_;
516              
517             # check for one line of POD
518 0           my $podfh;
519 0 0         if ($] < 5.006) {
520 0           $podfh = gensym();
521             }
522              
523 0 0         unless(open($podfh,"<$file")) {
524 0           warn "Error: $file is unreadable: $!\n";
525 0           return;
526             }
527            
528 0           local $/ = undef;
529 0           my $pod = <$podfh>;
530 0 0         close($podfh) || die "Error closing $file: $!\n";
531 0 0         unless($pod =~ /^=(head\d|pod|over|item|cut)\b/m) {
532 0 0         warn "No POD in $file, skipping.\n"
533             if($verbose);
534 0           return 0;
535             }
536              
537 0           return 1;
538             }
539              
540             =head1 AUTHOR
541              
542             Please report bugs using L.
543              
544             Marek Rouchal Emarekr@cpan.orgE,
545             heavily borrowing code from Nick Ing-Simmons' PodToHtml.
546              
547             Tim Jenness Et.jenness@jach.hawaii.eduE provided
548             C and C.
549              
550             B is part of the L distribution.
551              
552             =head1 SEE ALSO
553              
554             L, L, L
555              
556             =cut
557              
558             1;
559