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