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