ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/staticperl/perl/lib/Pod/Find.pm
Revision: 1.1
Committed: Thu Jun 30 14:27:14 2005 UTC (21 years, 4 months ago) by root
Branch: MAIN
CVS Tags: PERL-5-8-7, HEAD
Branch point for: PERL
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #############################################################################
2     # Pod/Find.pm -- finds files containing POD documentation
3     #
4     # Author: Marek Rouchal <marekr@cpan.org>
5     #
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
10     # as Perl itself.
11     #############################################################################
12    
13     package Pod::Find;
14    
15     use vars qw($VERSION);
16     $VERSION = 1.30; ## Current version of this package
17     require 5.005; ## requires this Perl version or later
18     use Carp;
19    
20     #############################################################################
21    
22     =head1 NAME
23    
24     Pod::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    
36     $location = pod_where( { -inc => 1 }, "Pod::Find" );
37    
38     =head1 DESCRIPTION
39    
40     B<Pod::Find> provides a set of functions to locate POD files. Note that
41     no function is exported by default to avoid pollution of your namespace,
42     so be sure to specify them in the B<use> statement if you need them:
43    
44     use Pod::Find qw(pod_find);
45    
46     From this version on the typical SCM (software configuration management)
47     files/directories like RCS, CVS, SCCS, .svn are ignored.
48    
49     =cut
50    
51     use strict;
52     #use diagnostics;
53     use Exporter;
54     use File::Spec;
55     use File::Find;
56     use Cwd;
57    
58     use 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
63     my $SIMPLIFY_RX;
64    
65     =head2 C<pod_find( { %opts } , @directories )>
66    
67     The function B<pod_find> searches for POD documents in a given set of
68     files and/or directories. It returns a hash with the file names as keys
69     and the POD name as value. The POD name is derived from the file name
70     and its position in the directory tree.
71    
72     E.g. when searching in F<$HOME/perl5lib>, the file
73     F<$HOME/perl5lib/MyModule.pm> would get the POD name I<MyModule>,
74     whereas F<$HOME/perl5lib/Myclass/Subclass.pm> would be
75     I<Myclass::Subclass>. The name information can be used for POD
76     translators.
77    
78     Only text files containing at least one valid POD command are found.
79    
80     A warning is printed if more than one POD file with the same POD name
81     is found, e.g. F<CPAN.pm> in different directories. This usually
82     indicates duplicate occurrences of modules in the I<@INC> search path.
83    
84     B<OPTIONS> The first argument for B<pod_find> may be a hash reference
85     with options. The rest are either directories that are searched
86     recursively or files. The POD names of files are the plain basenames
87     with any Perl-like extension (.pm, .pl, .pod) stripped.
88    
89     =over 4
90    
91     =item C<-verbose =E<gt> 1>
92    
93     Print progress information while scanning.
94    
95     =item C<-perl =E<gt> 1>
96    
97     Apply Perl-specific heuristics to find the correct PODs. This includes
98     stripping Perl-like extensions, omitting subdirectories that are numeric
99     but do I<not> match the current Perl interpreter's version id, suppressing
100     F<site_perl> as a module hierarchy name etc.
101    
102     =item C<-script =E<gt> 1>
103    
104     Search for PODs in the current Perl interpreter's installation
105     B<scriptdir>. This is taken from the local L<Config|Config> module.
106    
107     =item C<-inc =E<gt> 1>
108    
109     Search for PODs in the current Perl interpreter's I<@INC> paths. This
110     automatically considers paths specified in the C<PERL5LIB> environment
111     as this is prepended to I<@INC> by the Perl interpreter itself.
112    
113     =back
114    
115     =cut
116    
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
120     sub 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;
134     push(@search, $Config::Config{scriptdir})
135     if -d $Config::Config{scriptdir};
136     $opts{-perl} = 1;
137     }
138    
139     if($opts{-inc}) {
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    
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"
164     # * remove e.g. "i586-linux" (from 'archname')
165     # * remove e.g. 5.00503
166     # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
167    
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     }
179     }
180    
181     my %dirs_visited;
182     my %pods;
183     my %names;
184     my $pwd = cwd();
185    
186     foreach my $try (@search) {
187     unless(File::Spec->file_name_is_absolute($try)) {
188     # make path absolute
189     $try = File::Spec->catfile($pwd,$try);
190     }
191     # simplify path
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');
195     $try = VMS::Filespec::unixify($try) if ($^O eq 'VMS');
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     }
203     my $root_rx = $^O eq 'MacOS' ? qq!^\Q$try\E! : qq!^\Q$try\E/!;
204     File::Find::find( sub {
205     my $item = $File::Find::name;
206     if(-d) {
207     if($item =~ m{/(?:RCS|CVS|SCCS|\.svn)$}) {
208     $File::Find::prune = 1;
209     return;
210     }
211     elsif($dirs_visited{$item}) {
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" != $]) {
221     $File::Find::prune = 1;
222     warn "Perl $] version mismatch on $_, skipping.\n"
223     if($opts{-verbose});
224     }
225     return;
226     }
227     if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
228     _check_for_duplicates($item, $name, \%names, \%pods);
229     }
230     }, $try); # end of File::Find::find
231     }
232     chdir $pwd;
233     %pods;
234     }
235    
236     sub _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";
240     warn " Already seen in ",
241     join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
242     }
243     else {
244     $$names_ref{$name} = 1;
245     }
246     $$pods_ref{$file} = $name;
247     }
248    
249     sub _check_and_extract_name {
250     my ($file, $verbose, $root_rx) = @_;
251    
252     # check extension or executable flag
253     # this involves testing the .bat extension on Win32!
254     unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
255     return undef;
256     }
257    
258     return undef unless contains_pod($file,$verbose);
259    
260     # strip non-significant path components
261     # TODO what happens on e.g. Win32?
262     my $name = $file;
263     if(defined $root_rx) {
264     $name =~ s!$root_rx!!s;
265     $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
266     }
267     else {
268     if ($^O eq 'MacOS') {
269     $name =~ s/^.*://s;
270     } else {
271     $name =~ s:^.*/::s;
272     }
273     }
274     _simplify($name);
275     $name =~ s!/+!::!g; #/
276     if ($^O eq 'MacOS') {
277     $name =~ s!:+!::!g; # : -> ::
278     } else {
279     $name =~ s!/+!::!g; # / -> ::
280     }
281     $name;
282     }
283    
284     =head2 C<simplify_name( $str )>
285    
286     The function B<simplify_name> is equivalent to B<basename>, but also
287     strips Perl-like extensions (.pm, .pl, .pod) and extensions like
288     F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
289    
290     =cut
291    
292     # basic simplification of the POD name:
293     # basename & strip extension
294     sub simplify_name {
295     my ($str) = @_;
296     # remove all path components
297     if ($^O eq 'MacOS') {
298     $str =~ s/^.*://s;
299     } else {
300     $str =~ s:^.*/::s;
301     }
302     _simplify($str);
303     $str;
304     }
305    
306     # internal sub only
307     sub _simplify {
308     # strip Perl's own extensions
309     $_[0] =~ s/\.(pod|pm|plx?)\z//i;
310     # strip meaningless extensions on Win32 and OS/2
311     $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /mswin|os2/i);
312     # strip meaningless extensions on VMS
313     $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
314     }
315    
316     # contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
317    
318     =head2 C<pod_where( { %opts }, $pod )>
319    
320     Returns the location of a pod document given a search directory
321     and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
322    
323     Options:
324    
325     =over 4
326    
327     =item C<-inc =E<gt> 1>
328    
329     Search @INC for the pod and also the C<scriptdir> defined in the
330     L<Config|Config> module.
331    
332     =item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
333    
334     Reference to an array of search directories. These are searched in order
335     before looking in C<@INC> (if B<-inc>). Current directory is used if
336     none are specified.
337    
338     =item C<-verbose =E<gt> 1>
339    
340     List directories as they are searched
341    
342     =back
343    
344     Returns the full path of the first occurrence to the file.
345     Package names (eg 'A::B') are automatically converted to directory
346     names in the selected directory. (eg on unix 'A::B' is converted to
347     'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
348     search automatically if required.
349    
350     A subdirectory F<pod/> is also checked if it exists in any of the given
351     search directories. This ensures that e.g. L<perlfunc|perlfunc> is
352     found.
353    
354     It is assumed that if a module name is supplied, that that name
355     matches the file name. Pods are not opened to check for the 'NAME'
356     entry.
357    
358     A check is made to make sure that the file that is found does
359     contain some pod documentation.
360    
361     =cut
362    
363     sub pod_where {
364    
365     # default options
366     my %options = (
367     '-inc' => 0,
368     '-verbose' => 0,
369     '-dirs' => [ File::Spec->curdir ],
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
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     }
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    
426     warn "Search path is: ".join(' ', @search_dirs)."\n"
427     if $options{'-verbose'};
428    
429     # Loop over directories
430     Dir: foreach my $dir ( @search_dirs ) {
431    
432     # Don't bother if can't find the directory
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     }
456     # for some strange reason the path on MacOS/darwin/cygwin is
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
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')) {
464     $dir = File::Spec->catdir($dir,'pods');
465     redo Dir;
466     }
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    
478     Returns true if the supplied filename (not POD module) contains some pod
479     information.
480    
481     =cut
482    
483     sub 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";
497     unless($pod =~ /\n=(head\d|pod|over|item)\b/s) {
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    
508     Please report bugs using L<http://rt.cpan.org>.
509    
510     Marek Rouchal E<lt>marekr@cpan.orgE<gt>,
511     heavily borrowing code from Nick Ing-Simmons' PodToHtml.
512    
513     Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
514     C<pod_where> and C<contains_pod>.
515    
516     =head1 SEE ALSO
517    
518     L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
519    
520     =cut
521    
522     1;
523