This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
L<PerlIO> and Pod::Html
[perl5.git] / lib / Pod / Find.pm
CommitLineData
e2c3adef
GS
1#############################################################################
2# Pod/Find.pm -- finds files containing POD documentation
3#
aaa799f9 4# Author: Marek Rouchal <marekr@cpan.org>
e2c3adef 5#
66aff6dd
GS
6# Copyright (C) 1999-2000 by Marek Rouchal (and borrowing code
7# from Nick Ing-Simmon's PodToHtml). All rights reserved.
e2c3adef
GS
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
JH
17require 5.005; ## requires this Perl version or later
18use Carp;
e2c3adef
GS
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
JH
36 $location = pod_where( { -inc => 1 }, "Pod::Find" );
37
e2c3adef
GS
38=head1 DESCRIPTION
39
92e3d63a
JH
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
RGS
46From this version on the typical SCM (software configuration management)
47files/directories like RCS, CVS, SCCS, .svn are ignored.
48
92e3d63a
JH
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
GS
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
JH
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
GS
88
89=over 4
90
92e3d63a 91=item C<-verbose =E<gt> 1>
e2c3adef
GS
92
93Print progress information while scanning.
94
92e3d63a 95=item C<-perl =E<gt> 1>
e2c3adef
GS
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
GS
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
JH
110automatically considers paths specified in the C<PERL5LIB> environment
111as this is prepended to I<@INC> by the Perl interpreter itself.
e2c3adef
GS
112
113=back
114
e2c3adef
GS
115=cut
116
66aff6dd
GS
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
GS
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
JH
134 push(@search, $Config::Config{scriptdir})
135 if -d $Config::Config{scriptdir};
e2c3adef
GS
136 $opts{-perl} = 1;
137 }
138
139 if($opts{-inc}) {
dc459aad
JH
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
GS
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
GS
165 # * remove e.g. 5.00503
166 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
66aff6dd 167
dc459aad
JH
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
GS
179 }
180
181 my %dirs_visited;
182 my %pods;
183 my %names;
184 my $pwd = cwd();
185
186 foreach my $try (@search) {
2773b013
GS
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
PP
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
GS
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
GS
205 my $item = $File::Find::name;
206 if(-d) {
dfc878f2
AB
207 if($item =~ m{/(?:RCS|CVS|SCCS|\.svn)$}) {
208 $File::Find::prune = 1;
209 return;
210 }
211 elsif($dirs_visited{$item}) {
2773b013
GS
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
GS
221 $File::Find::prune = 1;
222 warn "Perl $] version mismatch on $_, skipping.\n"
2773b013
GS
223 if($opts{-verbose});
224 }
225 return;
226 }
e2c3adef
GS
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
GS
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
GS
240 warn " Already seen in ",
241 join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
e2c3adef
GS
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
GS
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
GS
256 }
257
92e3d63a 258 return undef unless contains_pod($file,$verbose);
e2c3adef
GS
259
260 # strip non-significant path components
92e3d63a 261 # TODO what happens on e.g. Win32?
e2c3adef
GS
262 my $name = $file;
263 if(defined $root_rx) {
fe6f1558
GS
264 $name =~ s!$root_rx!!s;
265 $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
e2c3adef
GS
266 }
267 else {
dc459aad
JH
268 if ($^O eq 'MacOS') {
269 $name =~ s/^.*://s;
270 } else {
271 $name =~ s:^.*/::s;
272 }
e2c3adef 273 }
2773b013
GS
274 _simplify($name);
275 $name =~ s!/+!::!g; #/
dc459aad
JH
276 if ($^O eq 'MacOS') {
277 $name =~ s!:+!::!g; # : -> ::
278 } else {
279 $name =~ s!/+!::!g; # / -> ::
280 }
e2c3adef
GS
281 $name;
282}
283
92e3d63a
JH
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
JH
289
290=cut
291
e2c3adef
GS
292# basic simplification of the POD name:
293# basename & strip extension
294sub simplify_name {
295 my ($str) = @_;
2773b013 296 # remove all path components
dc459aad
JH
297 if ($^O eq 'MacOS') {
298 $str =~ s/^.*://s;
299 } else {
300 $str =~ s:^.*/::s;
301 }
2773b013 302 _simplify($str);
e2c3adef
GS
303 $str;
304}
305
2773b013
GS
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
PP
312 # strip meaningless extensions on VMS
313 $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
2773b013
GS
314}
315
92e3d63a
JH
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
JH
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
JH
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
JH
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
JH
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
JH
426 warn "Search path is: ".join(' ', @search_dirs)."\n"
427 if $options{'-verbose'};
428
92e3d63a
JH
429 # Loop over directories
430 Dir: foreach my $dir ( @search_dirs ) {
431
dc459aad 432 # Don't bother if can't find the directory
92e3d63a
JH
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
JH
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
MR
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
JH
464 $dir = File::Spec->catdir($dir,'pods');
465 redo Dir;
466 }
92e3d63a
JH
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
JH
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
NC
508Please report bugs using L<http://rt.cpan.org>.
509
510Marek Rouchal E<lt>marekr@cpan.orgE<gt>,
92e3d63a
JH
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
GS
5221;
523