6d4907ce3796d8868d11aad6d5d87877951ae54c
[p5sagit/p5-mst-13.2.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.21;   ## 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         my $name;
167         if(-f $try) {
168             if($name = _check_and_extract_name($try, $opts{-verbose})) {
169                 _check_for_duplicates($try, $name, \%names, \%pods);
170             }
171             next;
172         }
173         my $root_rx = qq!^\Q$try\E/!;
174         File::Find::find( sub {
175             my $item = $File::Find::name;
176             if(-d) {
177                 if($dirs_visited{$item}) {
178                     warn "Directory '$item' already seen, skipping.\n"
179                         if($opts{-verbose});
180                     $File::Find::prune = 1;
181                     return;
182                 }
183                 else {
184                     $dirs_visited{$item} = 1;
185                 }
186                 if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
187                     $File::Find::prune = 1;
188                     warn "Perl $] version mismatch on $_, skipping.\n"
189                         if($opts{-verbose});
190                 }
191                 return;
192             }
193             if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
194                 _check_for_duplicates($item, $name, \%names, \%pods);
195             }
196         }, $try); # end of File::Find::find
197     }
198     chdir $pwd;
199     %pods;
200 }
201
202 sub _check_for_duplicates {
203     my ($file, $name, $names_ref, $pods_ref) = @_;
204     if($$names_ref{$name}) {
205         warn "Duplicate POD found (shadowing?): $name ($file)\n";
206         warn "    Already seen in ",
207             join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
208     }
209     else {
210         $$names_ref{$name} = 1;
211     }
212     $$pods_ref{$file} = $name;
213 }
214
215 sub _check_and_extract_name {
216     my ($file, $verbose, $root_rx) = @_;
217
218     # check extension or executable flag
219     # this involves testing the .bat extension on Win32!
220     unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
221       return undef;
222     }
223
224     return undef unless contains_pod($file,$verbose);
225
226     # strip non-significant path components
227     # TODO what happens on e.g. Win32?
228     my $name = $file;
229     if(defined $root_rx) {
230         $name =~ s!$root_rx!!s;
231         $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
232     }
233     else {
234         $name =~ s:^.*/::s;
235     }
236     _simplify($name);
237     $name =~ s!/+!::!g; #/
238     $name;
239 }
240
241 =head2 C<simplify_name( $str )>
242
243 The function B<simplify_name> is equivalent to B<basename>, but also
244 strips Perl-like extensions (.pm, .pl, .pod) and extensions like
245 F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
246
247 =cut
248
249 # basic simplification of the POD name:
250 # basename & strip extension
251 sub simplify_name {
252     my ($str) = @_;
253     # remove all path components
254     $str =~ s:^.*/::s;
255     _simplify($str);
256     $str;
257 }
258
259 # internal sub only
260 sub _simplify {
261     # strip Perl's own extensions
262     $_[0] =~ s/\.(pod|pm|plx?)\z//i;
263     # strip meaningless extensions on Win32 and OS/2
264     $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /win|os2/i);
265     # strip meaningless extensions on VMS
266     $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
267 }
268
269 # contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
270
271 =head2 C<pod_where( { %opts }, $pod )>
272
273 Returns the location of a pod document given a search directory
274 and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
275
276 Options:
277
278 =over 4
279
280 =item C<-inc =E<gt> 1>
281
282 Search @INC for the pod and also the C<scriptdir> defined in the
283 L<Config|Config> module.
284
285 =item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
286
287 Reference to an array of search directories. These are searched in order
288 before looking in C<@INC> (if B<-inc>). Current directory is used if
289 none are specified.
290
291 =item C<-verbose =E<gt> 1>
292
293 List directories as they are searched
294
295 =back
296
297 Returns the full path of the first occurence to the file.
298 Package names (eg 'A::B') are automatically converted to directory
299 names in the selected directory. (eg on unix 'A::B' is converted to
300 'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
301 search automatically if required.
302
303 A subdirectory F<pod/> is also checked if it exists in any of the given
304 search directories. This ensures that e.g. L<perlfunc|perlfunc> is
305 found.
306
307 It is assumed that if a module name is supplied, that that name
308 matches the file name. Pods are not opened to check for the 'NAME'
309 entry.
310
311 A check is made to make sure that the file that is found does 
312 contain some pod documentation.
313
314 =cut
315
316 sub pod_where {
317
318   # default options
319   my %options = (
320          '-inc' => 0,
321          '-verbose' => 0,
322          '-dirs' => [ '.' ],
323         );
324
325   # Check for an options hash as first argument
326   if (defined $_[0] && ref($_[0]) eq 'HASH') {
327     my $opt = shift;
328
329     # Merge default options with supplied options
330     %options = (%options, %$opt);
331   }
332
333   # Check usage
334   carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
335
336   # Read argument
337   my $pod = shift;
338
339   # Split on :: and then join the name together using File::Spec
340   my @parts = split (/::/, $pod);
341
342   # Get full directory list
343   my @search_dirs = @{ $options{'-dirs'} };
344
345   if ($options{'-inc'}) {
346
347     require Config;
348
349     # Add @INC
350     push (@search_dirs, @INC) if $options{'-inc'};
351
352     # Add location of pod documentation for perl man pages (eg perlfunc)
353     # This is a pod directory in the private install tree
354     #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'},
355     #                                   'pod');
356     #push (@search_dirs, $perlpoddir)
357     #  if -d $perlpoddir;
358
359     # Add location of binaries such as pod2text
360     push (@search_dirs, $Config::Config{'scriptdir'})
361       if -d $Config::Config{'scriptdir'};
362   }
363
364   # Loop over directories
365   Dir: foreach my $dir ( @search_dirs ) {
366
367     # Don't bother if cant find the directory
368     if (-d $dir) {
369       warn "Looking in directory $dir\n" 
370         if $options{'-verbose'};
371
372       # Now concatenate this directory with the pod we are searching for
373       my $fullname = File::Spec->catfile($dir, @parts);
374       warn "Filename is now $fullname\n"
375         if $options{'-verbose'};
376
377       # Loop over possible extensions
378       foreach my $ext ('', '.pod', '.pm', '.pl') {
379         my $fullext = $fullname . $ext;
380         if (-f $fullext && 
381          contains_pod($fullext, $options{'-verbose'}) ) {
382           warn "FOUND: $fullext\n" if $options{'-verbose'};
383           return $fullext;
384         }
385       }
386     } else {
387       warn "Directory $dir does not exist\n"
388         if $options{'-verbose'};
389       next Dir;
390     }
391     if(-d File::Spec->catdir($dir,'pod')) {
392       $dir = File::Spec->catdir($dir,'pod');
393       redo Dir;
394     }
395   }
396   # No match;
397   return undef;
398 }
399
400 =head2 C<contains_pod( $file , $verbose )>
401
402 Returns true if the supplied filename (not POD module) contains some pod
403 information.
404
405 =cut
406
407 sub contains_pod {
408   my $file = shift;
409   my $verbose = 0;
410   $verbose = shift if @_;
411
412   # check for one line of POD
413   unless(open(POD,"<$file")) {
414     warn "Error: $file is unreadable: $!\n";
415     return undef;
416   }
417   
418   local $/ = undef;
419   my $pod = <POD>;
420   close(POD) || die "Error closing $file: $!\n";
421   unless($pod =~ /\n=(head\d|pod|over|item)\b/s) {
422     warn "No POD in $file, skipping.\n"
423       if($verbose);
424     return 0;
425   }
426
427   return 1;
428 }
429
430 =head1 AUTHOR
431
432 Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>,
433 heavily borrowing code from Nick Ing-Simmons' PodToHtml.
434
435 Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
436 C<pod_where> and C<contains_pod>.
437
438 =head1 SEE ALSO
439
440 L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
441
442 =cut
443
444 1;
445