xref: /openbsd/gnu/usr.bin/perl/cpan/CPAN/lib/CPAN/Module.pm (revision 9f11ffb7)
1# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*-
2# vim: ts=4 sts=4 sw=4:
3package CPAN::Module;
4use strict;
5@CPAN::Module::ISA = qw(CPAN::InfoObj);
6
7use vars qw(
8            $VERSION
9);
10$VERSION = "5.5003";
11
12BEGIN {
13    # alarm() is not implemented in perl 5.6.x and earlier under Windows
14    *ALARM_IMPLEMENTED = sub () { $] >= 5.007 || $^O !~ /MSWin/ };
15}
16
17# Accessors
18#-> sub CPAN::Module::userid
19sub userid {
20    my $self = shift;
21    my $ro = $self->ro;
22    return unless $ro;
23    return $ro->{userid} || $ro->{CPAN_USERID};
24}
25#-> sub CPAN::Module::description
26sub description {
27    my $self = shift;
28    my $ro = $self->ro or return "";
29    $ro->{description}
30}
31
32#-> sub CPAN::Module::distribution
33sub distribution {
34    my($self) = @_;
35    CPAN::Shell->expand("Distribution",$self->cpan_file);
36}
37
38#-> sub CPAN::Module::_is_representative_module
39sub _is_representative_module {
40    my($self) = @_;
41    return $self->{_is_representative_module} if defined $self->{_is_representative_module};
42    my $pm = $self->cpan_file or return $self->{_is_representative_module} = 0;
43    $pm =~ s|.+/||;
44    $pm =~ s{\.(?:tar\.(bz2|gz|Z)|t(?:gz|bz)|zip)$}{}i; # see base_id
45    $pm =~ s|-\d+\.\d+.+$||;
46    $pm =~ s|-[\d\.]+$||;
47    $pm =~ s/-/::/g;
48    $self->{_is_representative_module} = $pm eq $self->{ID} ? 1 : 0;
49    # warn "DEBUG: $pm eq $self->{ID} => $self->{_is_representative_module}";
50    $self->{_is_representative_module};
51}
52
53#-> sub CPAN::Module::undelay
54sub undelay {
55    my $self = shift;
56    delete $self->{later};
57    if ( my $dist = CPAN::Shell->expand("Distribution", $self->cpan_file) ) {
58        $dist->undelay;
59    }
60}
61
62# mark as dirty/clean
63#-> sub CPAN::Module::color_cmd_tmps ;
64sub color_cmd_tmps {
65    my($self) = shift;
66    my($depth) = shift || 0;
67    my($color) = shift || 0;
68    my($ancestors) = shift || [];
69    # a module needs to recurse to its cpan_file
70
71    return if exists $self->{incommandcolor}
72        && $color==1
73        && $self->{incommandcolor}==$color;
74    return if $color==0 && !$self->{incommandcolor};
75    if ($color>=1) {
76        if ( $self->uptodate ) {
77            $self->{incommandcolor} = $color;
78            return;
79        } elsif (my $have_version = $self->available_version) {
80            # maybe what we have is good enough
81            if (@$ancestors) {
82                my $who_asked_for_me = $ancestors->[-1];
83                my $obj = CPAN::Shell->expandany($who_asked_for_me);
84                if (0) {
85                } elsif ($obj->isa("CPAN::Bundle")) {
86                    # bundles cannot specify a minimum version
87                    return;
88                } elsif ($obj->isa("CPAN::Distribution")) {
89                    if (my $prereq_pm = $obj->prereq_pm) {
90                        for my $k (keys %$prereq_pm) {
91                            if (my $want_version = $prereq_pm->{$k}{$self->id}) {
92                                if (CPAN::Version->vcmp($have_version,$want_version) >= 0) {
93                                    $self->{incommandcolor} = $color;
94                                    return;
95                                }
96                            }
97                        }
98                    }
99                }
100            }
101        }
102    } else {
103        $self->{incommandcolor} = $color; # set me before recursion,
104                                          # so we can break it
105    }
106    if ($depth>=$CPAN::MAX_RECURSION) {
107        my $e = CPAN::Exception::RecursiveDependency->new($ancestors);
108        if ($e->is_resolvable) {
109            return $self->{incommandcolor}=2;
110        } else {
111            die $e;
112        }
113    }
114    # warn "color_cmd_tmps $depth $color " . $self->id; # sleep 1;
115
116    if ( my $dist = CPAN::Shell->expand("Distribution", $self->cpan_file) ) {
117        $dist->color_cmd_tmps($depth+1,$color,[@$ancestors, $self->id]);
118    }
119    # unreached code?
120    # if ($color==0) {
121    #    delete $self->{badtestcnt};
122    # }
123    $self->{incommandcolor} = $color;
124}
125
126#-> sub CPAN::Module::as_glimpse ;
127sub as_glimpse {
128    my($self) = @_;
129    my(@m);
130    my $class = ref($self);
131    $class =~ s/^CPAN:://;
132    my $color_on = "";
133    my $color_off = "";
134    if (
135        $CPAN::Shell::COLOR_REGISTERED
136        &&
137        $CPAN::META->has_inst("Term::ANSIColor")
138        &&
139        $self->description
140       ) {
141        $color_on = Term::ANSIColor::color("green");
142        $color_off = Term::ANSIColor::color("reset");
143    }
144    my $uptodateness = " ";
145    unless ($class eq "Bundle") {
146        my $u = $self->uptodate;
147        $uptodateness = $u ? "=" : "<" if defined $u;
148    };
149    my $id = do {
150        my $d = $self->distribution;
151        $d ? $d -> pretty_id : $self->cpan_userid;
152    };
153    push @m, sprintf("%-7s %1s %s%-22s%s (%s)\n",
154                     $class,
155                     $uptodateness,
156                     $color_on,
157                     $self->id,
158                     $color_off,
159                     $id,
160                    );
161    join "", @m;
162}
163
164#-> sub CPAN::Module::dslip_status
165sub dslip_status {
166    my($self) = @_;
167    my($stat);
168    # development status
169    @{$stat->{D}}{qw,i c a b R M S,}     = qw,idea
170                                              pre-alpha alpha beta released
171                                              mature standard,;
172    # support level
173    @{$stat->{S}}{qw,m d u n a,}         = qw,mailing-list
174                                              developer comp.lang.perl.*
175                                              none abandoned,;
176    # language
177    @{$stat->{L}}{qw,p c + o h,}         = qw,perl C C++ other hybrid,;
178    # interface
179    @{$stat->{I}}{qw,f r O p h n,}       = qw,functions
180                                              references+ties
181                                              object-oriented pragma
182                                              hybrid none,;
183    # public licence
184    @{$stat->{P}}{qw,p g l b a 2 o d r n,} = qw,Standard-Perl
185                                              GPL LGPL
186                                              BSD Artistic Artistic_2
187                                              open-source
188                                              distribution_allowed
189                                              restricted_distribution
190                                              no_licence,;
191    for my $x (qw(d s l i p)) {
192        $stat->{$x}{' '} = 'unknown';
193        $stat->{$x}{'?'} = 'unknown';
194    }
195    my $ro = $self->ro;
196    return +{} unless $ro && $ro->{statd};
197    return {
198            D  => $ro->{statd},
199            S  => $ro->{stats},
200            L  => $ro->{statl},
201            I  => $ro->{stati},
202            P  => $ro->{statp},
203            DV => $stat->{D}{$ro->{statd}},
204            SV => $stat->{S}{$ro->{stats}},
205            LV => $stat->{L}{$ro->{statl}},
206            IV => $stat->{I}{$ro->{stati}},
207            PV => $stat->{P}{$ro->{statp}},
208           };
209}
210
211#-> sub CPAN::Module::as_string ;
212sub as_string {
213    my($self) = @_;
214    my(@m);
215    CPAN->debug("$self entering as_string") if $CPAN::DEBUG;
216    my $class = ref($self);
217    $class =~ s/^CPAN:://;
218    local($^W) = 0;
219    push @m, $class, " id = $self->{ID}\n";
220    my $sprintf = "    %-12s %s\n";
221    push @m, sprintf($sprintf, 'DESCRIPTION', $self->description)
222        if $self->description;
223    my $sprintf2 = "    %-12s %s (%s)\n";
224    my($userid);
225    $userid = $self->userid;
226    if ( $userid ) {
227        my $author;
228        if ($author = CPAN::Shell->expand('Author',$userid)) {
229            my $email = "";
230            my $m; # old perls
231            if ($m = $author->email) {
232                $email = " <$m>";
233            }
234            push @m, sprintf(
235                             $sprintf2,
236                             'CPAN_USERID',
237                             $userid,
238                             $author->fullname . $email
239                            );
240        }
241    }
242    push @m, sprintf($sprintf, 'CPAN_VERSION', $self->cpan_version)
243        if $self->cpan_version;
244    if (my $cpan_file = $self->cpan_file) {
245        push @m, sprintf($sprintf, 'CPAN_FILE', $cpan_file);
246        if (my $dist = CPAN::Shell->expand("Distribution",$cpan_file)) {
247            my $upload_date = $dist->upload_date;
248            if ($upload_date) {
249                push @m, sprintf($sprintf, 'UPLOAD_DATE', $upload_date);
250            }
251        }
252    }
253    my $sprintf3 = "    %-12s %1s%1s%1s%1s%1s (%s,%s,%s,%s,%s)\n";
254    my $dslip = $self->dslip_status;
255    push @m, sprintf(
256                     $sprintf3,
257                     'DSLIP_STATUS',
258                     @{$dslip}{qw(D S L I P DV SV LV IV PV)},
259                    ) if $dslip->{D};
260    my $local_file = $self->inst_file;
261    unless ($self->{MANPAGE}) {
262        my $manpage;
263        if ($local_file) {
264            $manpage = $self->manpage_headline($local_file);
265        } else {
266            # If we have already untarred it, we should look there
267            my $dist = $CPAN::META->instance('CPAN::Distribution',
268                                             $self->cpan_file);
269            # warn "dist[$dist]";
270            # mff=manifest file; mfh=manifest handle
271            my($mff,$mfh);
272            if (
273                $dist->{build_dir}
274                and
275                (-f  ($mff = File::Spec->catfile($dist->{build_dir}, "MANIFEST")))
276                and
277                $mfh = FileHandle->new($mff)
278               ) {
279                CPAN->debug("mff[$mff]") if $CPAN::DEBUG;
280                my $lfre = $self->id; # local file RE
281                $lfre =~ s/::/./g;
282                $lfre .= "\\.pm\$";
283                my($lfl); # local file file
284                local $/ = "\n";
285                my(@mflines) = <$mfh>;
286                for (@mflines) {
287                    s/^\s+//;
288                    s/\s.*//s;
289                }
290                while (length($lfre)>5 and !$lfl) {
291                    ($lfl) = grep /$lfre/, @mflines;
292                    CPAN->debug("lfl[$lfl]lfre[$lfre]") if $CPAN::DEBUG;
293                    $lfre =~ s/.+?\.//;
294                }
295                $lfl =~ s/\s.*//; # remove comments
296                $lfl =~ s/\s+//g; # chomp would maybe be too system-specific
297                my $lfl_abs = File::Spec->catfile($dist->{build_dir},$lfl);
298                # warn "lfl_abs[$lfl_abs]";
299                if (-f $lfl_abs) {
300                    $manpage = $self->manpage_headline($lfl_abs);
301                }
302            }
303        }
304        $self->{MANPAGE} = $manpage if $manpage;
305    }
306    my($item);
307    for $item (qw/MANPAGE/) {
308        push @m, sprintf($sprintf, $item, $self->{$item})
309            if exists $self->{$item};
310    }
311    for $item (qw/CONTAINS/) {
312        push @m, sprintf($sprintf, $item, join(" ",@{$self->{$item}}))
313            if exists $self->{$item} && @{$self->{$item}};
314    }
315    push @m, sprintf($sprintf, 'INST_FILE',
316                     $local_file || "(not installed)");
317    push @m, sprintf($sprintf, 'INST_VERSION',
318                     $self->inst_version) if $local_file;
319    if (%{$CPAN::META->{is_tested}||{}}) { # XXX needs to be methodified somehow
320        my $available_file = $self->available_file;
321        if ($available_file && $available_file ne $local_file) {
322            push @m, sprintf($sprintf, 'AVAILABLE_FILE', $available_file);
323            push @m, sprintf($sprintf, 'AVAILABLE_VERSION', $self->available_version);
324        }
325    }
326    join "", @m, "\n";
327}
328
329#-> sub CPAN::Module::manpage_headline
330sub manpage_headline {
331    my($self,$local_file) = @_;
332    my(@local_file) = $local_file;
333    $local_file =~ s/\.pm(?!\n)\Z/.pod/;
334    push @local_file, $local_file;
335    my(@result,$locf);
336    for $locf (@local_file) {
337        next unless -f $locf;
338        my $fh = FileHandle->new($locf)
339            or $Carp::Frontend->mydie("Couldn't open $locf: $!");
340        my $inpod = 0;
341        local $/ = "\n";
342        while (<$fh>) {
343            $inpod = m/^=(?!head1\s+NAME\s*$)/ ? 0 :
344                m/^=head1\s+NAME\s*$/ ? 1 : $inpod;
345            next unless $inpod;
346            next if /^=/;
347            next if /^\s+$/;
348            chomp;
349            push @result, $_;
350        }
351        close $fh;
352        last if @result;
353    }
354    for (@result) {
355        s/^\s+//;
356        s/\s+$//;
357    }
358    join " ", @result;
359}
360
361#-> sub CPAN::Module::cpan_file ;
362# Note: also inherited by CPAN::Bundle
363sub cpan_file {
364    my $self = shift;
365    # CPAN->debug(sprintf "id[%s]", $self->id) if $CPAN::DEBUG;
366    unless ($self->ro) {
367        CPAN::Index->reload;
368    }
369    my $ro = $self->ro;
370    if ($ro && defined $ro->{CPAN_FILE}) {
371        return $ro->{CPAN_FILE};
372    } else {
373        my $userid = $self->userid;
374        if ( $userid ) {
375            if ($CPAN::META->exists("CPAN::Author",$userid)) {
376                my $author = $CPAN::META->instance("CPAN::Author",
377                                                   $userid);
378                my $fullname = $author->fullname;
379                my $email = $author->email;
380                unless (defined $fullname && defined $email) {
381                    return sprintf("Contact Author %s",
382                                   $userid,
383                                  );
384                }
385                return "Contact Author $fullname <$email>";
386            } else {
387                return "Contact Author $userid (Email address not available)";
388            }
389        } else {
390            return "N/A";
391        }
392    }
393}
394
395#-> sub CPAN::Module::cpan_version ;
396sub cpan_version {
397    my $self = shift;
398
399    my $ro = $self->ro;
400    unless ($ro) {
401        # Can happen with modules that are not on CPAN
402        $ro = {};
403    }
404    $ro->{CPAN_VERSION} = 'undef'
405        unless defined $ro->{CPAN_VERSION};
406    $ro->{CPAN_VERSION};
407}
408
409#-> sub CPAN::Module::force ;
410sub force {
411    my($self) = @_;
412    $self->{force_update} = 1;
413}
414
415#-> sub CPAN::Module::fforce ;
416sub fforce {
417    my($self) = @_;
418    $self->{force_update} = 2;
419}
420
421#-> sub CPAN::Module::notest ;
422sub notest {
423    my($self) = @_;
424    # $CPAN::Frontend->mywarn("XDEBUG: set notest for Module");
425    $self->{notest}++;
426}
427
428#-> sub CPAN::Module::rematein ;
429sub rematein {
430    my($self,$meth) = @_;
431    $CPAN::Frontend->myprint(sprintf("Running %s for module '%s'\n",
432                                     $meth,
433                                     $self->id));
434    my $cpan_file = $self->cpan_file;
435    if ($cpan_file eq "N/A" || $cpan_file =~ /^Contact Author/) {
436        $CPAN::Frontend->mywarn(sprintf qq{
437  The module %s isn\'t available on CPAN.
438
439  Either the module has not yet been uploaded to CPAN, or it is
440  temporary unavailable. Please contact the author to find out
441  more about the status. Try 'i %s'.
442},
443                                $self->id,
444                                $self->id,
445                               );
446        return;
447    }
448    my $pack = $CPAN::META->instance('CPAN::Distribution',$cpan_file);
449    $pack->called_for($self->id);
450    if (exists $self->{force_update}) {
451        if ($self->{force_update} == 2) {
452            $pack->fforce($meth);
453        } else {
454            $pack->force($meth);
455        }
456    }
457    $pack->notest($meth) if exists $self->{notest} && $self->{notest};
458
459    $pack->{reqtype} ||= "";
460    CPAN->debug("dist-reqtype[$pack->{reqtype}]".
461                "self-reqtype[$self->{reqtype}]") if $CPAN::DEBUG;
462        if ($pack->{reqtype}) {
463            if ($pack->{reqtype} eq "b" && $self->{reqtype} =~ /^[rc]$/) {
464                $pack->{reqtype} = $self->{reqtype};
465                if (
466                    exists $pack->{install}
467                    &&
468                    (
469                     UNIVERSAL::can($pack->{install},"failed") ?
470                     $pack->{install}->failed :
471                     $pack->{install} =~ /^NO/
472                    )
473                   ) {
474                    delete $pack->{install};
475                    $CPAN::Frontend->mywarn
476                        ("Promoting $pack->{ID} from 'build_requires' to 'requires'");
477                }
478            }
479        } else {
480            $pack->{reqtype} = $self->{reqtype};
481        }
482
483    my $success = eval {
484        $pack->$meth();
485    };
486    my $err = $@;
487    $pack->unforce if $pack->can("unforce") && exists $self->{force_update};
488    $pack->unnotest if $pack->can("unnotest") && exists $self->{notest};
489    delete $self->{force_update};
490    delete $self->{notest};
491    if ($err) {
492        die $err;
493    }
494    return $success;
495}
496
497#-> sub CPAN::Module::perldoc ;
498sub perldoc { shift->rematein('perldoc') }
499#-> sub CPAN::Module::readme ;
500sub readme  { shift->rematein('readme') }
501#-> sub CPAN::Module::look ;
502sub look    { shift->rematein('look') }
503#-> sub CPAN::Module::cvs_import ;
504sub cvs_import { shift->rematein('cvs_import') }
505#-> sub CPAN::Module::get ;
506sub get     { shift->rematein('get',@_) }
507#-> sub CPAN::Module::make ;
508sub make    { shift->rematein('make') }
509#-> sub CPAN::Module::test ;
510sub test   {
511    my $self = shift;
512    # $self->{badtestcnt} ||= 0;
513    $self->rematein('test',@_);
514}
515
516#-> sub CPAN::Module::deprecated_in_core ;
517sub deprecated_in_core {
518    my ($self) = @_;
519    return unless $CPAN::META->has_inst('Module::CoreList') && Module::CoreList->can('is_deprecated');
520    return Module::CoreList::is_deprecated($self->{ID});
521}
522
523#-> sub CPAN::Module::inst_deprecated;
524# Indicates whether the *installed* version of the module is a deprecated *and*
525# installed as part of the Perl core library path
526sub inst_deprecated {
527    my ($self) = @_;
528    my $inst_file = $self->inst_file or return;
529    return $self->deprecated_in_core && $self->_in_priv_or_arch($inst_file);
530}
531
532#-> sub CPAN::Module::uptodate ;
533sub uptodate {
534    my ($self) = @_;
535    local ($_);
536    my $inst = $self->inst_version or return 0;
537    my $cpan = $self->cpan_version;
538    return 0 if CPAN::Version->vgt($cpan,$inst) || $self->inst_deprecated;
539    CPAN->debug
540        (join
541         ("",
542          "returning uptodate. ",
543          "cpan[$cpan]inst[$inst]",
544         )) if $CPAN::DEBUG;
545    return 1;
546}
547
548# returns true if installed in privlib or archlib
549sub _in_priv_or_arch {
550    my($self,$inst_file) = @_;
551    foreach my $pair (
552        [qw(sitearchexp archlibexp)],
553        [qw(sitelibexp privlibexp)]
554    ) {
555        my ($site, $priv) = @Config::Config{@$pair};
556        if ($^O eq 'VMS') {
557            for my $d ($site, $priv) { $d = VMS::Filespec::unixify($d) };
558        }
559        s!/*$!!g foreach $site, $priv;
560        next if $site eq $priv;
561
562        if ($priv eq substr($inst_file,0,length($priv))) {
563            return 1;
564        }
565    }
566    return 0;
567}
568
569#-> sub CPAN::Module::install ;
570sub install {
571    my($self) = @_;
572    my($doit) = 0;
573    if ($self->uptodate
574        &&
575        not exists $self->{force_update}
576       ) {
577        $CPAN::Frontend->myprint(sprintf("%s is up to date (%s).\n",
578                                         $self->id,
579                                         $self->inst_version,
580                                        ));
581    } else {
582        $doit = 1;
583    }
584    my $ro = $self->ro;
585    if ($ro && $ro->{stats} && $ro->{stats} eq "a") {
586        $CPAN::Frontend->mywarn(qq{
587\n\n\n     ***WARNING***
588     The module $self->{ID} has no active maintainer (CPAN support level flag 'abandoned').\n\n\n
589});
590        $CPAN::Frontend->mysleep(5);
591    }
592    return $doit ? $self->rematein('install') : 1;
593}
594#-> sub CPAN::Module::clean ;
595sub clean  { shift->rematein('clean') }
596
597#-> sub CPAN::Module::inst_file ;
598sub inst_file {
599    my($self) = @_;
600    $self->_file_in_path([@INC]);
601}
602
603#-> sub CPAN::Module::available_file ;
604sub available_file {
605    my($self) = @_;
606    my $sep = $Config::Config{path_sep};
607    my $perllib = $ENV{PERL5LIB};
608    $perllib = $ENV{PERLLIB} unless defined $perllib;
609    my @perllib = split(/$sep/,$perllib) if defined $perllib;
610    my @cpan_perl5inc;
611    if ($CPAN::Perl5lib_tempfile) {
612        my $yaml = CPAN->_yaml_loadfile($CPAN::Perl5lib_tempfile);
613        @cpan_perl5inc = @{$yaml->[0]{inc} || []};
614    }
615    $self->_file_in_path([@cpan_perl5inc,@perllib,@INC]);
616}
617
618#-> sub CPAN::Module::file_in_path ;
619sub _file_in_path {
620    my($self,$path) = @_;
621    my($dir,@packpath);
622    @packpath = split /::/, $self->{ID};
623    $packpath[-1] .= ".pm";
624    if (@packpath == 1 && $packpath[0] eq "readline.pm") {
625        unshift @packpath, "Term", "ReadLine"; # historical reasons
626    }
627    foreach $dir (@$path) {
628        my $pmfile = File::Spec->catfile($dir,@packpath);
629        if (-f $pmfile) {
630            return $pmfile;
631        }
632    }
633    return;
634}
635
636#-> sub CPAN::Module::xs_file ;
637sub xs_file {
638    my($self) = @_;
639    my($dir,@packpath);
640    @packpath = split /::/, $self->{ID};
641    push @packpath, $packpath[-1];
642    $packpath[-1] .= "." . $Config::Config{'dlext'};
643    foreach $dir (@INC) {
644        my $xsfile = File::Spec->catfile($dir,'auto',@packpath);
645        if (-f $xsfile) {
646            return $xsfile;
647        }
648    }
649    return;
650}
651
652#-> sub CPAN::Module::inst_version ;
653sub inst_version {
654    my($self) = @_;
655    my $parsefile = $self->inst_file or return;
656    my $have = $self->parse_version($parsefile);
657    $have;
658}
659
660#-> sub CPAN::Module::inst_version ;
661sub available_version {
662    my($self) = @_;
663    my $parsefile = $self->available_file or return;
664    my $have = $self->parse_version($parsefile);
665    $have;
666}
667
668#-> sub CPAN::Module::parse_version ;
669sub parse_version {
670    my($self,$parsefile) = @_;
671    if (ALARM_IMPLEMENTED) {
672        my $timeout = (exists($CPAN::Config{'version_timeout'}))
673                            ? $CPAN::Config{'version_timeout'}
674                            : 15;
675        alarm($timeout);
676    }
677    my $have = eval {
678        local $SIG{ALRM} = sub { die "alarm\n" };
679        MM->parse_version($parsefile);
680    };
681    if ($@) {
682        $CPAN::Frontend->mywarn("Error while parsing version number in file '$parsefile'\n");
683    }
684    alarm(0) if ALARM_IMPLEMENTED;
685    my $leastsanity = eval { defined $have && length $have; };
686    $have = "undef" unless $leastsanity;
687    $have =~ s/^ //; # since the %vd hack these two lines here are needed
688    $have =~ s/ $//; # trailing whitespace happens all the time
689
690    $have = CPAN::Version->readable($have);
691
692    $have =~ s/\s*//g; # stringify to float around floating point issues
693    $have; # no stringify needed, \s* above matches always
694}
695
696#-> sub CPAN::Module::reports
697sub reports {
698    my($self) = @_;
699    $self->distribution->reports;
700}
701
7021;
703