This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Add a test for the "character range should match in EBCDIC
[perl5.git] / lib / Pod / Find.pm
CommitLineData
e2c3adef
GS
1#############################################################################
2# Pod/Find.pm -- finds files containing POD documentation
3#
4# Author: Marek Rouchal <marek@saftsack.fs.uni-bayreuth.de>
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);
92e3d63a
JH
16$VERSION = 0.21; ## Current version of this package
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
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
GS
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
JH
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
GS
85
86=over 4
87
92e3d63a 88=item C<-verbose =E<gt> 1>
e2c3adef
GS
89
90Print progress information while scanning.
91
92e3d63a 92=item C<-perl =E<gt> 1>
e2c3adef
GS
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
GS
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
JH
107automatically considers paths specified in the C<PERL5LIB> environment
108as this is prepended to I<@INC> by the Perl interpreter itself.
e2c3adef
GS
109
110=back
111
e2c3adef
GS
112=cut
113
66aff6dd
GS
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
GS
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;
131 push(@search, $Config::Config{scriptdir});
132 $opts{-perl} = 1;
133 }
134
135 if($opts{-inc}) {
136 push(@search, grep($_ ne '.',@INC));
137 $opts{-perl} = 1;
138 }
139
140 if($opts{-perl}) {
141 require Config;
142 # this code simplifies the POD name for Perl modules:
143 # * remove "site_perl"
2773b013 144 # * remove e.g. "i586-linux" (from 'archname')
e2c3adef
GS
145 # * remove e.g. 5.00503
146 # * remove pod/ if followed by *.pod (e.g. in pod/perlfunc.pod)
147 $SIMPLIFY_RX =
fe6f1558 148 qq!^(?i:site_perl/|\Q$Config::Config{archname}\E/|\\d+\\.\\d+([_.]?\\d+)?/|pod/(?=.*?\\.pod\\z))*!;
66aff6dd 149
e2c3adef
GS
150 }
151
152 my %dirs_visited;
153 my %pods;
154 my %names;
155 my $pwd = cwd();
156
157 foreach my $try (@search) {
2773b013
GS
158 unless(File::Spec->file_name_is_absolute($try)) {
159 # make path absolute
160 $try = File::Spec->catfile($pwd,$try);
161 }
162 # simplify path
16be52b8
PP
163 # on VMS canonpath will vmsify:[the.path], but File::Find::find
164 # wants /unixy/paths
165 $try = File::Spec->canonpath($try) if ($^O ne 'VMS');
e2c3adef
GS
166 my $name;
167 if(-f $try) {
168 if($name = _check_and_extract_name($try, $opts{-verbose})) {
169 _check_for_duplicates($try, $name, \%names, \%pods);
170 }
171 next;
172 }
2773b013 173 my $root_rx = qq!^\Q$try\E/!;
e2c3adef 174 File::Find::find( sub {
2773b013
GS
175 my $item = $File::Find::name;
176 if(-d) {
177 if($dirs_visited{$item}) {
178 warn "Directory '$item' already seen, skipping.\n"
179 if($opts{-verbose});
180 $File::Find::prune = 1;
181 return;
182 }
183 else {
184 $dirs_visited{$item} = 1;
185 }
186 if($opts{-perl} && /^(\d+\.[\d_]+)\z/s && eval "$1" != $]) {
e2c3adef
GS
187 $File::Find::prune = 1;
188 warn "Perl $] version mismatch on $_, skipping.\n"
2773b013
GS
189 if($opts{-verbose});
190 }
191 return;
192 }
e2c3adef
GS
193 if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
194 _check_for_duplicates($item, $name, \%names, \%pods);
195 }
2773b013 196 }, $try); # end of File::Find::find
e2c3adef
GS
197 }
198 chdir $pwd;
199 %pods;
200}
201
202sub _check_for_duplicates {
203 my ($file, $name, $names_ref, $pods_ref) = @_;
204 if($$names_ref{$name}) {
205 warn "Duplicate POD found (shadowing?): $name ($file)\n";
2773b013
GS
206 warn " Already seen in ",
207 join(' ', grep($$pods_ref{$_} eq $name, keys %$pods_ref)),"\n";
e2c3adef
GS
208 }
209 else {
210 $$names_ref{$name} = 1;
211 }
212 $$pods_ref{$file} = $name;
213}
214
215sub _check_and_extract_name {
216 my ($file, $verbose, $root_rx) = @_;
217
2773b013
GS
218 # check extension or executable flag
219 # this involves testing the .bat extension on Win32!
92e3d63a
JH
220 unless(-f $file && -T _ && ($file =~ /\.(pod|pm|plx?)\z/i || -x _ )) {
221 return undef;
e2c3adef
GS
222 }
223
92e3d63a 224 return undef unless contains_pod($file,$verbose);
e2c3adef
GS
225
226 # strip non-significant path components
92e3d63a 227 # TODO what happens on e.g. Win32?
e2c3adef
GS
228 my $name = $file;
229 if(defined $root_rx) {
fe6f1558
GS
230 $name =~ s!$root_rx!!s;
231 $name =~ s!$SIMPLIFY_RX!!os if(defined $SIMPLIFY_RX);
e2c3adef
GS
232 }
233 else {
fe6f1558 234 $name =~ s:^.*/::s;
e2c3adef 235 }
2773b013
GS
236 _simplify($name);
237 $name =~ s!/+!::!g; #/
e2c3adef
GS
238 $name;
239}
240
92e3d63a
JH
241=head2 C<simplify_name( $str )>
242
243The function B<simplify_name> is equivalent to B<basename>, but also
244strips Perl-like extensions (.pm, .pl, .pod) and extensions like
2eec1a1e 245F<.bat>, F<.cmd> on Win32 and OS/2, or F<.com> on VMS, respectively.
92e3d63a
JH
246
247=cut
248
e2c3adef
GS
249# basic simplification of the POD name:
250# basename & strip extension
251sub simplify_name {
252 my ($str) = @_;
2773b013 253 # remove all path components
fe6f1558 254 $str =~ s:^.*/::s;
2773b013 255 _simplify($str);
e2c3adef
GS
256 $str;
257}
258
2773b013
GS
259# internal sub only
260sub _simplify {
261 # strip Perl's own extensions
262 $_[0] =~ s/\.(pod|pm|plx?)\z//i;
263 # strip meaningless extensions on Win32 and OS/2
264 $_[0] =~ s/\.(bat|exe|cmd)\z//i if($^O =~ /win|os2/i);
2eec1a1e
PP
265 # strip meaningless extensions on VMS
266 $_[0] =~ s/\.(com)\z//i if($^O eq 'VMS');
2773b013
GS
267}
268
92e3d63a
JH
269# contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
270
271=head2 C<pod_where( { %opts }, $pod )>
272
273Returns the location of a pod document given a search directory
274and a module (e.g. C<File::Find>) or script (e.g. C<perldoc>) name.
275
276Options:
277
278=over 4
279
280=item C<-inc =E<gt> 1>
281
282Search @INC for the pod and also the C<scriptdir> defined in the
283L<Config|Config> module.
284
285=item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
286
287Reference to an array of search directories. These are searched in order
288before looking in C<@INC> (if B<-inc>). Current directory is used if
289none are specified.
290
291=item C<-verbose =E<gt> 1>
292
293List directories as they are searched
294
295=back
296
297Returns the full path of the first occurence to the file.
298Package names (eg 'A::B') are automatically converted to directory
299names in the selected directory. (eg on unix 'A::B' is converted to
300'A/B'). Additionally, '.pm', '.pl' and '.pod' are appended to the
301search automatically if required.
302
303A subdirectory F<pod/> is also checked if it exists in any of the given
304search directories. This ensures that e.g. L<perlfunc|perlfunc> is
305found.
306
307It is assumed that if a module name is supplied, that that name
308matches the file name. Pods are not opened to check for the 'NAME'
309entry.
310
311A check is made to make sure that the file that is found does
312contain some pod documentation.
313
314=cut
315
316sub pod_where {
317
318 # default options
319 my %options = (
320 '-inc' => 0,
321 '-verbose' => 0,
322 '-dirs' => [ '.' ],
323 );
324
325 # Check for an options hash as first argument
326 if (defined $_[0] && ref($_[0]) eq 'HASH') {
327 my $opt = shift;
328
329 # Merge default options with supplied options
330 %options = (%options, %$opt);
331 }
332
333 # Check usage
334 carp 'Usage: pod_where({options}, $pod)' unless (scalar(@_));
335
336 # Read argument
337 my $pod = shift;
338
339 # Split on :: and then join the name together using File::Spec
340 my @parts = split (/::/, $pod);
341
342 # Get full directory list
343 my @search_dirs = @{ $options{'-dirs'} };
344
345 if ($options{'-inc'}) {
346
347 require Config;
348
349 # Add @INC
350 push (@search_dirs, @INC) if $options{'-inc'};
351
352 # Add location of pod documentation for perl man pages (eg perlfunc)
353 # This is a pod directory in the private install tree
354 #my $perlpoddir = File::Spec->catdir($Config::Config{'installprivlib'},
355 # 'pod');
356 #push (@search_dirs, $perlpoddir)
357 # if -d $perlpoddir;
358
359 # Add location of binaries such as pod2text
360 push (@search_dirs, $Config::Config{'scriptdir'})
361 if -d $Config::Config{'scriptdir'};
362 }
363
364 # Loop over directories
365 Dir: foreach my $dir ( @search_dirs ) {
366
367 # Don't bother if cant find the directory
368 if (-d $dir) {
369 warn "Looking in directory $dir\n"
370 if $options{'-verbose'};
371
372 # Now concatenate this directory with the pod we are searching for
373 my $fullname = File::Spec->catfile($dir, @parts);
374 warn "Filename is now $fullname\n"
375 if $options{'-verbose'};
376
377 # Loop over possible extensions
378 foreach my $ext ('', '.pod', '.pm', '.pl') {
379 my $fullext = $fullname . $ext;
380 if (-f $fullext &&
381 contains_pod($fullext, $options{'-verbose'}) ) {
382 warn "FOUND: $fullext\n" if $options{'-verbose'};
383 return $fullext;
384 }
385 }
386 } else {
387 warn "Directory $dir does not exist\n"
388 if $options{'-verbose'};
389 next Dir;
390 }
391 if(-d File::Spec->catdir($dir,'pod')) {
392 $dir = File::Spec->catdir($dir,'pod');
393 redo Dir;
394 }
395 }
396 # No match;
397 return undef;
398}
399
400=head2 C<contains_pod( $file , $verbose )>
401
402Returns true if the supplied filename (not POD module) contains some pod
403information.
404
405=cut
406
407sub contains_pod {
408 my $file = shift;
409 my $verbose = 0;
410 $verbose = shift if @_;
411
412 # check for one line of POD
413 unless(open(POD,"<$file")) {
414 warn "Error: $file is unreadable: $!\n";
415 return undef;
416 }
417
418 local $/ = undef;
419 my $pod = <POD>;
420 close(POD) || die "Error closing $file: $!\n";
421 unless($pod =~ /\n=(head\d|pod|over|item)\b/s) {
422 warn "No POD in $file, skipping.\n"
423 if($verbose);
424 return 0;
425 }
426
427 return 1;
428}
429
430=head1 AUTHOR
431
432Marek Rouchal E<lt>marek@saftsack.fs.uni-bayreuth.deE<gt>,
433heavily borrowing code from Nick Ing-Simmons' PodToHtml.
434
435Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
436C<pod_where> and C<contains_pod>.
437
438=head1 SEE ALSO
439
440L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
441
442=cut
443
e2c3adef
GS
4441;
445