<?xml version="1.0" encoding="windows-1252"?>
<node id="978606" title="CPAN Testers command-line viewer" created="2012-06-27 05:54:13" updated="2012-06-27 05:54:13">
<type id="1042">
CUFP</type>
<author id="757127">
tobyink</author>
<data>
<field name="doctext">
&lt;p&gt;This script acts as a handy interface to the CPAN testers service, allowing you to quickly check pass/fail rates for a particular release.&lt;/p&gt;
&lt;p&gt;It fetches &lt;del&gt;YAML&lt;/del&gt;&lt;ins&gt;JSON&lt;/ins&gt; and performs HTTP caching, so probably puts &lt;em&gt;less&lt;/em&gt; stress on the CPAN testers servers than a hit from a browser.&lt;/p&gt;
&lt;pre&gt;
Usage:
	cpan-testers Example-Distribution
	cpan-testers -v0.001 Example-Distribution
	cpan-testers -ov0.001 Example-Distribution
	cpan-testers -s Example-Distribution

Options:
	--version=V, -vV   Show results for particular release version.
	--os, -o           Break down by operating system.
	--summary, -s      Show summary for all versions
	                   (excludes "--version" and "--os" options).
	--stable, -S       Hide dev versions (excludes "--version").
&lt;/pre&gt;

&lt;p&gt;Requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perl 5.010&lt;/li&gt;
&lt;li&gt;[mod://Any::Moose]&lt;/li&gt;
&lt;li&gt;&lt;ins&gt;[mod://JSON]&lt;/ins&gt;&lt;/li&gt;
&lt;li&gt;[mod://LWP::Simple]&lt;/li&gt;
&lt;li&gt;[mod://PerlX::Maybe]&lt;/li&gt;
&lt;li&gt;&lt;del&gt;[mod://YAML::Any]&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;[mod://namespace::clean]&lt;/li&gt;
&lt;/ul&gt;

&lt;readmore&gt;
&lt;code&gt;
#!/usr/bin/env perl

use 5.010;
use strict;
use utf8;

{
	package App::CpanTesters;
	
	use Any::Moose       0;
	use File::Path       0 qw&lt; make_path &gt;;
	use File::Spec       0 qw&lt; &gt;;
	use Getopt::Long     0 qw&lt;
		GetOptionsFromArray
		:config permute bundling no_ignore_case no_auto_abbrev
	&gt;;
	use JSON             0 qw&lt; from_json &gt;;
	use LWP::Simple      0 qw&lt; mirror is_success &gt;;
	use List::Util       0 qw&lt; maxstr &gt;;
	use PerlX::Maybe     0 qw&lt; maybe &gt;;
	
	sub show_help
	{
		my $exit_status = shift || 0;
		print &lt;&lt;"HELP";
Usage:
	$0 Example-Distribution
	$0 -v0.001 Example-Distribution
	$0 -ov0.001 Example-Distribution
	$0 -s Example-Distribution

Options:
	--version=V, -vV   Show results for particular release version.
	--os, -o           Break down by operating system.
	--summary, -s      Show summary for all versions
	                   (excludes "--version" and "--os" options).
	--stable, -S       Hide dev versions (excludes "--version").
HELP
		exit($exit_status);
	}
	
	use namespace::clean;
	
	has cache_dir =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'Str',
		lazy     =&gt; 1,
		builder  =&gt; '_build_cache_dir',
	);
	
	has distro =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'Str',
		required =&gt; 1,
	);
	
	has results =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'ArrayRef',
		lazy     =&gt; 1,
		builder  =&gt; '_build_results',
	);
	
	has version =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'Str',
		lazy     =&gt; 1,
		builder  =&gt; '_build_version',
	);
	
	has os_data =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'Bool',
		default  =&gt; 0,
	);

	has stable =&gt; (
		is       =&gt; 'ro',
		isa      =&gt; 'Bool',
		default  =&gt; 0,
	);

	sub run
	{
		my ($class, @argv) = @_;
		my ($version, $summary, $os_data, $stable) = (undef, 0, 0, 0);
		
		GetOptionsFromArray(
			\@argv,
			'version|v=s'  =&gt; \$version,
			'summary|s'    =&gt; \$summary,
			'os|o'         =&gt; \$os_data,
			'stable|S'     =&gt; \$stable,
			'help|usage|h' =&gt; \&amp;show_help,
		);
		
		show_help(1) if $summary &amp;&amp; ($os_data or length $version);
		show_help(1) if $stable  &amp;&amp; length $version;
		
		my $distro = shift @argv or show_help(1);
		$distro =~ s{::}{-}g;
		
		my $self = $class-&gt;new(
			      distro  =&gt; $distro,
			      os_data =&gt; !!$os_data,
			      stable  =&gt; !!$stable,
			maybe version =&gt; $version,
		);
		
		if ($summary)
		{
			exit ! print $self-&gt;summary_report;
		}
		
		exit ! print $self-&gt;version_report;
	}
	
	sub version_data
	{
		my ($self) = @_;
		my %data;
		foreach (@{$self-&gt;results})
		{
			next unless $_-&gt;{version} eq $self-&gt;version;
			my ($pv) = ($_-&gt;{perl} =~ /^5\.(\d+)/) or next;
			next if $pv ~~ [9, 11, 13, 15];
			my $key = $self-&gt;os_data
				? sprintf("Perl 5.%03d, %s", $pv, $_-&gt;{ostext})
				: sprintf("Perl 5.%03d", $pv);
			my $num  = { PASS =&gt; 0, FAIL =&gt; 1 }-&gt;{$_-&gt;{status}} // 2;
			$data{$key}[$num]++;
		}
		return \%data;
	}
	
	sub summary_data
	{
		my ($self) = @_;
		my %data;
		foreach (@{$self-&gt;results})
		{
			my $key  = $_-&gt;{version};
			my $num  = { PASS =&gt; 0, FAIL =&gt; 1 }-&gt;{$_-&gt;{status}} // 2;
			$data{$key}[$num]++;
		}
		return \%data;
	}
	
	sub format_report
	{
		my ($self, $title, $data) = @_;
		no warnings;
		join "\n" =&gt; (
			$title,
			q(),
			sprintf("%-32s%6s%6s%6s", q(), qw(PASS FAIL ETC)),
			(
				map { sprintf "%-32s% 6d% 6d% 6d", $_, @{$data-&gt;{$_}} }
				sort keys %$data
			),
			q(),
		);
	}
	
	sub version_report
	{
		my ($self) = @_;
		
		$self-&gt;format_report(
			sprintf("CPAN Testers results for %s version %s", $self-&gt;distro, $self-&gt;version),
			$self-&gt;version_data,
		);
	}
	
	sub summary_report
	{
		my ($self, $os_data) = @_;
		
		$self-&gt;format_report(
			sprintf("CPAN Testers results for %s", $self-&gt;distro),
			$self-&gt;summary_data,
		);
	}
	
	sub _build_version
	{
		maxstr
			map { $_-&gt;{version} }
			@{ shift-&gt;results }
	}
	
	sub _build_results
	{
		my $self = shift;
		
		my $results_uri = sprintf(
			'http://www.cpantesters.org/distro/%s/%s.json',
			substr($self-&gt;distro, 0, 1),
			$self-&gt;distro,
		);
		my $results_file = File::Spec-&gt;catfile(
			$self-&gt;cache_dir,
			sprintf('%s.json', $self-&gt;distro),
		);
		
		is_success mirror($results_uri =&gt; $results_file)
			or do {
				unlink $results_file;
				die "Failed to retrieve URI $results_uri\n";
			};
			
		my $results = from_json do {
			open my $fh, '&lt;', $results_file
				or die "Could not open $results_file: $!";
			local $/ = &lt;$fh&gt;;
		};
		die "Unexpected non-ARRAY content from $results_uri\n"
			unless ref $results eq 'ARRAY';
		
		$self-&gt;stable
			? [ grep { $_-&gt;{version} !~ /_/ } @$results ]
			: $results;
	}
	
	sub _build_cache_dir
	{
		my $dir = File::Spec-&gt;catdir(
			File::Spec-&gt;tmpdir,
			'CpanTesters',
		);
		make_path $dir unless -d $dir;
		return $dir;
	}
}

App::CpanTesters-&gt;run(@ARGV)
	unless scalar caller(0);
&lt;/code&gt;
&lt;/readmore&gt;

&lt;p&gt;&lt;b&gt;Update:&lt;/b&gt; switched from retrieving YAML to retrieving JSON. The YAML files seem a bit smaller, but JSON parsing seems more reliable, and the JSON files served by the CPAN Testers site seem to have more cache-friendly headers.&lt;/p&gt;

&lt;!-- Node text goes above. Div tags should contain sig only --&gt;
&lt;div class="pmsig"&gt;&lt;div class="pmsig-757127"&gt;
&lt;small&gt;&lt;small&gt;
&lt;tt&gt;perl -E'sub Monkey::do{say$_,for@_,do{($monkey=&amp;#x5B;caller(0)]-&gt;&amp;#x5B;3])=~s{::}{ }and$monkey}}"Monkey say"-&gt;Monkey::do'
&lt;/tt&gt;&lt;/small&gt;&lt;/small&gt;
&lt;/div&gt;&lt;/div&gt;</field>
</data>
</node>
