Doc patches to clarify the stringification rules of {} and =>
[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);
dfc878f2 16$VERSION = 0.24_01; ## 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
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;
dc459aad 131 push(@search, $Config::Config{scriptdir})
132 if -d $Config::Config{scriptdir};
e2c3adef 133 $opts{-perl} = 1;
134 }
135
136 if($opts{-inc}) {
dc459aad 137 if ($^O eq 'MacOS') {
138 # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
139 my @new_INC = @INC;
140 for (@new_INC) {
141 if ( $_ eq '.' ) {
142 $_ = ':';
143 } elsif ( $_ =~ s|^((?:\.\./)+)|':' x (length($1)/3)|e ) {
144 $_ = ':'. $_;
145 } else {
146 $_ =~ s|^\./|:|;
147 }
148 }
149 push(@search, grep($_ ne File::Spec->curdir, @new_INC));
150 } else {
151 push(@search, grep($_ ne File::Spec->curdir, @INC));
152 }
153
e2c3adef 154 $opts{-perl} = 1;
155 }
156
157 if($opts{-perl}) {
158 require Config;
159 # this code simplifies the POD name for Perl modules:
160 # * remove "site_perl"
2773b013 161 # * remove e.g. "i586-linux" (from 'archname')
e2c3adef 162 # * remove e.g. 5.00503
163 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
66aff6dd 164
dc459aad 165 # Mac OS:
166 # * remove ":?site_perl:"
167 # * remove :?pod: if followed by *.pod (e.g. in :pod:perlfunc.pod)
168
169 if ($^O eq 'MacOS') {
170 $SIMPLIFY_RX =
171 qq!^(?i:\:?site_perl\:|\:?pod\:(?=.*?\\.pod\\z))*!;
172 } else {
173 $SIMPLIFY_RX =
174 qq!^(?i:site(_perl)?/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
175 }
e2c3adef 176 }
177
178 my %dirs_visited;
179 my %pods;
180 my %names;
181 my $pwd = cwd();
182
183 foreach my $try (@search) {
2773b013 184 unless(File::Spec->file_name_is_absolute($try)) {
185 # make path absolute
186 $try = File::Spec->catfile($pwd,$try);
187 }
188 # simplify path
16be52b8 189 # on VMS canonpath will vmsify:[the.path], but File::Find::find
190 # wants /unixy/paths
191 $try = File::Spec->canonpath($try) if ($^O ne 'VMS');
c4974618 192 $try = VMS::Filespec::unixify($try) if ($^O eq 'VMS');
e2c3adef 193 my $name;
194 if(-f $try) {
195 if($name = _check_and_extract_name($try, $opts{-verbose})) {
196 _check_for_duplicates($try, $name, \%names, \%pods);
197 }
198 next;
199 }
dc459aad 200 my $root_rx = $^O eq 'MacOS' ? qq!^\Q$try\E! : qq!^\Q$try\E/!;
e2c3adef 201 File::Find::find( sub {
2773b013 202 my $item = $File::Find::name;
203 if(-d) {
dfc878f2 204 if($item =~ m{/(?:RCS|CVS|SCCS|\.svn)$}) {
205 $File::Find::prune = 1;
206 return;
207 }
208 elsif($dirs_visited{$item}) {
2773b013 209 warn "Directory '$item' already seen, skipping.\n"
210 if($opts{-verbose});
211 $File::Find::prune = 1;
212 return;
213 }
214 else {
215 $dirs_visited{$item} = 1;
216 }
217 if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
e2c3adef 218 $File::Find::prune = 1;
219 warn "Perl $] version mismatch on $_, skipping.\n"
2773b013 220 if($opts{-verbose});
221 }
222 return;
223 }
e2c3adef 224 if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
225 _check_for_duplicates($item, $name, \%names, \%pods);
226 }
2773b013 227 }, $try); # end of File::Find::find
e2c3adef 228 }
229 chdir $pwd;
230 %pods;
231}
232
233sub _check_for_duplicates {
234 my ($file, $name, $names_ref, $pods_ref) = @_;
235 if($$names_ref{$name}) {
236 warn "Duplicate POD found (shadowing?): $name ($file)\n";
2773b013 237 warn " Already seen in ",
238 join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
e2c3adef 239 }
240 else {
241 $$names_ref{$name} = 1;
242 }
243 $$pods_ref{$file} = $name;
244}
245
246sub _check_and_extract_name {
247 my ($file, $verbose, $root_rx) = @_;
248
2773b013 249 # check extension or executable flag
250 # this involves testing the .bat extension on Win32!
92e3d63a 251 unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
252 return undef;
e2c3adef 253 }
254
92e3d63a 255 return undef unless contains_pod($file,$verbose);
e2c3adef 256
257 # strip non-significant path components
92e3d63a 258 # TODO what happens on e.g. Win32?
e2c3adef 259 my $name = $file;
260 if(defined $root_rx) {
fe6f1558 261 $name =~ s!$root_rx!!s;
262 $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
e2c3adef 263 }
264 else {
dc459aad 265 if ($^O eq 'MacOS') {
266 $name =~ s/^.*://s;
267 } else {
268 $name =~ s:^.*/::s;
269 }
e2c3adef 270 }
2773b013 271 _simplify($name);
272 $name =~ s!/+!::!g; #/
dc459aad 273 if ($^O eq 'MacOS') {
274 $name =~ s!:+!::!g; # : -> ::
275 } else {
276 $name =~ s!/+!::!g; # / -> ::
277 }
e2c3adef 278 $name;
279}
280
92e3d63a 281=head2 C<simplify_name( $str )>
282
283The function B<simplify_name> is equivalent to B<basename>, but also
284strips Perl-like extensions (.pm, .pl, .pod) and extensions like
2eec1a1e 285F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
92e3d63a 286
287=cut
288
e2c3adef 289# basic simplification of the POD name:
290# basename & strip extension
291sub simplify_name {
292 my ($str) = @_;
2773b013 293 # remove all path components
dc459aad 294 if ($^O eq 'MacOS') {
295 $str =~ s/^.*://s;
296 } else {
297 $str =~ s:^.*/::s;
298 }
2773b013 299 _simplify($str);
e2c3adef 300 $str;
301}
302
2773b013 303# internal sub only
304sub _simplify {
305 # strip Perl's own extensions
306 $_[0] =~ s/\.(pod|pm|plx?)\z//i;
307 # strip meaningless extensions on Win32 and OS/2
dbd1b0a2 308 $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i);
2eec1a1e 309 # strip meaningless extensions on VMS
310 $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
2773b013 311}
312
92e3d63a 313# contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
314
315=head2 C<pod_where( { %opts }, $pod )>
316
317Returns the location of a pod document given a search directory
318and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
319
320Options:
321
322=over 4
323
324=item C<-inc =E<gt> 1>
325
326Search @INC for the pod and also the C<scriptdir> defined in the
327L<Config|Config> module.
328
329=item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
330
331Reference to an array of search directories. These are searched in order
332before looking in C<@INC> (if B<-inc>). Current directory is used if
333none are specified.
334
335=item C<-verbose =E<gt> 1>
336
337List directories as they are searched
338
339=back
340
fb8eeed8 341Returns the full path of the first occurrence to the file.
92e3d63a 342Package names (eg 'A::B') are automatically converted to directory
343names in the selected directory. (eg on unix 'A::B' is converted to
344'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
345search automatically if required.
346
347A subdirectory F<pod/> is also checked if it exists in any of the given
348search directories. This ensures that e.g. L<perlfunc|perlfunc> is
349found.
350
351It is assumed that if a module name is supplied, that that name
352matches the file name. Pods are not opened to check for the 'NAME'
353entry.
354
355A check is made to make sure that the file that is found does
356contain some pod documentation.
357
358=cut
359
360sub pod_where {
361
362 # default options
363 my %options = (
364 '-inc' => 0,
365 '-verbose' => 0,
dc459aad 366 '-dirs' => [ File::Spec->curdir ],
92e3d63a 367 );
368
369 # Check for an options hash as first argument
370 if (defined $_[0] && ref($_[0]) eq 'HASH') {
371 my $opt = shift;
372
373 # Merge default options with supplied options
374 %options = (%options, %$opt);
375 }
376
377 # Check usage
378 carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
379
380 # Read argument
381 my $pod = shift;
382
383 # Split on :: and then join the name together using File::Spec
384 my @parts = split (/::/, $pod);
385
386 # Get full directory list
387 my @search_dirs = @{ $options{'-dirs'} };
388
389 if ($options{'-inc'}) {
390
391 require Config;
392
393 # Add @INC
dc459aad 394 if ($^O eq 'MacOS' && $options{'-inc'}) {
395 # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
396 my @new_INC = @INC;
397 for (@new_INC) {
398 if ( $_ eq '.' ) {
399 $_ = ':';
400 } elsif ( $_ =~ s|^((?:\.\./)+)|':' x (length($1)/3)|e ) {
401 $_ = ':'. $_;
402 } else {
403 $_ =~ s|^\./|:|;
404 }
405 }
406 push (@search_dirs, @new_INC);
407 } elsif ($options{'-inc'}) {
408 push (@search_dirs, @INC);
409 }
92e3d63a 410
411 # Add location of pod documentation for perl man pages (eg perlfunc)
412 # This is a pod directory in the private install tree
413 #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'},
414 # 'pod');
415 #push (@search_dirs, $perlpoddir)
416 # if -d $perlpoddir;
417
418 # Add location of binaries such as pod2text
419 push (@search_dirs, $Config::Config{'scriptdir'})
420 if -d $Config::Config{'scriptdir'};
421 }
422
2dd58eb2 423 warn "Search path is: ".join(' ', @search_dirs)."\n"
424 if $options{'-verbose'};
425
92e3d63a 426 # Loop over directories
427 Dir: foreach my $dir ( @search_dirs ) {
428
dc459aad 429 # Don't bother if can't find the directory
92e3d63a 430 if (-d $dir) {
431 warn "Looking in directory $dir\n"
432 if $options{'-verbose'};
433
434 # Now concatenate this directory with the pod we are searching for
435 my $fullname = File::Spec->catfile($dir, @parts);
436 warn "Filename is now $fullname\n"
437 if $options{'-verbose'};
438
439 # Loop over possible extensions
440 foreach my $ext ('', '.pod', '.pm', '.pl') {
441 my $fullext = $fullname . $ext;
442 if (-f $fullext &&
443 contains_pod($fullext, $options{'-verbose'}) ) {
444 warn "FOUND: $fullext\n" if $options{'-verbose'};
445 return $fullext;
446 }
447 }
448 } else {
449 warn "Directory $dir does not exist\n"
450 if $options{'-verbose'};
451 next Dir;
452 }
c23d1eb0 453 # for some strange reason the path on MacOS/darwin/cygwin is
2dd58eb2 454 # 'pods' not 'pod'
455 # this could be the case also for other systems that
456 # have a case-tolerant file system, but File::Spec
c23d1eb0 457 # does not recognize 'darwin' yet. And cygwin also has "pods",
458 # but is not case tolerant. Oh well...
459 if((File::Spec->case_tolerant || $^O =~ /macos|darwin|cygwin/i)
460 && -d File::Spec->catdir($dir,'pods')) {
2dd58eb2 461 $dir = File::Spec->catdir($dir,'pods');
462 redo Dir;
463 }
92e3d63a 464 if(-d File::Spec->catdir($dir,'pod')) {
465 $dir = File::Spec->catdir($dir,'pod');
466 redo Dir;
467 }
468 }
469 # No match;
470 return undef;
471}
472
473=head2 C<contains_pod( $file , $verbose )>
474
475Returns true if the supplied filename (not POD module) contains some pod
476information.
477
478=cut
479
480sub contains_pod {
481 my $file = shift;
482 my $verbose = 0;
483 $verbose = shift if @_;
484
485 # check for one line of POD
486 unless(open(POD,"<$file")) {
487 warn "Error: $file is unreadable: $!\n";
488 return undef;
489 }
490
491 local $/ = undef;
492 my $pod = <POD>;
493 close(POD) || die "Error closing $file: $!\n";
494 unless($pod =~ /\n=(head\d|pod|over|item)\b/s) {
495 warn "No POD in $file, skipping.\n"
496 if($verbose);
497 return 0;
498 }
499
500 return 1;
501}
502
503=head1 AUTHOR
504
aaa799f9 505Please report bugs using L<http://rt.cpan.org>.
506
507Marek Rouchal E<lt>marekr@cpan.orgE<gt>,
92e3d63a 508heavily borrowing code from Nick Ing-Simmons' PodToHtml.
509
510Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
511C<pod_where> and C<contains_pod>.
512
513=head1 SEE ALSO
514
515L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
516
517=cut
518
e2c3adef 5191;
520