1 #############################################################################
2 # Pod/Find.pm -- finds files containing POD documentation
4 # Author: Marek Rouchal <marekr@cpan.org>
6 # Copyright (C) 1999-2000 by Marek Rouchal (and borrowing code
7 # from Nick Ing-Simmon's PodToHtml). All rights reserved.
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
11 #############################################################################
16 use vars qw($VERSION);
17 $VERSION = '1.35'; ## Current version of this package
18 require 5.005; ## requires this Perl version or later
28 #############################################################################
32 Pod::Find - find POD documents in directory trees
36 use Pod::Find qw(pod_find simplify_name);
37 my %pods = pod_find({ -verbose => 1, -inc => 1 });
39 print "found library POD `$pods{$_}' in $_\n";
42 print "podname=",simplify_name('a/b/c/mymodule.pod'),"\n";
44 $location = pod_where( { -inc => 1 }, "Pod::Find" );
48 B<Pod::Find> provides a set of functions to locate POD files. Note that
49 no function is exported by default to avoid pollution of your namespace,
50 so be sure to specify them in the B<use> statement if you need them:
52 use Pod::Find qw(pod_find);
54 From this version on the typical SCM (software configuration management)
55 files/directories like RCS, CVS, SCCS, .svn are ignored.
65 use vars qw(@ISA @EXPORT_OK $VERSION);
67 @EXPORT_OK = qw(&pod_find &simplify_name &pod_where &contains_pod);
69 # package global variables
72 =head2 C<pod_find( { %opts } , @directories )>
74 The function B<pod_find> searches for POD documents in a given set of
75 files and/or directories. It returns a hash with the file names as keys
76 and the POD name as value. The POD name is derived from the file name
77 and its position in the directory tree.
79 E.g. when searching in F<$HOME/perl5lib>, the file
80 F<$HOME/perl5lib/MyModule.pm> would get the POD name I<MyModule>,
81 whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be
82 I<Myclass::Subclass>. The name information can be used for POD
85 Only text files containing at least one valid POD command are found.
87 A warning is printed if more than one POD file with the same POD name
88 is found, e.g. F<CPAN.pm> in different directories. This usually
89 indicates duplicate occurrences of modules in the I<@INC> search path.
91 B<OPTIONS> The first argument for B<pod_find> may be a hash reference
92 with options. The rest are either directories that are searched
93 recursively or files. The POD names of files are the plain basenames
94 with any Perl-like extension (.pm, .pl, .pod) stripped.
98 =item C<-verbose =E<gt> 1>
100 Print progress information while scanning.
102 =item C<-perl =E<gt> 1>
104 Apply Perl-specific heuristics to find the correct PODs. This includes
105 stripping Perl-like extensions, omitting subdirectories that are numeric
106 but do I<not> match the current Perl interpreter's version id, suppressing
107 F<site_perl> as a module hierarchy name etc.
109 =item C<-script =E<gt> 1>
111 Search for PODs in the current Perl interpreter's installation
112 B<scriptdir>. This is taken from the local L<Config|Config> module.
114 =item C<-inc =E<gt> 1>
116 Search for PODs in the current Perl interpreter's I<@INC> paths. This
117 automatically considers paths specified in the C<PERL5LIB> environment
118 as this is included in I<@INC> by the Perl interpreter itself.
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
134 $opts{-verbose} ||= 0;
141 push(@search, $Config::Config{scriptdir})
142 if -d $Config::Config{scriptdir};
147 if ($^O eq 'MacOS') {
148 # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
153 } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
159 push(@search, grep($_ ne File::Spec->curdir, @new_INC));
161 push(@search, grep($_ ne File::Spec->curdir, @INC));
169 # this code simplifies the POD name for Perl modules:
170 # * remove "site_perl"
171 # * remove e.g. "i586-linux" (from 'archname')
172 # * remove e.g. 5.00503
173 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
176 # * remove ":?site_perl:"
177 # * remove :?pod: if followed by *.pod (e.g. in :pod:perlfunc.pod)
179 if ($^O eq 'MacOS') {
181 qq!^(?i:\:?site_perl\:|\:?pod\:(?=.*?\\.pod\\z))*!;
184 qq!^(?i:site(_perl)?/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
193 foreach my $try (@search) {
194 unless(File::Spec->file_name_is_absolute($try)) {
196 $try = File::Spec->catfile($pwd,$try);
199 # on VMS canonpath will vmsify:[the.path], but File::Find::find
201 $try = File::Spec->canonpath($try) if ($^O ne 'VMS');
202 $try = VMS::Filespec::unixify($try) if ($^O eq 'VMS');
205 if($name = _check_and_extract_name($try, $opts{-verbose})) {
206 _check_for_duplicates($try, $name, \%names, \%pods);
210 my $root_rx = $^O eq 'MacOS' ? qq!^\Q$try\E! : qq!^\Q$try\E/!;
211 File::Find::find( sub {
212 my $item = $File::Find::name;
214 if($item =~ m{/(?:RCS|CVS|SCCS|\.svn)$}) {
215 $File::Find::prune = 1;
218 elsif($dirs_visited{$item}) {
219 warn "Directory '$item' already seen, skipping.\n"
221 $File::Find::prune = 1;
225 $dirs_visited{$item} = 1;
227 if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
228 $File::Find::prune = 1;
229 warn "Perl $] version mismatch on $_, skipping.\n"
234 if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
235 _check_for_duplicates($item, $name, \%names, \%pods);
237 }, $try); # end of File::Find::find
243 sub _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";
247 warn ' Already seen in ',
248 join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
251 $$names_ref{$name} = 1;
253 return $$pods_ref{$file} = $name;
256 sub _check_and_extract_name {
257 my ($file, $verbose, $root_rx) = @_;
259 # check extension or executable flag
260 # this involves testing the .bat extension on Win32!
261 unless(-f $file && -T $file && ($file =~ /\.(pod|pm|plx?)\z/i || -x $file )) {
265 return unless contains_pod($file,$verbose);
267 # strip non-significant path components
268 # TODO what happens on e.g. Win32?
270 if(defined $root_rx) {
271 $name =~ s/$root_rx//s;
272 $name =~ s/$SIMPLIFY_RX//s if(defined $SIMPLIFY_RX);
275 if ($^O eq 'MacOS') {
283 if ($^O eq 'MacOS') {
284 $name =~ s{:+}{::}g; # : -> ::
286 $name =~ s{/+}{::}g; # / -> ::
291 =head2 C<simplify_name( $str )>
293 The function B<simplify_name> is equivalent to B<basename>, but also
294 strips Perl-like extensions (.pm, .pl, .pod) and extensions like
295 F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
299 # basic simplification of the POD name:
300 # basename & strip extension
303 # remove all path components
304 if ($^O eq 'MacOS') {
315 # strip Perl's own extensions
316 $_[0] =~ s/\.(pod|pm|plx?)\z//i;
317 # strip meaningless extensions on Win32 and OS/2
318 $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i);
319 # strip meaningless extensions on VMS
320 $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
323 # contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
325 =head2 C<pod_where( { %opts }, $pod )>
327 Returns the location of a pod document given a search directory
328 and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
334 =item C<-inc =E<gt> 1>
336 Search @INC for the pod and also the C<scriptdir> defined in the
337 L<Config|Config> module.
339 =item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
341 Reference to an array of search directories. These are searched in order
342 before looking in C<@INC> (if B<-inc>). Current directory is used if
345 =item C<-verbose =E<gt> 1>
347 List directories as they are searched
351 Returns the full path of the first occurrence to the file.
352 Package names (eg 'A::B') are automatically converted to directory
353 names in the selected directory. (eg on unix 'A::B' is converted to
354 'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
355 search automatically if required.
357 A subdirectory F<pod/> is also checked if it exists in any of the given
358 search directories. This ensures that e.g. L<perlfunc|perlfunc> is
361 It is assumed that if a module name is supplied, that that name
362 matches the file name. Pods are not opened to check for the 'NAME'
365 A check is made to make sure that the file that is found does
366 contain some pod documentation.
376 '-dirs' => [ File::Spec->curdir ],
379 # Check for an options hash as first argument
380 if (defined $_[0] && ref($_[0]) eq 'HASH') {
383 # Merge default options with supplied options
384 %options = (%options, %$opt);
388 carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
393 # Split on :: and then join the name together using File::Spec
394 my @parts = split (/::/, $pod);
396 # Get full directory list
397 my @search_dirs = @{ $options{'-dirs'} };
399 if ($options{'-inc'}) {
404 if ($^O eq 'MacOS' && $options{'-inc'}) {
405 # tolerate '.', './some_dir' and '(../)+some_dir' on Mac OS
410 } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
416 push (@search_dirs, @new_INC);
417 } elsif ($options{'-inc'}) {
418 push (@search_dirs, @INC);
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'},
425 #push (@search_dirs, $perlpoddir)
428 # Add location of binaries such as pod2text
429 push (@search_dirs, $Config::Config{'scriptdir'})
430 if -d $Config::Config{'scriptdir'};
433 warn 'Search path is: '.join(' ', @search_dirs)."\n"
434 if $options{'-verbose'};
436 # Loop over directories
437 Dir: foreach my $dir ( @search_dirs ) {
439 # Don't bother if can't find the directory
441 warn "Looking in directory $dir\n"
442 if $options{'-verbose'};
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'};
449 # Loop over possible extensions
450 foreach my $ext ('', '.pod', '.pm', '.pl') {
451 my $fullext = $fullname . $ext;
453 contains_pod($fullext, $options{'-verbose'}) ) {
454 warn "FOUND: $fullext\n" if $options{'-verbose'};
459 warn "Directory $dir does not exist\n"
460 if $options{'-verbose'};
463 # for some strange reason the path on MacOS/darwin/cygwin is
465 # this could be the case also for other systems that
466 # have a case-tolerant file system, but File::Spec
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')) {
471 $dir = File::Spec->catdir($dir,'pods');
474 if(-d File::Spec->catdir($dir,'pod')) {
475 $dir = File::Spec->catdir($dir,'pod');
483 =head2 C<contains_pod( $file , $verbose )>
485 Returns true if the supplied filename (not POD module) contains some pod
493 $verbose = shift if @_;
495 # check for one line of POD
501 unless(open($podfh,"<$file")) {
502 warn "Error: $file is unreadable: $!\n";
508 close($podfh) || die "Error closing $file: $!\n";
509 unless($pod =~ /^=(head\d|pod|over|item|cut)\b/m) {
510 warn "No POD in $file, skipping.\n"
520 Please report bugs using L<http://rt.cpan.org>.
522 Marek Rouchal E<lt>marekr@cpan.orgE<gt>,
523 heavily borrowing code from Nick Ing-Simmons' PodToHtml.
525 Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
526 C<pod_where> and C<contains_pod>.
530 L<Pod::Parser>, L<Pod::Checker>, L<perldoc>