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