Skip to content

Instantly share code, notes, and snippets.

@xsawyerx
Forked from SineSwiper/MetaCPAN-mortality.pl
Created January 8, 2014 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xsawyerx/8323466 to your computer and use it in GitHub Desktop.
Save xsawyerx/8323466 to your computer and use it in GitHub Desktop.
# Maturity (Past)
# 45% = Number of releases
# 25% = Life span (last minus first release date)
# 20% = Size of code
# 10% = Test Pass percentage (global)
# TODO: Number of resolved RT/GH tickets
# Stability (Present)
# 45% = Last release span (now minus last release date)
# 35% = Test Pass percentage (this version)
# 20% = Average CPAN rating
# TODO: Kwalitee/CPANTS scoring
# Sustainability (Future)
# 40% = Author's last release date
# 35% = Stability score
# 25% = Number of modules that depend on this
# TODO: Number of open RT/GH tickets
# TODO: Oldest open RT/GH ticket date (non-wistlist)
DBI:
Maturity: 99% (Great)
Stability: 97% (Great)
Sustainability: 89% (Great)
DBIx::Class:
Maturity: 94% (Great)
Stability: 78% (Good)
Sustainability: 82% (Great)
perl:
Maturity: 94% (Great)
Stability: 78% (Good)
Sustainability: 82% (Great)
Pod::Coverage::TrustPod:
Maturity: 70% (Good)
Stability: 91% (Great)
Sustainability: 86% (Great)
subs::auto:
Maturity: 46% (Average)
Stability: 83% (Great)
Sustainability: 79% (Good)
Devel::Carp:
Maturity: 25% (Poor)
Stability: 46% (Average)
Sustainability: 42% (Average)
AnyData:
Maturity: 61% (Good)
Stability: 57% (Average)
Sustainability: 37% (Poor)
Net::SSH:
Maturity: 65% (Good)
Stability: 80% (Great)
Sustainability: 82% (Great)
Mail::IMAPClient:
Maturity: 98% (Great)
Stability: 96% (Great)
Sustainability: 88% (Great)
XML::Smart:
Maturity: 72% (Good)
Stability: 65% (Good)
Sustainability: 22% (Poor)
Proc::ProcessTable:
Maturity: 86% (Great)
Stability: 70% (Good)
Sustainability: 79% (Good)
Net::IRC:
Maturity: 90% (Great)
Stability: 87% (Great)
Sustainability: 60% (Good)
Net::MacMap:
Maturity: 25% (Poor)
Stability: 46% (Average)
Sustainability: 41% (Average)
Net::ARP:
Maturity: 73% (Good)
Stability: 40% (Average)
Sustainability: 27% (Poor)
pop:
Maturity: 34% (Poor)
Stability: 47% (Average)
Sustainability: 46% (Average)
use v5.10;
use MetaCPAN::API;
use Data::Dumper;
use List::AllUtils qw(min max);
use DateTime;
use DateTime::Format::ISO8601;
my @score_label = qw(
Abysmal
Poor
Average
Good
Great
);
# Example modules:
# Very stable: DBI, DBIx::Class, perl
# Newer but stable: Pod::Coverage::TrustPod
# Older but stable: subs::auto, Devel::Carp, AnyData
# Average: Net::SSH, Mail::IMAPClient
# Broken: XML::Smart, Proc::ProcessTable
# Abandoned: Net::IRC
# DEPRECATED: Dancer::Plugin::DebugDump
# Bad all around: Net::MacMap, Net::ARP, pop
my @modules = qw(
DBI DBIx::Class perl
Pod::Coverage::TrustPod
subs::auto Devel::Carp AnyData
Net::SSH Mail::IMAPClient
XML::Smart
Proc::ProcessTable
Net::IRC
Net::MacMap
Net::ARP
pop
);
my $dp = DateTime::Format::ISO8601->new();
my $mcpan = MetaCPAN::API->new();
foreach my $module (@modules) {
(my $dist_name = $module) =~ s/::/-/g;
my $latest_release = $mcpan->release( distribution => $dist_name );
my $global_testing = $mcpan->release(
search => {
size => 0,
query => { filtered => {
query => {match_all => {}},
filter => { term => { distribution => $dist_name } },
}},
facets => {
pass => { statistical => { field => 'tests.pass' } },
fail => { statistical => { field => 'tests.fail' } },
},
}
)->{facets};
my $score_vars = {
maturity => {
code_size => $latest_release->{stat}{size},
num_releases => $mcpan->release( search => { q => "distribution:$dist_name", size => 0, } )->{hits}{total},
last_release => $latest_release->{date},
first_release => $mcpan->release(
search => {
q => "distribution:$dist_name",
fields => 'date',
sort => 'date:asc',
size => 1,
}
)->{hits}{hits}[0]{fields}{date},
test_pass_global => $global_testing->{pass}{total},
test_fail_global => $global_testing->{fail}{total},
test_ttl_global => $global_testing->{pass}{total} + $global_testing->{fail}{total},
# Number of resolved RT/GH tickets === needs another module
},
stability => {
last_release => $latest_release->{date},
test_pass_latest => $latest_release->{tests}{pass},
test_fail_latest => $latest_release->{tests}{fail},
test_ttl_latest => $latest_release->{tests}{pass} + $latest_release->{tests}{fail},
avg_cpan_rating => $mcpan->rating(
search => {
size => 0,
query => { filtered => {
query => {match_all => {}},
filter => { term => { distribution => $dist_name } },
}},
facets => {
rating => { statistical => { field => 'rating' } },
},
}
)->{facets}{rating}{mean} || 3,
# Kwalitee/CPANTS scoring === not sure where this is
},
sustainability => {
reverse_depend => $mcpan->release( search => { q => "release.dependency.module:\"$module\"", size => 0, } )->{hits}{total},
author_last_release => $mcpan->release(
search => {
q => "author:".$latest_release->{author},
fields => 'date',
sort => 'date:desc',
size => 1,
}
)->{hits}{hits}[0]{fields}{date},
# Number of open RT/GH tickets === needs another module
# Oldest open RT/GH ticket date (non-wistlist) === needs another module
},
};
# Score calculating
# Maturity (Past)
# 45% = Number of releases
# 25% = Life span (last minus first release date)
# 20% = Size of code
# 10% = Test Pass percentage (global)
# TODO: Number of resolved RT/GH tickets
my $lvl = $score_vars->{maturity};
$lvl->{life_span} = $dp->parse_datetime($lvl->{last_release}) - $dp->parse_datetime($lvl->{first_release});
$lvl->{pass_per} = $lvl->{test_pass_global} / ($lvl->{test_ttl_global} || 1);
$lvl->{_score} =
# Number of releases = standard scale (max 15)
min($lvl->{num_releases}, 15) / 15 * 45 +
# Life span = standard scale (max 60 months)
min($lvl->{life_span}->in_units('months'), 60) / 60 * 25 +
# Size of code = SQRT scale (max 200K)
sqrt(min($lvl->{code_size}, 200000)) / 447.22 * 20 +
# Test Pass % = square scale (vs. fractions = downward slope)
$lvl->{pass_per} ** 2 * 10
;
# Stability (Present)
# 45% = Last release span (now minus last release date)
# 35% = Test Pass percentage (this version)
# 20% = Average CPAN rating
# TODO: Kwalitee/CPANTS scoring
$lvl = $score_vars->{stability};
$lvl->{last_span} = DateTime->now - $dp->parse_datetime($lvl->{last_release});
$lvl->{pass_per} = $lvl->{test_ttl_latest} ? $lvl->{test_pass_latest} / $lvl->{test_ttl_latest} : $score_vars->{maturity}{pass_per};
$lvl->{_score} =
# Last release span = inverse square scale (max 10 years)
max(1315 - $lvl->{last_span}->in_units('months') ** 1.5, 0) / 1315 * 45 +
# Test Pass % = square scale (vs. fractions = downward slope)
$lvl->{pass_per} ** 2 * 35 +
# Average CPAN rating = standard scale (max 5)
$lvl->{avg_cpan_rating} / 5 * 20
;
# Sustainability (Future)
# 40% = Author's last release date
# 35% = Stability score
# 25% = Number of modules that depend on this
# TODO: Number of open RT/GH tickets
# TODO: Oldest open RT/GH ticket date (non-wistlist)
$lvl = $score_vars->{sustainability};
$lvl->{last_span} = DateTime->now - $dp->parse_datetime($lvl->{author_last_release});
$lvl->{_score} =
# Author's last release date = inverse square scale (pivot at 5 years; could go into negative)
(465 - $lvl->{last_span}->in_units('months') ** 1.5) / 465 * 30 +
# Stability score
$score_vars->{stability}{_score} / 100 * 35 +
# Reverse dependencies = inverse reciprocal scale
($lvl->{reverse_depend} ?
(1 - 1/$lvl->{reverse_depend}) * 25 :
0
)
;
# Print score results
say "$module:";
foreach my $type (qw(maturity stability sustainability)) {
printf " %14s: %2u%% (%s)\n", ucfirst $type, $score_vars->{$type}{_score}, $score_label[int( $score_vars->{$type}{_score} / 20 )];
}
say;
#say Data::Dumper->new([$score_vars], [$module])->Indent(1)->Pad(' ')->Sortkeys(1)->Dump();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment