Subversion Repositories DevTools

Rev

Rev 311 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
311 dpurdie 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
use strict;
15
 
16
use vars qw($VERSION);
17
$VERSION = '1.35';   ## Current version of this package
18
require  5.005;   ## requires this Perl version or later
19
use Carp;
20
 
21
BEGIN {
22
   if ($] < 5.006) {
23
      require Symbol;
24
      import Symbol;
25
   }
26
}
27
 
28
#############################################################################
29
 
30
=head1 NAME
31
 
32
Pod::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
 
44
  $location = pod_where( { -inc => 1 }, "Pod::Find" );
45
 
46
=head1 DESCRIPTION
47
 
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:
51
 
52
  use Pod::Find qw(pod_find);
53
 
54
From this version on the typical SCM (software configuration management)
55
files/directories like RCS, CVS, SCCS, .svn are ignored.
56
 
57
=cut
58
 
59
#use diagnostics;
60
use Exporter;
61
use File::Spec;
62
use File::Find;
63
use Cwd;
64
 
65
use 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
70
my $SIMPLIFY_RX;
71
 
72
=head2 C<pod_find( { %opts } , @directories )>
73
 
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.
78
 
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
83
translators.
84
 
85
Only text files containing at least one valid POD command are found.
86
 
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.
90
 
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.
95
 
96
=over 4
97
 
98
=item C<-verbose =E<gt> 1>
99
 
100
Print progress information while scanning.
101
 
102
=item C<-perl =E<gt> 1>
103
 
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.
108
 
109
=item C<-script =E<gt> 1>
110
 
111
Search for PODs in the current Perl interpreter's installation 
112
B<scriptdir>. This is taken from the local L<Config|Config> module.
113
 
114
=item C<-inc =E<gt> 1>
115
 
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.
119
 
120
=back
121
 
122
=cut
123
 
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
127
sub 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;
141
        push(@search, $Config::Config{scriptdir})
142
            if -d $Config::Config{scriptdir};
143
        $opts{-perl} = 1;
144
    }
145
 
146
    if($opts{-inc}) {
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
                    $_ = ':';
153
                } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
154
                    $_ = ':'. $_;
155
                } else {
156
                    $_ =~ s{^\./}{:};
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
 
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"
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)
174
 
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
        }
186
    }
187
 
188
    my %dirs_visited;
189
    my %pods;
190
    my %names;
191
    my $pwd = cwd();
192
 
193
    foreach my $try (@search) {
194
        unless(File::Spec->file_name_is_absolute($try)) {
195
            # make path absolute
196
            $try = File::Spec->catfile($pwd,$try);
197
        }
198
        # simplify path
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');
202
        $try = VMS::Filespec::unixify($try) if ($^O eq 'VMS');
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
        }
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;
213
            if(-d) {
5848 dpurdie 214
                if($item =~ m{/(?:RCS|CVS|SCCS|\.svn|\.git)$}) {
311 dpurdie 215
                    $File::Find::prune = 1;
216
                    return;
217
                }
218
                elsif($dirs_visited{$item}) {
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" != $]) {
228
                    $File::Find::prune = 1;
229
                    warn "Perl $] version mismatch on $_, skipping.\n"
230
                        if($opts{-verbose});
231
                }
232
                return;
233
            }
234
            if($name = _check_and_extract_name($item, $opts{-verbose}, $root_rx)) {
235
                _check_for_duplicates($item, $name, \%names, \%pods);
236
            }
237
        }, $try); # end of File::Find::find
238
    }
239
    chdir $pwd;
240
    return %pods;
241
}
242
 
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";
249
    }
250
    else {
251
        $$names_ref{$name} = 1;
252
    }
253
    return $$pods_ref{$file} = $name;
254
}
255
 
256
sub _check_and_extract_name {
257
    my ($file, $verbose, $root_rx) = @_;
258
 
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 )) {
262
      return;
263
    }
264
 
265
    return unless contains_pod($file,$verbose);
266
 
267
    # strip non-significant path components
268
    # TODO what happens on e.g. Win32?
269
    my $name = $file;
270
    if(defined $root_rx) {
271
        $name =~ s/$root_rx//s;
272
        $name =~ s/$SIMPLIFY_RX//s if(defined $SIMPLIFY_RX);
273
    }
274
    else {
275
        if ($^O eq 'MacOS') {
276
            $name =~ s/^.*://s;
277
        } else {
278
            $name =~ s{^.*/}{}s;
279
        }
280
    }
281
    _simplify($name);
282
    $name =~ s{/+}{::}g;
283
    if ($^O eq 'MacOS') {
284
        $name =~ s{:+}{::}g; # : -> ::
285
    } else {
286
        $name =~ s{/+}{::}g; # / -> ::
287
    }
288
    return $name;
289
}
290
 
291
=head2 C<simplify_name( $str )>
292
 
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.
296
 
297
=cut
298
 
299
# basic simplification of the POD name:
300
# basename & strip extension
301
sub simplify_name {
302
    my ($str) = @_;
303
    # remove all path components
304
    if ($^O eq 'MacOS') {
305
        $str =~ s/^.*://s;
306
    } else {
307
        $str =~ s{^.*/}{}s;
308
    }
309
    _simplify($str);
310
    return $str;
311
}
312
 
313
# internal sub only
314
sub _simplify {
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');
321
}
322
 
323
# contribution from Tim Jenness <t.jenness@jach.hawaii.edu>
324
 
325
=head2 C<pod_where( { %opts }, $pod )>
326
 
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.
329
 
330
Options:
331
 
332
=over 4
333
 
334
=item C<-inc =E<gt> 1>
335
 
336
Search @INC for the pod and also the C<scriptdir> defined in the
337
L<Config|Config> module.
338
 
339
=item C<-dirs =E<gt> [ $dir1, $dir2, ... ]>
340
 
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
343
none are specified.
344
 
345
=item C<-verbose =E<gt> 1>
346
 
347
List directories as they are searched
348
 
349
=back
350
 
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.
356
 
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
359
found.
360
 
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'
363
entry.
364
 
365
A check is made to make sure that the file that is found does 
366
contain some pod documentation.
367
 
368
=cut
369
 
370
sub pod_where {
371
 
372
  # default options
373
  my %options = (
374
         '-inc' => 0,
375
         '-verbose' => 0,
376
         '-dirs' => [ File::Spec->curdir ],
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
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
                $_ = ':';
410
            } elsif ( $_ =~ s{^((?:\.\./)+)}{':' x (length($1)/3)}e ) {
411
                $_ = ':'. $_;
412
            } else {
413
                $_ =~ s{^\./}{:};
414
            }
415
        }
416
        push (@search_dirs, @new_INC);
417
    } elsif ($options{'-inc'}) {
418
        push (@search_dirs, @INC);
419
    }
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
 
433
  warn 'Search path is: '.join(' ', @search_dirs)."\n"
434
        if $options{'-verbose'};
435
 
436
  # Loop over directories
437
  Dir: foreach my $dir ( @search_dirs ) {
438
 
439
    # Don't bother if can't find the directory
440
    if (-d $dir) {
441
      warn "Looking in directory $dir\n"
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;
452
        if (-f $fullext &&
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
    }
463
    # for some strange reason the path on MacOS/darwin/cygwin is
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
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');
472
      redo Dir;
473
    }
474
    if(-d File::Spec->catdir($dir,'pod')) {
475
      $dir = File::Spec->catdir($dir,'pod');
476
      redo Dir;
477
    }
478
  }
479
  # No match;
480
  return;
481
}
482
 
483
=head2 C<contains_pod( $file , $verbose )>
484
 
485
Returns true if the supplied filename (not POD module) contains some pod
486
information.
487
 
488
=cut
489
 
490
sub contains_pod {
491
  my $file = shift;
492
  my $verbose = 0;
493
  $verbose = shift if @_;
494
 
495
  # check for one line of POD
496
  my $podfh;
497
  if ($] < 5.006) {
498
    $podfh = gensym();
499
  }
500
 
501
  unless(open($podfh,"<$file")) {
502
    warn "Error: $file is unreadable: $!\n";
503
    return;
504
  }
505
 
506
  local $/ = undef;
507
  my $pod = <$podfh>;
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"
511
      if($verbose);
512
    return 0;
513
  }
514
 
515
  return 1;
516
}
517
 
518
=head1 AUTHOR
519
 
520
Please report bugs using L<http://rt.cpan.org>.
521
 
522
Marek Rouchal E<lt>marekr@cpan.orgE<gt>,
523
heavily borrowing code from Nick Ing-Simmons' PodToHtml.
524
 
525
Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt> provided
526
C<pod_where> and C<contains_pod>.
527
 
528
=head1 SEE ALSO
529
 
530
L<Pod::Parser>, L<Pod::Checker>, L<perldoc>
531
 
532
=cut
533
 
534
1;
535