#!/usr/bin/perl -- -*- mode: cperl -*- =head1 NAME ctgetreports - Quickly fetch cpantesters results with all reports =head1 SYNOPSIS ctgetreports [options] distroname ctgetreports [options] --report number ... ctgetreports -h =head1 OPTIONS A distroname is either unversioned as in C or versioned as in C. =over 2 =cut my $optpod = <<'=back'; =item B<--cachedir=s> Directory to keep mirrored data in. Defaults to C<$HOME/var/cpantesters>. =item B<--ctdb=s> If you have your own copy of the cpan testers I database you can use this to set the path to the database. Must not be used together with C<--cturl>. Requires C and C installed. =item B<--ctformat=s> Format of the cpan-testers file that should be downloaded. Available options were originally C and C. With major construction works going on the HTML on cpantesters is now unsupported for the time being. =item B<--cturl=s> Base URL of the cpantesters website. Defaults to C but sometimes it is interesting to set it to the old URL, C to diagnose bugs or whatever. =item B<--dumpfile=s> If dumpvars are specified, dump them into this file. Defaults to "ctgetreports.out". =item B<--dumpvars=s> Dump all queryable variables matching the regular expression given as argument at the end of the loop for a distro. =item B<--help|h> Prints a brief message and exists. =item B<--interactive|i> After every parsed report asks if you want to see it in a pager. =item B<--local> Do not mirror, use a local *.html file. Dies if the HTML or YAML file is missing, skips missing report files. =item B<--pager=s> Pager (needed when -i is given). Defaults to C. =item B<--parse-common-errors|pce> While the C<<--q qr:...> syntax ultimately offers free parsing it is cumbersome to use. The C<--parse-common-errors> option is a placeholder for a variety of frequent errors to watch. Currently it stands for the following additional options: -q qr:(Failed test\s+\S+.*) -q qr:(Failed to load .*) -q qr:(Can't load .*) -q qr:(.*could not find.*) -q qr:(Can't locate object method .+) -q qr:(Can't locate \S+pm) -q qr:(Please\s+install\s+\S+) -q qr:(You tried to run a test without a plan.*) -q qr:(.*Server didn't start.*) -q qr:(You planned.*) This list is subject to change in future releases. =item B<--q=s@> Query, may be repeated. Example: C<--q mod:Clone --q meta:writer> =item B<--quiet!> Do not output the usual query lines per parsed report. Quiet overrules verbose. =item B<--raw!> Boolean which, if set, causes the full (HTML) report to be concatenated to STDOUT after every status line. =item B<--report=s@> Avert going through a cpan testers index, go straight to the report with this number. Example: C<--report 1238673> If report is set and dumpvars is not set, dumpvars will be set to a dot (meaning that all variables shall be dumped into dumpfile). =item B<--solve!> Calls the solve function which tries to identify the best contenders for a blame using Statistics::Regression. Currently only limited to single variables and with simple heuristics. Implies C<--dumpvars=.> unless the caller sets dumpvars himself. The function prints at the moment to STDOUT the top 3 (set with C<--solvetop>) candidates according to R^2 with their regression analysis. A few words of advise: do not take the results as a prove ever. Take them just as a hint where you can most probablt prove a causal relationship. And keep in mind that causal relationships can be the other direction as well. If you want to extend on that approach, I recommend you study the ctgetreports.out file where you find all the data you'd need and feed your assumptions to Statistics::Regression. =item B<--solvetop=i> The number of top candidates from the C<--solve> regression analysis to display. =item B<--transport=s> Specifies transport to get the reports. C uses Net::NNTP, C uses LWP::UserAgent. Defaults to nntp. =item B<--vdistro=s> Versioned distro, e.g. IPC-Run-0.80 Usually not needed because a versioned distro name can be specified as normal commandline argument. =item B<--verbose|v+> Feedback during download. =item B<--ycb=s> Only used during --solve. Provides perl code to be used as a callback from the regression to determine the B of the regression equation. The callback function gets a record (hashref) as the only argument and must return a value or undefined. If it returns undefined, the record is skipped, otherwise this record is processed with the returned value. The callback is pure perl code without any surrounding sub declaration. The following example analyses diagnostic output from Acme-Study-Perl: ctgetreports --q qr:"#(.*native big math float/int.*)" --solve \ --ycb 'my $rec = shift; my $nbfi = $rec->{"qr:#(.*native big math float/int.*)"}; return undef unless defined $nbfi; my $VAR1 = eval($nbfi); return $VAR1->{">"}' Acme-Study-Perl =back =head1 DESCRIPTION !!!!Alert: alpha quality software, subject to change without warning!!!! The intent is to get at both the summary at cpantesters and the individual reports and parse the reports and collect the data for further inspection. We always only fetch the reports for the most recent (optionally picked) release. Target root directory is C<$HOME/var/cpantesters> (can be overridden with the --cachedir option). The C<--q> paramater can be repeated. It takes one argument which stands for a query. This query must consist of two parts, a qualifier and the query itself. Qualifiers are one of the following conf parameters from the output of 'perl -V' e.g.: conf:usethreads, conf:cc mod for installed modules, either from prerequisites or from the toolchain e.g.: mod:Test::Simple, mod:Imager env environment variables e.g.: env:TERM meta all other parameters e.g.: meta:perl, meta:from, meta:date, meta:writer qr boolean set if the appended regexp matches the report e.g.: qr:'division by zero' The conf parameters specify a word used by the C module. The mod parameters consist of a package name. The meta parameters are the following: C for the perl version, C for the sender of the report, C for the date in the mail header, C for the module that produced the report, C for the line that is reported to have produced the output. =head2 Examples This gets all recent reports for Object-Relation and outputs the version number of the prerequisite Clone: $0 --q mod:Clone Object-Relation Collects reports about Clone and reports the default set of metadata: $0 Clone Collect reports for Devel-Events and report the version number of Moose in thses reports and sort by success/failure. If Moose broke Devel-Events is becomes pretty obvious: $0 --q mod:Moose Devel-Events |sort Which tool was used to write how many reports, sorted by frequency: $0 --q meta:writer Template-Timer | sed -e 's/.*meta:writer//' | sort | uniq -c | sort -n Who was in the From field of the mails whose report writer was not determined: $0 --q meta:writer --q meta:from Template-Timer | grep 'UNDEF' At the time of this writing this collected the results of IPC-Run-0.80_91 which was not really the latest release. In this case manual investigations were necessary to find out that 0.80 was the most recent: $0 IPC-Run Pick the specific release IPC-Run-0.80: $0 IPC-Run-0.80 The following displays in its own column if the report contains the regexp C: $0 --q qr:"division by zero" CPAN-Testers-ParseReport-0.0.7 The following is a simple job to refresh all HTML pages we already have and fetch new reports referenced there too: perl -le ' for my $dirent (glob "$ENV{HOME}/var/cpantesters/cpantesters-show/*.html"){ my($distro) = $dirent =~ m|/([^/]+)\.html$| or next; print $distro; my $system = "ctgetreports --verbose --verbose $distro"; 0 == system $system or die; }' =cut use strict; use warnings; use CPAN::Testers::ParseReport; use Getopt::Long; use Pod::Usage qw(pod2usage); our %Opt; my @opt = $optpod =~ /B<--(\S+)>/g; for (@opt) { $_ .= "!" unless /[+!=]/; } GetOptions(\%Opt, @opt, ) or pod2usage(2); if ($Opt{help}) { pod2usage(0); } if ($Opt{report}) { if (@ARGV) { pod2usage(2); } } else { if (1 != @ARGV) { pod2usage(2); } } if ($Opt{solve}) { eval { require Statistics::Regression }; if ($@) { die "Statistics::Regression required for solved option: $@"; } } if ($Opt{report}) { $Opt{dumpvars} ||= "."; } if ($Opt{dumpvars}) { eval { require YAML::Syck }; if ($@) { die "YAML::Syck required for dumpvars option: $@"; } } if ($Opt{"parse-common-errors"}) { $Opt{q} ||= []; my($para) = grep {/^\s+-q qr:/} split /\n\n/, $optpod; for my $line (split /\n/, $para) { my($qr) = $line =~ /-q (qr:.*)/; push @{$Opt{q}}, $qr; } } $|=1; if (my $reports = delete $Opt{report}) { my $dumpvars = {}; REPORT: for my $report (@$reports) { CPAN::Testers::ParseReport::parse_single_report({id => $report},$dumpvars,%Opt); last REPORT if $CPAN::Testers::ParseReport::Signal; } my $dumpfile = $Opt{dumpfile} || "ctgetreports.out"; YAML::Syck::DumpFile($dumpfile,$dumpvars); } else { $ARGV[0] =~ s|.+/||; CPAN::Testers::ParseReport::parse_distro($ARGV[0],%Opt); } __END__