Update to Pod::Parser 1.17, from Brad Appleton.
[p5sagit/p5-mst-13.2.git] / lib / Pod / Find.pm
CommitLineData
e2c3adef 1#############################################################################
2# Pod/Find.pm -- finds files containing POD documentation
3#
4# Author: Marek Rouchal <marek@saftsack.fs.uni-bayreuth.de>
5#
66aff6dd 6# Copyright (C) 1999-2000 by Marek Rouchal (and borrowing code
7# from Nick Ing-Simmon's PodToHtml). All rights reserved.
e2c3adef 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
13package Pod::Find;
14
15use vars qw($VERSION);
92e3d63a 16$VERSION = 0.21; ## Current version of this package
17require 5.005; ## requires this Perl version or later
18use Carp;
e2c3adef 19
20#############################################################################
21
22=head1 NAME
23
24Pod::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
92e3d63a 36 $location = pod_where( { -inc => 1 }, "Pod::Find" );
37
e2c3adef 38=head1 DESCRIPTION
39
92e3d63a 40B<Pod::Find> provides a set of functions to locate POD files. Note that
41no function is exported by default to avoid pollution of your namespace,
42so 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
48use strict;
49#use diagnostics;
50use Exporter;
51use File::Spec;
52use File::Find;
53use Cwd;
54
55use 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
60my $SIMPLIFY_RX;
61
62=head2 C<pod_find( { %opts } , @directories )>
63
64The function B<pod_find> searches for POD documents in a given set of
65files and/or directories. It returns a hash with the file names as keys
66and the POD name as value. The POD name is derived from the file name
67and its position in the directory tree.
e2c3adef 68
69E.g. when searching in F<$HOME/perl5lib>, the file
70F<$HOME/perl5lib/MyModule.pm> would get the POD name I<MyModule>,
71whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be
72I<Myclass::Subclass>. The name information can be used for POD
73translators.
74
75Only text files containing at least one valid POD command are found.
76
77A warning is printed if more than one POD file with the same POD name
78is found, e.g. F<CPAN.pm> in different directories. This usually
2773b013 79indicates duplicate occurrences of modules in the I<@INC> search path.
e2c3adef 80
92e3d63a 81B<OPTIONS> The first argument for B<pod_find> may be a hash reference
82with options. The rest are either directories that are searched
83recursively or files. The POD names of files are the plain basenames
84with any Perl-like extension (.pm, .pl, .pod) stripped.
e2c3adef 85
86=over 4
87
92e3d63a 88=item C<-verbose =E<gt> 1>
e2c3adef 89
90Print progress information while scanning.
91
92e3d63a 92=item C<-perl =E<gt> 1>
e2c3adef 93
94Apply Perl-specific heuristics to find the correct PODs. This includes
95stripping Perl-like extensions, omitting subdirectories that are numeric
96but do I<not> match the current Perl interpreter's version id, suppressing
97F<site_perl> as a module hierarchy name etc.
98
92e3d63a 99=item C<-script =E<gt> 1>
e2c3adef 100
101Search for PODs in the current Perl interpreter's installation
102B<scriptdir>. This is taken from the local L<Config|Config> module.
103
92e3d63a 104=item C<-inc =E<gt> 1>
e2c3adef 105
2773b013 106Search for PODs in the current Perl interpreter's I<@INC> paths. This
92e3d63a 107automatically considers paths specified in the C<PERL5LIB> environment
108as this is prepended to I<@INC> by the Perl interpreter itself.
e2c3adef 109
110=back
111
e2c3adef 112=cut
113
66aff6dd 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
e2c3adef 117sub 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"
2773b013 144 # * remove e.g. "i586-linux" (from 'archname')
e2c3adef 145 # * remove e.g. 5.00503
146 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
147 $SIMPLIFY_RX =
fe6f1558 148 qq!^(?i:site_perl/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
66aff6dd 149
e2c3adef 150 }
151
152 my %dirs_visited;
153 my %pods;
154 my %names;
155 my $pwd = cwd();
156
157 foreach my $try (@search) {
2773b013 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);
e2c3adef 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 }
2773b013 171 my $root_rx = qq!^\Q$try\E/!;
e2c3adef 172 File::Find::find( sub {
2773b013 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" != $]) {
e2c3adef 185 $File::Find::prune = 1;
186 warn "Perl $] version mismatch on $_, skipping.\n"
2773b013 187 if($opts{-verbose});
188 }
189 return;
190 }
e2c3adef 191 if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
192 _check_for_duplicates($item, $name, \%names, \%pods);
193 }
2773b013 194 }, $try); # end of File::Find::find
e2c3adef 195 }
196 chdir $pwd;
197 %pods;
198}
199
200sub _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";
2773b013 204 warn " Already seen in ",
205 join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
e2c3adef 206 }
207 else {
208 $$names_ref{$name} = 1;
209 }
210 $$pods_ref{$file} = $name;
211}
212
213sub _check_and_extract_name {
214 my ($file, $verbose, $root_rx) = @_;
215
2773b013 216 # check extension or executable flag
217 # this involves testing the .bat extension on Win32!
92e3d63a 218 unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
219 return undef;
e2c3adef 220 }
221
92e3d63a 222 return undef unless contains_pod($file,$verbose);
e2c3adef 223
224 # strip non-significant path components
92e3d63a 225 # TODO what happens on e.g. Win32?
e2c3adef 226 my $name = $file;
227 if(defined $root_rx) {
fe6f1558 228 $name =~ s!$root_rx!!s;
229 $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
e2c3adef 230 }
231 else {
fe6f1558 232 $name =~ s:^.*/::s;
e2c3adef 233 }
2773b013 234 _simplify($name);
235 $name =~ s!/+!::!g; #/
e2c3adef 236 $name;
237}
238
92e3d63a 239=head2 C<simplify_name( $str )>
240
241The function B<simplify_name> is equivalent to B<basename>, but also
242strips Perl-like extensions (.pm, .pl, .pod) and extensions like
243F<.bat>, F<.cmd> on Win32 and OS/2, respectively.
244
245=cut
246
e2c3adef 247# basic simplification of the POD name:
248# basename & strip extension
249sub simplify_name {
250 my ($str) = @_;
2773b013 251 # remove all path components
fe6f1558 252 $str =~ s:^.*/::s;
2773b013 253 _simplify($str);
e2c3adef 254 $str;
255}
256
2773b013 257# internal sub only
258sub _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
92e3d63a 265# contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
266
267=head2 C<pod_where( { %opts }, $pod )>
268
269Returns the location of a pod document given a search directory
270and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
271
272Options:
273
274=over 4
275
276=item C<-inc =E<gt> 1>
277
278Search @INC for the pod and also the C<scriptdir> defined in the
279L<Config|Config> module.
280
281=item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
282
283Reference to an array of search directories. These are searched in order
284before looking in C<@INC> (if B<-inc>). Current directory is used if
285none are specified.
286
287=item C<-verbose =E<gt> 1>
288
289List directories as they are searched
290
291=back
292
293Returns the full path of the first occurence to the file.
294Package names (eg 'A::B') are automatically converted to directory
295names in the selected directory. (eg on unix 'A::B' is converted to
296'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
297search automatically if required.
298
299A subdirectory F<pod/> is also checked if it exists in any of the given
300search directories. This ensures that e.g. L<perlfunc|perlfunc> is
301found.
302
303It is assumed that if a module name is supplied, that that name
304matches the file name. Pods are not opened to check for the 'NAME'
305entry.
306
307A check is made to make sure that the file that is found does
308contain some pod documentation.
309
310=cut
311
312sub 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
398Returns true if the supplied filename (not POD module) contains some pod
399information.
400
401=cut
402
403sub 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
428Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>,
429heavily borrowing code from Nick Ing-Simmons' PodToHtml.
430
431Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
432C<pod_where> and C<contains_pod>.
433
434=head1 SEE ALSO
435
436L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
437
438=cut
439
e2c3adef 4401;
441