This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Integrate mainline
[perl5.git] / lib / Pod / Find.pm
1 #############################################################################  
2 # Pod/Find.pm -- finds files containing POD documentation
3 #
4 # Author: Marek Rouchal <marek@saftsack.fs.uni-bayreuth.de>
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
15 use vars qw($VERSION);
16 $VERSION = 0.22;   ## Current version of this package
17 require  5.005;   ## requires this Perl version or later
18 use Carp;
19
20 #############################################################################
21
22 =head1 NAME
23
24 Pod::Find - find POD documents in directory trees
25
26 =head1 SYNOPSIS
27
28   use Pod::Find qw(pod_find simplify_name);
29   my %pods = pod_find({ -verbose => 1, -inc => 1 });
30   foreach(keys %pods) {
31      print "found library POD `$pods{$_}' in $_\n";
32   }
33
34   print "podname=",simplify_name('a/b/c/mymodule.pod'),"\n";
35
36   $location = pod_where( { -inc => 1 }, "Pod::Find" );
37
38 =head1 DESCRIPTION
39
40 B<Pod::Find> provides a set of functions to locate POD files.  Note that
41 no function is exported by default to avoid pollution of your namespace,
42 so be sure to specify them in the B<use> statement if you need them:
43
44   use Pod::Find qw(pod_find);
45
46 =cut
47
48 use strict;
49 #use diagnostics;
50 use Exporter;
51 use File::Spec;
52 use File::Find;
53 use Cwd;
54
55 use vars qw(@ISA @EXPORT_OK $VERSION);
56 @ISA = qw(Exporter);
57 @EXPORT_OK = qw(&pod_find &simplify_name &pod_where &contains_pod);
58
59 # package global variables
60 my $SIMPLIFY_RX;
61
62 =head2 C<pod_find( { %opts } , @directories )>
63
64 The function B<pod_find> searches for POD documents in a given set of
65 files and/or directories. It returns a hash with the file names as keys
66 and the POD name as value. The POD name is derived from the file name
67 and its position in the directory tree.
68
69 E.g. when searching in F<$HOME/perl5lib>, the file
70 F<$HOME/perl5lib/MyModule.pm> would get the POD name I<MyModule>,
71 whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be
72 I<Myclass::Subclass>. The name information can be used for POD
73 translators.
74
75 Only text files containing at least one valid POD command are found.
76
77 A warning is printed if more than one POD file with the same POD name
78 is found, e.g. F<CPAN.pm> in different directories. This usually
79 indicates duplicate occurrences of modules in the I<@INC> search path.
80
81 B<OPTIONS> The first argument for B<pod_find> may be a hash reference
82 with options. The rest are either directories that are searched
83 recursively or files.  The POD names of files are the plain basenames
84 with any Perl-like extension (.pm, .pl, .pod) stripped.
85
86 =over 4
87
88 =item C<-verbose =E<gt> 1>
89
90 Print progress information while scanning.
91
92 =item C<-perl =E<gt> 1>
93
94 Apply Perl-specific heuristics to find the correct PODs. This includes
95 stripping Perl-like extensions, omitting subdirectories that are numeric
96 but do I<not> match the current Perl interpreter's version id, suppressing
97 F<site_perl> as a module hierarchy name etc.
98
99 =item C<-script =E<gt> 1>
100
101 Search for PODs in the current Perl interpreter's installation 
102 B<scriptdir>. This is taken from the local L<Config|Config> module.
103
104 =item C<-inc =E<gt> 1>
105
106 Search for PODs in the current Perl interpreter's I<@INC> paths. This
107 automatically considers paths specified in the C<PERL5LIB> environment
108 as this is prepended to I<@INC> by the Perl interpreter itself.
109
110 =back
111
112 =cut
113
114 # return a hash of the POD files found
115 # first argument may be a hashref (options),
116 # rest is a list of directories to search recursively
117 sub pod_find
118 {
119     my %opts;
120     if(ref $_[0]) {
121         %opts = %{shift()};
122     }
123
124     $opts{-verbose} ||= 0;
125     $opts{-perl}    ||= 0;
126
127     my (@search) = @_;
128
129     if($opts{-script}) {
130         require Config;
131         push(@search, $Config::Config{scriptdir});
132         $opts{-perl} = 1;
133     }
134
135     if($opts{-inc}) {
136         push(@search, grep($_ ne '.',@INC));
137         $opts{-perl} = 1;
138     }
139
140     if($opts{-perl}) {
141         require Config;
142         # this code simplifies the POD name for Perl modules:
143         # * remove "site_perl"
144         # * remove e.g. "i586-linux" (from 'archname')
145         # * remove e.g. 5.00503
146         # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
147         $SIMPLIFY_RX =
148           qq!^(?i:site(_perl)?/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
149
150     }
151
152     my %dirs_visited;
153     my %pods;
154     my %names;
155     my $pwd = cwd();
156
157     foreach my $try (@search) {
158         unless(File::Spec->file_name_is_absolute($try)) {
159             # make path absolute
160             $try = File::Spec->catfile($pwd,$try);
161         }
162         # simplify path
163         # on VMS canonpath will vmsify:[the.path], but File::Find::find
164         # wants /unixy/paths
165         $try = File::Spec->canonpath($try) if ($^O ne 'VMS');
166         $try = VMS::Filespec::unixify($try) if ($^O eq 'VMS');
167         my $name;
168         if(-f $try) {
169             if($name = _check_and_extract_name($try, $opts{-verbose})) {
170                 _check_for_duplicates($try, $name, \%names, \%pods);
171             }
172             next;
173         }
174         my $root_rx = qq!^\Q$try\E/!;
175         File::Find::find( sub {
176             my $item = $File::Find::name;
177             if(-d) {
178                 if($dirs_visited{$item}) {
179                     warn "Directory '$item' already seen, skipping.\n"
180                         if($opts{-verbose});
181                     $File::Find::prune = 1;
182                     return;
183                 }
184                 else {
185                     $dirs_visited{$item} = 1;
186                 }
187                 if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
188                     $File::Find::prune = 1;
189                     warn "Perl $] version mismatch on $_, skipping.\n"
190                         if($opts{-verbose});
191                 }
192                 return;
193             }
194             if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
195                 _check_for_duplicates($item, $name, \%names, \%pods);
196             }
197         }, $try); # end of File::Find::find
198     }
199     chdir $pwd;
200     %pods;
201 }
202
203 sub _check_for_duplicates {
204     my ($file, $name, $names_ref, $pods_ref) = @_;
205     if($$names_ref{$name}) {
206         warn "Duplicate POD found (shadowing?): $name ($file)\n";
207         warn "    Already seen in ",
208             join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
209     }
210     else {
211         $$names_ref{$name} = 1;
212     }
213     $$pods_ref{$file} = $name;
214 }
215
216 sub _check_and_extract_name {
217     my ($file, $verbose, $root_rx) = @_;
218
219     # check extension or executable flag
220     # this involves testing the .bat extension on Win32!
221     unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
222       return undef;
223     }
224
225     return undef unless contains_pod($file,$verbose);
226
227     # strip non-significant path components
228     # TODO what happens on e.g. Win32?
229     my $name = $file;
230     if(defined $root_rx) {
231         $name =~ s!$root_rx!!s;
232         $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
233     }
234     else {
235         $name =~ s:^.*/::s;
236     }
237     _simplify($name);
238     $name =~ s!/+!::!g; #/
239     $name;
240 }
241
242 =head2 C<simplify_name( $str )>
243
244 The function B<simplify_name> is equivalent to B<basename>, but also
245 strips Perl-like extensions (.pm, .pl, .pod) and extensions like
246 F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
247
248 =cut
249
250 # basic simplification of the POD name:
251 # basename & strip extension
252 sub simplify_name {
253     my ($str) = @_;
254     # remove all path components
255     $str =~ s:^.*/::s;
256     _simplify($str);
257     $str;
258 }
259
260 # internal sub only
261 sub _simplify {
262     # strip Perl's own extensions
263     $_[0] =~ s/\.(pod|pm|plx?)\z//i;
264     # strip meaningless extensions on Win32 and OS/2
265     $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i);
266     # strip meaningless extensions on VMS
267     $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
268 }
269
270 # contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
271
272 =head2 C<pod_where( { %opts }, $pod )>
273
274 Returns the location of a pod document given a search directory
275 and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
276
277 Options:
278
279 =over 4
280
281 =item C<-inc =E<gt> 1>
282
283 Search @INC for the pod and also the C<scriptdir> defined in the
284 L<Config|Config> module.
285
286 =item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
287
288 Reference to an array of search directories. These are searched in order
289 before looking in C<@INC> (if B<-inc>). Current directory is used if
290 none are specified.
291
292 =item C<-verbose =E<gt> 1>
293
294 List directories as they are searched
295
296 =back
297
298 Returns the full path of the first occurence to the file.
299 Package names (eg 'A::B') are automatically converted to directory
300 names in the selected directory. (eg on unix 'A::B' is converted to
301 'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
302 search automatically if required.
303
304 A subdirectory F<pod/> is also checked if it exists in any of the given
305 search directories. This ensures that e.g. L<perlfunc|perlfunc> is
306 found.
307
308 It is assumed that if a module name is supplied, that that name
309 matches the file name. Pods are not opened to check for the 'NAME'
310 entry.
311
312 A check is made to make sure that the file that is found does 
313 contain some pod documentation.
314
315 =cut
316
317 sub pod_where {
318
319   # default options
320   my %options = (
321          '-inc' => 0,
322          '-verbose' => 0,
323          '-dirs' => [ '.' ],
324         );
325
326   # Check for an options hash as first argument
327   if (defined $_[0] && ref($_[0]) eq 'HASH') {
328     my $opt = shift;
329
330     # Merge default options with supplied options
331     %options = (%options, %$opt);
332   }
333
334   # Check usage
335   carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
336
337   # Read argument
338   my $pod = shift;
339
340   # Split on :: and then join the name together using File::Spec
341   my @parts = split (/::/, $pod);
342
343   # Get full directory list
344   my @search_dirs = @{ $options{'-dirs'} };
345
346   if ($options{'-inc'}) {
347
348     require Config;
349
350     # Add @INC
351     push (@search_dirs, @INC) if $options{'-inc'};
352
353     # Add location of pod documentation for perl man pages (eg perlfunc)
354     # This is a pod directory in the private install tree
355     #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'},
356     #                                   'pod');
357     #push (@search_dirs, $perlpoddir)
358     #  if -d $perlpoddir;
359
360     # Add location of binaries such as pod2text
361     push (@search_dirs, $Config::Config{'scriptdir'})
362       if -d $Config::Config{'scriptdir'};
363   }
364
365   # Loop over directories
366   Dir: foreach my $dir ( @search_dirs ) {
367
368     # Don't bother if cant find the directory
369     if (-d $dir) {
370       warn "Looking in directory $dir\n" 
371         if $options{'-verbose'};
372
373       # Now concatenate this directory with the pod we are searching for
374       my $fullname = File::Spec->catfile($dir, @parts);
375       warn "Filename is now $fullname\n"
376         if $options{'-verbose'};
377
378       # Loop over possible extensions
379       foreach my $ext ('', '.pod', '.pm', '.pl') {
380         my $fullext = $fullname . $ext;
381         if (-f $fullext && 
382          contains_pod($fullext, $options{'-verbose'}) ) {
383           warn "FOUND: $fullext\n" if $options{'-verbose'};
384           return $fullext;
385         }
386       }
387     } else {
388       warn "Directory $dir does not exist\n"
389         if $options{'-verbose'};
390       next Dir;
391     }
392     if(-d File::Spec->catdir($dir,'pod')) {
393       $dir = File::Spec->catdir($dir,'pod');
394       redo Dir;
395     }
396   }
397   # No match;
398   return undef;
399 }
400
401 =head2 C<contains_pod( $file , $verbose )>
402
403 Returns true if the supplied filename (not POD module) contains some pod
404 information.
405
406 =cut
407
408 sub contains_pod {
409   my $file = shift;
410   my $verbose = 0;
411   $verbose = shift if @_;
412
413   # check for one line of POD
414   unless(open(POD,"<$file")) {
415     warn "Error: $file is unreadable: $!\n";
416     return undef;
417   }
418   
419   local $/ = undef;
420   my $pod = <POD>;
421   close(POD) || die "Error closing $file: $!\n";
422   unless($pod =~ /\n=(head\d|pod|over|item)\b/s) {
423     warn "No POD in $file, skipping.\n"
424       if($verbose);
425     return 0;
426   }
427
428   return 1;
429 }
430
431 =head1 AUTHOR
432
433 Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>,
434 heavily borrowing code from Nick Ing-Simmons' PodToHtml.
435
436 Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
437 C<pod_where> and C<contains_pod>.
438
439 =head1 SEE ALSO
440
441 L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
442
443 =cut
444
445 1;
446