1 package CPAN
::Testers
::ParseReport
;
6 use DateTime
::Format
::Strptime
;
7 use DateTime
::Format
::DateParse
;
8 use File
::Basename
qw(basename);
9 use File
::Path
qw(mkpath);
10 use HTML
::Entities
qw(decode_entities);
12 use List
::Util
qw(max min sum);
16 use XML
::LibXML
::XPathContext
;
18 our $default_ctformat = "yaml";
19 our $default_transport = "nntp";
20 our $default_cturl = "http://www.cpantesters.org/show";
27 CPAN::Testers::ParseReport - parse reports to www.cpantesters.org from various sources
31 use version
; our $VERSION = qv
('0.1.0');
35 The documentation in here is normally not needed because the code is
36 meant to be run from a standalone program, L<ctgetreports>.
38 ctgetreports --q mod:Moose Devel-Events
42 This is the core module for CPAN::Testers::ParseReport. If you're not
43 looking to extend or alter the behaviour of this module, you probably
44 want to look at L<ctgetreports> instead.
48 Are described in the L<ctgetreports> manpage and are passed through to
49 the functions unaltered.
53 =head2 parse_distro($distro,%options)
55 reads the cpantesters HTML page or the YAML file for the distro and
56 loops through the reports for the specified or most recent version of
57 that distro found in these data.
59 =head2 parse_single_report($report,$dumpvars,%options)
61 mirrors and reads this report. $report is of the form
65 $dumpvar is a hashreference that gets filled with data.
73 $ua = LWP
::UserAgent
->new
86 return $nntp if $nntp;
87 $nntp = Net
::NNTP
->new("nntp.perl.org");
88 $nntp->group("perl.cpan.testers");
97 $xp = XML
::LibXML
->new;
99 $xp->clean_namespaces(1);
100 my $catalog = __FILE__
;
101 $catalog =~ s
|ParseReport
.pm
$|ParseReport
/catalog
|;
102 $xp->load_catalog($catalog);
107 sub _download_overview
{
108 my($cts_dir, $distro, %Opt) = @_;
109 my $format = $Opt{ctformat
} ||= $default_ctformat;
110 my $cturl = $Opt{cturl
} ||= $default_cturl;
111 my $ctarget = "$cts_dir/$distro.$format";
112 my $cheaders = "$cts_dir/$distro.headers";
114 unless (-e
$ctarget) {
115 die "Alert: No local file '$ctarget' found, cannot continue\n";
118 if (! -e
$ctarget or -M
$ctarget > .25) {
119 if (-e
$ctarget && $Opt{verbose
}) {
121 my $timestamp = gmtime $stat[9];
122 print STDERR
"(timestamp $timestamp GMT)\n" unless $Opt{quiet
};
124 print STDERR
"Fetching $ctarget..." if $Opt{verbose
} && !$Opt{quiet
};
125 my $uri = "$cturl/$distro.$format";
126 my $resp = _ua
->mirror($uri,$ctarget);
127 if ($resp->is_success) {
128 print STDERR
"DONE\n" if $Opt{verbose
} && !$Opt{quiet
};
129 open my $fh, ">", $cheaders or die;
130 for ($resp->headers->as_string) {
132 if ($Opt{verbose
} && $Opt{verbose
}>1) {
133 print STDERR
$_ unless $Opt{quiet
};
136 } elsif (304 == $resp->code) {
137 print STDERR
"DONE (not modified)\n" if $Opt{verbose
} && !$Opt{quiet
};
138 my $atime = my $mtime = time;
139 utime $atime, $mtime, $cheaders;
143 "No success downloading %s: %s",
154 my($ctarget, %Opt) = @_;
155 my $content = do { open my $fh, $ctarget or die; local $/; <$fh> };
156 my $preprocesswithtreebuilder = 0; # not needed since barbie switched to XHTML
157 if ($preprocesswithtreebuilder) {
158 require HTML
::TreeBuilder
;
159 my $tree = HTML
::TreeBuilder
->new;
160 $tree->implicit_tags(1);
162 $tree->ignore_ignorable_whitespace(0);
163 $tree->parse_content($content);
165 $content = $tree->as_XML;
168 my $doc = eval { $parser->parse_string($content) };
171 my $distro = basename
$ctarget;
172 die sprintf "Error while parsing %s\: %s", $distro, $err;
174 my $xc = XML
::LibXML
::XPathContext
->new($doc);
175 my $nsu = $doc->documentElement->namespaceURI;
176 $xc->registerNs('x', $nsu) if $nsu;
177 my($selected_release_ul,$selected_release_distrov,$excuse_string);
180 $xc->findnodes("/x:html/x:body/x:div[\@id = 'doc']") :
181 $doc->findnodes("/html/body/div[\@id = 'doc']");
182 my(@releasedivs) = $nsu ?
183 $xc->findnodes("//x:div[x:h2 and x:ul]",$cparentdiv) :
184 $cparentdiv->findnodes("//div[h2 and ul]");
187 $excuse_string = "selected distro '$Opt{vdistro}'";
188 my($fallbacktoversion) = $Opt{vdistro
} =~ /(\d+\..*)/;
189 $fallbacktoversion = 0 unless defined $fallbacktoversion;
190 RELEASE
: for my $i (0..$#releasedivs) {
193 $xc->findvalue("x:h2/x:a[2]/\@name",$releasedivs[$i]) :
194 $releasedivs[$i]->findvalue("h2/a[2]/\@name");
196 if ($x eq $Opt{vdistro
}) {
198 $picked = " (picked)";
200 print STDERR
"FOUND DISTRO: $x$picked\n" unless $Opt{quiet
};
203 $xc->findvalue("x:h2/x:a[1]/\@name",$releasedivs[$i]) :
204 $releasedivs[$i]->findvalue("h2/a[1]/\@name");
205 if ($x eq $fallbacktoversion) {
207 $picked = " (picked)";
209 print STDERR
"FOUND VERSION: $x$picked\n" unless $Opt{quiet
};
213 $excuse_string = "any distro";
215 unless (defined $releasediv) {
218 # using a[1] because a[2] is missing on the first entry
219 ($selected_release_distrov) = $nsu ?
220 $xc->findvalue("x:h2/x:a[1]/\@name",$releasedivs[$releasediv]) :
221 $releasedivs[$releasediv]->findvalue("h2/a[1]/\@name");
222 ($selected_release_ul) = $nsu ?
223 $xc->findnodes("x:ul",$releasedivs[$releasediv]) :
224 $releasedivs[$releasediv]->findnodes("ul");
225 unless (defined $selected_release_distrov) {
226 warn "Warning: could not find $excuse_string in '$ctarget'";
229 print STDERR
"SELECTED: $selected_release_distrov\n" unless $Opt{quiet
};
233 $xc->findnodes("x:li",$selected_release_ul) :
234 $selected_release_ul->findnodes("li")) {
236 $xc->findvalue("x:a[1]/text()",$test) :
237 $test->findvalue("a[1]/text()");
238 push @all, {id
=>$id};
245 my($ctarget, %Opt) = @_;
247 my $arr = YAML
::Syck
::LoadFile
($ctarget);
248 my($selected_release_ul,$selected_release_distrov,$excuse_string);
250 $excuse_string = "selected distro '$Opt{vdistro}'";
251 $arr = [grep {$_->{distversion
} eq $Opt{vdistro
}} @
$arr];
252 ($selected_release_distrov) = $arr->[0]{distversion
};
254 $excuse_string = "any distro";
257 for my $report (@
$arr) {
258 unless ($seen{$report->{distversion
}}++) {
259 $last_addition = $report->{distversion
};
262 $arr = [grep {$_->{distversion
} eq $last_addition} @
$arr];
263 ($selected_release_distrov) = $last_addition;
265 unless ($selected_release_distrov) {
266 warn "Warning: could not find $excuse_string in '$ctarget'";
269 print STDERR
"SELECTED: $selected_release_distrov\n" unless $Opt{quiet
};
271 for my $test (@
$arr) {
272 my $id = $test->{id
};
273 push @all, {id
=>$id};
276 @all = sort { $b->{id
} <=> $a->{id
} } @all;
280 sub parse_single_report
{
281 my($report, $dumpvars, %Opt) = @_;
282 my($id) = $report->{id
};
283 $Opt{cachedir
} ||= "$ENV{HOME}/var/cpantesters";
284 my $nnt_dir = "$Opt{cachedir}/nntp-testers";
286 my $target = "$nnt_dir/$id";
288 unless (-e
$target) {
289 die {severity
=>0,text
=>"Warning: No local file '$target' found, skipping\n"};
293 print STDERR
"Fetching $target..." if $Opt{verbose
} && !$Opt{quiet
};
294 $Opt{transport
} ||= $default_transport;
295 if ($Opt{transport
} eq "nntp") {
296 my $article = _nntp
->article($id);
298 die {severity
=>0,text
=>"NNTP-Server did not return an article for id[$id]"};
300 open my $fh, ">", $target or die {severity
=>1,text
=>"Could not open >$target: $!"};
302 } elsif ($Opt{transport
} eq "http") {
303 my $resp = _ua
->mirror("http://www.nntp.perl.org/group/perl.cpan.testers/$id",$target);
304 if ($resp->is_success) {
306 my(@stat) = stat $target;
307 my $timestamp = gmtime $stat[9];
308 print STDERR
"(timestamp $timestamp GMT)\n" unless $Opt{quiet
};
309 if ($Opt{verbose
} > 1) {
310 print STDERR
$resp->headers->as_string unless $Opt{quiet
};
313 my $headers = "$target.headers";
314 open my $fh, ">", $headers or die {severity
=>1,text
=>"Could not open >$headers: $!"};
315 print $fh $resp->headers->as_string;
318 text
=>sprintf "HTTP Server Error[%s] for id[%s]", $resp->status_line, $id};
321 die {severity
=>1,text
=>"Illegal value for --transport: '$Opt{transport}'"};
325 parse_report
($target, $dumpvars, %Opt);
329 my($distro,%Opt) = @_;
331 $Opt{cachedir
} ||= "$ENV{HOME}/var/cpantesters";
332 my $cts_dir = "$Opt{cachedir}/cpantesters-show";
335 require Statistics
::Regression
;
336 $Opt{dumpvars
} = "." unless defined $Opt{dumpvars
};
338 if (!$Opt{vdistro
} && $distro =~ /^(.+)-(?i:v?\d+)(?:\.\d+)*\w*$/) {
339 $Opt{vdistro
} = $distro;
342 my $ctarget = _download_overview
($cts_dir, $distro, %Opt);
344 $Opt{ctformat
} ||= $default_ctformat;
345 if ($Opt{ctformat
} eq "html") {
346 $reports = _parse_html
($ctarget,%Opt);
348 $reports = _parse_yaml
($ctarget,%Opt);
350 return unless $reports;
351 for my $report (@
$reports) {
352 eval {parse_single_report
($report, \
%dumpvars, %Opt)};
355 if ($@
->{severity
}) {
366 if ($Opt{dumpvars
}) {
368 my $dumpfile = $Opt{dumpfile
} || "ctgetreports.out";
369 open my $fh, ">", $dumpfile or die "Could not open '$dumpfile' for writing: $!";
370 print $fh YAML
::Syck
::Dump
(\
%dumpvars);
371 close $fh or die "Could not close '$dumpfile': $!"
374 solve
(\
%dumpvars,%Opt);
378 =head2 $extract = parse_report($target,$dumpvars,%Opt)
380 Reads one report. $target is the local filename to read. $dumpvars is
381 a hashref which gets filled with descriptive stats about
382 PASS/FAIL/etc. %Opt are the options as described in the
383 C<ctgetreports> manpage. $extract is a hashref containing the found
386 Note: this parsing is a bit dirty but as it seems good enough I'm not
387 inclined to change it. We parse HTML with regexps only, not an HTML
388 parser. Only the entities are decoded.
390 Update around version 0.0.17: switching to nntp now but keeping the
391 parser for HTML around to read old local copies.
393 Update around 0.0.18: In %Options you can use
395 article => $some_full_article_as_scalar
397 to use this function to parse one full article as text. When this is
398 given, the argument $target is not read, but its basename is taken to
399 be the id of the article. (OMG, hackers!)
403 my($target,$dumpvars,%Opt) = @_;
405 my $id = basename
($target);
411 if ($report = $Opt{article
}) {
412 $isHTML = $report =~ /^</;
416 open my $fh, $target or die "Could not open '$target': $!";
418 my $raw_report = <$fh>;
419 $isHTML = $raw_report =~ /^</;
420 $report = $isHTML ? decode_entities
($raw_report) : $raw_report;
423 my @qr = map /^qr:(.+)/, @
{$Opt{q
}};
424 if ($Opt{raw
} || @qr) {
426 my $cqr = eval "qr{$qr}";
427 die "Could not compile regular expression '$qr': $@" if $@
;
428 my(@matches) = $report =~ $cqr;
434 $v = join "", map {"($_)"} @matches;
439 $extract{"qr:$qr"} = $v;
444 my $moduleunpack = {};
445 my $expect_prereq = 0;
446 my $expect_toolchain = 0;
447 my $expecting_toolchain_soon = 0;
450 my $in_prg_output = 0;
451 my $in_env_context = 0;
453 my $current_headline;
454 my @previous_line = ""; # so we can neutralize line breaks
455 my @rlines = split /\r?\n/, $report;
456 LINE
: for (@rlines) {
457 next LINE
unless ($isHTML ?
m/<title>(\S+)\s+(\S+)/ : m/^Subject: (\S+)\s+(\S+)/);
460 $extract{"meta:ok"} = $ok;
461 $extract{"meta:about"} = $about;
464 LINE
: while (@rlines) {
467 my $followupline = shift @rlines;
468 $followupline =~ s/^\s+//; # remo leading space
471 if (/^--------/ && $previous_line[-2] && $previous_line[-2] =~ /^--------/) {
472 $current_headline = $previous_line[-1];
473 if ($current_headline =~ /PROGRAM OUTPUT/) {
478 if ($current_headline =~ /ENVIRONMENT AND OTHER CONTEXT/) {
484 if ($extract{"meta:perl"}) {
486 and !$extract{"conf:git_commit_id"}
487 and /Commit id: ([[:xdigit:]]+)/) {
488 $extract{"conf:git_commit_id"} = $1;
493 } elsif (/Summary of my perl5 \((.+)\) configuration:/) {
500 if (($r,$v,$s,$p) = $p5 =~ /revision (\S+) version (\S+) subversion (\S+) patch (\S+)/) {
501 $r =~ s/\.0//; # 5.0 6 2!
502 $extract{"meta:perl"} = "$r.$v.$s\@$p";
503 } elsif (($r,$v,$s) = $p5 =~ /revision (\S+) version (\S+) subversion (\S+)/) {
505 $extract{"meta:perl"} = "$r.$v.$s";
506 } elsif (($r,$v,$s) = $p5 =~ /(\d+\S*) patchlevel (\S+) subversion (\S+)/) {
508 $extract{"meta:perl"} = "$r.$v.$s";
510 $extract{"meta:perl"} = $p5;
514 unless ($extract{"meta:from"}) {
517 m
|<div
class="h_name">From
:</div> <b>(.+?)</b
><br
/>| :
520 $extract{"meta:from"} = $1;
522 $extract{"meta:from"} =~ s/\.$// if $extract{"meta:from"};
524 unless ($extract{"meta:date"}) {
527 m
|<div
class="h_name">Date
:</div> (.+?)<br/>| :
534 $p = DateTime
::Format
::Strptime
->new(
537 # April 13, 2005 23:50
538 pattern
=> "%b %d, %Y %R",
540 $dt = $p->parse_datetime($date);
542 # Sun, 28 Sep 2008 12:23:12 +0100 # but was not consistent
543 # pattern => "%a, %d %b %Y %T %z",
544 $dt = DateTime
::Format
::DateParse
->parse_datetime($date);
547 warn "Could not parse date[$date], setting to epoch 0";
548 $dt = DateTime
->from_epoch( epoch
=> 0 );
550 $extract{"meta:date"} = $dt->datetime;
552 $extract{"meta:date"} =~ s/\.$// if $extract{"meta:date"};
554 unless ($extract{"meta:writer"}) {
555 for ("$previous_line[-1] $_") {
557 } elsif (/CPANPLUS, version (\S+)/) {
558 $extract{"meta:writer"} = "CPANPLUS $1";
559 } elsif (/created (?:automatically )?by (\S+)/) {
560 $extract{"meta:writer"} = $1;
561 } elsif (/This report was machine-generated by (\S+) (\S+)/) {
562 $extract{"meta:writer"} = "$1 $2";
564 $extract{"meta:writer"} =~ s/[\.,]$// if $extract{"meta:writer"};
568 # we do that first three lines a bit too often
569 my $qr = $Opt{dumpvars
} || "";
570 $qr = qr/$qr/ if $qr;
573 @q = qw(meta:perl conf:archname conf:usethreads conf:optimize meta:writer meta:from) unless @q;
576 my %conf_vars = map {($_ => 1)} grep { /^conf:/ } @q;
578 if (/^\s*$/ || m
|</pre
>|) {
579 # if not html, we have reached the end now
582 my(%kv) = /\G,?\s*([^=]+)=('[^']+?'|\S+)/gc;
583 while (my($k,$v) = each %kv) {
587 if ($v =~ /^'(.*)'$/) {
592 if ($qr && $ck =~ $qr) {
594 } elsif ($conf_vars{$ck}) {
600 if ($in_prg_output) {
601 unless ($extract{"meta:output_from"}) {
602 if (/Output from (.+):$/) {
603 $extract{"meta:output_from"} = $1
607 if ($in_env_context) {
608 if (/^\s{4}(\S+)\s*=\s*(.*)$/) {
609 $extract{"env:$1"} = $2;
612 push @previous_line, $_;
613 if ($expect_prereq || $expect_toolchain) {
614 if (exists $moduleunpack->{type
}) {
615 my($module,$v,$needwant);
616 # type 1 and 2 are about prereqs, type three about toolchain
617 if ($moduleunpack->{type
} == 1) {
618 (my $leader,$module,$needwant,$v) = eval { unpack $moduleunpack->{tpl
}, $_; };
620 if ($leader =~ /^-/) {
624 } elsif ($leader =~ /^(
625 buil
# build_requires:
628 } elsif ($module =~ /^(
633 } elsif ($moduleunpack->{type
} == 2) {
634 (my $leader,$module,$v,$needwant) = eval { unpack $moduleunpack->{tpl
}, $_; };
636 if ($leader =~ /^\*/) {
640 } elsif ($v =~ /\s/) {
641 ($module,$v) = split " ", $_;
643 } elsif ($moduleunpack->{type
} == 3) {
644 (my $leader,$module,$v) = eval { unpack $moduleunpack->{tpl
}, $_; };
648 $expect_toolchain = 0;
650 } elsif ($module =~ /^-/) {
661 $extract{"mod:$module"} = $v;
663 $needwant =~ s/^\s+//;
664 $needwant =~ s/\s+$//;
665 $extract{"prereq:$module"} = $needwant;
669 if (/(\s+)(Module\s+)(Need\s+)Have/) {
672 tpl
=> 'a'.length($1).'a'.length($2).'a'.length($3).'a*',
675 } elsif (/(\s+)(Module Name\s+)(Have)(\s+)Want/) {
678 my $adjust_2 = -length($4);
679 my $adjust_3 = length($4);
680 # two pass would be required to see where the
681 # columns really are. Or could we get away with split?
683 tpl
=> 'a'.length($1).'a'.(length($2)+$adjust_2).'a'.(length($3)+$adjust_3).'a*',
688 if (/PREREQUISITES|Prerequisite modules loaded/) {
692 if ($expecting_toolchain_soon) {
693 if (/(\s+)(Module\s+) Have/) {
696 $expecting_toolchain_soon=0;
698 tpl
=> 'a'.length($1).'a'.length($2).'a*',
703 if (/toolchain versions installed/) {
705 $expecting_toolchain_soon=1;
710 if ($extract{"conf:osvers"} && $extract{"conf:archname"}) {
711 $extract{"conf:archame+osvers"} = join " ", @extract{"conf:archname","conf:osvers"};
713 my $data = $dumpvars->{"==DATA=="} ||= [];
714 push @
$data, \
%extract;
716 # ---- %extract finished ----
718 if (my $qr = $Opt{dumpvars
}) {
720 while (my($k,$v) = each %extract) {
722 $dumpvars->{$k}{$v}{$ok}++;
727 my $have = $extract{$want} || "";
728 $diag .= " $want\[$have]";
730 printf STDERR
" %-4s %8d%s\n", $ok, $id, $diag unless $Opt{quiet
};
732 $report =~ s/\s+\z//;
733 print STDERR
$report, "\n================\n" unless $Opt{quiet
};
735 if ($Opt{interactive
}) {
739 my $ans = IO
::Prompt
::prompt
741 -p
=> "View $id? [onechar: ynq] ",
746 print STDERR
"\n" unless $Opt{quiet
};
748 open my $ifh, "<", $target or die "Could not open $target: $!";
749 $Opt{pager
} ||= "less";
750 open my $lfh, "|-", $Opt{pager
} or die "Could not fork '$Opt{pager}': $!";
753 close $ifh or die "Could not close $target: $!";
754 close $lfh or die "Could not close pager: $!"
755 } elsif ($ans eq "q") {
765 Feeds a couple of potentially interesting data to
766 Statistics::Regression and sorts the result by R^2 descending. Do not
767 confuse this with a prove, rather take it as a useful hint. It can
768 save you minutes of staring at data and provide a quick overview where
769 one should look closer. Displays the N top candidates, where N
770 defaults to 3 and can be set with the C<$Opt{solvetop}> variable.
771 Regressions results with an R^2 of 1.00 are displayed in any case.
773 The function is called when the option C<-solve> is give on the
774 commandline. Several extra config variables are calculated, see source
779 my %never_solve_on = map {($_ => 1)}
793 'env:PERL5_CPANPLUS_IS_RUNNING',
794 'env:PERL5_CPAN_IS_RUNNING',
795 'env:PERL5_CPAN_IS_RUNNING_IN_RECURSION',
798 my %normalize_numeric =
800 id
=> sub { return shift },
802 my($Y,$M,$D,$h,$m,$s) = shift =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/;
803 Time
::Local
::timegm
($s,$m,$h,$D,$M-1,$Y);
806 my %normalize_value =
809 my($perlatpatchlevel) = shift;
810 my $perl = $perlatpatchlevel;
817 require Statistics
::Regression
;
820 if (my $ycbbody = $Opt{ycb
}) {
821 $ycb = eval('sub {'.$ycbbody.'}');
827 if ($rec->{"meta:ok"} eq "PASS") {
829 } elsif ($rec->{"meta:ok"} eq "FAIL") {
835 VAR
: for my $variable (sort keys %$V) {
836 next if $variable eq "==DATA==";
837 if ($never_solve_on{$variable}){
838 warn "Skipping '$variable'\n" unless $Opt{quiet
};
841 my $value_distribution = $V->{$variable};
842 my $keys = keys %$value_distribution;
844 if ($normalize_numeric{$variable}) {
845 push @X, "n_$variable";
848 for my $value (sort keys %$value_distribution) {
849 my $pf = $value_distribution->{$value};
852 if ($pf->{PASS
} || $pf->{FAIL
}) {
853 my $Xele = sprintf "eq_%s",
855 $normalize_value{$variable} ?
856 $normalize_value{$variable}->($value) :
859 push @X, $Xele unless $seen{$Xele}++;
863 $pf->{PASS
} xor $pf->{FAIL
}
866 substr($value,$vl) = "..." if length $value > 3+$vl;
867 my $poor_mans_freehand_estimation = 0;
868 if ($poor_mans_freehand_estimation) {
871 "%4d %4d %-23s | %s\n",
881 warn "variable[$variable]keys[$keys]X[@X]\n" unless $Opt{quiet
};
882 next VAR
unless @X > 1;
888 RECORD
: for my $rec (@
{$V->{"==DATA=="}}) {
889 my $y = $ycb->($rec);
890 next RECORD
unless defined $y;
896 if ($x =~ /^eq_(.+)/) {
898 if (exists $rec->{$variable}
899 && defined $rec->{$variable}
902 $normalize_value{$variable} ?
903 $normalize_value{$variable}->($rec->{$variable}) :
906 if ($use_v eq $read_v) {
910 # warn "DEBUG: y[$y]x[$x]obs[$obs{$x}]\n";
911 } elsif ($x =~ /^n_(.+)/) {
913 $obs{$x} = $normalize_numeric{$v}->($rec->{$v});
916 push @
{$regdata{data
}}, \
%obs;
918 _run_regression
($variable, \
%regdata, \
@regression, \
%Opt);
920 my $top = min
($Opt{solvetop
} || 3, scalar @regression);
921 my $max_rsq = sum
map {1==$_->rsq ?
1 : 0} @regression;
922 $top = $max_rsq if $max_rsq && $max_rsq > $top;
926 "State after regression testing: %d results, showing top %d\n\n",
935 printf "(%d)\n", ++$score;
936 eval { $reg->print; };
938 printf "\n\nOops, Statistics::Regression died during ->print() with error message[$@]\n\n";
945 sub _run_regression
{
946 my($variable,$regdata,$regression,$opt) = @_;
947 my @X = @
{$regdata->{X
}};
949 my $reg = Statistics
::Regression
->new($variable,\
@X);
950 for my $obs (@
{$regdata->{data
}}) {
951 my $y = delete $obs->{Y
};
952 $reg->include($y, $obs);
956 my @e = $reg->standarderrors;
957 die "found standarderrors == 0" if grep { 0 == $_ } @e;
960 if ($opt->{verbose
} && $opt->{verbose
}>=2) {
962 warn YAML
::Syck
::Dump
963 ({error
=>"could not determine some regression parameters",
971 # reduce k in case that linear dependencies disturbed us
975 push @
$regression, $reg;
987 Please report any bugs or feature requests through the web
989 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CPAN-Testers-ParseReport>.
990 I will be notified, and then you'll automatically be notified of
991 progress on your bug as I make changes.
995 You can find documentation for this module with the perldoc command.
997 perldoc CPAN::Testers::ParseReport
1000 You can also look for information at:
1004 =item * RT: CPAN's request tracker
1006 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CPAN-Testers-ParseReport>
1008 =item * AnnoCPAN: Annotated CPAN documentation
1010 L<http://annocpan.org/dist/CPAN-Testers-ParseReport>
1012 =item * CPAN Ratings
1014 L<http://cpanratings.perl.org/d/CPAN-Testers-ParseReport>
1018 L<http://search.cpan.org/dist/CPAN-Testers-ParseReport>
1023 =head1 ACKNOWLEDGEMENTS
1025 Thanks to RJBS for module-starter.
1027 =head1 COPYRIGHT & LICENSE
1029 Copyright 2008 Andreas König.
1031 This program is free software; you can redistribute it and/or modify it
1032 under the same terms as Perl itself.
1037 1; # End of CPAN::Testers::ParseReport